Esempio n. 1
0
        /// <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);
                }
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Start all POP3 proxy instances from the specified settings file.
        /// </summary>
        /// <param name="fileName">File containing the POP3 proxy settings.</param>
        public static List <Pop3Proxy> StartProxiesFromSettingsFile(string fileName)
        {
            List <Pop3Proxy> pop3Proxies = new List <Pop3Proxy>();

            try
            {
                if (File.Exists(fileName))
                {
                    XPathDocument  document  = new XPathDocument(fileName);
                    XPathNavigator navigator = document.CreateNavigator();

                    int pop3ServiceCount = ProxyFunctions.GetXmlIntValue(navigator, "/Settings/IMAP/ServiceCount");
                    for (int i = 1; i <= pop3ServiceCount; i++)
                    {
                        Pop3ProxyArguments arguments = new Pop3ProxyArguments();
                        arguments.AcceptedIPs = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/SMTP/Service" + i + "/AcceptedIPs");

                        string localIpAddress = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/LocalIPAddress").ToUpper();
                        switch (localIpAddress)
                        {
                        // Treat blank values as "Any".
                        case "":
                        case "ANY":
                            arguments.LocalIpAddress = IPAddress.Any;
                            break;

                        case "BROADCAST":
                            arguments.LocalIpAddress = IPAddress.Broadcast;
                            break;

                        case "IPV6ANY":
                            arguments.LocalIpAddress = IPAddress.IPv6Any;
                            break;

                        case "IPV6LOOPBACK":
                            arguments.LocalIpAddress = IPAddress.IPv6Loopback;
                            break;

                        case "LOOPBACK":
                            arguments.LocalIpAddress = IPAddress.Loopback;
                            break;

                        default:
                            // Try to parse the local IP address.  If unable to, proceed to the next service instance.
                            if (!IPAddress.TryParse(localIpAddress, out arguments.LocalIpAddress))
                            {
                                continue;
                            }
                            break;
                        }

                        arguments.LocalPort = ProxyFunctions.GetXmlIntValue(navigator, "/Settings/POP3/Service" + i + "/LocalPort");
                        // If the port is invalid, proceed to the next service instance.
                        if (arguments.LocalPort < 1)
                        {
                            continue;
                        }

                        arguments.LocalEnableSsl = ProxyFunctions.GetXmlBoolValue(navigator, "/Settings/POP3/Service" + i + "/LocalEnableSSL");

                        arguments.RemoteServerHostName = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/RemoteServerHostName");
                        // If the host name is invalid, proceed to the next service instance.
                        if (string.IsNullOrEmpty(arguments.RemoteServerHostName))
                        {
                            continue;
                        }

                        arguments.RemoteServerPort = ProxyFunctions.GetXmlIntValue(navigator, "/Settings/POP3/Service" + i + "/RemoteServerPort");
                        // If the port is invalid, proceed to the next service instance.
                        if (arguments.RemoteServerPort < 1)
                        {
                            continue;
                        }

                        arguments.RemoteServerEnableSsl = ProxyFunctions.GetXmlBoolValue(navigator, "/Settings/POP3/Service" + i + "/RemoteServerEnableSSL");

                        string remoteServerUsername = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/RemoteServerUsername");
                        if (!string.IsNullOrEmpty(remoteServerUsername))
                        {
                            arguments.RemoteServerCredential          = new NetworkCredential();
                            arguments.RemoteServerCredential.UserName = remoteServerUsername;
                            arguments.RemoteServerCredential.Password = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/RemoteServerPassword");
                        }

                        string        certificateLocationValue = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/Certificate/Location");
                        StoreLocation certificateLocation      = StoreLocation.LocalMachine;
                        if (certificateLocationValue.ToUpper() == "CURRENTUSER")
                        {
                            certificateLocation = StoreLocation.CurrentUser;
                        }

                        // Try to load the signing certificate based on its serial number first, then fallback to its subject name.
                        string certificateValue = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/Certificate/SerialNumber");
                        if (!string.IsNullOrEmpty(certificateValue))
                        {
                            arguments.Certificate = CertHelper.GetCertificateBySerialNumber(certificateLocation, certificateValue);
                        }
                        else
                        {
                            certificateValue = ProxyFunctions.GetXmlStringValue(navigator, "/Settings/POP3/Service" + i + "/Certificate/SubjectName");
                            if (!string.IsNullOrEmpty(certificateValue))
                            {
                                arguments.Certificate = CertHelper.GetCertificateBySubjectName(certificateLocation, certificateValue);
                            }
                        }

                        arguments.ExportDirectory = ProxyFunctions.GetXmlStringValue(navigator, "Settings/POP3/Service" + i + "/ExportDirectory");
                        arguments.LogFile         = ProxyFunctions.GetXmlStringValue(navigator, "Settings/POP3/Service" + i + "/LogFile");

                        string logLevel = ProxyFunctions.GetXmlStringValue(navigator, "Settings/POP3/Service" + i + "/LogLevel");
                        switch (logLevel.ToUpper())
                        {
                        case "NONE":
                            arguments.LogLevel = LogLevel.None;
                            break;

                        case "CRITICAL":
                            arguments.LogLevel = LogLevel.Critical;
                            break;

                        case "ERROR":
                            arguments.LogLevel = LogLevel.Error;
                            break;

                        case "RAW":
                            arguments.LogLevel = LogLevel.Raw;
                            break;

                        case "VERBOSE":
                            arguments.LogLevel = LogLevel.Verbose;
                            break;

                        case "WARNING":
                            arguments.LogLevel = LogLevel.Warning;
                            break;

                        case "INFORMATION":
                        default:
                            arguments.LogLevel = LogLevel.Information;
                            break;
                        }

                        arguments.InstanceId = i;
                        arguments.DebugMode  = ProxyFunctions.GetXmlBoolValue(navigator, "Settings/POP3/Service" + i + "/Debug");

                        // Remember the proxy in order to close it when the service stops.
                        arguments.Proxy = new Pop3Proxy();
                        pop3Proxies.Add(arguments.Proxy);

                        Thread proxyThread = new Thread(new ParameterizedThreadStart(StartProxy));
                        proxyThread.Name = "OpaqueMail POP3 Proxy";
                        proxyThread.Start(arguments);
                    }
                }
            }
            catch
            {
                // Ignore errors if the XML settings file is malformed.
            }

            return(pop3Proxies);
        }
