/// <summary> /// Sends the specified message to an SMTP server for delivery. /// </summary> /// <param name="message">An OpaqueMail.MailMessage that contains the message to send.</param> public async Task SendAsync(MailMessage message) { // If the message isn't encoded, do so now. if (string.IsNullOrEmpty(message.RawBody)) { message.Prepare(); } // Perform requested S/MIME signing and/or encryption. message.SmimePrepare(this); string rawBody = message.RawBody; // 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, null, SslProtocols, true); 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(rawBody) && !string.IsNullOrEmpty(message.Body)) { bool extendedCharacterFound = false; foreach (char headerCharacter in message.Body.ToCharArray()) { if (headerCharacter > 127) { extendedCharacterFound = true; break; } } if (extendedCharacterFound) { message.ContentTransferEncoding = "base64"; message.Body = Functions.ToBase64String(message.Body); } rawBody = message.Body; } if (!string.IsNullOrEmpty(message.ContentTransferEncoding)) { message.Headers["Content-Transfer-Encoding"] = message.ContentTransferEncoding; } // 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, 32)) + "\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" + 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> /// 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; } // 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(Functions.ToMailAddressString(message.To))) + "\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(Functions.ToMailAddressString(message.CC))) + "\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 + "'."); } } // 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 + "'."); } // If a read-only mail message is passed in with its raw headers and body, save a few steps by sending that directly. if (message is ReadOnlyMailMessage) { await writer.WriteAsync(((ReadOnlyMailMessage)message).RawHeaders + "\r\n" + ((ReadOnlyMailMessage)message).RawBody + "\r\n.\r\n"); } else { 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.Body + "\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(); }