join 1.0
lightweight network framework library
Loading...
Searching...
No Matches
smtp_client.hpp
Go to the documentation of this file.
1
25#ifndef JOIN_SERVICES_SMTP_CLIENT_HPP
26#define JOIN_SERVICES_SMTP_CLIENT_HPP
27
28// libjoin.
31#include <join/smtp_message.hpp>
32#include <join/tls_stream.hpp>
33#include <join/resolver.hpp>
34#include <join/base64.hpp>
35
36namespace join
37{
41 template <class Protocol>
42 class BasicSmtpClient
43 {
44 public:
45 using Endpoint = typename Protocol::Endpoint;
46 using Stream = typename Protocol::Stream;
47
53 BasicSmtpClient (const char* host, uint16_t port = Protocol::defaultPort)
54 : _host (host)
55 , _port (port)
56 {
57 }
58
64 BasicSmtpClient (const std::string& host, uint16_t port = Protocol::defaultPort)
65 : BasicSmtpClient (host.c_str (), port)
66 {
67 }
68
75 BasicSmtpClient (TlsContext ctx, const char* host, uint16_t port = Protocol::defaultPort)
76 : _stream (std::move (ctx))
77 , _host (host)
78 , _port (port)
79 {
80 }
81
88 BasicSmtpClient (TlsContext ctx, const std::string& host, uint16_t port = Protocol::defaultPort)
89 : BasicSmtpClient (std::move (ctx), host.c_str (), port)
90 {
91 }
92
97 BasicSmtpClient (const BasicSmtpClient& other) = delete;
98
105
111 : _stream (std::move (other._stream))
112 , _host (std::move (other._host))
113 , _port (other._port)
114 , _login (std::move (other._login))
115 , _password (std::move (other._password))
116 {
117 }
118
125 {
126 this->_stream = std::move (other._stream);
127 this->_host = std::move (other._host);
128 this->_port = other._port;
129 this->_login = std::move (other._login);
130 this->_password = std::move (other._password);
131 return *this;
132 }
133
137 virtual ~BasicSmtpClient () = default;
138
143 virtual std::string scheme () const
144 {
145 return "smtp";
146 }
147
152 const std::string& host () const
153 {
154 return this->_host;
155 }
156
161 uint16_t port () const
162 {
163 return this->_port;
164 }
165
170 std::string authority () const
171 {
172 std::string auth;
173
174 if (IpAddress::isIpv6Address (this->host ()))
175 {
176 auth += "[" + this->host () + "]";
177 }
178 else
179 {
180 auth += this->host ();
181 }
182
183 if (this->port () != Dns::Resolver::resolveService (this->scheme ()))
184 {
185 auth += ":" + std::to_string (this->port ());
186 }
187
188 return auth;
189 }
190
195 std::string url () const
196 {
197 return this->scheme () + "://" + this->authority ();
198 }
199
205 void credentials (const std::string& login, const std::string& password)
206 {
207 this->_login = login;
208 this->_password = password;
209 }
210
216 int send (const SmtpMessage& message)
217 {
218 Endpoint endpoint{Dns::Resolver::lookupAddress (this->host ()), this->port ()};
219 endpoint.hostname (this->host ());
220
221 if (this->connect (endpoint) == -1)
222 {
223 this->close ();
224 return -1;
225 }
226
227 if (this->greeting () == -1)
228 {
229 this->close ();
230 return -1;
231 }
232
233 std::vector<std::string> replies;
234
235 for (;;)
236 {
237 replies.clear ();
238
239 if (this->initialize (replies) == -1)
240 {
241 this->close ();
242 return -1;
243 }
244
245 if (std::find (replies.begin (), replies.end (), "STARTTLS") != replies.end ())
246 {
247 if (this->startTls () == -1)
248 {
249 this->close ();
250 return -1;
251 }
252 continue;
253 }
254
255 break;
256 }
257
258 auto auth = std::find_if (replies.begin (), replies.end (), [] (auto const& r) {
259 return r.find ("AUTH") != std::string::npos;
260 });
261 if (auth != replies.end ())
262 {
263 if (auth->find ("LOGIN") != std::string::npos)
264 {
265 if (this->loginAuthenticate () == -1)
266 {
267 this->close ();
268 return -1;
269 }
270 }
271 else if (auth->find ("PLAIN") != std::string::npos)
272 {
273 if (this->plainAuthenticate () == -1)
274 {
275 this->close ();
276 return -1;
277 }
278 }
279 }
280
281 if (this->sendFrom (message) == -1)
282 {
283 this->close ();
284 return -1;
285 }
286
287 if (this->sendTo (message) == -1)
288 {
289 this->close ();
290 return -1;
291 }
292
293 if (this->sendData (message) == -1)
294 {
295 this->close ();
296 return -1;
297 }
298
299 if (this->quit () == -1)
300 {
301 this->close ();
302 return -1;
303 }
304
305 this->close ();
306 return 0;
307 }
308
309 protected:
315 virtual int connect (const Endpoint& endpoint)
316 {
317 this->_stream.connect (endpoint);
318 return this->_stream.fail () ? -1 : 0;
319 }
320
325 void close ()
326 {
327 this->_stream.disconnect ();
328 this->_stream.close ();
329 this->_stream.clear ();
330 }
331
337 int sendMessage (const std::string& message)
338 {
339#ifdef DEBUG
340 std::cout << message << std::endl;
341#endif
342 this->_stream.write (message.c_str (), message.size ());
343 this->_stream.write ("\r\n", 2);
344 this->_stream.flush ();
345 return this->_stream.fail () ? -1 : 0;
346 }
347
353 std::string readReplies (std::vector<std::string>* replies = nullptr)
354 {
355 std::string reply, code;
356 for (;;)
357 {
358 if (!join::getline (this->_stream, reply))
359 {
360 return {};
361 }
362#ifdef DEBUG
363 std::cout << reply << std::endl;
364#endif
365 if ((reply.find ("-") != 3) && (reply.find (" ") != 3))
366 {
368 return {};
369 }
370 if (code.empty ())
371 {
372 code = reply.substr (0, 3);
373 }
374 if (replies)
375 {
376 replies->push_back (reply.substr (4));
377 }
378 if (reply[3] == ' ')
379 {
380 break;
381 }
382 }
383 return code;
384 }
385
390 int greeting ()
391 {
392 if (this->readReplies () != "220")
393 {
394 return -1;
395 }
396 return 0;
397 }
398
404 int initialize (std::vector<std::string>& replies)
405 {
406 if (this->sendMessage ("EHLO " + this->hostname ()) == -1)
407 {
408 return -1;
409 }
410 if (this->readReplies (&replies) != "250")
411 {
412 return -1;
413 }
414 return 0;
415 }
416
421 int startTls ()
422 {
423 if (this->sendMessage ("STARTTLS") == -1)
424 {
425 return -1;
426 }
427 if (this->readReplies () != "220")
428 {
429 return -1;
430 }
431 this->_stream.handshake ();
432 if (this->_stream.fail ())
433 {
434 return -1;
435 }
436 return 0;
437 }
438
444 {
445 if (this->sendMessage ("AUTH LOGIN") == -1)
446 {
447 return -1;
448 }
449 if (this->readReplies () != "334")
450 {
451 return -1;
452 }
453 if (this->sendMessage (Base64::encode (this->_login)) == -1)
454 {
455 return -1;
456 }
457 if (this->readReplies () != "334")
458 {
459 return -1;
460 }
461 if (this->sendMessage (Base64::encode (this->_password)) == -1)
462 {
463 return -1;
464 }
465 if (this->readReplies () != "235")
466 {
467 return -1;
468 }
469 return 0;
470 }
471
477 {
478 if (this->sendMessage ("AUTH PLAIN") == -1)
479 {
480 return -1;
481 }
482 if (this->readReplies () != "334")
483 {
484 return -1;
485 }
486 if (this->sendMessage (Base64::encode ('\0' + this->_login + '\0' + this->_password)) == -1)
487 {
488 return -1;
489 }
490 if (this->readReplies () != "235")
491 {
492 return -1;
493 }
494 return 0;
495 }
496
502 int sendFrom (const SmtpMessage& message)
503 {
504 if (this->sendMessage ("MAIL FROM: <" + message.sender ().address () + ">") == -1)
505 {
506 return -1;
507 }
508 if (this->readReplies () != "250")
509 {
510 return -1;
511 }
512 return 0;
513 }
514
520 int sendTo (const SmtpMessage& message)
521 {
522 for (auto const& recipient : message.recipients ())
523 {
524 if (this->sendMessage ("RCPT TO: <" + recipient.address () + ">") == -1)
525 {
526 return -1;
527 }
528 if (this->readReplies () != "250")
529 {
530 return -1;
531 }
532 }
533 return 0;
534 }
535
541 int sendData (const SmtpMessage& message)
542 {
543 if (this->sendMessage ("DATA") == -1)
544 {
545 return -1;
546 }
547 if (this->readReplies () != "354")
548 {
549 return -1;
550 }
551 if (message.writeHeaders (this->_stream) == -1)
552 {
553 return -1;
554 }
555 if (message.writeContent (this->_stream) == -1)
556 {
557 return -1;
558 }
559 if (this->readReplies () != "250")
560 {
561 return -1;
562 }
563 return 0;
564 }
565
570 int quit ()
571 {
572 if (this->sendMessage ("QUIT") == -1)
573 {
574 return -1;
575 }
576 if (this->readReplies () != "221")
577 {
578 return -1;
579 }
580 return 0;
581 }
582
587 std::string hostname () const
588 {
589 char name[255] = {};
590 gethostname (name, sizeof (name));
591 return name;
592 }
593
596
598 std::string _host;
599
601 uint16_t _port;
602
604 std::string _login;
605
607 std::string _password;
608 };
609
613 template <class Protocol>
614 class BasicSmtpSecureClient : public BasicSmtpClient<Protocol>
615 {
616 public:
617 using Endpoint = typename Protocol::Endpoint;
618
625 BasicSmtpSecureClient (TlsContext ctx, const char* host, uint16_t port = Protocol::defaultPort)
626 : BasicSmtpClient<Protocol> (std::move (ctx), host, port)
627 {
628 }
629
636 BasicSmtpSecureClient (TlsContext ctx, const std::string& host, uint16_t port = Protocol::defaultPort)
637 : BasicSmtpSecureClient (std::move (ctx), host.c_str (), port)
638 {
639 }
640
646
653
659 : BasicSmtpClient<Protocol> (std::move (other))
660 {
661 }
662
669 {
670 BasicSmtpClient<Protocol>::operator= (std::move (other));
671 return *this;
672 }
673
677 virtual ~BasicSmtpSecureClient () = default;
678
683 std::string scheme () const override
684 {
685 return "smtps";
686 }
687
688 protected:
694 int connect (const Endpoint& endpoint) override
695 {
696 this->_stream.connect (endpoint);
697 if (this->_stream.fail ())
698 {
699 return -1;
700 }
701 this->_stream.handshake ();
702 return this->_stream.fail () ? -1 : 0;
703 }
704 };
705}
706
707#endif
static std::string encode(const char *data, size_t size)
encode data.
Definition base64.cpp:345
static IpAddress lookupAddress(const std::string &host, int family)
resolve host name using system name servers.
Definition resolver.hpp:260
static uint16_t resolveService(const std::string &service) noexcept
resolve service name.
Definition resolver.hpp:673
basic SMTP client.
Definition smtp_protocol.hpp:34
int sendFrom(const SmtpMessage &message)
send sender address.
Definition smtp_client.hpp:502
BasicSmtpClient & operator=(const BasicSmtpClient &other)=delete
assign the basic SMTP client instance by copy.
typename Protocol::Endpoint Endpoint
Definition smtp_client.hpp:45
int greeting()
handle greeting.
Definition smtp_client.hpp:390
int initialize(std::vector< std::string > &replies)
client init.
Definition smtp_client.hpp:404
int sendTo(const SmtpMessage &message)
send recpient address.
Definition smtp_client.hpp:520
BasicSmtpClient(const char *host, uint16_t port=Protocol::defaultPort)
create the basic SMTP client instance.
Definition smtp_client.hpp:53
uint16_t port() const
get port.
Definition smtp_client.hpp:161
std::string _password
SMTP password.
Definition smtp_client.hpp:607
void credentials(const std::string &login, const std::string &password)
set credentials.
Definition smtp_client.hpp:205
int sendMessage(const std::string &message)
send message.
Definition smtp_client.hpp:337
BasicSmtpClient(const BasicSmtpClient &other)=delete
create the basic SMTP client instance by copy.
Stream _stream
stream.
Definition smtp_client.hpp:595
uint16_t _port
SMTP port.
Definition smtp_client.hpp:601
typename Protocol::Stream Stream
Definition smtp_client.hpp:46
BasicSmtpClient(const std::string &host, uint16_t port=Protocol::defaultPort)
create the basic SMTP client instance.
Definition smtp_client.hpp:64
BasicSmtpClient(TlsContext ctx, const char *host, uint16_t port=Protocol::defaultPort)
create the basic SMTP client instance using the given context.
Definition smtp_client.hpp:75
std::string authority() const
get authority.
Definition smtp_client.hpp:170
int sendData(const SmtpMessage &message)
send message.
Definition smtp_client.hpp:541
int loginAuthenticate()
authenticate using LOGIN.
Definition smtp_client.hpp:443
BasicSmtpClient(TlsContext ctx, const std::string &host, uint16_t port=Protocol::defaultPort)
create the basic SMTP client instance using the given context.
Definition smtp_client.hpp:88
virtual ~BasicSmtpClient()=default
destroy the basic SMTP client instance.
virtual std::string scheme() const
get scheme.
Definition smtp_client.hpp:143
int startTls()
start encryption.
Definition smtp_client.hpp:421
void close()
close the connection.
Definition smtp_client.hpp:325
virtual int connect(const Endpoint &endpoint)
make a connection to the given endpoint.
Definition smtp_client.hpp:315
int send(const SmtpMessage &message)
send mail message.
Definition smtp_client.hpp:216
BasicSmtpClient(BasicSmtpClient &&other)
create the basic SMTP client instance by move.
Definition smtp_client.hpp:110
std::string _login
SMTP login.
Definition smtp_client.hpp:604
const std::string & host() const
get host.
Definition smtp_client.hpp:152
std::string url() const
get URL.
Definition smtp_client.hpp:195
std::string _host
SMTP host.
Definition smtp_client.hpp:598
int plainAuthenticate()
authenticate using PLAIN.
Definition smtp_client.hpp:476
int quit()
send quit.
Definition smtp_client.hpp:570
std::string readReplies(std::vector< std::string > *replies=nullptr)
read replies.
Definition smtp_client.hpp:353
std::string hostname() const
get host name.
Definition smtp_client.hpp:587
Basic SMTPS client.
Definition smtp_protocol.hpp:37
BasicSmtpSecureClient(BasicSmtpSecureClient &&other)
create the basic SMTPS client instance by move.
Definition smtp_client.hpp:658
BasicSmtpSecureClient(TlsContext ctx, const std::string &host, uint16_t port=Protocol::defaultPort)
create the basic SMTPS client instance using the given context.
Definition smtp_client.hpp:636
typename Protocol::Endpoint Endpoint
Definition smtp_client.hpp:617
BasicSmtpSecureClient & operator=(const BasicSmtpSecureClient &other)=delete
assign the basic SMTPS client instance by copy.
BasicSmtpSecureClient(const BasicSmtpSecureClient &other)=delete
create the basic SMTPS client instance by copy.
int connect(const Endpoint &endpoint) override
make a connection to the given endpoint.
Definition smtp_client.hpp:694
BasicSmtpSecureClient(TlsContext ctx, const char *host, uint16_t port=Protocol::defaultPort)
create the basic SMTPS client instance using the given context.
Definition smtp_client.hpp:625
std::string scheme() const override
get scheme.
Definition smtp_client.hpp:683
virtual ~BasicSmtpSecureClient()=default
destroy the basic SMTPS client instance.
bool isIpv6Address() const
check if IP address is an IPv6 address.
Definition ip_address.cpp:1394
mail message.
Definition smtp_message.hpp:230
int writeContent(std::ostream &out) const
write content to the given output stream.
Definition smtp_message.cpp:449
void sender(const SmtpSender &from)
set mail sender.
Definition smtp_message.cpp:287
const SmtpRecipients & recipients() const
get mail recipients.
Definition smtp_message.cpp:323
int writeHeaders(std::ostream &out) const
write header to the given output stream.
Definition smtp_message.cpp:368
TLS/DTLS context.
Definition tls_context.hpp:42
Definition acceptor.hpp:32
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
thread_local std::error_code lastError
last error.
Definition error.cpp:32
bool getline(std::istream &in, std::string &line, std::streamsize max=1024)
read line (delimiter "\r\n").
Definition utils.hpp:307
Definition error.hpp:144