/// <summary> /// Relay data read from one connection to another. /// </summary> /// <param name="o">A TransmitArguments object containing local and remote server parameters.</param> private async void RelayData(object o) { // Cast the passed-in parameters back to their original objects. TransmitArguments arguments = (TransmitArguments)o; Stream clientStream = arguments.ClientStream; Stream remoteServerStream = arguments.RemoteServerStream; // A byte array to streamline bit shuffling. char[] buffer = new char[Constants.SMALLBUFFERSIZE]; // Placeholder variables to track the current message being transmitted. StringBuilder messageBuilder = new StringBuilder(Constants.SMALLSBSIZE); // The overall number of bytes transmitted on this connection. ulong bytesTransmitted = 0; if (arguments.Credential != null) { UserName = arguments.Credential.UserName; } bool stillReceiving = true; try { using (StreamReader clientStreamReader = new StreamReader(clientStream)) { using (StreamWriter remoteServerStreamWriter = new StreamWriter(remoteServerStream)) { remoteServerStreamWriter.AutoFlush = true; while (Started && stillReceiving) { // Read data from the source and send it to its destination. string stringRead = await clientStreamReader.ReadLineAsync(); if (stringRead != null) { int bytesRead = stringRead.Length; bytesTransmitted += (ulong)bytesRead; messageBuilder.AppendLine(stringRead); // If this data comes from the client, log it. Otherwise, process it. if (arguments.IsClient) { bool messageRelayed = false; string[] commandParts = stringRead.Substring(0, stringRead.Length).Split(new char[] { ' ' }, 2); // Optionally replace credentials with those from our settings file. if (commandParts.Length == 2) { if (commandParts[0] == "USER") { if (arguments.Credential != null) { await remoteServerStreamWriter.WriteLineAsync("USER " + arguments.Credential.UserName); ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "C: USER " + arguments.Credential.UserName, Proxy.LogLevel.Raw, LogLevel); messageRelayed = true; } else { UserName = commandParts[1]; } } else if (arguments.Credential != null && commandParts[0] == "PASS") { await remoteServerStreamWriter.WriteLineAsync("PASS" + arguments.Credential.Password); ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "C: PASS " + arguments.Credential.Password, Proxy.LogLevel.Raw, LogLevel); messageRelayed = true; } } if (LogLevel == Proxy.LogLevel.Verbose) { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "Command {" + commandParts[0] + "} processed.", Proxy.LogLevel.Verbose, LogLevel); } if (!messageRelayed) { await remoteServerStreamWriter.WriteLineAsync(stringRead); ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "C: " + stringRead, Proxy.LogLevel.Raw, LogLevel); } } else { await remoteServerStreamWriter.WriteLineAsync(stringRead); ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "S: " + stringRead, Proxy.LogLevel.Raw, LogLevel); // If we see a period between two linebreaks, treat it as the end of a message. if (stringRead == ".") { string message = messageBuilder.ToString(); int endPos = message.IndexOf("\r\n.\r\n"); if (message.Contains("\r\n\r\n")) { int lastOkPos = message.LastIndexOf("+OK", endPos); if (lastOkPos > -1) { message = message.Substring(message.IndexOf("\r\n", lastOkPos) + 2); if (message.IndexOf("application/x-pkcs7-signature") > -1 || message.IndexOf("application/pkcs7-mime") > -1 || !string.IsNullOrEmpty(arguments.ExportDirectory)) { Thread processThread = new Thread(new ParameterizedThreadStart(ProcessMessage)); processThread.Name = "OpaqueMail POP3 Proxy Signature Processor"; ProcessMessageArguments processMessageArguments = new ProcessMessageArguments(); processMessageArguments.MessageText = message.Substring(0, message.Length - 5); processMessageArguments.ConnectionId = ConnectionId.ToString(); processMessageArguments.ExportDirectory = arguments.ExportDirectory; processMessageArguments.InstanceId = arguments.InstanceId; processMessageArguments.DebugMode = arguments.DebugMode; processMessageArguments.UserName = UserName; processThread.Start(processMessageArguments); } messageBuilder.Remove(0, endPos + 5); } } messageBuilder.Clear(); } } } else { stillReceiving = false; } } } } } catch (IOException) { // Ignore either stream being closed. } catch (ObjectDisposedException) { // Ignore either stream being closed. } catch (Exception ex) { // Log other exceptions. if (arguments.DebugMode || System.Diagnostics.Debugger.IsAttached) { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Exception while transmitting data: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel); } else { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Exception while transmitting data: " + ex.Message, Proxy.LogLevel.Error, LogLevel); } } finally { // If sending to the local client, log the connection being closed. if (!arguments.IsClient) { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Connection from {" + arguments.IPAddress + "} closed after transmitting {" + bytesTransmitted.ToString("N0") + "} bytes.", Proxy.LogLevel.Information, LogLevel); } if (clientStream != null) { clientStream.Dispose(); } if (remoteServerStream != null) { remoteServerStream.Dispose(); } } }
/// <summary> /// Handle an incoming POP3 connection, from connection to completion. /// </summary> /// <param name="parameters">Pop3ProxyConnectionArguments object containing all parameters for this connection.</param> private void ProcessConnection(object parameters) { // Cast the passed-in parameters back to their original objects. Pop3ProxyConnectionArguments arguments = (Pop3ProxyConnectionArguments)parameters; try { TcpClient client = arguments.TcpClient; Stream clientStream = client.GetStream(); // Capture the client's IP information. PropertyInfo pi = clientStream.GetType().GetProperty("Socket", BindingFlags.NonPublic | BindingFlags.Instance); string ip = ((Socket)pi.GetValue(clientStream, null)).RemoteEndPoint.ToString(); if (ip.IndexOf(":") > -1) { ip = ip.Substring(0, ip.IndexOf(":")); } // If the IP address range filter contains the localhost entry 0.0.0.0, check if the client IP is a local address and update it to 0.0.0.0 if so. if (arguments.AcceptedIPs.IndexOf("0.0.0.0") > -1) { if (ip == "127.0.0.1") { ip = "0.0.0.0"; } else { IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName()); foreach (IPAddress hostIP in hostEntry.AddressList) { if (hostIP.ToString() == ip) { ip = "0.0.0.0"; break; } } } } // Validate that the IP address is within an accepted range. if (!ProxyFunctions.ValidateIP(arguments.AcceptedIPs, ip)) { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Connection rejected from {" + ip + "} due to its IP address.", Proxy.LogLevel.Warning, LogLevel); Functions.SendStreamString(clientStream, new byte[Constants.SMALLBUFFERSIZE], "500 IP address [" + ip + "] rejected.\r\n"); if (clientStream != null) { clientStream.Dispose(); } if (client != null) { client.Close(); } return; } ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "New connection established from {" + ip + "}.", Proxy.LogLevel.Information, LogLevel); // If supported, upgrade the session's security through a TLS handshake. if (arguments.LocalEnableSsl) { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Starting local TLS/SSL protection for {" + ip + "}.", Proxy.LogLevel.Information, LogLevel); clientStream = new SslStream(clientStream); ((SslStream)clientStream).AuthenticateAsServer(arguments.Certificate); } // Connect to the remote server. TcpClient remoteServerClient = new TcpClient(arguments.RemoteServerHostName, arguments.RemoteServerPort); Stream remoteServerStream = remoteServerClient.GetStream(); // If supported, upgrade the session's security through a TLS handshake. if (arguments.RemoteServerEnableSsl) { ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Starting remote TLS/SSL protection with {" + arguments.RemoteServerHostName + "}.", Proxy.LogLevel.Information, LogLevel); remoteServerStream = new SslStream(remoteServerStream); ((SslStream)remoteServerStream).AuthenticateAsClient(arguments.RemoteServerHostName); } // Relay server data to the client. TransmitArguments remoteServerToClientArguments = new TransmitArguments(); remoteServerToClientArguments.ClientStream = remoteServerStream; remoteServerToClientArguments.RemoteServerStream = clientStream; remoteServerToClientArguments.IsClient = false; remoteServerToClientArguments.ConnectionId = ConnectionId.ToString(); remoteServerToClientArguments.InstanceId = arguments.InstanceId; remoteServerToClientArguments.DebugMode = arguments.DebugMode; remoteServerToClientArguments.IPAddress = ip; remoteServerToClientArguments.ExportDirectory = arguments.ExportDirectory; Thread remoteServerToClientThread = new Thread(new ParameterizedThreadStart(RelayData)); remoteServerToClientThread.Name = "OpaqueMail POP3 Proxy Server to Client"; remoteServerToClientThread.Start(remoteServerToClientArguments); // Relay client data to the remote server. TransmitArguments clientToRemoteServerArguments = new TransmitArguments(); clientToRemoteServerArguments.ClientStream = clientStream; clientToRemoteServerArguments.RemoteServerStream = remoteServerStream; clientToRemoteServerArguments.IsClient = true; clientToRemoteServerArguments.ConnectionId = ConnectionId.ToString(); clientToRemoteServerArguments.InstanceId = arguments.InstanceId; clientToRemoteServerArguments.DebugMode = arguments.DebugMode; clientToRemoteServerArguments.IPAddress = ip; clientToRemoteServerArguments.Credential = arguments.RemoteServerCredential; Thread clientToRemoteServerThread = new Thread(new ParameterizedThreadStart(RelayData)); clientToRemoteServerThread.Name = "OpaqueMail POP3 Proxy Client to Server"; clientToRemoteServerThread.Start(clientToRemoteServerArguments); } catch (SocketException ex) { if (arguments.DebugMode || System.Diagnostics.Debugger.IsAttached) { ProxyFunctions.Log(LogWriter, SessionId, "Exception communicating with {" + arguments.RemoteServerHostName + "} on port {" + arguments.RemoteServerPort + "}: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel); } else { ProxyFunctions.Log(LogWriter, SessionId, "Exception communicating with {" + arguments.RemoteServerHostName + "} on port {" + arguments.RemoteServerPort + "}: " + ex.Message, Proxy.LogLevel.Error, LogLevel); } } catch (Exception ex) { if (arguments.DebugMode || System.Diagnostics.Debugger.IsAttached) { ProxyFunctions.Log(LogWriter, SessionId, "Exception: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel); } else { ProxyFunctions.Log(LogWriter, SessionId, "Exception: " + ex.Message, Proxy.LogLevel.Error, LogLevel); } } }