LINUX.ORG.RU

Как отправить почту?

 , , ,


1

3

Мне надо, чтобы мой сервер отправлял регистрационные коды пользователям. Использую такую разработку

#include <string>
#include <sstream>
#include <iostream>
#include <ctime>
#include <algorithm>
#include <random>
#include <curl/curl.h>

class EmailAddress 
{
public:
    EmailAddress(const char *email)
        : email_{email}
        {
        }

    EmailAddress(const std::string &email)
        : email_{email}
        {
        }

    EmailAddress(const std::string &email, const std::string &displayName)
        : email_{email.empty() ? "" : "<"+email+">"},
          name_{"\"" + displayName + "\""}
        {
        }

    std::string domain() const
        {
            return email_.substr(email_.find('@') + 1);
        }

    explicit operator const char *() const
        {
            return email_.c_str();
        }

    friend std::ostream &operator<<(std::ostream &out, const EmailAddress &email)
        {
            return out << email.name_ << " " << email.email_;
        }

private:
    std::string email_;
    std::string name_;
};

typedef std::vector<EmailAddress> EmailAddresses;
std::ostream &operator<<(std::ostream &out, const EmailAddresses &emailAddresses);

class Email
{
public:
    Email(const EmailAddress   &from,
          const EmailAddress   &to,
          const std::string    &subject,
          const std::string    &body,
          const EmailAddresses &cc = EmailAddresses())
        : from_{from}
        , to_{to}
        , cc_{cc.empty() ? EmailAddresses(1, to) : cc}
        , subject_{subject}
        , body_{body}
        {
        }

    CURLcode send(const std::string &url,
                  const std::string &userName, 
                  const std::string &password);

private:
    struct StringData {
            std::string msg;
            size_t bytesLeft;
            StringData(std::string &&m) : msg{m}, bytesLeft{msg.size()} {}
            StringData(std::string  &m) = delete;
        };

    static std::string dateTimeNow_();
    static size_t payloadSource_(void *ptr, size_t size, size_t nmemb, void *userp);
    std::string generateMessageId_() const;
    std::string setPayloadText_();

    EmailAddress from_, to_;
    EmailAddresses cc_;
    std::string subject_, body_;
};


CURLcode Email::send(const std::string &url,
                     const std::string &userName,
                     const std::string &password)
{
    CURLcode ret = CURLE_OK;

    struct curl_slist *recipients = NULL;

    CURL *curl = curl_easy_init();

    StringData textData { setPayloadText_() };

    if (curl) {
        std::ostringstream cc;
        cc << cc_;

        curl_easy_setopt(curl, CURLOPT_USERNAME,     userName.c_str());
        curl_easy_setopt(curl, CURLOPT_PASSWORD,     password.c_str());
        curl_easy_setopt(curl, CURLOPT_URL,          url     .c_str());

        curl_easy_setopt(curl, CURLOPT_USE_SSL,      (long)CURLUSESSL_ALL);
        //curl_easy_setopt(curl, CURLOPT_CAINFO,      "/path/to/certificate.pem");

        curl_easy_setopt(curl, CURLOPT_MAIL_FROM,    (const char *)from_);
        recipients = curl_slist_append(recipients,   (const char *)to_);
        recipients = curl_slist_append(recipients,   cc.str().c_str());

        curl_easy_setopt(curl, CURLOPT_MAIL_RCPT,    recipients);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, payloadSource_);
        curl_easy_setopt(curl, CURLOPT_READDATA,     &textData);
        curl_easy_setopt(curl, CURLOPT_UPLOAD,       1L);
        curl_easy_setopt(curl, CURLOPT_VERBOSE,      1L);

        ret = curl_easy_perform(curl);

        if (ret != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: "
                      << curl_easy_strerror(ret)
                      << std::endl;
        }

        curl_slist_free_all(recipients);
        curl_easy_cleanup(curl);
    }

    return ret;
}

