source: nscp/modules/SMTPClient/smtp.cpp @ b24c97f

0.4.00.4.10.4.2
Last change on this file since b24c97f was 40fca56, checked in by Michael Medin <michael@…>, 19 months ago
  • Added initial SMTPClient to allow sending messages via SMTP. Still requires template support and configuration options (mainly PoC right now).
  • Fixed unicode log issue (still not sure message format is correct)
  • Renamed Message to log in internal wrapper API
  • Fixed issue in settings old (regarding readin new keys, not 100% supported yet)
  • Property mode set to 100644
File size: 6.7 KB
Line 
1#include "stdafx.h"
2#include "smtp.hpp"
3
4#include <boost/algorithm/string/case_conv.hpp>
5#include <boost/bind.hpp>
6#include <boost/date_time/posix_time/posix_time.hpp>
7#include <error.hpp>
8
9namespace smtp {
10        namespace client {
11                void smtp_client::send_mail(const std::string sender, const std::list<std::string> &recipients, std::string message) {
12                        BOOST_FOREACH(std::string r, recipients) {
13                                boost::shared_ptr<envelope> en(new envelope);
14                                en->sender = sender;
15                                en->recipient = r;
16                                en->data = message;
17
18                                {
19                                        boost::lock_guard<boost::mutex> lg(m);
20                                        ready.push_front(en);
21                                }
22                        }
23                        async_run_queue();
24                }
25
26                void smtp_client::tick(bool) {
27                        size_t n = 0;
28                        bool active;
29                        {
30                                boost::lock_guard<boost::mutex> lg(m);
31                                while (!deferred.empty())
32                                {
33                                        ready.push_back(deferred.front());
34                                        deferred.pop_front();
35                                        n++;
36                                }
37                                active = !ready.empty();
38                        }
39
40                        if (active)
41                                async_run_queue();
42
43                        if (n > 0) {
44                                NSC_DEBUG_MSG(_T("activated ") + strEx::itos(n) + _T(" deferred emails"));
45                        }
46                }
47
48                void smtp_client::async_run_queue() {
49                        boost::lock_guard<boost::mutex> lg(m);
50                        if (!active_connection) {
51                                active_connection.reset(new connection(shared_from_this()));
52                                if (active_connection)
53                                        active_connection->start();
54                        }
55                }
56
57                smtp_client::connection::connection(boost::shared_ptr<smtp_client> sc)
58                        : sc(sc)
59                        , res(sc->io_service)
60                        , que("imap.medin.name", "1234")
61                        , serv(sc->io_service)
62                {
63                        config["canonical-name"] = "test.medin.name";
64                }
65
66                void smtp_client::connection::start() {
67                        res.async_resolve(que, boost::bind(&connection::resolved, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::iterator));
68                }
69
70                void smtp_client::connection::resolved(boost::system::error_code ec,boost::asio::ip::tcp::resolver::iterator iter) {
71                        if (ec) {
72                                NSC_LOG_ERROR("smtp failed resolving: " + ec.message());
73                                boost::lock_guard<boost::mutex> lg(sc->m);
74                                sc->active_connection.reset();
75                                return;
76                        }
77
78                        if (iter == boost::asio::ip::tcp::resolver::iterator()) {
79                                NSC_LOG_ERROR("smtp ran out of server addresses");
80                                boost::lock_guard<boost::mutex> lg(sc->m);
81                                sc->active_connection.reset();
82                                return;
83                        }
84
85                        NSC_DEBUG_MSG("smtp connecting to " + iter->endpoint().address().to_string());
86                        serv.async_connect(*iter, boost::bind(&connection::connected, shared_from_this(), iter, _1));
87                }
88
89                void smtp_client::connection::connected(boost::asio::ip::tcp::resolver::iterator iter, boost::system::error_code ec) {
90                        if (ec) {
91                                NSC_LOG_ERROR("smtp failed to connect to " + iter->endpoint().address().to_string() + ": " + ec.message());
92                                iter++;
93                                resolved(boost::system::error_code(), iter);
94                                return;
95                        }
96
97                        NSC_DEBUG_MSG("smtp connected to " + iter->endpoint().address().to_string());
98                        state = BANNER;
99                        async_read_response();
100                }
101
102                void smtp_client::connection::async_read_response() {
103                        boost::asio::async_read_until(serv, readbuf, "\r\n", boost::bind(&connection::got_response, shared_from_this(), "", _1, _2));
104                }
105
106                void smtp_client::connection::got_response(std::string resp, boost::system::error_code ec, size_t bytes) {
107                        if (ec) {
108                                NSC_LOG_ERROR("smtp failure in reading: " + ec.message());
109                                boost::lock_guard<boost::mutex> lg(sc->m);
110                                if (cur)
111                                        sc->ready.push_back(cur);
112                                sc->active_connection.reset();
113                                return;
114                        }
115
116                        std::string line;
117                        line.reserve(bytes);
118                        for (size_t i = 0; i < bytes; i++)
119                                line += char(readbuf.sbumpc());
120
121                        resp += line;
122
123                        if (line.length() >= 4 && line[3] == '-') {
124                                boost::asio::async_read_until(serv, readbuf, "\r\n", boost::bind(&connection::got_response,  shared_from_this(), resp, _1, _2));
125                                return;
126                        }
127                        NSC_DEBUG_MSG("smtp read " + resp);
128
129                        bool broken_resp = resp.empty() || !('2' <= resp[0] && resp[0] <= '5') || (resp[0] == '3' && (state != DATA || resp.substr(0,3) != "354"));
130
131                        // FIXME deferral / drop-on-the-floor notifications
132
133                        if (broken_resp || resp[0] == '4')
134                        {
135                                boost::lock_guard<boost::mutex> lg(sc->m);
136                                if (cur)
137                                        sc->deferred.push_back(cur);
138                        }
139
140                        if (broken_resp || state == QUIT) {
141                                NSC_LOG_ERROR("smtp terminating");
142
143                                boost::lock_guard<boost::mutex> lg(sc->m);
144                                sc->active_connection.reset();
145                                return;
146                        }
147
148                        if ((resp[0] == '4' || resp[0] == '5' || state == DATA_354) && (resp.substr(0,3) != "502" || state != EHLO)) {
149                                cur.reset();
150                                if (sc->ready.empty() || state <= RSET) {
151                                        state = QUIT;
152                                        send_line("QUIT");
153                                } else {
154                                        state = RSET;
155                                        send_line("RSET");
156                                }
157                                return;
158                        }
159
160                        assert(!resp.empty());
161
162                        switch (state) {
163                        case BANNER:
164                                assert(resp[0] == '2');
165                                state = EHLO;
166                                send_line("EHLO " + config["canonical-name"]);
167                                break;
168                        case EHLO:
169                                if (resp.substr(0,3) == "502")
170                                {
171                                        state = HELO;
172                                        send_line("HELO" + config["canonical-name"]);
173                                        break;
174                                }
175                                assert(resp[0] == '2');
176                                /* passthrough */
177                        case HELO:
178                        case RSET:
179                                assert(resp[0] == '2');
180                                assert(!cur);
181                                {
182                                        boost::lock_guard<boost::mutex> lg(sc->m);
183                                        if (sc->ready.empty())
184                                        {
185                                                state = QUIT;
186                                                send_line("QUIT");
187                                                break;
188                                        }
189                                        cur = sc->ready.front();
190                                        sc->ready.pop_front();
191                                }
192                                assert(cur);
193                                state = MAIL_FROM;
194                                send_line("MAIL FROM: <" + cur->sender + ">");
195                                break;
196                        case MAIL_FROM:
197                                assert(resp[0] == '2');
198                                assert(cur);
199                                state = RCPT_TO;
200                                send_line("RCPT TO: <" + cur->recipient + ">");
201                                break;
202                        case RCPT_TO:
203                                assert(resp[0] == '2');
204                                assert(cur);
205                                state = DATA;
206                                send_line("DATA");
207                                break;
208                        case DATA:
209                                assert(resp.substr(0,3) == "354");
210                                assert(cur);
211                                state = DATA_354;
212                                send_raw(cur->data + ".\r\n");
213                                break;
214                        case DATA_354:
215                        case QUIT:
216                                assert(0); // handled above
217                                break;
218                        }
219                }
220
221                void smtp_client::connection::send_line(std::string line) {
222                        send_raw(line + "\r\n");
223                }
224
225                void smtp_client::connection::send_raw(std::string raw) {
226                        NSC_DEBUG_MSG("smtp sending " + raw);
227                        boost::shared_ptr<boost::asio::const_buffers_1> rb(new boost::asio::const_buffers_1 (boost::asio::buffer(raw)));
228                        boost::asio::async_write(serv, *rb, boost::bind(&connection::sent, shared_from_this(), rb, _1, _2));
229                }
230
231                void smtp_client::connection::sent(boost::shared_ptr<boost::asio::const_buffers_1>, boost::system::error_code ec, size_t) {
232                        if (ec) {
233                                NSC_LOG_ERROR("smtp failure in reading: " + ec.message());
234                                boost::lock_guard<boost::mutex> lg(sc->m);
235                                if (cur)
236                                        sc->ready.push_back(cur);
237                                sc->active_connection.reset();
238                                return;
239                        }
240                        async_read_response();
241                }
242        }
243}
Note: See TracBrowser for help on using the repository browser.