Я пытаюсь отправить электронное письмо с помощью TcpClient на C #. и я не знаю, возможно это или нет.

******* Я знаю, что могу использовать SmtpClient, но это домашнее задание, и мне просто нужно делать это с сокетами ******

Я написал такой код:

TcpClient tcpclient = new TcpClient();

            // HOST NAME POP SERVER and gmail uses port number 995 for POP 

            //tcpclient.Connect("pop.gmail.com", 995);
            tcpclient.Connect("smtp.gmail.com", 465);
            // This is Secure Stream // opened the connection between client and POP Server
            System.Net.Security.SslStream sslstream = new SslStream(tcpclient.GetStream());
            // authenticate as client  
            //sslstream.AuthenticateAsClient("pop.gmail.com");
            sslstream.AuthenticateAsClient("smtp.gmail.com");
            //bool flag = sslstream.IsAuthenticated;   // check flag
            // Asssigned the writer to stream 
            System.IO.StreamWriter sw = new StreamWriter(sslstream);
            // Assigned reader to stream
            System.IO.StreamReader reader = new StreamReader(sslstream);
            // refer POP rfc command, there very few around 6-9 command
            sw.WriteLine("EHLO " + "smtp.gmail.com");
            sw.Flush();
            sw.WriteLine("AUTH LOGIN/r/n");
            sw.Flush();
            sw.WriteLine("******@gmail.com/r/n");
            sw.Flush();
            // sent to server
            sw.WriteLine("***********/r/n");
            sw.Flush();
            //// this will retrive your first email
            //sw.WriteLine("RETR 1");
            //sw.Flush();
            //// close the connection
            //sw.WriteLine("Quit ");
            //sw.Flush();
            sw.WriteLine("MAIL FROM:<" + "******@gmail.com" + ">\r\n");
            sw.Flush();
            sw.WriteLine("RCPT TO:<" + "*******@***.com" + ">\r\n");
            sw.Flush();
            sw.WriteLine("DATA\r\n");
            sw.Flush();
            sw.WriteLine("Subject: Email test\r\n");
            sw.Flush();
            sw.WriteLine("Test 1 2 3\r\n");
            sw.Flush();
            sw.WriteLine(".\r\n");
            sw.Flush();
            sw.WriteLine("QUIT\r\n");
            sw.Flush();

            string str = string.Empty;
            string strTemp = string.Empty;
            while ((strTemp = reader.ReadLine()) != null)
            {
                // find the . character in line
                if (strTemp == ".")
                {
                    break;
                }
                if (strTemp.IndexOf("-ERR") != -1)
                {
                    break;
                }
                str += strTemp;
            }
        }

Сообщение, которое получает читатель:

"250-smtp.gmail.com at your service, [151.238.124.27]\r\n250-SIZE 35882577\r\n250-8BITMIME\r\n250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH\r\n250-ENHANCEDSTATUSCODES\r\n250-PIPELINING\r\n250-CHUNKING\r\n250 SMTPUTF8\r\n451 4.5.0 SMTP protocol violation, see RFC 2821 x17-v6sm5346253edx.53 - gsmtp\r\n"

Есть идеи, какая часть неправильная, поэтому я могу отправить электронное письмо через TcpClient?

Итак, если этого не произойдет, как я могу отправить электронное письмо с помощью сокетов?

-1
Amir.S 1 Май 2018 в 10:58

1 ответ

Лучший ответ

Проблема в том, что вы неправильно следуете протоколу SMTP, как описано в RFC 5321, < a href = "https://tools.ietf.org/html/rfc2920" rel = "nofollow noreferrer"> RFC 2920 и RFC 2554.

Это видно по сообщению об ошибке, которое вы получаете от сервера в ответ на команду AUTH LOGIN:

451 4.5.0 Нарушение протокола SMTP , см. RFC 2821 x17-v6sm5346253edx.53 - gsmtp