std::string Email::dateTimeNow_()
{
    const int RFC5322_TIME_LEN = 32;

    std::string ret;
    ret.resize(RFC5322_TIME_LEN);

    time_t tt;

#ifdef _MSC_VER
    time(&tt);
    tm *t = localtime(&tt);
#else
    tm tv, *t = &tv;
    tt = time(&tt);
    localtime_r(&tt, t);
#endif

    strftime(&ret[0], RFC5322_TIME_LEN, "%a, %d %b %Y %H:%M:%S %z", t);

    return ret;
}

inline std::string Email::generateMessageId_() const
{
    const size_t MESSAGE_ID_LEN = 37;

    tm t;
    time_t tt;
    time(&tt);

#ifdef _MSC_VER
    gmtime_s(&t, &tt);
#else
    gmtime_r(&tt, &t);
#endif

    std::string ret;
    ret.resize(MESSAGE_ID_LEN);
    size_t dateLen = std::strftime(&ret[0], MESSAGE_ID_LEN, "%Y%m%d%H%M%S", &t);

    static const std::string alphaNum {
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz" };

    std::mt19937 gen;
    std::uniform_int_distribution<> distr(0, alphaNum.length() - 1);

    std::generate_n(ret.begin() + dateLen,
                    MESSAGE_ID_LEN - dateLen,
                    [&]() { return alphaNum[distr(gen)]; });

    return ret;
}

size_t Email::payloadSource_(void *ptr, size_t size, size_t nmemb, void *userp)
{
    StringData *text = reinterpret_cast<StringData *>(userp);

    if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1) || (text->bytesLeft == 0)) {
        return 0;
    }

    if ((nmemb * size) >= text->msg.size()) {
        text->bytesLeft = 0;
        return text->msg.copy(reinterpret_cast<char *>(ptr), text->msg.size());
    }

    return 0;
}

std::string Email::setPayloadText_()
{
    std::string ret = "Date: " + dateTimeNow_() + "\r\n";

    std::ostringstream oss;
    oss << "To: "   << to_   << "\r\n"
           "From: " << from_ << "\r\n";

    if (cc_.size() > 1) {
        oss <<   "Cc: "   << cc_   << "\r\n";
    }

    ret += oss.str();

    ret +=
        "Message-ID: <" + generateMessageId_() + "@" + from_.domain() + ">\r\n"
        "Subject: " + subject_ + "\r\n"
        "\r\n" +
        body_ + "\r\n"
        "\r\n";

    return ret;
}

std::ostream &operator<<(std::ostream &out, const EmailAddresses &emailAddresses)
{
    if (!emailAddresses.empty()) {
        auto it = emailAddresses.begin();
        out << *it;
        while (++it != emailAddresses.end()) {
            out << "," << *it;
        }
    }

    return out;
}

Если я отправляю с mail.ru тогда всё работает хорошо, например:

Email email({ "...@mail.ru", "Name" },
              "...@mail.ru",
              "Subject",
              "Body" );

email.send ( "smtp://smtp.mail.ru:25",
              "...",
              "Password"  );

Но если я отправляю с gmail

Email email({ "...@gmail.com", "Name" },
              "...@gmail.com",
              "Subj",
              "Body");

email.send(   "smtp://smtp.gmail.com:465",
              "...@gmail.com",
              "Password");

я получаю ошибку

* Rebuilt URL to: smtp://smtp.gmail.com:587/ * Hostname was NOT found in DNS cache * Trying 173.194.221.109... * Connected to smtp.gmail.com (173.194.221.109) port 587 (#0) < 220 smtp.gmail.com ESMTP 125sm1125430ljj.26 - gsmtp

EHLO DNPC

< 250-smtp.gmail.com at your service, [xx.xx.xxx.xx] < 250-SIZE 35882577 < 250-8BITMIME < 250-STARTTLS < 250-ENHANCEDSTATUSCODES < 250-PIPELINING < 250-CHUNKING < 250 SMTPUTF8

STARTTLS

< 220 2.0.0 Ready to start TLS * found 174 certificates in /etc/ssl/certs/ca-certificates.crt * server certificate verification OK * common name: smtp.gmail.com (matched) * server certificate expiration date OK * server certificate activation date OK * certificate public key: RSA * certificate version: #3 * subject: C=US,ST=California,L=Mountain View,O=Google Inc,CN=smtp.gmail.com * start date: Thu, 08 Dec 2016 10:53:19 GMT

