Exemple #1
0
        /// <summary>
        /// Process a transmitted message to import any signing certificates for subsequent S/MIME encryption.
        /// </summary>
        /// <param name="o">A ProcessMessageArguments object containing message parameters.</param>
        private void ProcessMessage(object o)
        {
            ProcessMessageArguments arguments = (ProcessMessageArguments)o;

            // Export the message to a local directory.
            if (!string.IsNullOrEmpty(arguments.ExportDirectory))
            {
                string messageId = Functions.ReturnBetween(arguments.MessageText.ToLower(), "message-id: <", ">");
                if (string.IsNullOrEmpty(messageId))
                    messageId = Guid.NewGuid().ToString();

                string fileName = ProxyFunctions.GetExportFileName(arguments.ExportDirectory, messageId, arguments.InstanceId, arguments.UserName);
                File.WriteAllText(fileName, arguments.MessageText);
            }

            // Only parse the message if it contains a known S/MIME content type.
            string canonicalMessageText = arguments.MessageText.ToLower();
            if (canonicalMessageText.IndexOf("application/x-pkcs7-signature") > -1 || canonicalMessageText.IndexOf("application/pkcs7-mime") > -1)
            {
                try
                {
                    // Parse the message.
                    ReadOnlyMailMessage message = new ReadOnlyMailMessage(arguments.MessageText);

                    // If the message contains a signing certificate that we haven't processed on this session, import it.
                    if (message.SmimeSigningCertificate != null && !SmimeCertificatesReceived.Contains(message.SmimeSigningCertificate))
                    {
                        // Import the certificate to the Local Machine store.
                        ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Importing certificate with Serial Number {" + message.SmimeSigningCertificate.SerialNumber + "}.", Proxy.LogLevel.Information, LogLevel);
                        CertHelper.InstallWindowsCertificate(message.SmimeSigningCertificate, StoreLocation.LocalMachine);

                        // Remember this ceriticate to avoid importing it again this session.
                        SmimeCertificatesReceived.Add(message.SmimeSigningCertificate);
                    }
                }
                catch (Exception ex)
                {
                    if (arguments.DebugMode || System.Diagnostics.Debugger.IsAttached)
                        ProxyFunctions.Log(LogWriter, SessionId, "Exception while processing message: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel);
                    else
                        ProxyFunctions.Log(LogWriter, SessionId, "Exception while processing message: " + ex.Message, Proxy.LogLevel.Error, LogLevel);
                }
            }
        }
 /// <summary>
 /// Appends a message to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="message">The raw message to append.</param>
 public bool AppendMessage(string mailboxName, ReadOnlyMailMessage message)
 {
     return Task.Run(() => AppendMessageAsync(mailboxName, message)).Result;
 }
 /// <summary>
 /// Appends a message to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="message">The raw message to append.</param>
 /// <param name="flags">Optional flags to be applied for the message.</param>
 /// <param name="date">Optional date for the message.</param>
 public bool AppendMessage(string mailboxName, ReadOnlyMailMessage message, string[] flags, DateTime? date)
 {
     return Task.Run(() => AppendMessageAsync(mailboxName, message, flags, date)).Result;
 }
Exemple #4
0
 /// <summary>
 /// Cast a ReadOnlyMailMessage as a regular MailMessage.
 /// </summary>
 /// <param name="message">ReadOnlyMailMessage to import properties from.</param>
 public MailMessage FromReadOnlyMailMessage(ReadOnlyMailMessage message)
 {
     return message as MailMessage;
 }
 /// <summary>
 /// Appends messages to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="messages">The raw messages to append.</param>
 public bool AppendMessages(string mailboxName, ReadOnlyMailMessage[] messages)
 {
     return Task.Run(() => AppendMessagesAsync(mailboxName, messages, new string[] { }, null)).Result;
 }
Exemple #6
0
 /// <summary>
 /// Appends a message to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="message">The raw message to append.</param>
 /// <param name="flags">Optional flags to be applied for the message.</param>
 /// <param name="date">Optional date for the message.</param>
 public async Task<bool> AppendMessageAsync(string mailboxName, ReadOnlyMailMessage message, string[] flags, DateTime? date)
 {
     return await AppendMessageAsync(mailboxName, message.RawMessage, flags, date);
 }
Exemple #7
0
        /// <summary>
        /// Appends messages to the specified mailbox.
        /// </summary>
        /// <param name="mailboxName">The name of the mailbox to append to.</param>
        /// <param name="messages">The raw messages to append.</param>
        /// <param name="flags">Optional flags to be applied for the message.</param>
        /// <param name="date">Optional date for the message.</param>
        public async Task<bool> AppendMessagesAsync(string mailboxName, ReadOnlyMailMessage[] messages, string[] flags, DateTime? date)
        {
            string[] rawMessages = new string[messages.Length];
            for (int i = 0; i < messages.Length; i++)
                rawMessages[i] = messages[i].RawMessage;

            return await AppendMessagesAsync(mailboxName, rawMessages, flags, date);
        }
Exemple #8
0
        /// <summary>
        /// Helper function to load an instance of a message in a specified mailbox based on its index, optionally returning only headers and/or setting the "Seen" flag.
        /// </summary>
        /// <param name="mailboxName">The mailbox to load from.</param>
        /// <param name="id">The identifier of the message to load, either its index or UID.</param>
        /// <param name="headersOnly">Return only the message's headers when true; otherwise, return the message and body.</param>        
        /// <param name="setSeenFlag">Whether to touch the message and set its "Seen" flag.</param>
        /// <param name="isUid">Whether the identifer is an UID.</param>
        private async Task<ReadOnlyMailMessage> GetMessageHelper(string mailboxName, int id, bool headersOnly, bool setSeenFlag, bool isUid)
        {
            // Protect against commands being called out of order.
            if (!IsAuthenticated)
                throw new ImapException("Must be connected to the server and authenticated prior to calling the FETCH command.");

            if (mailboxName != CurrentMailboxName)
                await SelectMailboxAsync(mailboxName);

            string uidPrefix = isUid ? "UID " : "";

            // Generate a unique command tag for tracking this command and its response.
            string commandTag = UniqueCommandTag();

            // Format the command depending on whether we want headers only and whether we want to mark messages as seen.
            if (headersOnly)
                await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY[HEADER] UID)\r\n");
            else
            {
                if (setSeenFlag)
                    await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY[] UID FLAGS)\r\n");
                else
                    await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY.PEEK[] UID FLAGS)\r\n");
            }

            string response = await ReadDataAsync(commandTag, "FETCH");

            // Ensure the message was actually found.
            if (response.IndexOf("\r\n", StringComparison.Ordinal) > -1)
            {
                // Read the message's UID and flags.
                int uid = 0;
                int.TryParse(Functions.ReturnBetween(response, "UID ", " "), out uid);
                string flagsString = Functions.ReturnBetween(response, "FLAGS (", ")");

                // Strip IMAP response padding.
                response = response.Substring(response.IndexOf("\r\n") + 2);
                response = response.Substring(0, response.Length - 3);

                ReadOnlyMailMessage message = new ReadOnlyMailMessage(response, ProcessingFlags);
                message.ImapUid = uid;
                message.Mailbox = mailboxName;
                message.ParseFlagsString(flagsString);
                return message;
            }
            else
                return null;
        }