В частности,

  • некоторые из ваших команд завершаются /r/n, что одновременно неверно (должно быть \r\n) и избыточно (поскольку вы используете WriteLine(), который отправляет вам \r\n) .

  • вы отправляете кучу SMTP-команд, не читая ни одного ответа между каждой командой. Это известно как командная настройка. Однако вы не проверяете ответ сервера EHLO, чтобы убедиться, что сервер даже поддерживает конвейерную обработку. Вы не можете конвейерно выполнять команды, если сервер сначала не сообщит вам, что все в порядке.

  • вы неправильно читаете ответы. Независимо от того, используете ли вы конвейерную обработку или нет, ответы SMTP имеют определенный формат, как указано в RFC 5321. Раздел 4.2. Ваш код чтения не соответствует этому формату, даже близко.

  • вы неправильно аутентифицируетесь на SMTP-сервере. В частности, значения, которые вы отправляете на сервер, должны быть закодированы в UTF-8 и base64. И вам нужно обратить внимание на подсказки сервера, чтобы знать, когда отправлять имя пользователя, а когда - пароль. Некоторым серверам не требуются оба значения.

С учетом сказанного, попробуйте что-то более похожее на это:

private System.IO.StreamReader reader;
private System.IO.StreamWriter writer;

public class SmtpCmdFailedException : Exception
{
    public int ReplyCode;

    public SmtpCmdFailedException(int code, string message)
        : base(message)
    {
        ReplyCode = code;
    }
}

private int readResponse(ref string replyText, params int[] expectedReplyCodes)
{
    string line = reader.ReadLine();
    if (line == null)
        throw new EndOfStreamException();

    // extract the 3-digit reply code
    string replyCodeStr = line.Substring(0, 3);

    // extract the text message, if any
    replyText = line.Substring(4);

    // check for a multi-line response
    if ((line.Length > 3) && (line[3] == '-'))
    {
        // keep reading until the final line is received
        string contStr = replyCodeStr + "-";
        do
        {
            line = reader.ReadLine();
            if (line == null)
                throw new EndOfStreamException();    
            replyText += "\n" + line.Substring(4);
        }
        while (line.StartsWith(contStr));
    }

    int replyCode = Int32.Parse(replyCodeStr);

    // if the caller expects specific reply code(s), check
    // for a match and throw an exception if not found...
    if (expectedReplyCodes.Length > 0)
    {
        if (Array.IndexOf(expectedReplyCodes, replyCode) == -1)
            throw new SmtpCmdFailedException(replyCode, replyText);
    }

    // return the actual reply code that was received
    return replyCode;
}

private int readResponse(params int[] expectedReplyCodes)
{
    string ignored;
    return readResponse(ignored, expectedReplyCodes);
}

private int sendCommand(string command, ref string replyText, params int[] expectedReplyCodes)
{
    writer.WriteLine(command);
    writer.Flush();
    return readResponse(replyText, expectedReplyCodes);
}

private int sendCommand(string command, params int[] expectedReplyCodes)
{
    string ignored;
    return sendCommand(command, ignored, expectedReplyCodes);
}

void doAuthLogin(string username, string password)
{
    // an authentication command returns 235 if authentication
    // is finished successfully, or 334 to prompt for more data.
    // Anything else is an error...

    string replyText;
    int replyCode = sendCommand("AUTH LOGIN", replyText, 235, 334);

    if (replyCode == 334)
    {
        // in the original spec for LOGIN (draft-murchison-sasl-login-00.txt), the
        // username prompt is defined as 'User Name' and the password prompt is
        // defined as 'Password'. However, the spec also mentions that there is at
        // least one widely deployed client that expects 'Username:' and 'Password:'
        // instead, and those are the prompts that most 3rd party documentations
        // of LOGIN describe.  So we will look for all known prompts and act accordingly.
        // Also throwing in 'Username' just for good measure, as that one has been seen
        // in the wild, too...

        string[] challenges = new string[]{"Username:", "User Name", "Username", "Password:", "Password"};

        do
        {
            string challenge = Encoding.UTF8.GetString(Convert.FromBase64String(replyText));

            switch (Array.IndexOf(challenges, challenge))
            {
                case 0:
                case 1:
                case 2:
                    replyCode = sendCommand(Convert.ToBase64String(Encoding.UTF8.GetBytes(username)), replyText, 235, 334);
                    break;

                case 3:
                case 4:
                    replyCode = sendCommand(Convert.ToBase64String(Encoding.UTF8.GetBytes(password)), replyText, 235, 334);
                    break;

                default:
                    throw new SmtpCmdFailedException(replyCode, replyText);
            }
        }
        while (replyCode == 334);
    }
}

