Пример #1
0
        /// <summary>
        /// Attempt to connect to the specified MX server.
        /// </summary>
        /// <param name="mx">MX Record of the server to connect to.</param>
        public async Task <bool> ConnectAsync(MXRecord mx)
        {
            _LastActive = DateTime.UtcNow;
            IsActive    = true;
            await base.ConnectAsync(mx.Host, MtaParameters.Client.SMTP_PORT);

            _LastActive = DateTime.UtcNow;
            SmtpStream  = new SmtpStreamHandler(this as TcpClient);
            _MXRecord   = mx;

            // Read the Server greeting.
            string response = SmtpStream.ReadAllLines();

            _LastActive = DateTime.UtcNow;

            if (!response.StartsWith("2"))
            {
                // If the MX is actively denying use service access, SMTP code 421 then we should inform
                // the ServiceNotAvailableManager manager so it limits our attepts to this MX to 1/minute.
                if (response.StartsWith("421"))
                {
                    ServiceNotAvailableManager.Add(SmtpStream.LocalAddress.ToString(), MXRecord.Host, DateTime.UtcNow);
                }

                base.Close();
                return(false);
            }

            IsActive    = false;
            _LastActive = DateTime.UtcNow;
            return(true);
        }
Пример #2
0
        /// <summary>
        /// Attempt to connect to the specified MX server.
        /// </summary>
        /// <param name="mx">MX Record of the server to connect to.</param>
        private async Task <MantaOutboundClientSendResult> ConnectAsync()
        {
            TcpClient = CreateTcpClient();
            try
            {
                await TcpClient.ConnectAsync(_MXRecord.Host, MtaParameters.Client.SMTP_PORT);
            }
            catch (Exception)
            {
                return(new MantaOutboundClientSendResult(MantaOutboundClientResult.FailedToConnect, "421 Failed to connect", _VirtualMta, _MXRecord));
            }

            SmtpStream = new SmtpStreamHandler(TcpClient);

            // Read the Server greeting.
            string response = SmtpStream.ReadAllLines();

            // Check we get a valid banner.
            if (!response.StartsWith("2"))
            {
                TcpClient.Close();

                // If the MX is actively denying use service access, SMTP code 421 then we should inform
                // the ServiceNotAvailableManager manager so it limits our attepts to this MX to 1/minute.
                if (response.StartsWith("421"))
                {
                    return(new MantaOutboundClientSendResult(MantaOutboundClientResult.ServiceNotAvalible, response, _VirtualMta, _MXRecord));
                }


                return(new MantaOutboundClientSendResult(MantaOutboundClientResult.FailedToConnect, response, _VirtualMta, _MXRecord));
            }

            // We have connected, so say helli
            return(await ExecHeloAsync());
        }