Exemple #9
0
 /// <summary>
 /// Appends a message to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="message">The raw message to append.</param>
 public async Task<bool> AppendMessageAsync(string mailboxName, ReadOnlyMailMessage message)
 {
     return await AppendMessageAsync(mailboxName, message.RawMessage, message.RawFlags.ToArray(), message.Date);
 }
Exemple #10
0
 /// <summary>
 /// Cast a ReadOnlyMailMessage as a regular MailMessage.
 /// </summary>
 /// <param name="message">ReadOnlyMailMessage to import properties from.</param>
 public MailMessage FromReadOnlyMailMessage(ReadOnlyMailMessage message)
 {
     return(message as MailMessage);
 }
Exemple #11
0
        /// <summary>
        /// Helper function to retrieve a specific message from the server based on its index or UID, optionally returning only headers.
        /// </summary>
        /// <param name="index">The index number of the message to return.</param>
        /// <param name="uid">The UID of the message, as returned by a UIDL command.</param>
        /// <param name="headersOnly">Return only the message's headers when true; otherwise, return the message and body.</param>
        private async Task<ReadOnlyMailMessage> GetMessageHelper(int index, string uid, bool headersOnly)
        {
            // Protect against commands being called out of order.
            if (!IsAuthenticated)
                throw new Pop3Exception("Must be connected to the server and authenticated prior to calling the RETR command.");

            bool processed = false;
            string response = "";

            // Determine whether we're using the index number or UID string.
            string messageID = index > -1 ? index.ToString() : uid;

            // If retrieving headers only, first try the POP3 TOP command.
            if (headersOnly && (ServerSupportsTop != false))
            {
                await SendCommandAsync("TOP " + messageID + " 0\r\n");
                response = await ReadDataAsync("\r\n.\r\n");

                if (LastCommandResult)
                    processed = true;
                ServerSupportsTop = processed;
            }
            if (!processed)
            {
                await SendCommandAsync("RETR " + messageID + "\r\n");
                response = await ReadDataAsync("\r\n.\r\n");
            }

            if (LastCommandResult && response.Length > 0)
            {
                ReadOnlyMailMessage message = new ReadOnlyMailMessage(response, ProcessingFlags);

                if (string.IsNullOrEmpty(uid) && ServerSupportsUIDL != null)
                {
                    message.Index = index;
                    message.Pop3Uidl = await GetUidlAsync(index);
                }
                else
                    message.Pop3Uidl = uid;

                return message;
            }

            // If unable to find or parse the message, return null.
            return null;
        }