* expire date: Thu, 02 Mar 2017 10:18:00 GMT

* issuer: C=US,O=Google Inc,CN=Google Internet Authority G2 * compression: NULL * cipher: AES-128-GCM * MAC: AEAD

EHLO DNPC

< 250-smtp.gmail.com at your service, [xx.xx.xxx.xx] < 250-SIZE 35882577 < 250-8BITMIME < 250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH < 250-ENHANCEDSTATUSCODES < 250-PIPELINING < 250-CHUNKING < 250 SMTPUTF8

AUTH LOGIN

< 334 VXNlcm5hbWU6

Z2lnaWN1Y3UwODhAZ21haWwuY29t

< 334 UGFzc3dvcmQ6

ZGt0Y2Voamxia2ZjbXRranhyZg==

< 235 2.7.0 Accepted

MAIL FROM:<...@gmail.com>

< 250 2.1.0 OK 125sm1125430ljj.26 - gsmtp

RCPT TO:<...@gmail.com>

< 250 2.1.5 OK 125sm1125430ljj.26 - gsmtp

RCPT TO:< ...@gmail.com>

< 553-5.1.2 The recipient address < ...@gmail.com> is not a valid RFC-5321 < 553 5.1.2 address. 125sm1125430ljj.26 - gsmtp * RCPT failed: 553

QUIT

< 221 2.0.0 closing connection 125sm1125430ljj.26 - gsmtp * Closing connection 0 curl_easy_perform() failed: Failed sending data to the peer

Что мне надо поправить, чтобы отправка почты заработала?


Ответ на: комментарий от hateyoufeel

Ты это сам писал? Потому что в логах написано почему гмыл тебя послал.

Это я писал. А что надо поправить, чтобы заработало?

user08 ()

Адрес-то существует?

Сторонним приложениям разрешён логин по паролю или создан специальный пароль для приложения? Гугл-то требует так.

Deleted ()

< 553-5.1.2 The recipient address < ...@gmail.com> is not a valid RFC-5321 < 553 5.1.2 address. 125sm1125430ljj.26 - gsmtp * RCPT failed: 553

Адрес < ...@gmail.com> кривой.

dryukov ()
Ответ на: комментарий от dryukov

Сторонним приложениям разрешён логин по паролю или создан специальный пароль для приложения? Гугл-то требует так.

lesssecureapps включен

Адрес < ...@gmail.com> кривой.

Я его здесь заменил на "...". Адрес и пароль точно правильные. Сейчас копипастом зашёл в этот аккаунт.

user08 ()

Хрен ты гуглу отправишь почту обычным способом.
Они хотят теперь OAuth2.
Возможно есть какой-то обходной костыль, но маловероятно.

Stahl ★★☆ ()
Ответ на: комментарий от Stahl

Хрен ты гуглу отправишь почту обычным способом.
Они хотят теперь OAuth2.
Возможно есть какой-то обходной костыль, но маловероятно.

Ну Thunderbird же отправляет как-то.

user08 ()
Ответ на: комментарий от user08

Ну вот через OAuth2 скорее всего и отправляет.

Stahl ★★☆ ()
Ответ на: комментарий от invy

Во всяком случае когда я пытался curl'ом отправить почту Гуглу, то ничего у меня не вышло. Да, SSL'ем тоже обмазывался.
Ни в какую.

Stahl ★★☆ ()
Ответ на: комментарий от invy

Попробуй smtp.googlemail.com:465