Пример #3
0
        /// <summary>
        /// Method handles a single connection from a client.
        /// </summary>
        /// <param name="obj">Connection with the client.</param>
        private async Task <bool> HandleSmtpConnection(object obj)
        {
            TcpClient client = (TcpClient)obj;

            client.ReceiveTimeout = MtaParameters.Client.ConnectionReceiveTimeoutInterval * 1000;
            client.SendTimeout    = MtaParameters.Client.ConnectionSendTimeoutInterval * 1000;

            try
            {
                Smtp.SmtpStreamHandler smtpStream = new Smtp.SmtpStreamHandler(client);
                string serverHostname             = await GetServerHostnameAsync(client);

                // Identify our MTA
                await smtpStream.WriteLineAsync("220 " + serverHostname + " ESMTP " + MtaParameters.MTA_NAME + " Ready");

                // Set to true when the client has sent quit command.
                bool quit = false;

                // Set to true when the client has said hello.
                bool hasHello = false;

                // Hostname of the client as it self identified in the HELO.
                string heloHost = string.Empty;

                SmtpServerTransaction mailTransaction = null;

                // As long as the client is connected and hasn't sent the quit command then keep accepting commands.
                while (client.Connected && !quit)
                {
                    // Read the next command. If no line then this will wait for one.
                    string cmd = await smtpStream.ReadLineAsync();

                    // Client Disconnected.
                    if (cmd == null)
                    {
                        break;
                    }

                    #region SMTP Commands that can be run before HELO is issued by client.

                    // Handle the QUIT command. Should return 221 and close connection.
                    if (cmd.Equals("QUIT", StringComparison.OrdinalIgnoreCase))
                    {
                        quit = true;
                        await smtpStream.WriteLineAsync("221 Goodbye");

                        continue;
                    }

                    // Reset the mail transaction state. Forget any mail from rcpt to data.
                    if (cmd.Equals("RSET", StringComparison.OrdinalIgnoreCase))
                    {
                        mailTransaction = null;
                        await smtpStream.WriteLineAsync("250 Ok");

                        continue;
                    }

                    // Do nothing except return 250. Do nothing just return success (250).
                    if (cmd.Equals("NOOP", StringComparison.OrdinalIgnoreCase))
                    {
                        await smtpStream.WriteLineAsync("250 Ok");

                        continue;
                    }

                    #endregion

                    // EHLO should 500 Bad Command as we don't support enhanced services, clients should then HELO.
                    // We need to get the hostname provided by the client as it will be used in the recivied header.
                    if (cmd.StartsWith("HELO", StringComparison.OrdinalIgnoreCase) || cmd.StartsWith("EHLO", StringComparison.OrdinalIgnoreCase))
                    {
                        // Helo should be followed by a hostname, if not syntax error.
                        if (cmd.IndexOf(" ") < 0)
                        {
                            await smtpStream.WriteLineAsync("501 Syntax error");

                            continue;
                        }

                        // Grab the hostname.
                        heloHost = cmd.Substring(cmd.IndexOf(" ")).Trim();

                        // There should not be any spaces in the hostname if it is sytax error.
                        if (heloHost.IndexOf(" ") >= 0)
                        {
                            await smtpStream.WriteLineAsync("501 Syntax error");

                            heloHost = string.Empty;
                            continue;
                        }

                        // Client has now said hello so set connection variable to true and 250 back to the client.
                        hasHello = true;
                        if (cmd.StartsWith("HELO", StringComparison.OrdinalIgnoreCase))
                        {
                            await smtpStream.WriteLineAsync("250 Hello " + heloHost + "[" + smtpStream.RemoteAddress.ToString() + "]");
                        }
                        else
                        {
                            // EHLO was sent, let the client know what extensions we support.
                            await smtpStream.WriteLineAsync("250-Hello " + heloHost + "[" + smtpStream.RemoteAddress.ToString() + "]");

                            await smtpStream.WriteLineAsync("250-8BITMIME");

                            await smtpStream.WriteLineAsync("250-PIPELINING");

                            await smtpStream.WriteLineAsync("250 Ok");
                        }
                        continue;
                    }

                    #region Commands that must be after a HELO

                    // Client MUST helo before being allowed to do any of these commands.
                    if (!hasHello)
                    {
                        await smtpStream.WriteLineAsync("503 HELO first");

                        continue;
                    }

                    // Mail From should have a valid email address parameter, if it doesn't system error.
                    // Mail From should also begin new transaction and forget any previous mail from, rcpt to or data commands.
                    // Do this by creating new instance of SmtpTransaction class.
                    if (cmd.StartsWith("MAIL FROM:", StringComparison.OrdinalIgnoreCase))
                    {
                        mailTransaction = new SmtpServerTransaction();

                        // Check for the 8BITMIME body parameter
                        int    bodyParaIndex = cmd.IndexOf(" BODY=", StringComparison.OrdinalIgnoreCase);
                        string mimeMode      = "";

                        if (bodyParaIndex > -1)
                        {
                            // The body parameter was passed in.
                            // Extract the mime mode, if it isn't reconised inform the client of invalid syntax.
                            mimeMode = cmd.Substring(bodyParaIndex + " BODY=".Length).Trim();
                            cmd      = cmd.Substring(0, bodyParaIndex);

                            if (mimeMode.Equals("7BIT", StringComparison.OrdinalIgnoreCase))
                            {
                                mailTransaction.TransportMIME = SmtpTransportMIME._7BitASCII;
                            }
                            else if (mimeMode.Equals("8BITMIME", StringComparison.OrdinalIgnoreCase))
                            {
                                mailTransaction.TransportMIME = SmtpTransportMIME._8BitUTF;
                            }
                            else
                            {
                                await smtpStream.WriteLineAsync("501 Syntax error");

                                continue;
                            }
                        }

                        string mailFrom = string.Empty;
                        try
                        {
                            string address = cmd.Substring(cmd.IndexOf(":") + 1);
                            if (address.Trim().Equals("<>"))
                            {
                                mailFrom = null;
                            }
                            else
                            {
                                mailFrom = new System.Net.Mail.MailAddress(address).Address;
                            }
                        }
                        catch (Exception)
                        {
                            // Mail from not valid email.
                            smtpStream.WriteLine("501 Syntax error");
                            continue;
                        }

                        // If we got this far mail from has an valid email address parameter so set it in the transaction
                        // and return success to the client.
                        mailTransaction.MailFrom = mailFrom;
                        await smtpStream.WriteLineAsync("250 Ok");

                        continue;
                    }

                    // RCPT TO should have an email address parameter. It can only be set if MAIL FROM has already been set,
                    // multiple RCPT TO addresses can be added.
                    if (cmd.StartsWith("RCPT TO:", StringComparison.OrdinalIgnoreCase))
                    {
                        // Check we have a Mail From address.
                        if (mailTransaction == null ||
                            !mailTransaction.HasMailFrom)
                        {
                            await smtpStream.WriteLineAsync("503 Bad sequence of commands");

                            continue;
                        }

                        // Check that the RCPT TO has a valid email address parameter.
                        MailAddress rcptTo = null;
                        try
                        {
                            rcptTo = new MailAddress(cmd.Substring(cmd.IndexOf(":") + 1));
                        }
                        catch (Exception)
                        {
                            // Mail from not valid email.
                            smtpStream.WriteLine("501 Syntax error");
                            continue;
                        }


                        // Check to see if mail is to be delivered locally or relayed for delivery somewhere else.
                        if (MtaParameters.LocalDomains.Count(ld => ld.Hostname.Equals(rcptTo.Host, StringComparison.OrdinalIgnoreCase)) < 1)
                        {
                            // Messages isn't for delivery on this server.
                            // Check if we are allowed to relay for the client IP
                            if (!MtaParameters.IPsToAllowRelaying.Contains(smtpStream.RemoteAddress.ToString()))
                            {
                                // This server cannot deliver or relay message for the MAIL FROM + RCPT TO addresses.
                                // This should be treated as a permament failer, tell client not to retry.
                                await smtpStream.WriteLineAsync("554 Cannot relay");

                                continue;
                            }

                            // Message is for relaying.
                            mailTransaction.MessageDestination = MessageDestination.Relay;
                        }
                        else
                        {
                            // Message to be delivered locally. Make sure mailbox is abuse/postmaster or feedback loop.
                            if (!rcptTo.User.Equals("abuse", StringComparison.OrdinalIgnoreCase) &&
                                !rcptTo.User.Equals("postmaster", StringComparison.OrdinalIgnoreCase) &&
                                !rcptTo.User.StartsWith("return-", StringComparison.OrdinalIgnoreCase) &&
                                !FeedbackLoopEmailAddressDB.IsFeedbackLoopEmailAddress(rcptTo.Address))
                            {
                                await smtpStream.WriteLineAsync("550 Unknown mailbox");

                                continue;
                            }

                            mailTransaction.MessageDestination = MessageDestination.Self;
                        }

                        // Add the recipient.
                        mailTransaction.RcptTo.Add(rcptTo.ToString());
                        await smtpStream.WriteLineAsync("250 Ok");

                        continue;
                    }

                    // Handle the data command, all commands from the client until single line with only '.' should be treated as
                    // a single blob of data.
                    if (cmd.Equals("DATA", StringComparison.OrdinalIgnoreCase))
                    {
                        // Must have a MAIL FROM before data.
                        if (mailTransaction == null ||
                            !mailTransaction.HasMailFrom)
                        {
                            await smtpStream.WriteLineAsync("503 Bad sequence of commands");

                            continue;
                        }
                        // Must have RCPT's before data.
                        else if (mailTransaction.RcptTo.Count < 1)
                        {
                            await smtpStream.WriteLineAsync("554 No valid recipients");

                            continue;
                        }

                        // Tell the client we are now accepting there data.
                        await smtpStream.WriteLineAsync("354 Go ahead");

                        // Set the transport MIME to default or as specified by mail from body
                        smtpStream.SetSmtpTransportMIME(mailTransaction.TransportMIME);

                        // Wait for the first data line. Don't log data in SMTP log file.
                        string dataline = await smtpStream.ReadLineAsync(false);

                        StringBuilder dataBuilder = new StringBuilder();
                        // Loop until data client stops sending us data.
                        while (!dataline.Equals("."))
                        {
                            // Add the line to existing data.
                            dataBuilder.AppendLine(dataline);

                            // Wait for the next data line. Don't log data in SMTP log file.
                            dataline = await smtpStream.ReadLineAsync(false);
                        }
                        mailTransaction.Data = dataBuilder.ToString();

                        // Data has been received, return to 7 bit ascii.
                        smtpStream.SetSmtpTransportMIME(SmtpTransportMIME._7BitASCII);

                        // Once data is finished we have mail for delivery or relaying.
                        // Add the Received header.
                        mailTransaction.AddHeader("Received", string.Format("from {0}[{1}] by {2}[{3}] on {4}",
                                                                            heloHost,
                                                                            smtpStream.RemoteAddress.ToString(),
                                                                            serverHostname,
                                                                            smtpStream.LocalAddress.ToString(),
                                                                            DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH':'mm':'ss -0000 (UTC)")));


                        // Complete the transaction,either saving to local mailbox or queueing for relay.
                        SmtpServerTransaction.SmtpServerTransactionAsyncResult result = await mailTransaction.SaveAsync();

                        // Send a response to the client depending on the result of saving the transaction.
                        switch (result)
                        {
                        case SmtpServerTransaction.SmtpServerTransactionAsyncResult.SuccessMessageDelivered:
                        case SmtpServerTransaction.SmtpServerTransactionAsyncResult.SuccessMessageQueued:
                            await smtpStream.WriteLineAsync("250 Message queued for delivery");

                            break;

                        case SmtpServerTransaction.SmtpServerTransactionAsyncResult.FailedSendDiscarding:
                            await smtpStream.WriteLineAsync("554 Send Discarding.");

                            break;

                        case SmtpServerTransaction.SmtpServerTransactionAsyncResult.FailedToEnqueue:
                            await smtpStream.WriteLineAsync("421 Service unavailable");

                            break;

                        case SmtpServerTransaction.SmtpServerTransactionAsyncResult.Unknown:
                        default:
                            await smtpStream.WriteLineAsync("451 Requested action aborted: local error in processing.");

                            break;
                        }

                        // Done with transaction, clear it and inform client message success and QUEUED
                        mailTransaction = null;

                        // Go and wait for the next client command.
                        continue;
                    }

                    #endregion


                    // If got this far then we don't known the command.
                    await smtpStream.WriteLineAsync("500 Unknown command");
                }
            }
            catch (System.IO.IOException) { /* Connection timeout */ }
            finally
            {
                // Client has issued QUIT command or connecion lost.
                if (client.GetStream() != null)
                {
                    client.GetStream().Close();
                }
                client.Close();
            }

            return(true);
        }