čtvrtek 5. března 2015

Na pokec se SMTP serverem

Často ani my programátoři nevíme, že komunikace server klient se odehrává často v textovém režimu a ani se příliš neliší od běžné řeči - naše počítače, tedy programy na nich běžící,  si mezi sebou povídají pomocí klíčových slov a dohodnutých kódu. Ovšem jako programátoři jsme od této komunikace většinou odděleni a netušíme tak, co probíhá pod pokličkou objektů v našem kódu - možná není špatné si to čas od času připomenout.

Pracuji teď na jednom POC (to je zkratka pro anglické proof of concept - prostě testuji proveditelnost jedné myšlenky v jejich podstatných bodech) a musím zde přímo komunikovat s SMTP serverem.

Pokud chcete poslat nějaký email z vaší aplikace, je to vlastně velmi jednoduché a i na tomto blogu lze nalézt pár příkladů, třeba:

nebo

Takový  kód  pro odeslání pošty vypadá nejčastěji nějak takto:

using (MailMessage mail = new MailMessage())
{
    mail.From = new MailAddress(FromAddress);

    foreach (var address in ToAddresses)
        mail.To.Add(address);

    mail.IsBodyHtml = true;
    mail.Body = content;
    mail.BodyTransferEncoding = TransferEncoding.Unknown;
    mail.Subject = subject + " encoded as " + mail.BodyTransferEncoding.ToString();

    using (var client = new SmtpClient(SmtpServer, SmtpPort))
    {
        client.UseDefaultCredentials = false;
        client.Credentials = new NetworkCredential(FromAddress, SmtpPassword);
        client.EnableSsl = true;
        client.Send(mail);
    }
}

Ale lze to řešit i jinak, můžete si se serverem skutečně pokecat. Je dobré si o tom něco přečíst, třeba tady http://cr.yp.to/smtp/mail.html se probírají základni klíčová slova.

V principu něco na server pošleme - příkaz či data a server nám přijetí potvrdí či poskytne další informace. typické příkazy jsou EHLO (Hello...), AUTH LOGIN, MAIL FROM a server nám naopak odpovídá řádkem s kódem a dalšími informacemi. Podle kódu poznáme, jestli je vše v pořádku a můžeme pokračovat v posílání dalších dat apod. Na jeden příkaz můžeme dostat i více řádků, v tom případě obvykle platí, že poslední takový řádek má kód a mezeru, přičemž předchozí mají kód a -. Popisuji to zjednodušeně  a je lepší se podívat do specifikací.


A nebo je lepší si to vše pěkně vyzkoušet, tady je prográmek, který místo tříd MailMessage a SmtpClient používá třídu TcpClient a zároven vypisuje i obsah komunikace (aby vám fungoval, musíte použít své jméno a heslo pro Google a v nastavení vašeho Gmail účtu povolit přístup i pro méně bezpečné aplikace):

static void Send()
{
    string server = "smtp.gmail.com";
    string username = "mstrimpfl@gmail.com";
    string password = ".......";
    int port = 465;

    IPHostEntry ip = Dns.GetHostEntry(server);

    IPEndPoint endpoint = new IPEndPoint(ip.AddressList[0], port);

    using (var client = new TcpClient())
    {
        client.Connect(endpoint);

        using (var stream = client.GetStream())
        {
            using (var sslStream = new SslStream(stream))  //transfer data with SSL
            {
                sslStream.AuthenticateAsClient(server);

                using (var writer = new StreamWriter(sslStream) { AutoFlush = true })
                using (var reader = new StreamReader(sslStream))
                {
                    writer.WriteLine(string.Format("EHLO {0}", server));
                    WaitForResponseCode(reader, "250 ");

                    writer.WriteLine("auth login");
                    WaitForResponseCode(reader, "334");

                    writer.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(username)));
                    writer.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(password)));

                    WaitForResponseCode(reader, "235 ");

                    writer.WriteLine(string.Format("MAIL FROM: <{0}>", "test@gmail.com"));
                    WaitForResponseCode(reader, "250 ");

                    writer.WriteLine(string.Format("RCPT TO: <{0}>", "strimpfl@hotmail.com"));
                    WaitForResponseCode(reader, "250 ");

                    writer.WriteLine(string.Format("RCPT TO: <{0}>", "mstrimpfl@gmail.com"));
                    WaitForResponseCode(reader, "250 ");

                    writer.WriteLine("DATA");
                    WaitForResponseCode(reader, "354");

                    writer.WriteLine("Subject: ukazka jak na pokec");
                    writer.WriteLine("From: \"MartinStrimpfl\"");
                    writer.WriteLine("To: \"Komukoliv\" <everybody@helloworld.com>");

                    writer.WriteLine("Tohle je jen ukázka");
                    writer.WriteLine("");

                    writer.WriteLine("\n");
                    writer.WriteLine("\n");
                    writer.WriteLine(".");
                    WaitForResponseCode(reader, "250");

                    writer.WriteLine("QUIT");
                    WaitForResponseCode(reader, "221");

                }
            }
        }
    }

    Console.ReadLine();
}

static void WaitForResponseCode(StreamReader reader, string code)
{
    string response = string.Empty;

    do
    {
        response = reader.ReadLine();
        Console.WriteLine(response);

    } while (string.IsNullOrEmpty(response) || response.IndexOf(code) < 0);
}

Po spuštění vypíše na obrazovku i průběh komunikace, tedy odpovědi obdržené od SMTP služby:


Je to jen výsek plné komunikace, nechtěl jsem příliš řešit, co je ještě senzitivní informace (například moje IP adresa ;-)  a co už ne - samozřejmě že při spuštění se objeví všechny informace poslané SMTP zpět.

Žádné komentáře:

Okomentovat