/// <summary> /// Send the RCPT TO command to the server using <paramref name="rcptTo"/> as parameter. /// </summary> /// <param name="rcptTo">Email address to use as parameter.</param> /// <param name="failedCallback">Action to call if command fails.</param> public async Task <bool> ExecRcptToAsync(MailAddress rcptTo, Action <string> failedCallback) { if (!base.Connected) { return(false); } IsActive = true; _LastActive = DateTime.UtcNow; await SmtpStream.WriteLineAsync("RCPT TO: <" + rcptTo.Address + ">"); // If the remote MX doesn't support pipelining then wait and check the response. if (!_CanPipeline) { string response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { failedCallback(response); } } _LastActive = DateTime.UtcNow; IsActive = false; return(true); }
/// <summary> /// Send the MAIL FROM command to the server using <paramref name="mailFrom"/> as parameter. /// </summary> /// <param name="mailFrom">Email address to use as parameter.</param> /// <param name="failedCallback">Action to call if command fails.</param> public async Task <bool> ExecMailFromAsync(MailAddress mailFrom, Action <string> failedCallback) { if (!base.Connected) { return(false); } _LastActive = DateTime.UtcNow; IsActive = true; await SmtpStream.WriteLineAsync("MAIL FROM: <" + (mailFrom == null ? string.Empty : mailFrom.Address) + ">" + (_DataTransportMime == SmtpTransportMIME._8BitUTF ? " BODY=8BITMIME" : string.Empty)); // If the remote MX doesn't support pipelining then wait and check the response. if (!_CanPipeline) { string response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { failedCallback(response); } } _LastActive = DateTime.UtcNow; IsActive = false; return(true); }
/// <summary> /// Send the data to the server /// </summary> /// <param name="data">Data to send to the server</param> /// <param name="failedCallback">Action to call if fails to send.</param> private async Task <MantaOutboundClientSendResult> ExecDataAsync(string data) { await SmtpStream.WriteLineAsync("DATA"); string response = await SmtpStream.ReadAllLinesAsync(); // Data response or Mail From if pipelining // If the remote MX supports pipelining then we need to check the MAIL FROM and RCPT to responses. if (_CanPipeline) { // Check MAIL FROM OK. if (!response.StartsWith("250")) { await SmtpStream.ReadAllLinesAsync(); // RCPT TO await SmtpStream.ReadAllLinesAsync(); // DATA return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } // Check RCPT TO OK. response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { await SmtpStream.ReadAllLinesAsync(); // DATA return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } // Get the Data Command response. response = await SmtpStream.ReadAllLinesAsync(); } if (!response.StartsWith("354")) { return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } // Send the message data using the correct transport MIME SmtpStream.SetSmtpTransportMIME(_DataTransportMime); await SmtpStream.WriteAsync(data, false); await SmtpStream.WriteAsync(MtaParameters.NewLine + "." + MtaParameters.NewLine, false); // Data done so return to 7-Bit mode. SmtpStream.SetSmtpTransportMIME(SmtpTransportMIME._7BitASCII); response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } _MessagesAccepted++; return(new MantaOutboundClientSendResult(MantaOutboundClientResult.Success, response, _VirtualMta, _MXRecord)); }
public void TestSeek() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { Assert.Throws <NotSupportedException> (() => stream.Seek(0, SeekOrigin.Begin)); Assert.Throws <NotSupportedException> (() => stream.Position = 500); Assert.AreEqual(0, stream.Position); Assert.AreEqual(0, stream.Length); } }
public void TestCanReadWriteSeek() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { Assert.IsTrue(stream.CanRead); Assert.IsTrue(stream.CanWrite); Assert.IsFalse(stream.CanSeek); Assert.IsTrue(stream.CanTimeout); } }
public void TestRead() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { var buffer = new byte[16]; Assert.Throws <NotImplementedException> (() => stream.Read(buffer, 0, buffer.Length)); Assert.ThrowsAsync <NotImplementedException> (async() => await stream.ReadAsync(buffer, 0, buffer.Length)); } }
public void TestGetSetTimeouts() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { stream.ReadTimeout = 5; Assert.AreEqual(5, stream.ReadTimeout, "ReadTimeout"); stream.WriteTimeout = 7; Assert.AreEqual(7, stream.WriteTimeout, "WriteTimeout"); } }
public void TestReadResponseInvalidResponseCode() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { var buffer = Encoding.ASCII.GetBytes("XXX This is an invalid response.\r\n"); var dummy = (MemoryStream)stream.Stream; dummy.Write(buffer, 0, buffer.Length); dummy.Position = 0; Assert.Throws <SmtpProtocolException> (() => stream.ReadResponse(CancellationToken.None)); } }
public void TestReadResponseMismatchedResponseCodes() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { var buffer = Encoding.ASCII.GetBytes("250-This is the first line of a response.\r\n340 And this is a mismatched response code.\r\n"); var dummy = (MemoryStream)stream.Stream; dummy.Write(buffer, 0, buffer.Length); dummy.Position = 0; Assert.Throws <SmtpProtocolException> (() => stream.ReadResponse(CancellationToken.None)); } }
/// <summary> /// Say EHLO/HELO to the server. /// Will also check to see if 8BITMIME is supported. /// </summary> /// <param name="failedCallback">Action to call if hello fail.</param> private async Task <MantaOutboundClientSendResult> ExecHeloAsync() { // We have connected to the MX, Say EHLO. await SmtpStream.WriteLineAsync("EHLO " + _VirtualMta.Hostname); string response = await SmtpStream.ReadAllLinesAsync(); if (response.StartsWith("421")) { return(new MantaOutboundClientSendResult(MantaOutboundClientResult.ServiceNotAvalible, response, _VirtualMta, _MXRecord)); } try { if (!response.StartsWith("2")) { // If server didn't respond with a success code on hello then we should retry with HELO await SmtpStream.WriteLineAsync("HELO " + _VirtualMta.Hostname); response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { TcpClient.Close(); return(new MantaOutboundClientSendResult(MantaOutboundClientResult.ServiceNotAvalible, response, _VirtualMta, _MXRecord)); } } else { // Server responded to EHLO // Check to see if it supports 8BITMIME if (response.IndexOf("8BITMIME", StringComparison.OrdinalIgnoreCase) > -1) { _DataTransportMime = SmtpTransportMIME._8BitUTF; } else { _DataTransportMime = SmtpTransportMIME._7BitASCII; } // Check to see if the server supports pipelining _CanPipeline = response.IndexOf("PIPELINING", StringComparison.OrdinalIgnoreCase) > -1; } } catch (IOException) { // Remote Endpoint Disconnected Mid HELO. return(new MantaOutboundClientSendResult(MantaOutboundClientResult.ServiceNotAvalible, response, _VirtualMta, _MXRecord)); } return(new MantaOutboundClientSendResult(MantaOutboundClientResult.Success, null, _VirtualMta, _MXRecord)); }
/// <summary> /// Send the RCPT TO command to the server using <paramref name="rcptTo"/> as parameter. /// </summary> /// <param name="rcptTo">Email address to use as parameter.</param> /// <param name="failedCallback">Action to call if command fails.</param> private async Task <MantaOutboundClientSendResult> ExecRcptToAsync(MailAddress rcptTo) { await SmtpStream.WriteLineAsync("RCPT TO: <" + rcptTo.Address + ">"); // If the remote MX doesn't support pipelining then wait and check the response. if (!_CanPipeline) { string response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } } return(new MantaOutboundClientSendResult(MantaOutboundClientResult.Success, null, _VirtualMta, _MXRecord)); }
/// <summary> /// Send the RSET command to the server. /// </summary> public async Task <bool> ExecRsetAsync() { if (!base.Connected) { Logging.Debug("Cannot RSET connection has been closed."); throw new Exception(); } IsActive = true; await SmtpStream.WriteLineAsync("RSET"); await SmtpStream.ReadAllLinesAsync(); _LastActive = DateTime.UtcNow; IsActive = false; return(true); }
public void TestReadResponseLatin1Fallback() { const string input = "250-Wikipédia est un projet d'encyclopédie collective en ligne,\r\n250-universelle, multilingue et fonctionnant sur le principe du wiki.\r\n250-Ce projet vise à offrir un contenu librement réutilisable, objectif\r\n250 et vérifiable, que chacun peut modifier et améliorer.\r\n"; var expected = input.Replace("250-", "").Replace("250 ", "").Replace("\r\n", "\n").TrimEnd(); using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { var buffer = Encoding.GetEncoding(28591).GetBytes(input); var dummy = (MemoryStream)stream.Stream; dummy.Write(buffer, 0, buffer.Length); dummy.Position = 0; var response = stream.ReadResponse(CancellationToken.None); Assert.AreEqual(250, (int)response.StatusCode); Assert.AreEqual(expected, response.Response); } }
/// <summary> /// Send the SMTP Quit command to the Server. /// </summary> private async Task ExecQuitAsync() { if (TcpClient.Connected) { try { await SmtpStream.WriteLineAsync("QUIT"); // Don't read response as don't care. // Close the TCP connection. TcpClient.GetStream().Close(); TcpClient.Close(); } catch (ObjectDisposedException) { Logging.Debug("SmtpOutboundClient: Tried to quit an already disposed client."); } } }
/// <summary> /// Send the RSET command to the server. /// </summary> private async Task <MantaOutboundClientSendResult> ExecRsetAsync() { if (!await SmtpStream.WriteLineAsync("RSET")) { throw new ObjectDisposedException("Connection"); } var response = await SmtpStream.ReadAllLinesAsync(); switch (response[0]) { case '2': return(new MantaOutboundClientSendResult(MantaOutboundClientResult.Success, response, _VirtualMta, _MXRecord)); case '4': case '5': default: return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } }
/// <summary> /// Send the SMTP Quit command to the Server. /// </summary> public async Task <bool> ExecQuitAsync() { if (!base.Connected) { return(false); } IsActive = true; try { await SmtpStream.WriteLineAsync("QUIT"); // Don't read response as don't care. // Close the TCP connection. base.GetStream().Close(); base.Close(); } catch (ObjectDisposedException) { Logging.Debug("SmtpOutboundClient: Tried to quit an already disposed client."); } IsActive = false; return(true); }
/// <summary> /// Send the MAIL FROM command to the server using <paramref name="mailFrom"/> as parameter. /// </summary> /// <param name="mailFrom">Email address to use as parameter.</param> /// <param name="failedCallback">Action to call if command fails.</param> private async Task <MantaOutboundClientSendResult> ExecMailFromAsync(MailAddress mailFrom) { await SmtpStream.WriteLineAsync("MAIL FROM: <" + (mailFrom == null ? string.Empty : mailFrom.Address) + ">" + (_DataTransportMime == SmtpTransportMIME._8BitUTF ? " BODY=8BITMIME" : string.Empty)); // If the remote MX doesn't support pipelining then wait and check the response. if (!_CanPipeline) { string response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { if (response.StartsWith("421")) { return(new MantaOutboundClientSendResult(MantaOutboundClientResult.ServiceNotAvalible, response, _VirtualMta, _MXRecord)); } return(new MantaOutboundClientSendResult(MantaOutboundClientResult.RejectedByRemoteServer, response, _VirtualMta, _MXRecord)); } } return(new MantaOutboundClientSendResult(MantaOutboundClientResult.Success, null, _VirtualMta, _MXRecord)); }
public void TestReadResponseOver4K() { string expected; string input; using (var rng = new RNGCryptoServiceProvider()) { var builder = new StringBuilder(); var buffer = new byte[72]; while (builder.Length < 5120) { rng.GetBytes(buffer); var base64 = Convert.ToBase64String(buffer); builder.AppendFormat("250-{0}\r\n", base64); } builder.Append("250 Okay, now we're done.\r\n"); input = builder.ToString(); expected = input.Replace("250-", "").Replace("250 ", "").Replace("\r\n", "\n").TrimEnd(); } using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { var buffer = Encoding.ASCII.GetBytes(input); var dummy = (MemoryStream)stream.Stream; dummy.Write(buffer, 0, buffer.Length); dummy.Position = 0; var response = stream.ReadResponse(CancellationToken.None); Assert.AreEqual(250, (int)response.StatusCode); Assert.AreEqual(expected, response.Response); } }
/// <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(); }
public async Task TestWriteAsync() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { var memory = (MemoryStream)stream.Stream; var buffer = new byte[8192]; var buf1k = new byte[1024]; var buf4k = new byte[4096]; var buf9k = new byte[9216]; byte [] mem; using (var rng = new RNGCryptoServiceProvider()) { rng.GetBytes(buf1k); rng.GetBytes(buf4k); rng.GetBytes(buf9k); } // Test #1: write less than 4K to make sure that SmtpStream buffers it await stream.WriteAsync(buf1k, 0, buf1k.Length); Assert.AreEqual(0, memory.Length, "#1"); // Test #2: make sure that flushing the SmtpStream flushes the entire buffer out to the network await stream.FlushAsync(); Assert.AreEqual(buf1k.Length, memory.Length, "#2"); mem = memory.GetBuffer(); for (int i = 0; i < buf1k.Length; i++) { Assert.AreEqual(buf1k[i], mem[i], "#2 byte[{0}]", i); } memory.SetLength(0); // Test #3: write exactly 4K to make sure it passes through w/o the need to flush await stream.WriteAsync(buf4k, 0, buf4k.Length); Assert.AreEqual(buf4k.Length, memory.Length, "#3"); mem = memory.GetBuffer(); for (int i = 0; i < buf4k.Length; i++) { Assert.AreEqual(buf4k[i], mem[i], "#3 byte[{0}]", i); } memory.SetLength(0); // Test #4: write 1k and then write 4k, make sure that only 4k passes thru (last 1k gets buffered) await stream.WriteAsync(buf1k, 0, buf1k.Length); await stream.WriteAsync(buf4k, 0, buf4k.Length); Assert.AreEqual(4096, memory.Length, "#4"); await stream.FlushAsync(); Assert.AreEqual(buf1k.Length + buf4k.Length, memory.Length, "#4"); Array.Copy(buf1k, 0, buffer, 0, buf1k.Length); Array.Copy(buf4k, 0, buffer, buf1k.Length, buf4k.Length); mem = memory.GetBuffer(); for (int i = 0; i < buf1k.Length + buf4k.Length; i++) { Assert.AreEqual(buffer[i], mem[i], "#4 byte[{0}]", i); } memory.SetLength(0); // Test #5: write 9k and make sure only the first 8k goes thru (last 1k gets buffered) await stream.WriteAsync(buf9k, 0, buf9k.Length); Assert.AreEqual(8192, memory.Length, "#5"); await stream.FlushAsync(); Assert.AreEqual(buf9k.Length, memory.Length, "#5"); mem = memory.GetBuffer(); for (int i = 0; i < buf9k.Length; i++) { Assert.AreEqual(buf9k[i], mem[i], "#5 byte[{0}]", i); } memory.SetLength(0); } }
public void TestSetLength() { using (var stream = new SmtpStream(new DummyNetworkStream(), new NullProtocolLogger())) { Assert.Throws <NotSupportedException> (() => stream.SetLength(500)); } }
/// <summary> /// Send the data to the server /// </summary> /// <param name="data">Data to send to the server</param> /// <param name="failedCallback">Action to call if fails to send.</param> public async Task <bool> ExecDataAsync(string data, Action <string> failedCallback, Func <string, Task> successCallbackAsync) { if (!base.Connected) { return(false); } _LastActive = DateTime.UtcNow; IsActive = true; await SmtpStream.WriteLineAsync("DATA"); string response = await SmtpStream.ReadAllLinesAsync(); // If the remote MX supports pipelining then we need to check the MAIL FROM and RCPT to responses. if (_CanPipeline) { // Check MAIL FROM OK. if (!response.StartsWith("250")) { failedCallback(response); IsActive = false; return(false); } // Check RCPT TO OK. response = await SmtpStream.ReadAllLinesAsync(); if (!response.StartsWith("250")) { failedCallback(response); IsActive = false; return(false); } // Get the Data Command response. response = await SmtpStream.ReadAllLinesAsync(); } _LastActive = DateTime.UtcNow; if (!response.StartsWith("354")) { failedCallback(response); IsActive = false; return(false); } // Increment the data commands as server has responded positiely. _DataCommands++; // Send the message data using the correct transport MIME SmtpStream.SetSmtpTransportMIME(_DataTransportMime); await SmtpStream.WriteAsync(data, false); await SmtpStream.WriteAsync(MtaParameters.NewLine + "." + MtaParameters.NewLine, false); _LastActive = DateTime.UtcNow; // Data done so return to 7-Bit mode. SmtpStream.SetSmtpTransportMIME(SmtpTransportMIME._7BitASCII); response = await SmtpStream.ReadAllLinesAsync(); _LastActive = DateTime.UtcNow; IsActive = false; if (!response.StartsWith("250")) { failedCallback(response); } else { await successCallbackAsync(response); } // If max messages have been sent quit the connection. if (_DataCommands >= OutboundRuleManager.GetMaxMessagesPerConnection(MXRecord, MtaIpAddress)) { ExecQuitAsync().Wait(); } return(true); }
/// <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> /// Say EHLO/HELO to the server. /// Will also check to see if 8BITMIME is supported. /// </summary> /// <param name="failedCallback">Action to call if hello fail.</param> public async Task <bool> ExecHeloAsync(Action <string> failedCallback) { if (!base.Connected) { return(false); } _LastActive = DateTime.UtcNow; IsActive = true; // We have connected to the MX, Say EHLO. _LastActive = DateTime.UtcNow; await SmtpStream.WriteLineAsync("EHLO " + MtaIpAddress.Hostname); string response = await SmtpStream.ReadAllLinesAsync(); if (response.StartsWith("421")) { failedCallback(response); } _LastActive = DateTime.UtcNow; try { if (!response.StartsWith("2")) { // If server didn't respond with a success code on hello then we should retry with HELO await SmtpStream.WriteLineAsync("HELO " + MtaIpAddress.Hostname); response = await SmtpStream.ReadAllLinesAsync(); _LastActive = DateTime.UtcNow; if (!response.StartsWith("250")) { failedCallback(response); base.Close(); } } else { // Server responded to EHLO // Check to see if it supports 8BITMIME if (response.IndexOf("8BITMIME", StringComparison.OrdinalIgnoreCase) > -1) { _DataTransportMime = SmtpTransportMIME._8BitUTF; } // Check to see if the server supports pipelining if (response.IndexOf("PIPELINING", StringComparison.OrdinalIgnoreCase) > -1) { _CanPipeline = true; } } } catch (IOException) { // Remote Endpoint Disconnected Mid HELO. Most likly Yahoo throttling. } _HasHelloed = true; _LastActive = DateTime.UtcNow; IsActive = false; return(true); }