public String GetLastMail(AccountData account) { for (int i = 0; i < 20; i++) { Pop3Client pop3Client = new Pop3Client("localhost", 110, account.Name, account.Password, false); pop3Client.Connect(); pop3Client.Authenticate(); if (pop3Client.GetMessageCount() > 0) { OpaqueMail.MailMessage message = pop3Client.GetMessage(1); string body = message.Body; pop3Client.DeleteMessage(1); pop3Client.LogOut(); return(body); } else { System.Threading.Thread.Sleep(3000); } } return(null); }
/// <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. MailMessage message = new MailMessage(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> /// <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, MailMessage 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, MailMessage message) { return Task.Run(() => AppendMessageAsync(mailboxName, message)).Result; }
/// <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, MailMessage[] messages) { return Task.Run(() => AppendMessagesAsync(mailboxName, messages, new string[] { }, null)).Result; }
/// <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, MailMessage[] 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> /// 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, MailMessage message, string[] flags, DateTime? date) { return await AppendMessageAsync(mailboxName, message.RawMessage, flags, date); }
/// <summary> /// Sends the specified message to an SMTP server for delivery. /// Performs requested S/MIME signing and encryption. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> public async Task SendAsync(MailMessage message) { await SmimeSendRawAsync(message); }
/// <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> /// <param name="seekStart">Index of first character of the message body to return.</param> /// <param name="seekEnd">Number of characters of the message body to return.</param> private async Task<MailMessage> GetMessageHelper(string mailboxName, int id, bool headersOnly, bool setSeenFlag, int seekStart, int seekEnd, 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, // whether this is a partial request, 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) { if (seekStart > -1) { if (seekEnd > -1) await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY[]<" + seekStart + "> UID FLAGS)\r\n"); else await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY[]<" + seekStart + "." + seekEnd + "> UID FLAGS)\r\n"); } else await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY[] UID FLAGS)\r\n"); } else { if (seekStart > -1) { if (seekEnd > -1) await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY.PEEK[]<" + seekStart + "> UID FLAGS)\r\n"); else await SendCommandAsync(commandTag, uidPrefix + "FETCH " + id + " (BODY.PEEK[]<" + seekStart + "." + seekEnd + "> 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") > -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. int lineBreak = response.IndexOf("\r\n"); string firstLine = response.Substring(0, lineBreak).ToUpper(); int bodyLength = -1; int.TryParse(Functions.ReturnBetween(firstLine, "BODY[] {", "}"), out bodyLength); if (bodyLength > 0) response = response.Substring(lineBreak + 2, bodyLength); else { response = response.Substring(response.IndexOf("\r\n") + 2); response = response.Substring(0, response.Length - 2); } MailMessage message = new MailMessage(response, ProcessingFlags); message.ImapUid = uid; message.Mailbox = mailboxName; message.ParseFlagsString(flagsString); return message; } else return null; }
/// <summary> /// Create a byte array containing a signed S/MIME message. /// </summary> /// <param name="buffer">A byte array to streamline bit shuffling.</param> /// <param name="contentBytes">The contents of the envelope to be encrypted.</param> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> /// <param name="alreadyEncrypted">Whether a portion of the message has previously been signed, as when triple wrapping.</param> private byte[] SmimeSign(byte[] buffer, byte[] contentBytes, MailMessage message, bool alreadyEncrypted) { if (message.SmimeSigningCertificate == null) { // If the implementation requires S/MIME signing (the default), throw an error if there's no certificate. if ((message.SmimeSettingsMode & SmimeSettingsMode.RequireExactSettings) > 0) throw new SmtpException("Trying to send a signed message, but no signing certificate has been assigned."); else return contentBytes; } // First, create a buffer for tracking the unsigned portion of this message. StringBuilder unsignedMessageBuilder = new StringBuilder(Constants.SMALLSBSIZE); // If triple wrapping, the previous layer was an encrypted envelope and needs to be Base64 encoded. if (alreadyEncrypted) { unsignedMessageBuilder.Append("Content-Type: application/pkcs7-mime; smime-type=enveloped-data;\r\n\tname=\"smime.p7m\"\r\n"); unsignedMessageBuilder.Append("Content-Transfer-Encoding: base64\r\n"); unsignedMessageBuilder.Append("Content-Description: \"S/MIME Cryptographic envelopedCms\"\r\n"); unsignedMessageBuilder.Append("Content-Disposition: attachment; filename=\"smime.p7m\"\r\n\r\n"); unsignedMessageBuilder.Append(Functions.ToBase64String(contentBytes)); } else unsignedMessageBuilder.Append(Encoding.UTF8.GetString(contentBytes)); // Prepare the signing parameters. ContentInfo contentInfo = new ContentInfo(Encoding.UTF8.GetBytes(unsignedMessageBuilder.ToString())); SignedCms signedCms = new SignedCms(contentInfo, true); CmsSigner signer = new CmsSigner(message.SubjectIdentifierType, message.SmimeSigningCertificate); signer.IncludeOption = X509IncludeOption.WholeChain; // Sign the current time. if ((message.SmimeSigningOptionFlags & SmimeSigningOptionFlags.SignTime) > 0) { Pkcs9SigningTime signingTime = new Pkcs9SigningTime(); signer.SignedAttributes.Add(signingTime); } // Encode the signed message. signedCms.ComputeSignature(signer); byte[] signedBytes = signedCms.Encode(); // Embed the signed and original version of the message using MIME. StringBuilder messageBuilder = new StringBuilder(Constants.SMALLSBSIZE); // Build the MIME message by embedding the unsigned and signed portions. messageBuilder.Append("This is a multi-part S/MIME signed message.\r\n\r\n"); messageBuilder.Append("--" + (alreadyEncrypted ? SmimeTripleSignedCmsBoundaryName : SmimeSignedCmsBoundaryName) + "\r\n"); messageBuilder.Append(unsignedMessageBuilder.ToString()); messageBuilder.Append("\r\n--" + (alreadyEncrypted ? SmimeTripleSignedCmsBoundaryName : SmimeSignedCmsBoundaryName) + "\r\n"); messageBuilder.Append("Content-Type: application/x-pkcs7-signature; smime-type=signed-data; name=\"smime.p7s\"\r\n"); messageBuilder.Append("Content-Transfer-Encoding: base64\r\n"); messageBuilder.Append("Content-Description: \"S/MIME Cryptographic signedCms\"\r\n"); messageBuilder.Append("Content-Disposition: attachment; filename=\"smime.p7s\"\r\n\r\n"); messageBuilder.Append(Functions.ToBase64String(signedBytes, 0, signedBytes.Length)); messageBuilder.Append("\r\n--" + (alreadyEncrypted ? SmimeTripleSignedCmsBoundaryName : SmimeSignedCmsBoundaryName) + "--\r\n"); return Encoding.UTF8.GetBytes(messageBuilder.ToString()); }
/// <summary> /// Helper function for sending the specified message to an SMTP server for delivery with S/MIME encoding. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> private async Task SmimeSendAsync(MailMessage message) { // Require one or more recipients. if (message.To.Count + message.CC.Count + message.Bcc.Count < 1) throw new SmtpException("One or more recipients must be specified via the '.To', '.CC', or '.Bcc' collections."); // Require a signing certificate to be specified. if ((message.SmimeSigned || message.SmimeTripleWrapped) && message.SmimeSigningCertificate == null) throw new SmtpException("A signing certificate must be passed prior to signing."); // Ensure the rendering engine expects MIME encoding. message.Headers["MIME-Version"] = "1.0"; // OpaqueMail optional setting for protecting the subject. // Note: This is not part of the current RFC specifcation and should only be used when sending to other OpaqueMail agents. if ((message.SmimeEncryptedEnvelope || message.SmimeTripleWrapped) && (message.SmimeEncryptionOptionFlags & (SmimeEncryptionOptionFlags.EncryptSubject)) > 0) { message.Headers["X-Subject-Encryption"] = "true"; message.Body = "Subject: " + message.Subject + "\r\n" + message.Body; message.Subject = Guid.NewGuid().ToString(); } // Generate a multipart/mixed message containing the email's body, alternate views, and attachments. string MIMEMessage = await message.MimeEncode("7bit", SmimeBoundaryName); message.Headers["Content-Type"] = "multipart/mixed; boundary=\"" + SmimeBoundaryName + "\""; message.Headers["Content-Transfer-Encoding"] = "7bit"; // Skip the MIME header. message.Body = MIMEMessage; message.Body = message.Body.Substring(message.Body.IndexOf("\r\n\r\n") + 4); // Determine the body encoding, defaulting to UTF-8. Encoding bodyEncoding = message.BodyEncoding != null ? message.BodyEncoding : new UTF8Encoding(); Encoder bodyEncoder = bodyEncoding.GetEncoder(); // Encode and return the message. char[] chars = MIMEMessage.ToCharArray(); byte[] MIMEMessageBytes = new byte[bodyEncoder.GetByteCount(chars, 0, chars.Length, false)]; int byteCount = bodyEncoder.GetBytes(chars, 0, chars.Length, MIMEMessageBytes, 0, true); // Handle S/MIME signing. bool successfullySigned = false; if (message.SmimeSigned || message.SmimeTripleWrapped) { int unsignedSize = MIMEMessageBytes.Length; MIMEMessageBytes = SmimeSign(buffer, MIMEMessageBytes, message, false); successfullySigned = MIMEMessageBytes.Length != unsignedSize; if (successfullySigned) { // Remove any prior content dispositions. if (message.Headers["Content-Disposition"] != null) message.Headers.Remove("Content-Disposition"); message.Headers["Content-Type"] = "multipart/signed; protocol=\"application/x-pkcs7-signature\"; micalg=sha1;\r\n\tboundary=\"" + SmimeSignedCmsBoundaryName + "\""; message.Headers["Content-Transfer-Encoding"] = "7bit"; message.Body = Encoding.UTF8.GetString(MIMEMessageBytes); } } // Handle S/MIME envelope encryption. bool successfullyEncrypted = false; if (message.SmimeEncryptedEnvelope || message.SmimeTripleWrapped) { int unencryptedSize = MIMEMessageBytes.Length; MIMEMessageBytes = SmimeEncryptEnvelope(MIMEMessageBytes, message, successfullySigned); successfullyEncrypted = MIMEMessageBytes.Length != unencryptedSize; // If the message won't be triple-wrapped, wrap the encrypted message with MIME. if (successfullyEncrypted && (!successfullySigned || !message.SmimeTripleWrapped)) { message.Headers["Content-Type"] = "application/pkcs7-mime; name=smime.p7m;\r\n\tsmime-type=enveloped-data"; message.Headers["Content-Transfer-Encoding"] = "base64"; message.Body = Functions.ToBase64String(MIMEMessageBytes) + "\r\n"; } } // Handle S/MIME triple wrapping (i.e. signing, envelope encryption, then signing again). if (successfullyEncrypted) { if (message.SmimeTripleWrapped) { message.Headers["Content-Type"] = "multipart/signed; protocol=\"application/x-pkcs7-signature\"; micalg=sha1;\r\n\tboundary=\"" + SmimeTripleSignedCmsBoundaryName + "\""; message.Headers["Content-Transfer-Encoding"] = "7bit"; message.Body = Encoding.UTF8.GetString(SmimeSign(buffer, MIMEMessageBytes, message, true)); } else message.Headers["Content-Disposition"] = "attachment; filename=smime.p7m"; } await SmimeSendRawAsync(message); }
/// <summary> /// Create a byte array containing an encrypted S/MIME envelope. /// </summary> /// <param name="contentBytes">The contents of the envelope to be encrypted.</param> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> private byte[] SmimeEncryptEnvelope(byte[] contentBytes, MailMessage message, bool alreadySigned) { // Resolve recipient public keys. Dictionary<string, MailAddress> addressesNeedingPublicKeys; HashSet<string> addressesWithPublicKeys; ResolvePublicKeys(message, out addressesWithPublicKeys, out addressesNeedingPublicKeys); // Throw an error if we're unable to encrypt the message for one or more recipients and encryption is explicitly required. if (addressesNeedingPublicKeys.Count > 0) { // If the implementation requires S/MIME encryption (the default), throw an error if there's no certificate. if ((message.SmimeSettingsMode & SmimeSettingsMode.RequireExactSettings) > 0) { StringBuilder exceptionMessage = new StringBuilder(Constants.TINYSBSIZE); exceptionMessage.Append("Trying to send encrypted message to one or more recipients without a trusted public key.\r\nRecipients without public keys: "); foreach (string addressNeedingPublicKey in addressesNeedingPublicKeys.Keys) exceptionMessage.Append(addressNeedingPublicKey + ", "); exceptionMessage.Remove(exceptionMessage.Length - 2, 2); throw new SmtpException(exceptionMessage.ToString()); } else return contentBytes; } if (alreadySigned) { // If already signed, prepend S/MIME headers. StringBuilder contentBuilder = new StringBuilder(Constants.TINYSBSIZE); contentBuilder.Append("Content-Type: multipart/signed; protocol=\"application/x-pkcs7-signature\"; micalg=sha1;\r\n\tboundary=\"" + SmimeSignedCmsBoundaryName + "\"\r\n"); contentBuilder.Append("Content-Transfer-Encoding: 7bit\r\n\r\n"); contentBytes = Encoding.UTF8.GetBytes(contentBuilder.ToString() + Encoding.UTF8.GetString(contentBytes)); } // Prepare the encryption envelope. ContentInfo contentInfo = new ContentInfo(contentBytes); EnvelopedCms envelope; // If a specific algorithm is specified, choose that. Otherwise, negotiate which algorithm to use. if (SmimeAlgorithmIdentifier != null) envelope = new EnvelopedCms(contentInfo, SmimeAlgorithmIdentifier); else envelope = new EnvelopedCms(contentInfo); // Encrypt the symmetric session key using each recipient's public key. foreach (string addressWithPublicKey in addressesWithPublicKeys) { CmsRecipient recipient = new CmsRecipient(SmimeCertificateCache[addressWithPublicKey]); envelope.Encrypt(recipient); } return envelope.Encode(); }
/// <summary> /// Sends the specified message to an SMTP server for delivery without making modifications to the body. /// Necessary because the standard SmtpClient.Send() may slightly alter messages, invalidating signatures. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> private async Task SmimeSendRawAsync(MailMessage message) { // Connect to the SMTP server. TcpClient SmtpTcpClient = new TcpClient(); SmtpTcpClient.Connect(Host, Port); Stream SmtpStream = SmtpTcpClient.GetStream(); // Use stream readers and writers to simplify I/O. StreamReader reader = new StreamReader(SmtpStream); StreamWriter writer = new StreamWriter(SmtpStream); writer.AutoFlush = true; // Read the welcome message. string response = await reader.ReadLineAsync(); // Send EHLO and find out server capabilities. await writer.WriteLineAsync("EHLO " + Host); char[] charBuffer = new char[Constants.SMALLBUFFERSIZE]; int bytesRead = await reader.ReadAsync(charBuffer, 0, Constants.SMALLBUFFERSIZE); response = new string(charBuffer, 0, bytesRead); if (!response.StartsWith("2")) throw new SmtpException("Unable to connect to remote server '" + Host + "'. Sent 'EHLO' and received '" + response + "'."); // Stand up a TLS/SSL stream. if (EnableSsl) { await writer.WriteLineAsync("STARTTLS"); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Unable to start TLS/SSL protection with '" + Host + "'. Received '" + response + "'."); SmtpStream = new SslStream(SmtpStream); ((SslStream)SmtpStream).AuthenticateAsClient(Host); reader = new StreamReader(SmtpStream); writer = new StreamWriter(SmtpStream); writer.AutoFlush = true; await writer.WriteLineAsync("EHLO " + Host); bytesRead = await reader.ReadAsync(charBuffer, 0, Constants.SMALLBUFFERSIZE); response = new string(charBuffer, 0, bytesRead); } // Authenticate using the AUTH LOGIN command. if (Credentials != null) { NetworkCredential cred = (NetworkCredential)Credentials; await writer.WriteLineAsync("AUTH LOGIN"); response = await reader.ReadLineAsync(); if (!response.StartsWith("3")) throw new SmtpException("Unable to authenticate with server '" + Host + "'. Received '" + response + "'."); await writer.WriteLineAsync(Functions.ToBase64String(cred.UserName)); response = await reader.ReadLineAsync(); await writer.WriteLineAsync(Functions.ToBase64String(cred.Password)); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Unable to authenticate with server '" + Host + "'. Received '" + response + "'."); } // Build our raw headers block. StringBuilder rawHeaders = new StringBuilder(Constants.SMALLSBSIZE); // Specify who the message is from. rawHeaders.Append(Functions.SpanHeaderLines("From: " + Functions.EncodeMailHeader(Functions.ToMailAddressString(message.From))) + "\r\n"); await writer.WriteLineAsync("MAIL FROM:<" + message.From.Address + ">"); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Exception communicating with server '" + Host + "'. Sent 'MAIL FROM' and received '" + response + "'."); // Identify all recipients of the message. if (message.To.Count > 0) rawHeaders.Append(Functions.SpanHeaderLines("To: " + Functions.EncodeMailHeader(message.To.ToString())) + "\r\n"); foreach (MailAddress address in message.To) { await writer.WriteLineAsync("RCPT TO:<" + address.Address + ">"); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Exception communicating with server '" + Host + "'. Sent 'RCPT TO' and received '" + response + "'."); } if (message.CC.Count > 0) rawHeaders.Append(Functions.SpanHeaderLines("CC: " + Functions.EncodeMailHeader(message.CC.ToString())) + "\r\n"); foreach (MailAddress address in message.CC) { await writer.WriteLineAsync("RCPT TO:<" + address.Address + ">"); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Exception communicating with server '" + Host + "'. Sent 'RCPT TO' and received '" + response + "'."); } foreach (MailAddress address in message.Bcc) { await writer.WriteLineAsync("RCPT TO:<" + address.Address + ">"); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Exception communicating with server '" + Host + "'. Sent 'RCPT TO' and received '" + response + "'."); } // Ensure a content type is set. if (string.IsNullOrEmpty(message.ContentType)) { if (Functions.AppearsHTML(message.Body)) message.ContentType = "text/html"; else message.ContentType = "text/plain"; } message.Headers["Content-Type"] = message.ContentType + (!string.IsNullOrEmpty(message.CharSet) ? "; charset=\"" + message.CharSet + "\"" : ""); // If the body hasn't been processed, handle encoding of extended characters. if (string.IsNullOrEmpty(message.RawBody)) { bool extendedCharacterFound = false; foreach (char headerCharacter in message.Body.ToCharArray()) { if (headerCharacter > 127) { extendedCharacterFound = true; break; } } if (extendedCharacterFound) { message.Headers["Content-Transfer-Encoding"] = "base64"; message.Body = Functions.ToBase64String(message.Body); } message.RawBody = message.Body; } // Send the raw message. await writer.WriteLineAsync("DATA"); response = await reader.ReadLineAsync(); if (!response.StartsWith("3")) throw new SmtpException("Exception communicating with server '" + Host + "'. Sent 'DATA' and received '" + response + "'."); rawHeaders.Append(Functions.SpanHeaderLines("Subject: " + Functions.EncodeMailHeader(message.Subject)) + "\r\n"); foreach (string rawHeader in message.Headers) { switch (rawHeader.ToUpper()) { case "BCC": case "CC": case "FROM": case "SUBJECT": case "TO": break; default: rawHeaders.Append(Functions.SpanHeaderLines(rawHeader + ": " + message.Headers[rawHeader]) + "\r\n"); break; } } await writer.WriteAsync(rawHeaders.ToString() + "\r\n" + message.RawBody + "\r\n.\r\n"); response = await reader.ReadLineAsync(); if (!response.StartsWith("2")) throw new SmtpException("Exception communicating with server '" + Host + "'. Sent message and received '" + response + "'."); // Clean up this connection. await writer.WriteLineAsync("QUIT"); writer.Dispose(); reader.Dispose(); SmtpStream.Dispose(); SmtpTcpClient.Close(); }
/// <summary> /// Helper function to look up and validate public keys for each recipient. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> /// <param name="addressesWithPublicKeys">Collection containing recipients with valid public keys.</param> /// <param name="addressesNeedingPublicKeys">Collection containing recipients without valid public keys.</param> private void ResolvePublicKeys(MailMessage message, out HashSet<string> addressesWithPublicKeys, out Dictionary<string, MailAddress> addressesNeedingPublicKeys) { // Initialize collections for all recipients. addressesWithPublicKeys = new HashSet<string>(); addressesNeedingPublicKeys = new Dictionary<string, MailAddress>(); MailAddressCollection[] addressRanges = new MailAddressCollection[] { message.To, message.CC, message.Bcc }; foreach (MailAddressCollection addressRange in addressRanges) { foreach (MailAddress toAddress in addressRange) { string canonicalToAddress = toAddress.Address.ToUpper(); if (SmimeCertificateCache.ContainsKey(canonicalToAddress)) { if (!addressesWithPublicKeys.Contains(canonicalToAddress)) addressesWithPublicKeys.Add(canonicalToAddress); } else { if (!addressesNeedingPublicKeys.ContainsKey(canonicalToAddress)) addressesNeedingPublicKeys.Add(canonicalToAddress, toAddress); } } } // If any addresses haven't been mapped to public keys, map them. if (addressesNeedingPublicKeys.Count > 0) { // Read from the Windows certificate store if valid certificates aren't specified. if (SmimeValidCertificates == null || SmimeValidCertificates.Count < 1) { // Load from the current user. X509Store store = new X509Store(StoreLocation.CurrentUser); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); SmimeValidCertificates = store.Certificates; store.Close(); // Add any tied to the local machine. store = new X509Store(StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); SmimeValidCertificates.AddRange(store.Certificates); store.Close(); } // Loop through certificates and check for matching recipients. foreach (X509Certificate2 cert in SmimeValidCertificates) { // Look at certificates with email subject names. string canonicalCertSubject = ""; if (cert.Subject.StartsWith("E=")) canonicalCertSubject = cert.Subject.Substring(2).ToUpper(); else if (cert.Subject.StartsWith("CN=")) canonicalCertSubject = cert.Subject.Substring(3).ToUpper(); else canonicalCertSubject = cert.Subject.ToUpper(); int certSubjectComma = canonicalCertSubject.IndexOf(","); if (certSubjectComma > -1) canonicalCertSubject = canonicalCertSubject.Substring(0, certSubjectComma); // Only proceed if the key is for a recipient of this email. if (!addressesNeedingPublicKeys.ContainsKey(canonicalCertSubject)) continue; // Verify the certificate chain. if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireCertificateVerification) > 0) { if (!cert.Verify()) continue; } // Ensure valid key usage scenarios. if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireKeyUsageOfDataEncipherment) > 0 || (message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireEnhancedKeyUsageofSecureEmail) > 0) { bool keyDataEncipherment = false, enhancedKeySecureEmail = false; foreach (X509Extension extension in cert.Extensions) { if (!keyDataEncipherment && extension.Oid.FriendlyName == "Key Usage") { X509KeyUsageExtension ext = (X509KeyUsageExtension)extension; if ((ext.KeyUsages & X509KeyUsageFlags.DataEncipherment) != X509KeyUsageFlags.None) { keyDataEncipherment = true; if (!((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireEnhancedKeyUsageofSecureEmail) > 0)) break; } } if (!enhancedKeySecureEmail && extension.Oid.FriendlyName == "Enhanced Key Usage") { X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)extension; OidCollection oids = ext.EnhancedKeyUsages; foreach (Oid oid in oids) { if (oid.FriendlyName == "Secure Email") { enhancedKeySecureEmail = true; break; } } } } if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireKeyUsageOfDataEncipherment) > 0 && !keyDataEncipherment) continue; if ((message.SmimeEncryptionOptionFlags & SmimeEncryptionOptionFlags.RequireEnhancedKeyUsageofSecureEmail) > 0 && !enhancedKeySecureEmail) continue; } // If we've made it this far, we can use the certificate for a recipient. MailAddress originalAddress = addressesNeedingPublicKeys[canonicalCertSubject]; SmimeCertificateCache.Add(canonicalCertSubject, cert); addressesWithPublicKeys.Add(canonicalCertSubject); addressesNeedingPublicKeys.Remove(canonicalCertSubject); // Shortcut to abort processing of additional certificates if all recipients are accounted for. if (addressesNeedingPublicKeys.Count < 1) break; } } }
/// <summary> /// Check whether all recipients on the message have valid public keys and will be able to receive S/MIME encrypted envelopes. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> public bool SmimeVerifyAllRecipientsHavePublicKeys(MailMessage message) { // Prepare recipient keys if this message will be encrypted. Dictionary<string, MailAddress> addressesNeedingPublicKeys; HashSet<string> addressesWithPublicKeys; ResolvePublicKeys(message, out addressesWithPublicKeys, out addressesNeedingPublicKeys); return addressesNeedingPublicKeys.Count < 1; }
/// <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, MailMessage message) { return await AppendMessageAsync(mailboxName, message.RawMessage, message.RawFlags.ToArray(), message.Date); }
/// <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<MailMessage> 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) { MailMessage message = new MailMessage(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> /// Sends the specified message to an SMTP server for delivery. /// Performs requested S/MIME signing and encryption. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> public void Send(MailMessage message) { Task.Run(() => SmimeSendRawAsync(message)).Wait(); }