using System; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Text; using System.Threading; using Misuzilla.Net.Irc; using System.Security; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace Misuzilla.Applications.TwitterIrcGateway { /// /// ユーザからの接続を管理するクラス /// public abstract class ConnectionBase : MarshalByRefObject, IDisposable, IIrcMessageSendable { private StreamWriter _writer; #region Events /// /// IRCメッセージ受信時、TwitterIrcGatewayが処理する前のイベント /// public event EventHandler PreMessageReceived; /// /// IRCメッセージ受信時のイベント /// public event EventHandler MessageReceived; /// /// IRCメッセージ受信時、TwitterIrcGatewayが処理した後のイベント /// public event EventHandler PostMessageReceived; /// /// セッション開始時のイベント /// public event EventHandler ConnectionStarted; /// /// セッション終了時のイベント /// public event EventHandler ConnectionEnded; #endregion public ConnectionBase(Server server, TcpClient tcpClient) { MessageReceived += new EventHandler(MessageReceived_USER); MessageReceived += new EventHandler(MessageReceived_NICK); MessageReceived += new EventHandler(MessageReceived_PASS); MessageReceived += new EventHandler(MessageReceived_QUIT); MessageReceived += new EventHandler(MessageReceived_PING); CurrentServer = server; this.Encoding = CurrentServer.Encoding; this.TcpClient = tcpClient; this.UserInfo = new UserInfo() {EndPoint = (IPEndPoint) (tcpClient.Client.RemoteEndPoint)}; } ~ConnectionBase() { Dispose(); } /// /// 接続しているクライアントを取得します /// public TcpClient TcpClient { get; private set; } /// /// 関連づけられているサーバを取得します /// public Server CurrentServer { get; private set; } /// /// クライアント情報を取得します /// public UserInfo UserInfo { get; private set; } /// /// クライアントが認証済みかどうかを取得します /// public Boolean IsAuthenticated { get; private set; } /// /// /// public Encoding Encoding { get; set; } /// /// セッションを開始します。 /// public void Start() { CheckDisposed(); try { using (Stream stream = TcpClient.GetStream()) { Stream targetStream = stream; if (CurrentServer.IsSslConnection) { SslStream sslStream = new SslStream(stream); sslStream.AuthenticateAsServer(CurrentServer.Certificate, false, SslProtocols.Default, false); targetStream = sslStream; } using (StreamReader sr = new StreamReader(targetStream, Encoding)) using (StreamWriter sw = new StreamWriter(targetStream, Encoding)) { _writer = sw; String line; PermissionSet permissionSet = null; while (TcpClient.Connected && (line = sr.ReadLine()) != null) { try { IRCMessage msg = IRCMessage.CreateMessage(line); OnMessageReceived(msg); } catch (IRCException ircE) { Trace.TraceWarning(ircE.ToString()); } } } } } catch (IOException) {} catch (NullReferenceException) {} catch (AuthenticationException) // SSL {} finally { Close(); } } #region イベント実行メソッド protected virtual void OnMessageReceived(IRCMessage msg) { Debug.WriteLine(msg.ToString()); if (FireEvent(PreMessageReceived, new MessageReceivedEventArgs(msg, _writer, TcpClient))) { if (FireEvent(MessageReceived, new MessageReceivedEventArgs(msg, _writer, TcpClient))) { FireEvent(PostMessageReceived, new MessageReceivedEventArgs(msg, _writer, TcpClient)); } } } protected virtual void OnConnectionStarted(String username) { FireEvent(ConnectionStarted, new SessionStartedEventArgs(username, null, (IPEndPoint)TcpClient.Client.RemoteEndPoint)); } protected virtual void OnConnectionEnded() { FireEvent(ConnectionEnded, EventArgs.Empty); } #endregion #region 認証 private void DoAuthenticate() { if (String.IsNullOrEmpty(UserInfo.Nick) || String.IsNullOrEmpty(UserInfo.UserName)) return; AuthenticateResult result = OnAuthenticate(UserInfo); if (result.IsAuthenticated) { Type t = typeof (Server); SendNumericReply(NumericReply.RPL_WELCOME , String.Format("Welcome to the Internet Relay Network {0}", UserInfo.ClientHost)); SendNumericReply(NumericReply.RPL_YOURHOST, String.Format("Your host is {0}, running version {1}", t.FullName, t.Assembly.GetName().Version)); SendNumericReply(NumericReply.RPL_CREATED , String.Format("This server was created {0}", DateTime.Now)); SendNumericReply(NumericReply.RPL_MYINFO, String.Format("{0} {1}-{2} {3} {4}", Environment.MachineName, t.FullName, t.Assembly.GetName().Version, "", "")); IsAuthenticated = true; // イベントハンドラを外す MessageReceived -= MessageReceived_USER; MessageReceived -= MessageReceived_PASS; MessageReceived -= MessageReceived_NICK; OnAuthenticateSucceeded(); } else { SendErrorReply(result.ErrorReply, result.ErrorMessage); OnAuthenticateFailed(result); Thread.Sleep(10 * 1000); Close(); } } protected abstract AuthenticateResult OnAuthenticate(UserInfo userInfo); protected abstract void OnAuthenticateSucceeded(); protected abstract void OnAuthenticateFailed(AuthenticateResult authenticateResult); #endregion #region メッセージ処理イベント private void MessageReceived_USER(object sender, MessageReceivedEventArgs e) { if (!(e.Message is UserMessage)) return; UserInfo.UserName = e.Message.CommandParams[0]; UserInfo.RealName = e.Message.CommandParams[3]; DoAuthenticate(); } private void MessageReceived_NICK(object sender, MessageReceivedEventArgs e) { if (!(e.Message is NickMessage)) return; UserInfo.Nick = ((NickMessage)(e.Message)).NewNick; DoAuthenticate(); } private void MessageReceived_PASS(object sender, MessageReceivedEventArgs e) { if (String.Compare(e.Message.Command, "PASS", true) != 0) return; if (e.Message.CommandParam.Length != 0) { UserInfo.Password = e.Message.CommandParam.Substring(1); } } private void MessageReceived_QUIT(object sender, MessageReceivedEventArgs e) { if (!(e.Message is QuitMessage)) return; try { e.Client.Close(); } catch { } } private void MessageReceived_PING(object sender, MessageReceivedEventArgs e) { if (String.Compare(e.Message.Command, "PING", true) != 0) return; Send(IRCMessage.CreateMessage("PONG :" + e.Message.CommandParam)); } #endregion /// /// IRCメッセージを送信します /// /// public void Send(IRCMessage msg) { //CheckDisposed(); lock (_writer) { try { if (TcpClient != null && TcpClient.Connected && _writer.BaseStream.CanWrite) { _writer.WriteLine(msg.RawMessage); _writer.Flush(); } } catch (IOException) { Close(); } } } /// /// JOIN などクライアントに返すメッセージを送信します /// /// public void SendServer(IRCMessage msg) { msg.Sender = UserInfo.ClientHost; Send(msg); } /// /// IRCサーバからのメッセージを送信します /// /// public void SendServerMessage(IRCMessage msg) { msg.Prefix = Server.ServerName; Send(msg); } /// /// Gatewayからのメッセージを送信します /// /// public void SendGatewayServerMessage(String message) { NoticeMessage noticeMsg = new NoticeMessage(); noticeMsg.Sender = ""; noticeMsg.Receiver = UserInfo.Nick; noticeMsg.Content = message.Replace("\n", " "); Send(noticeMsg); } /// /// サーバのエラーメッセージを送信します /// /// public void SendServerErrorMessage(String message) { SendGatewayServerMessage("エラー: " + message); } /// /// サーバからクライアントにエラーリプライを返します。 /// /// エラーリプライ番号 /// リプライコマンドパラメータ public void SendErrorReply(ErrorReply errorNum, params String[] commandParams) { SendNumericReply((NumericReply)errorNum, commandParams); } /// /// サーバからクライアントにニュメリックリプライを返します。 /// /// リプライ番号 /// リプライコマンドパラメータ public void SendNumericReply(NumericReply numReply, params String[] commandParams) { if (commandParams.Length > 14 || commandParams.Length < 0) throw new ArgumentOutOfRangeException("commandParams"); NumericReplyMessage numMsg = new NumericReplyMessage(numReply); numMsg.CommandParams[0] = UserInfo.Nick; for (Int32 i = 0; i < commandParams.Length; i++) numMsg.CommandParams[i+1] = commandParams[i]; SendServerMessage(numMsg); } /// /// /// public void Close() { if (TcpClient != null && TcpClient.Connected) { TcpClient.Close(); } OnConnectionEnded(); Dispose(); } /// /// /// private void CheckDisposed() { if (_isDisposed) throw new ObjectDisposedException(this.GetType().Name); } public override string ToString() { return String.Format("Session: User={0}, Client={1}", UserInfo.UserName, UserInfo.EndPoint); } #region ヘルパーメソッド /// /// /// /// /// /// /// キャンセルされた場合にはfalseが返ります。 [DebuggerStepThrough] private Boolean FireEvent(EventHandler handlers, TEventArgs e) where TEventArgs:EventArgs { if (handlers != null) { foreach (EventHandler eventHandler in handlers.GetInvocationList()) { try { eventHandler(this, e); } catch (Exception ex) { Trace.TraceError(ex.ToString()); } } } if (e is CancelableEventArgs) { return !((e as CancelableEventArgs).Cancel); } return true; } #endregion #region IDisposable メンバ private Boolean _isDisposed = false; public void Dispose() { if (!_isDisposed) { if (TcpClient != null && TcpClient.Connected) { TcpClient.Close(); TcpClient = null; } GC.SuppressFinalize(this); _isDisposed = true; } } #endregion } }