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