Esempio n. 3
0
        /// <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.
            bool          inMessage      = false;
            int           messageLength  = 0;
            StringBuilder messageBuilder = new StringBuilder(Constants.SMALLSBSIZE);

            // The overall number of bytes transmitted on this connection.
            ulong bytesTransmitted = 0;

            //  When a "[THROTTLED]" notice was last received.
            DateTime lastThrottleTime = new DateTime(1900, 1, 1);

            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;

                                // If this data comes from the client, log it.  Otherwise, process it.
                                if (arguments.IsClient)
                                {
                                    bool messageRelayed = false;

                                    string[] commandParts = stringRead.Split(new char[] { ' ' }, 4);
                                    if (commandParts.Length > 1)
                                    {
                                        // Optionally, transform the login details.
                                        if (commandParts[1] == "LOGIN" && commandParts.Length == 4)
                                        {
                                            messageRelayed = TransformLogin(remoteServerStreamWriter, stringRead, arguments, ref UserName);
                                        }
                                        else
                                        {
                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "C: " + stringRead, Proxy.LogLevel.Raw, LogLevel);
                                        }

                                        // Remember the previous command.
                                        if (commandParts[1].ToUpper() == "UID" && commandParts.Length > 2)
                                        {
                                            LastCommandReceived = (commandParts[1] + " " + commandParts[2]).ToUpper();
                                        }
                                        else
                                        {
                                            LastCommandReceived = commandParts[1].ToUpper();
                                        }

                                        // Stop after a logout order is received.
                                        if (LastCommandReceived == "LOGOUT")
                                        {
                                            stillReceiving = false;
                                        }

                                        if (LogLevel == Proxy.LogLevel.Verbose)
                                        {
                                            switch (LastCommandReceived)
                                            {
                                            case "APPEND":
                                            case "AUTHENTICATE":
                                            case "CAPABILITY":
                                            case "CHECK":
                                            case "CLOSE":
                                            case "COPY":
                                            case "CREATE":
                                            case "DELETE":
                                            case "ENABLE":
                                            case "EXAMINE":
                                            case "EXPUNGE":
                                            case "FETCH":
                                            case "GETQUOTA":
                                            case "GETQUOTAROOT":
                                            case "LIST":
                                            case "LOGIN":
                                            case "LOGOUT":
                                            case "LSUB":
                                            case "MOVE":
                                            case "NOOP":
                                            case "NOTIFY":
                                            case "RENAME":
                                            case "SEARCH":
                                            case "SELECT":
                                            case "SETQUOTA":
                                            case "STATUS":
                                            case "STORE":
                                            case "SUBSCRIBE":
                                            case "UID COPY":
                                            case "UID FETCH":
                                            case "UID SEARCH":
                                            case "UID STORE":
                                            case "UNSUBSCRIBE":
                                            case "XLIST":
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "Command {" + LastCommandReceived + "} processed.", Proxy.LogLevel.Verbose, LogLevel);
                                                break;
                                            }
                                        }
                                    }

                                    // If the command wasn't processed, send the raw command.
                                    if (!messageRelayed)
                                    {
                                        await remoteServerStreamWriter.WriteLineAsync(stringRead);
                                    }
                                }
                                else
                                {
                                    // If we're currently receiving a message, check to see if it's completed.
                                    if (inMessage)
                                    {
                                        messageBuilder.AppendLine(stringRead);
                                        if (messageBuilder.Length >= messageLength)
                                        {
                                            // If the message has been completed and it contains a signature, process it.
                                            string message = messageBuilder.ToString(0, messageLength);
                                            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 IMAP Proxy Signature Processor";
                                                ProcessMessageArguments processMessageArguments = new ProcessMessageArguments();
                                                processMessageArguments.MessageText     = message;
                                                processMessageArguments.ConnectionId    = ConnectionId.ToString();
                                                processMessageArguments.ExportDirectory = arguments.ExportDirectory;
                                                processMessageArguments.InstanceId      = arguments.InstanceId;
                                                processMessageArguments.DebugMode       = arguments.DebugMode;
                                                processMessageArguments.UserName        = UserName;
                                                processThread.Start(processMessageArguments);
                                            }

                                            // We're no longer receiving a message, so continue.
                                            inMessage = false;
                                            messageBuilder.Clear();
                                        }
                                    }
                                    else
                                    {
                                        // Disallow proxy stream compression.
                                        if (stringRead.StartsWith("* CAPABILITY"))
                                        {
                                            stringRead = stringRead.Replace(" COMPRESS=DEFLATE", "");
                                        }

                                        // Check for IMAP meta flags.
                                        string betweenBraces = Functions.ReturnBetween(stringRead, "[", "]");
                                        switch (betweenBraces)
                                        {
                                        case "CLIENTBUG":
                                        case "CORRUPTION":
                                        case "OVERQUOTA":
                                        case "SERVERBUG":
                                        case "UNAVAILABLE":
                                            ProxyFunctions.Log(LogWriter, SessionId, ConnectionId.ToString(), stringRead.Substring(stringRead.IndexOf("[")), Proxy.LogLevel.Warning, LogLevel);
                                            break;

                                        case "THROTTLED":
                                            if (DateTime.Now - lastThrottleTime >= new TimeSpan(0, 20, 0))
                                            {
                                                ProxyFunctions.Log(LogWriter, SessionId, ConnectionId.ToString(), "Connection speed throttled by the remote server.", Proxy.LogLevel.Warning, LogLevel);
                                                lastThrottleTime = DateTime.Now;
                                            }
                                            break;
                                        }

                                        // Messages are denoted by FETCH headers with their lengths in curly braces.
                                        if (stringRead.ToUpper().Contains(" FETCH "))
                                        {
                                            int openBrace = stringRead.IndexOf("{");
                                            if (openBrace > -1)
                                            {
                                                int closeBrace = stringRead.IndexOf("}", openBrace);
                                                if (closeBrace > -1)
                                                {
                                                    // Only proceed if we can parse the size of the message.
                                                    if (int.TryParse(stringRead.Substring(openBrace + 1, closeBrace - openBrace - 1), out messageLength))
                                                    {
                                                        inMessage = true;
                                                    }
                                                }
                                            }
                                        }
                                    }

                                    await remoteServerStreamWriter.WriteLineAsync(stringRead);

                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId.ToString(), "S: " + stringRead, Proxy.LogLevel.Raw, LogLevel);
                                }
                            }
                            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();
                }
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Start a IMAP proxy instance.
        /// </summary>
        /// <param name="acceptedIPs">IP addresses to accept connections from.</param>
        /// <param name="localIPAddress">Local IP address to bind to.</param>
        /// <param name="localPort">Local port to listen on.</param>
        /// <param name="localEnableSsl">Whether the local server supports TLS/SSL.</param>
        /// <param name="remoteServerHostName">Remote server hostname to forward all IMAP messages to.</param>
        /// <param name="remoteServerPort">Remote server port to connect to.</param>
        /// <param name="remoteServerEnableSsl">Whether the remote IMAP server requires TLS/SSL.</param>
        /// <param name="remoteServerCredential">(Optional) Credentials to be used for all connections to the remote IMAP server.  When set, this overrides any credentials passed locally.</param>
        /// <param name="exportDirectory">(Optional) Location where all incoming messages are saved as EML files.</param>
        /// <param name="logFile">File where event logs and exception information will be written.</param>
        /// <param name="logLevel">Proxy logging level, determining how much information is logged.</param>
        /// <param name="instanceId">The instance number of the proxy.</param>
        /// <param name="debugMode">Whether the proxy instance is running in DEBUG mode and should output full exception messages.</param>
        public void Start(string acceptedIPs, IPAddress localIPAddress, int localPort, bool localEnableSsl, string remoteServerHostName, int remoteServerPort, bool remoteServerEnableSsl, NetworkCredential remoteServerCredential, string exportDirectory, string logFile, LogLevel logLevel, int instanceId, bool debugMode)
        {
            // Create the log writer.
            string logFileName = "";

            if (!string.IsNullOrEmpty(logFile))
            {
                logFileName         = ProxyFunctions.GetLogFileName(logFile, instanceId, localIPAddress.ToString(), remoteServerHostName, localPort, remoteServerPort);
                LogWriter           = new StreamWriter(logFileName, true, Encoding.UTF8, Constants.SMALLBUFFERSIZE);
                LogWriter.AutoFlush = true;

                LogLevel = logLevel;
            }

            // Make sure the remote server isn't an infinite loop back to this server.
            string fqdn = Functions.GetLocalFQDN();

            if (remoteServerHostName.ToUpper() == fqdn.ToUpper() && remoteServerPort == localPort)
            {
                ProxyFunctions.Log(LogWriter, SessionId, "Cannot start service because the remote server host name {" + remoteServerHostName + "} and port {" + remoteServerPort.ToString() + "} is the same as this proxy, which would cause an infinite loop.", Proxy.LogLevel.Critical, LogLevel);
                return;
            }
            IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName());

            foreach (IPAddress hostIP in hostEntry.AddressList)
            {
                if (remoteServerHostName == hostIP.ToString() && remoteServerPort == localPort)
                {
                    ProxyFunctions.Log(LogWriter, SessionId, "Cannot start service because the remote server hostname {" + remoteServerHostName + "} and port {" + remoteServerPort.ToString() + "} is the same as this proxy, which would cause an infinite loop.", Proxy.LogLevel.Critical, LogLevel);
                    return;
                }
            }

            ProxyFunctions.Log(LogWriter, SessionId, "Starting service.", Proxy.LogLevel.Information, LogLevel);

            // Attempt to start up to 3 times in case another service using the port is shutting down.
            int startAttempts = 0;

            while (startAttempts < 3)
            {
                startAttempts++;

                // If we've failed to start once, wait an extra 10 seconds.
                if (startAttempts > 1)
                {
                    ProxyFunctions.Log(LogWriter, SessionId, "Attempting to start for the " + (startAttempts == 2 ? "2nd" : "3rd") + " time.", Proxy.LogLevel.Information, LogLevel);
                    Thread.Sleep(10000 * startAttempts);
                }

                try
                {
                    X509Certificate serverCertificate = null;

                    // Generate a unique session ID for logging.
                    SessionId    = Guid.NewGuid().ToString();
                    ConnectionId = 0;

                    // If local SSL is supported via STARTTLS, ensure we have a valid server certificate.
                    if (localEnableSsl)
                    {
                        // Load the SSL certificate for the listening server name.
                        serverCertificate = CertHelper.GetCertificateBySubjectName(StoreLocation.LocalMachine, fqdn);

                        // In case the service as running as the current user, check the Current User certificate store as well.
                        if (serverCertificate == null)
                        {
                            serverCertificate = CertHelper.GetCertificateBySubjectName(StoreLocation.CurrentUser, fqdn);
                        }

                        // If no certificate was found, generate a self-signed certificate.
                        if (serverCertificate == null)
                        {
                            ProxyFunctions.Log(LogWriter, SessionId, "No signing certificate found, so generating new certificate.", Proxy.LogLevel.Warning, LogLevel);

                            List <string> oids = new List <string>();
                            oids.Add("1.3.6.1.5.5.7.3.1");    // Server Authentication.

                            // Generate the certificate with a duration of 10 years, 4096-bits, and a key usage of server authentication.
                            serverCertificate = CertHelper.CreateSelfSignedCertificate(fqdn, fqdn, StoreLocation.LocalMachine, true, 4096, 10, oids);

                            ProxyFunctions.Log(LogWriter, SessionId, "Certificate generated with Serial Number {" + serverCertificate.GetSerialNumberString() + "}.", Proxy.LogLevel.Information, LogLevel);
                        }
                    }

                    Listener = new TcpListener(localIPAddress, localPort);
                    Listener.Start();

                    ProxyFunctions.Log(LogWriter, SessionId, "Service started.", Proxy.LogLevel.Information, LogLevel);
                    ProxyFunctions.Log(LogWriter, SessionId, "Listening on address {" + localIPAddress.ToString() + "}, port {" + localPort + "}.", Proxy.LogLevel.Information, LogLevel);

                    Started = true;

                    // Accept client requests, forking each into its own thread.
                    while (Started)
                    {
                        TcpClient client = Listener.AcceptTcpClient();

                        string newLogFileName = ProxyFunctions.GetLogFileName(logFile, instanceId, localIPAddress.ToString(), remoteServerHostName, localPort, remoteServerPort);
                        if (newLogFileName != logFileName)
                        {
                            if (LogWriter != null)
                            {
                                LogWriter.Close();
                            }
                            LogWriter           = new StreamWriter(newLogFileName, true, Encoding.UTF8, Constants.SMALLBUFFERSIZE);
                            LogWriter.AutoFlush = true;
                        }

                        try
                        {
                            // Prepare the arguments for our new thread.
                            ImapProxyConnectionArguments arguments = new ImapProxyConnectionArguments();
                            arguments.AcceptedIPs            = acceptedIPs;
                            arguments.TcpClient              = client;
                            arguments.Certificate            = serverCertificate;
                            arguments.ExportDirectory        = exportDirectory;
                            arguments.LocalIpAddress         = localIPAddress;
                            arguments.LocalPort              = localPort;
                            arguments.LocalEnableSsl         = localEnableSsl;
                            arguments.RemoteServerHostName   = remoteServerHostName;
                            arguments.RemoteServerPort       = remoteServerPort;
                            arguments.RemoteServerEnableSsl  = remoteServerEnableSsl;
                            arguments.RemoteServerCredential = remoteServerCredential;

                            // Increment the connection counter;
                            arguments.ConnectionId = (unchecked (++ConnectionId)).ToString();
                            arguments.InstanceId   = instanceId;
                            arguments.DebugMode    = debugMode;

                            // Fork the thread and continue listening for new connections.
                            Task.Run(() => ProcessConnection(arguments));
                        }
                        catch (Exception ex)
                        {
                            ProxyFunctions.Log(LogWriter, SessionId, "Error while processing connection: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel);
                        }
                    }
                    return;
                }
                catch (Exception ex)
                {
                    if (debugMode || System.Diagnostics.Debugger.IsAttached)
                    {
                        ProxyFunctions.Log(LogWriter, SessionId, "Exception when starting proxy: " + ex.ToString(), Proxy.LogLevel.Critical, LogLevel);
                    }
                    else
                    {
                        ProxyFunctions.Log(LogWriter, SessionId, "Exception when starting proxy: " + ex.Message, Proxy.LogLevel.Critical, LogLevel);
                    }
                }
            }
        }
Esempio n. 5
0
        /// <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();
                }
            }
        }