...

TcpClient tcpclient = new TcpClient();

tcpclient.Connect("smtp.gmail.com", 465);

// implicit SSL is always used on SMTP port 465
System.Net.Security.SslStream sslstream = new SslStream(tcpclient.GetStream());
sslstream.AuthenticateAsClient("smtp.gmail.com");
//bool flag = sslstream.IsAuthenticated;   // check flag

writer = new StreamWriter(sslstream);
reader = new StreamReader(sslstream);

string replyText;
string[] capabilities = null;
string[] authTypes = null;

// read the server's initial greeting
readResponse(220);

// identify myself and get the server's capabilities
if (sendCommand("EHLO myClientName", replyText) == 250)
{
    // parse capabilities
    capabilities = replyText.Split(new Char[]{'\n'});
    string auth = Array.Find(capabilities, s => s.StartsWith("AUTH ", true, null));
    authTypes = auth.Substring(5).Split(new Char[]{' '});

    // authenticate as needed...
    if (Array.IndexOf(authTypes, "LOGIN") != -1)
        doAuthLogin("******@gmail.com", "***********");
}
else
{
    // EHLO not supported, have to use HELO instead, but then
    // the server's capabilities are unknown...

    capabilities = new string[]{};
    authTypes = new string[]{};

    sendCommand("HELO myclientname", 250);

    // try to authenticate anyway...
    doAuthLogin("******@gmail.com", "***********");
}

// check for pipelining support... (OPTIONAL!!!)
if (Array.IndexOf(capabilities, "PIPELINING") != -1)
{
    // can pipeline...

    // send all commands first without reading responses in between
    writer.WriteLine("MAIL FROM:<" + "******@gmail.com" + ">");
    writer.WriteLine("RCPT TO:<" + "*******@***.com" + ">");
    writer.WriteLine("DATA");
    writer.Flush();

    // now read the responses...

    Exception e = null;

    // MAIL FROM
    int replyCode = readResponse(replyText);
    if (replyCode != 250)
        e = new SmtpCmdFailedException(replyCode, replyText);

    // RCPT TO
    replyCode = readResponse(replyText);
    if ((replyCode != 250) && (replyCode != 251) && (e == null))
        e = new SmtpCmdFailedException(replyCode, replyText);

    // DATA
    replyCode = readResponse(replyText);
    if (replyCode == 354)
    {
        // DATA accepted, must send email followed by "."
        writer.WriteLine("Subject: Email test");
        writer.WriteLine("Test 1 2 3");
        writer.WriteLine(".");
        writer.Flush();

        // read the response
        replyCode = readResponse(replyText);
        if ((replyCode != 250) && (e == null))
            e = new SmtpCmdFailedException(replyCode, replyText);
    }
    else
    {
        // DATA rejected, do not send email
        if (e == null)
            e = new SmtpCmdFailedException(replyCode, replyText);
    }

    if (e != null)
    {
        // if any command failed, reset the session
        sendCommand("RSET");
        throw e;
    }
}
else
{
    // not pipelining, MUST read each response before sending the next command...

    sendCommand("MAIL FROM:<" + "******@gmail.com" + ">", 250);
    try
    {
        sendCommand("RCPT TO:<" + "*******@***.com" + ">", 250, 251);
        sendCommand("DATA", 354);
        writer.WriteLine("Subject: Email test");
        writer.WriteLine("");
        writer.WriteLine("Test 1 2 3");
        writer.Flush();
        sendCommand(".", 250);
    }
    catch (SmtpCmdFailedException e)
    {
        // if any command failed, reset the session
        sendCommand("RSET");
        throw;
    }
}

// all done
sendCommand("QUIT", 221);
3
Remy Lebeau 1 Май 2018 в 18:35