Exemple #12
0
        /// <summary>
        /// Handle an incoming SMTP connection, from connection to completion.
        /// </summary>
        /// <param name="parameters">SmtpProxyConnectionArguments object containing all parameters for this connection.</param>
        private async void ProcessConnection(object parameters)
        {
            // Cast the passed-in parameters back to their original objects.
            SmtpProxyConnectionArguments arguments = (SmtpProxyConnectionArguments)parameters;

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

            TcpClient client = null;
            Stream clientStream = null;
            StreamReader clientStreamReader = null;
            StreamWriter clientStreamWriter = null;

            string ip = "";

            try
            {
                client = arguments.TcpClient;
                clientStream = client.GetStream();

                // Placeholder variables to be populated throughout the client session.
                NetworkCredential credential = arguments.RemoteServerCredential;
                string fromAddress = "";
                string identity = "";
                List<string> toList = new List<string>();
                bool sending = false, inPlainAuth = false, inLoginAuth = false;

                // A byte array to streamline bit shuffling.
                char[] buffer = new char[Constants.SMALLBUFFERSIZE];

                // Capture the client's IP information.
                PropertyInfo pi = clientStream.GetType().GetProperty("Socket", BindingFlags.NonPublic | BindingFlags.Instance);
                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;
                            }
                        }
                    }
                }

                clientStreamReader = new StreamReader(clientStream);
                clientStreamWriter = new StreamWriter(clientStream);
                clientStreamWriter.AutoFlush = true;

                // 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);

                    await Functions.SendStreamStringAsync(clientStreamWriter, "500 IP address [" + ip + "] rejected.\r\n");
                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 500 IP address [" + ip + "] rejected.", Proxy.LogLevel.Raw, LogLevel);

                    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);

                // Send our welcome message.
                await Functions.SendStreamStringAsync(clientStreamWriter, "220 " + WelcomeMessage + "\r\n");
                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "220 " + WelcomeMessage, Proxy.LogLevel.Raw, LogLevel);

                // Instantiate an SmtpClient for sending messages to the remote server.
                using (OpaqueMail.Net.SmtpClient smtpClient = new OpaqueMail.Net.SmtpClient(arguments.RemoteServerHostName, arguments.RemoteServerPort))
                {
                    smtpClient.EnableSsl = arguments.RemoteServerEnableSsl;
                    smtpClient.Credentials = arguments.RemoteServerCredential;

                    if (arguments.SmimeValidCertificates != null)
                        smtpClient.SmimeValidCertificates = arguments.SmimeValidCertificates;

                    // Loop through each received command.
                    string command = "";
                    bool stillReceiving = true;
                    while (Started && stillReceiving)
                    {
                        int bytesRead = await clientStreamReader.ReadAsync(buffer, 0, Constants.SMALLBUFFERSIZE);

                        if (bytesRead > 0)
                        {
                            bytesTransmitted += (ulong)bytesRead;

                            command += new string(buffer, 0, bytesRead);

                            if (command.EndsWith("\r\n"))
                            {
                                // Handle continuations of current "DATA" commands.
                                if (sending)
                                {
                                    // Handle the finalization of a "DATA" command.
                                    if (command.EndsWith("\r\n.\r\n"))
                                    {
                                        sending = false;

                                        string messageFrom = "", messageSubject = "", messageSize = "";
                                        try
                                        {
                                            string messageText = command.Substring(0, command.Length - 5);

                                            // Export the message to a local directory.
                                            if (!string.IsNullOrEmpty(arguments.ExportDirectory))
                                            {
                                                string messageId = Functions.ReturnBetween(messageText.ToLower(), "message-id: <", ">");
                                                if (string.IsNullOrEmpty(messageId))
                                                    messageId = Guid.NewGuid().ToString();

                                                string userName = "";
                                                if (smtpClient.Credentials != null)
                                                    userName = ((NetworkCredential)smtpClient.Credentials).UserName;

                                                string fileName = ProxyFunctions.GetExportFileName(arguments.ExportDirectory, messageId, arguments.InstanceId, userName);
                                                File.WriteAllText(fileName, messageText);
                                            }

                                            ReadOnlyMailMessage message = new ReadOnlyMailMessage(messageText, ReadOnlyMailMessageProcessingFlags.IncludeRawHeaders | ReadOnlyMailMessageProcessingFlags.IncludeRawBody);

                                            if (!string.IsNullOrEmpty(arguments.FixedFrom))
                                            {
                                                message.From = Functions.FromMailAddressString(arguments.FixedFrom)[0];

                                                if (message.RawHeaders.Contains("From: "))
                                                    message.RawHeaders = Functions.ReplaceBetween(message.RawHeaders, "From: ", "\r\n", Functions.ToMailAddressString(message.From));
                                                else
                                                    message.RawHeaders = message.RawHeaders.Replace("\r\nSubject: ", "\r\nFrom: " + Functions.ToMailAddressString(message.From) + "\r\nSubject: ");
                                            }

                                            if (!string.IsNullOrEmpty(arguments.FixedTo))
                                            {
                                                MailAddressCollection addresses = Functions.FromMailAddressString(arguments.FixedTo);
                                                foreach (MailAddress address in addresses)
                                                {
                                                    bool addressFound = false;
                                                    foreach (MailAddress existingAddress in message.To)
                                                    {
                                                        if (existingAddress.Address.ToUpper() == address.Address.ToUpper())
                                                            addressFound = true;
                                                    }

                                                    if (!addressFound)
                                                        message.To.Add(address);
                                                }

                                                if (message.RawHeaders.Contains("To: "))
                                                    message.RawHeaders = Functions.ReplaceBetween(message.RawHeaders, "To: ", "\r\n", Functions.ToMailAddressString(message.To));
                                                else
                                                    message.RawHeaders = message.RawHeaders.Replace("\r\nSubject: ", "\r\nTo: " + Functions.ToMailAddressString(message.To) + "\r\nSubject: ");
                                            }

                                            if (!string.IsNullOrEmpty(arguments.FixedCC))
                                            {
                                                MailAddressCollection addresses = Functions.FromMailAddressString(arguments.FixedCC);
                                                foreach (MailAddress address in addresses)
                                                {
                                                    bool addressFound = false;
                                                    foreach (MailAddress existingAddress in message.CC)
                                                    {
                                                        if (existingAddress.Address.ToUpper() == address.Address.ToUpper())
                                                            addressFound = true;
                                                    }

                                                    if (!addressFound)
                                                        message.CC.Add(address);
                                                }

                                                if (message.RawHeaders.Contains("CC: "))
                                                    message.RawHeaders = Functions.ReplaceBetween(message.RawHeaders, "CC: ", "\r\n", Functions.ToMailAddressString(message.To));
                                                else
                                                    message.RawHeaders = message.RawHeaders.Replace("\r\nSubject: ", "\r\nTo: " + Functions.ToMailAddressString(message.To) + "\r\nSubject: ");
                                            }

                                            if (!string.IsNullOrEmpty(arguments.FixedBcc))
                                            {
                                                MailAddressCollection addresses = Functions.FromMailAddressString(arguments.FixedBcc);
                                                foreach (MailAddress address in addresses)
                                                {
                                                    bool addressFound = false;
                                                    foreach (MailAddress existingAddress in message.Bcc)
                                                    {
                                                        if (existingAddress.Address.ToUpper() == address.Address.ToUpper())
                                                            addressFound = true;
                                                    }

                                                    if (!addressFound)
                                                        message.Bcc.Add(address);
                                                }
                                            }

                                            // Insert the fixed signature if one exists.
                                            if (!string.IsNullOrEmpty(arguments.FixedSignature))
                                            {
                                                int endBodyPos = message.Body.IndexOf("</BODY>", StringComparison.OrdinalIgnoreCase);
                                                if (endBodyPos > -1)
                                                    message.Body = message.Body.Substring(0, endBodyPos) + arguments.FixedSignature + message.Body.Substring(endBodyPos);
                                                else
                                                    message.Body += arguments.FixedSignature;
                                            }

                                            // If the received message is already signed or encrypted and we don't want to remove previous S/MIME operations, forward it as-is.
                                            string contentType = message.ContentType;
                                            if ((contentType.StartsWith("application/pkcs7-mime") || contentType.StartsWith("application/x-pkcs7-mime") || contentType.StartsWith("application/x-pkcs7-signature")) && !arguments.SmimeRemovePreviousOperations)
                                            {
                                                message.SmimeSigned = message.SmimeEncryptedEnvelope = message.SmimeTripleWrapped = false;
                                                await smtpClient.SendAsync(message);
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: " + message, Proxy.LogLevel.Raw, LogLevel);
                                            }
                                            else
                                            {
                                                messageFrom = message.From.Address;
                                                messageSubject = message.Subject;
                                                messageSize = message.Size.ToString("N0");

                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Forwarding message from {" + message.From.Address + "} with subject {" + message.Subject + "} and size of {" + message.Size.ToString("N0") + "}.", Proxy.LogLevel.Verbose, LogLevel);

                                                foreach (string toListAddress in toList)
                                                {
                                                    if (!message.AllRecipients.Contains(toListAddress))
                                                    {
                                                        message.AllRecipients.Add(toListAddress);
                                                        message.Bcc.Add(toListAddress);
                                                    }
                                                }

                                                // Attempt to sign and encrypt the envelopes of all messages, but still send if unable to.
                                                message.SmimeSettingsMode = SmimeSettingsMode.BestEffort;

                                                // Apply S/MIME settings.
                                                message.SmimeSigned = arguments.SmimeSigned;
                                                message.SmimeEncryptedEnvelope = arguments.SmimeEncryptedEnvelope;
                                                message.SmimeTripleWrapped = arguments.SmimeTripleWrapped;

                                                // Look up the S/MIME signing certificate for the current sender.  If it doesn't exist, create one.
                                                message.SmimeSigningCertificate = CertHelper.GetCertificateBySubjectName(StoreLocation.LocalMachine, message.From.Address);
                                                if (message.SmimeSigningCertificate == null)
                                                    message.SmimeSigningCertificate = CertHelper.CreateSelfSignedCertificate("E=" + message.From.Address, message.From.Address, StoreLocation.LocalMachine, true, 4096, 10);

                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "C: " + message.RawHeaders + "\r\n\r\n" + message.RawBody, Proxy.LogLevel.Raw, LogLevel);

                                                // Send the message.
                                                await smtpClient.SendAsync(message.AsMailMessage());

                                                // Check the signing certificate's expiration to determine if we should send a reminder.
                                                if (arguments.SendCertificateReminders && message.SmimeSigningCertificate != null)
                                                {
                                                    string expirationDateString = message.SmimeSigningCertificate.GetExpirationDateString();
                                                    TimeSpan expirationTime = DateTime.Parse(expirationDateString) - DateTime.Now;
                                                    if (expirationTime.TotalDays < 30)
                                                    {
                                                        bool sendReminder = true;
                                                        if (CertificateReminders.ContainsKey(message.SmimeSigningCertificate))
                                                        {
                                                            TimeSpan timeSinceLastReminder = DateTime.Now - CertificateReminders[message.SmimeSigningCertificate];
                                                            if (timeSinceLastReminder.TotalHours < 24)
                                                                sendReminder = false;
                                                        }

                                                        // Send the reminder message.
                                                        if (sendReminder)
                                                        {
                                                            OpaqueMail.Net.MailMessage reminderMessage = new OpaqueMail.Net.MailMessage(message.From, message.From);
                                                            reminderMessage.Subject = "OpaqueMail: S/MIME Certificate Expires " + expirationDateString;
                                                            reminderMessage.Body = "Your OpaqueMail S/MIME Certificate will expire in " + ((int)expirationTime.TotalDays) + " days on " + expirationDateString + ".\r\n\r\n" +
                                                                "Certificate Subject Name: " + message.SmimeSigningCertificate.Subject + "\r\n" +
                                                                "Certificate Serial Number: " + message.SmimeSigningCertificate.SerialNumber + "\r\n" +
                                                                "Certificate Issuer: " + message.SmimeSigningCertificate.Issuer + "\r\n\r\n" +
                                                                "Please renew or enroll a new certificate to continue protecting your e-mail privacy.\r\n\r\n" +
                                                                "This is an automated message sent from the OpaqueMail Proxy on " + Functions.GetLocalFQDN() + ".  " +
                                                                "For more information, visit http://opaquemail.org/.";

                                                            reminderMessage.SmimeEncryptedEnvelope = message.SmimeEncryptedEnvelope;
                                                            reminderMessage.SmimeEncryptionOptionFlags = message.SmimeEncryptionOptionFlags;
                                                            reminderMessage.SmimeSettingsMode = message.SmimeSettingsMode;
                                                            reminderMessage.SmimeSigned = message.SmimeSigned;
                                                            reminderMessage.SmimeSigningCertificate = message.SmimeSigningCertificate;
                                                            reminderMessage.SmimeSigningOptionFlags = message.SmimeSigningOptionFlags;
                                                            reminderMessage.SmimeTripleWrapped = message.SmimeTripleWrapped;

                                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Certificate with Serial Number {" + message.SmimeSigningCertificate.SerialNumber + "} expiring.  Sending reminder to {" + message.From.Address + "}.", Proxy.LogLevel.Information, LogLevel);

                                                            await smtpClient.SendAsync(reminderMessage);

                                                            CertificateReminders[message.SmimeSigningCertificate] = DateTime.Now;
                                                        }
                                                    }
                                                }
                                            }

                                            await Functions.SendStreamStringAsync(clientStreamWriter, "250 Forwarded\r\n");
                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250 Forwarded", Proxy.LogLevel.Raw, LogLevel);

                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Message from {" + message.From.Address + "} with subject {" + message.Subject + "} and size of {" + message.Size.ToString("N0") + "} successfully forwarded.", Proxy.LogLevel.Verbose, LogLevel);
                                        }
                                        catch (Exception ex)
                                        {
                                            // Report if an exception was encountering sending the message.
                                            Functions.SendStreamString(clientStreamWriter, "500 Error occurred when forwarding\r\n");
                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 500 Error occurred when forwarding", Proxy.LogLevel.Raw, LogLevel);

                                            if (arguments.DebugMode || System.Diagnostics.Debugger.IsAttached)
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Error when forwarding message from {" + messageFrom + "} with subject {" + messageSubject + "} and size of {" + messageSize + "}.  Exception: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel);
                                            else
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Error when forwarding message from {" + messageFrom + "} with subject {" + messageSubject + "} and size of {" + messageSize + "}.  Exception: " + ex.Message, Proxy.LogLevel.Error, LogLevel);
                                        }
                                        command = "";
                                    }
                                }
                                else
                                {
                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "C: " + new string(buffer, 0, bytesRead), Proxy.LogLevel.Raw, LogLevel);

                                    // Handle continuations of current "AUTH PLAIN" commands.
                                    if (inPlainAuth)
                                    {
                                        inPlainAuth = false;
                                        // Split up an AUTH PLAIN handshake into its components.
                                        string authString = Encoding.UTF8.GetString(Convert.FromBase64String(command));
                                        string[] authStringParts = authString.Split(new char[] { '\0' }, 3);
                                        if (authStringParts.Length > 2 && arguments.RemoteServerCredential == null)
                                            smtpClient.Credentials = new NetworkCredential(authStringParts[1], authStringParts[2]);

                                        await Functions.SendStreamStringAsync(clientStreamWriter, "235 OK\r\n");
                                        ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 235 OK", Proxy.LogLevel.Raw, LogLevel);

                                        command = "";
                                    }
                                    // Handle continuations of current "AUTH LOGIN" commands.
                                    else if (inLoginAuth)
                                    {
                                        if (smtpClient.Credentials == null)
                                        {
                                            // Handle the username being received for the first time.
                                            smtpClient.Credentials = new NetworkCredential();
                                            ((NetworkCredential)smtpClient.Credentials).UserName = Functions.FromBase64(command.Substring(0, command.Length - 2));

                                            await Functions.SendStreamStringAsync(clientStreamWriter, "334 UGFzc3dvcmQ6\r\n");
                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 334 UGFzc3dvcmQ6", Proxy.LogLevel.Raw, LogLevel);
                                        }
                                        else
                                        {
                                            // Handle the password.
                                            inLoginAuth = false;
                                            ((NetworkCredential)smtpClient.Credentials).Password = Functions.FromBase64(command.Substring(0, command.Length - 2));

                                            await Functions.SendStreamStringAsync(clientStreamWriter, "235 OK\r\n");
                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 235 OK", Proxy.LogLevel.Raw, LogLevel);
                                        }
                                        command = "";
                                    }
                                    else
                                    {
                                        // Otherwise, look at the verb of the incoming command.
                                        string[] commandParts = command.Substring(0, command.Length - 2).Replace("\r", "").Split(new char[] { ' ' }, 2);

                                        if (LogLevel == Proxy.LogLevel.Verbose)
                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Command {" + commandParts[0] + "} received.", Proxy.LogLevel.Verbose, LogLevel);

                                        switch (commandParts[0].ToUpper())
                                        {
                                            case "AUTH":
                                                // Support authentication.
                                                if (commandParts.Length > 1)
                                                {
                                                    commandParts = command.Substring(0, command.Length - 2).Replace("\r", "").Split(new char[] { ' ' });
                                                    switch (commandParts[1].ToUpper())
                                                    {
                                                        case "PLAIN":
                                                            // Prepare to handle a continuation command.
                                                            inPlainAuth = true;
                                                            await Functions.SendStreamStringAsync(clientStreamWriter, "334 Proceed\r\n");
                                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 334 Proceed", Proxy.LogLevel.Raw, LogLevel);

                                                            break;
                                                        case "LOGIN":
                                                            inLoginAuth = true;
                                                            if (commandParts.Length > 2)
                                                            {
                                                                // Parse the username and request a password.
                                                                smtpClient.Credentials = new NetworkCredential();
                                                                ((NetworkCredential)smtpClient.Credentials).UserName = Functions.FromBase64(commandParts[2]);

                                                                await Functions.SendStreamStringAsync(clientStreamWriter, "334 UGFzc3dvcmQ6\r\n");
                                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 334 UGFzc3dvcmQ6", Proxy.LogLevel.Raw, LogLevel);
                                                            }
                                                            else
                                                            {
                                                                // Request a username only.
                                                                await Functions.SendStreamStringAsync(clientStreamWriter, "334 VXNlcm5hbWU6\r\n");
                                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 334 VXNlcm5hbWU6", Proxy.LogLevel.Raw, LogLevel);
                                                            }
                                                            break;
                                                        default:
                                                            // Split up an AUTH PLAIN handshake into its components.
                                                            string authString = Encoding.UTF8.GetString(Convert.FromBase64String(commandParts[1].Substring(6)));
                                                            string[] authStringParts = authString.Split(new char[] { '\0' }, 3);
                                                            if (authStringParts.Length > 2 && arguments.RemoteServerCredential == null)
                                                                smtpClient.Credentials = new NetworkCredential(authStringParts[1], authStringParts[2]);

                                                            await Functions.SendStreamStringAsync(clientStreamWriter, "235 OK\r\n");
                                                            ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 235 OK", Proxy.LogLevel.Raw, LogLevel);
                                                            break;
                                                    }
                                                }
                                                else
                                                {
                                                    await Functions.SendStreamStringAsync(clientStreamWriter, "500 Unknown verb\r\n");
                                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 500 Unknown verb", Proxy.LogLevel.Raw, LogLevel);
                                                }
                                                break;
                                            case "DATA":
                                                // Prepare to handle continuation data.
                                                sending = true;
                                                command = command.Substring(6);
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "354 Send message content; end with <CRLF>.<CRLF>\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 354 Send message content; end with <CRLF>.<CRLF>", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "EHLO":
                                                // Proceed with the login and send a list of supported commands.
                                                if (commandParts.Length > 1)
                                                    identity = commandParts[1] + " ";
                                                if (arguments.LocalEnableSsl)
                                                {
                                                    await Functions.SendStreamStringAsync(clientStreamWriter, "250-Hello " + identity + "[" + ip + "], please proceed\r\n250-AUTH LOGIN PLAIN\r\n250-RSET\r\n250 STARTTLS\r\n");
                                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250-Hello " + identity + "[" + ip + "], please proceed\r\n250-AUTH LOGIN PLAIN\r\n250-RSET\r\n250 STARTTLS", Proxy.LogLevel.Raw, LogLevel);
                                                }
                                                else
                                                {
                                                    await Functions.SendStreamStringAsync(clientStreamWriter, "250-Hello " + identity + "[" + ip + "], please proceed\r\n250-AUTH LOGIN PLAIN\r\n250 RSET\r\n");
                                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250-Hello " + identity + "[" + ip + "], please proceed\r\n250-AUTH LOGIN PLAIN\r\n250 RSET", Proxy.LogLevel.Raw, LogLevel);
                                                }
                                                break;
                                            case "HELO":
                                                // Proceed with the login.
                                                if (commandParts.Length > 1)
                                                    identity = commandParts[1];
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "250 Hello " + identity + " [" + ip + "], please proceed\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: Hello " + identity + " [" + ip + "], please proceed", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "MAIL":
                                            case "SAML":
                                            case "SEND":
                                            case "SOML":
                                                // Accept the from address.
                                                if (commandParts.Length > 1 && commandParts[1].Length > 5)
                                                    fromAddress = commandParts[1].Substring(5);
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "250 OK\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250 OK", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "NOOP":
                                                // Prolong the current session.
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "250 Still here\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250 Still here", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "PASS":
                                                // Support authentication.
                                                if (commandParts.Length > 1 && arguments.RemoteServerCredential == null)
                                                    ((NetworkCredential)smtpClient.Credentials).Password = commandParts[1];
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "235 OK\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 235 OK", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "QUIT":
                                                // Wait one second then force the current connection closed.
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "221 Bye\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 221 Bye", Proxy.LogLevel.Raw, LogLevel);

                                                Thread.Sleep(1000);

                                                if (clientStream != null)
                                                    clientStream.Dispose();
                                                if (client != null)
                                                    client.Close();
                                                break;
                                            case "RCPT":
                                                // Acknolwedge recipients.
                                                if (commandParts.Length > 1 && commandParts[1].Length > 6)
                                                    toList.Add(commandParts[1].Substring(5, commandParts[1].Length - 6));
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "250 OK\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250 OK", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "RSET":
                                                // Reset the current message arguments.
                                                fromAddress = "";
                                                toList.Clear();

                                                await Functions.SendStreamStringAsync(clientStreamWriter, "250 OK\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 250 OK", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "STARTTLS":
                                                // If supported, upgrade the session's security through a TLS handshake.
                                                if (arguments.LocalEnableSsl)
                                                {
                                                    await Functions.SendStreamStringAsync(clientStreamWriter, "220 Go ahead\r\n");
                                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 220 Go ahead", Proxy.LogLevel.Raw, LogLevel);

                                                    if (!(clientStream is SslStream))
                                                    {
                                                        clientStream = new SslStream(clientStream);
                                                        ((SslStream)clientStream).AuthenticateAsServer(arguments.Certificate);

                                                        clientStreamReader = new StreamReader(clientStream);
                                                        clientStreamWriter = new StreamWriter(clientStream);
                                                        clientStreamWriter.AutoFlush = true;
                                                    }
                                                }
                                                else
                                                {
                                                    await Functions.SendStreamStringAsync(clientStreamWriter, "500 Unknown verb\r\n");
                                                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 500 Unknown verb", Proxy.LogLevel.Raw, LogLevel);
                                                }
                                                break;
                                            case "USER":
                                                // Support authentication.
                                                if (commandParts.Length > 1 && arguments.RemoteServerCredential == null)
                                                    ((NetworkCredential)smtpClient.Credentials).UserName = commandParts[1];

                                                await Functions.SendStreamStringAsync(clientStreamWriter, "235 OK\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 235 OK", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            case "VRFY":
                                                // Notify that we can't verify addresses.
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "252 I'm just a proxy\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 252 I'm just a proxy", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                            default:
                                                await Functions.SendStreamStringAsync(clientStreamWriter, "500 Unknown verb\r\n");
                                                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "S: 500 Unknown verb", Proxy.LogLevel.Raw, LogLevel);
                                                break;
                                        }

                                        command = "";
                                    }
                                }
                            }
                        }
                        else
                            stillReceiving = false;
                    }
                }
            }
            catch (ObjectDisposedException)
            {
                // Ignore either stream being closed.
            }
            catch (SocketException ex)
            {
                if (arguments.DebugMode || System.Diagnostics.Debugger.IsAttached)
                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Exception communicating with {" + arguments.RemoteServerHostName + "} on port {" + arguments.RemoteServerPort + "}: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel);
                else
                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "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, arguments.ConnectionId, "Exception: " + ex.ToString(), Proxy.LogLevel.Error, LogLevel);
                else
                    ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Exception: " + ex.Message, Proxy.LogLevel.Error, LogLevel);
            }
            finally
            {
                ProxyFunctions.Log(LogWriter, SessionId, arguments.ConnectionId, "Connection from {" + ip + "} closed after transmitting {" + bytesTransmitted.ToString("N0") + "} bytes.", Proxy.LogLevel.Information, LogLevel);

                // Clean up after any unexpectedly closed connections.
                if (clientStreamWriter != null)
                    clientStreamWriter.Dispose();
                if (clientStreamReader != null)
                    clientStreamReader.Dispose();
                if (clientStream != null)
                    clientStream.Dispose();
                if (client != null)
                    client.Close();
            }
        }
 /// <summary>
 /// Appends a message to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="message">The raw message to append.</param>
 /// <param name="flags">Optional flags to be applied for the message.</param>
 /// <param name="date">Optional date for the message.</param>
 public bool AppendMessage(string mailboxName, ReadOnlyMailMessage message, string[] flags, DateTime?date)
 {
     return(Task.Run(() => AppendMessageAsync(mailboxName, message, flags, date)).Result);
 }
 /// <summary>
 /// Appends a message to the specified mailbox.
 /// </summary>
 /// <param name="mailboxName">The name of the mailbox to append to.</param>
 /// <param name="message">The raw message to append.</param>
 public bool AppendMessage(string mailboxName, ReadOnlyMailMessage message)
 {
     return(Task.Run(() => AppendMessageAsync(mailboxName, message)).Result);
 }