* Rebuilt URL to: smtp://smtp.googlemail.com:465/
* Trying 173.194.220.16...
* Connected to smtp.googlemail.com (173.194.220.16) port 465 (#0)
* response reading failed
* Closing connection 0
curl_easy_perform() failed: Failure when receiving data from the peer

user08 ()
Ответ на: комментарий от user08
$ openssl s_client -connect smtp.googlemail.com:465
CONNECTED(00000003)
[...сертификаты...]
ehlo myhostname
250-smtp.googlemail.com at your service, [XXX.XXX.XXX.XXX]
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8

Так что надо убедиться, что curl точно по SSL/TLS пробует в порт 465 стучать.

gag ★★★★★ ()
Ответ на: комментарий от gag

Я здесь вижу намерение послать письмо одному получателю:

Но если я отправляю с gmail

Email email({ "...@gmail.com", "Name" },
              "...@gmail.com",
              "Subj",
              "Body");

email.send(   "smtp://smtp.gmail.com:465",
              "...@gmail.com",
              "Password");

Но выходит, что их двое:

RCPT TO:<...@gmail.com>

< 250 2.1.5 OK 125sm1125430ljj.26 - gsmtp

RCPT TO:< ...@gmail.com>

< 553-5.1.2 The recipient address < ...@gmail.com> is not a valid RFC-5321 < 553 5.1.2 address. 125sm1125430ljj.26 - gsmtp * RCPT failed: 553

Вот тут и должна быть проблема.

    EmailAddress(const std::string &email, const std::string &displayName)
        : email_{email.empty() ? "" : "<"+email+">"},
          name_{"\"" + displayName + "\""}
        {
        }

Наоборот: displayName опционален и может быть пустым, но никак ни email. Т.е. сейчас возможен случай: EmailAddress({"", "Вася"}) Или ещё хуже: EmailAddress({"", ""}). EmailAddress({"to@domain.tld", ""}) тоже будет выглядеть странно: "" <to@domain.tld>.

gag ★★★★★ ()
Последнее исправление: gag (всего исправлений: 1)

Именно из-за таких вот простыней для элементарной задачи «отправить email» сипляспляс должен быть разжалован в низкоуровневые.

В нормалных ЯП это делается максимум так:

setup-acc: does [
  system/user/email: herzavalish@example.com
  system/schemes/esmtp/host: "smtp.example.com"
  system/schemes/esmtp/port-id: 25
  system/schemes/esmtp/user: "herzavalish@example.com"
  system/schemes/esmtp/pass: "lohopassword"
]

true-mail: func [subj addr-from send-to cc-to bcc-to msg /local header][
  header: make system/standard/email [
    from: addr-from
    to: replace/all form send-to " " ", "
    cc: replace/all form cc-to " " ", "
    subject: subj
  ]
  send/header join join send-to cc-to bcc-to msg header
]

setup-acc
true-mail "C++ suxx"
	 herzavalish@example.com
         [lorsux@mailnesia.com] ; TO list
         [lor-user-1@mailnesia.com bomzh@gmail.com] ; CC list
         [hidden-addressee@mailnesia.com] ; BCC list
	 "This is the true high-level language"
anonymous ()
Ответ на: комментарий от anonymous

Анонимус, если уж ты занимаешься просветительством/пропагандой, хоть бы название своего нормального ЯП приведи. Нет, я понимаю, что это один из функциональных языков, но поскольку я ни одного из них не знаю от слова «совсем»...

hobbit ★★★★★ ()

Мне очень понравилась библиотека vmime для работы с электронной почтой из плюсов. Может ее глянь.

rumgot ★★★★★ ()
Ответ на: комментарий от hobbit

REBOL 2.7.8 в данном случае. Надеюсь, в скором времени примеры будем писать на свободном во всех смыслах Red.

anonymous ()
Ответ на: комментарий от anonymous

В нормалных ЯП это делается максимум так:

А если надо авторизация по OAuth2?

monk ★★★★★ ()
Ответ на: комментарий от monk

http://masashi-k.blogspot.ru/2013/06/sending-mail-with-gmail-using-xoauth2.html
А здесь можно посмотреть, как получить токен по логину и паролю: https://labs.hybris.com/2012/06/18/trying-out-oauth2-via-curl/

Спасибо за помощь. Но я так и не понял, как именно мне надо подредактировать мой код, чтобы он заработал.

user08 ()
Ответ на: комментарий от user08

как именно мне надо подредактировать мой код, чтобы он заработал

Писать за тебя программу — это перебор.

Тебе надо воткнуть перед авторизацией соединение с гуглом, которое получит токен (там ответ в виде JSON, его надо разобрать).

Вот на питоне почти весь код: https://raw.githubusercontent.com/google/gmail-oauth2-tools/master/python/oau...

monk ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.