1 /* $OpenBSD: smtp_client.c,v 1.17 2022/12/28 21:30:18 jmc Exp $ */
2
3 /*
4 * Copyright (c) 2018 Eric Faurot <eric@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/types.h>
20 #include <sys/socket.h>
21
22 #include <netinet/in.h>
23
24 #include <ctype.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <resolv.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "log.h"
33 #include "ioev.h"
34 #include "smtp.h"
35
36 #define TRACE_SMTPCLT 2
37 #define TRACE_IO 3
38
39 enum {
40 STATE_INIT = 0,
41 STATE_BANNER,
42 STATE_EHLO,
43 STATE_HELO,
44 STATE_LHLO,
45 STATE_STARTTLS,
46 STATE_AUTH,
47 STATE_AUTH_PLAIN,
48 STATE_AUTH_LOGIN,
49 STATE_AUTH_LOGIN_USER,
50 STATE_AUTH_LOGIN_PASS,
51 STATE_READY,
52 STATE_MAIL,
53 STATE_RCPT,
54 STATE_DATA,
55 STATE_BODY,
56 STATE_EOM,
57 STATE_RSET,
58 STATE_QUIT,
59
60 STATE_LAST
61 };
62
63 #define base64_encode __b64_ntop
64 #define base64_decode __b64_pton
65
66 #define FLAG_TLS 0x01
67 #define FLAG_TLS_VERIFIED 0x02
68
69 #define SMTP_EXT_STARTTLS 0x01
70 #define SMTP_EXT_PIPELINING 0x02
71 #define SMTP_EXT_AUTH 0x04
72 #define SMTP_EXT_AUTH_PLAIN 0x08
73 #define SMTP_EXT_AUTH_LOGIN 0x10
74 #define SMTP_EXT_DSN 0x20
75 #define SMTP_EXT_SIZE 0x40
76
77 struct smtp_client {
78 void *tag;
79 struct smtp_params params;
80
81 int state;
82 int flags;
83 int ext;
84 size_t ext_size;
85
86 struct io *io;
87 char *reply;
88 size_t replysz;
89
90 struct smtp_mail *mail;
91 int rcptidx;
92 int rcptok;
93 };
94
95 void log_trace_verbose(int);
96 void log_trace(int, const char *, ...)
97 __attribute__((format (printf, 2, 3)));
98
99 static void smtp_client_io(struct io *, int, void *);
100 static void smtp_client_free(struct smtp_client *);
101 static void smtp_client_state(struct smtp_client *, int);
102 static void smtp_client_abort(struct smtp_client *, int, const char *);
103 static void smtp_client_cancel(struct smtp_client *, int, const char *);
104 static void smtp_client_sendcmd(struct smtp_client *, char *, ...);
105 static void smtp_client_sendbody(struct smtp_client *);
106 static int smtp_client_readline(struct smtp_client *);
107 static int smtp_client_replycat(struct smtp_client *, const char *);
108 static void smtp_client_response(struct smtp_client *, const char *);
109 static void smtp_client_mail_abort(struct smtp_client *);
110 static void smtp_client_mail_status(struct smtp_client *, const char *);
111 static void smtp_client_rcpt_status(struct smtp_client *, struct smtp_rcpt *, const char *);
112
113 static const char *strstate[STATE_LAST] = {
114 "INIT",
115 "BANNER",
116 "EHLO",
117 "HELO",
118 "LHLO",
119 "STARTTLS",
120 "AUTH",
121 "AUTH_PLAIN",
122 "AUTH_LOGIN",
123 "AUTH_LOGIN_USER",
124 "AUTH_LOGIN_PASS",
125 "READY",
126 "MAIL",
127 "RCPT",
128 "DATA",
129 "BODY",
130 "EOM",
131 "RSET",
132 "QUIT",
133 };
134
135 struct smtp_client *
smtp_connect(const struct smtp_params * params,void * tag)136 smtp_connect(const struct smtp_params *params, void *tag)
137 {
138 struct smtp_client *proto;
139
140 proto = calloc(1, sizeof *proto);
141 if (proto == NULL)
142 return NULL;
143
144 memmove(&proto->params, params, sizeof(*params));
145 proto->tag = tag;
146 proto->io = io_new();
147 if (proto->io == NULL) {
148 free(proto);
149 return NULL;
150 }
151 io_set_callback(proto->io, smtp_client_io, proto);
152 io_set_timeout(proto->io, proto->params.timeout);
153
154 if (io_connect(proto->io, proto->params.dst, proto->params.src) == -1) {
155 smtp_client_abort(proto, FAIL_CONN, io_error(proto->io));
156 return NULL;
157 }
158
159 return proto;
160 }
161
162 void
smtp_cert_verified(struct smtp_client * proto,int verified)163 smtp_cert_verified(struct smtp_client *proto, int verified)
164 {
165 if (verified == CERT_OK)
166 proto->flags |= FLAG_TLS_VERIFIED;
167
168 else if (proto->params.tls_verify) {
169 errno = EAUTH;
170 smtp_client_abort(proto, FAIL_CONN,
171 "Invalid server certificate");
172 return;
173 }
174
175 io_resume(proto->io, IO_IN);
176
177 if (proto->state == STATE_INIT)
178 smtp_client_state(proto, STATE_BANNER);
179 else {
180 /* Clear extensions before re-issueing an EHLO command. */
181 proto->ext = 0;
182 smtp_client_state(proto, STATE_EHLO);
183 }
184 }
185
186 void
smtp_set_tls(struct smtp_client * proto,void * ctx)187 smtp_set_tls(struct smtp_client *proto, void *ctx)
188 {
189 io_connect_tls(proto->io, ctx, proto->params.tls_servname);
190 }
191
192 void
smtp_quit(struct smtp_client * proto)193 smtp_quit(struct smtp_client *proto)
194 {
195 if (proto->state != STATE_READY)
196 fatalx("connection is not ready");
197
198 smtp_client_state(proto, STATE_QUIT);
199 }
200
201 void
smtp_sendmail(struct smtp_client * proto,struct smtp_mail * mail)202 smtp_sendmail(struct smtp_client *proto, struct smtp_mail *mail)
203 {
204 if (proto->state != STATE_READY)
205 fatalx("connection is not ready");
206
207 proto->mail = mail;
208 smtp_client_state(proto, STATE_MAIL);
209 }
210
211 static void
smtp_client_free(struct smtp_client * proto)212 smtp_client_free(struct smtp_client *proto)
213 {
214 if (proto->mail)
215 fatalx("current task should have been deleted already");
216
217 smtp_closed(proto->tag, proto);
218
219 if (proto->io)
220 io_free(proto->io);
221
222 free(proto->reply);
223 free(proto);
224 }
225
226 /*
227 * End the session immediately.
228 */
229 static void
smtp_client_abort(struct smtp_client * proto,int err,const char * reason)230 smtp_client_abort(struct smtp_client *proto, int err, const char *reason)
231 {
232 smtp_failed(proto->tag, proto, err, reason);
233
234 if (proto->mail)
235 smtp_client_mail_abort(proto);
236
237 smtp_client_free(proto);
238 }
239
240 /*
241 * Properly close the session.
242 */
243 static void
smtp_client_cancel(struct smtp_client * proto,int err,const char * reason)244 smtp_client_cancel(struct smtp_client *proto, int err, const char *reason)
245 {
246 if (proto->mail)
247 fatal("not supposed to have a mail");
248
249 smtp_failed(proto->tag, proto, err, reason);
250
251 smtp_client_state(proto, STATE_QUIT);
252 }
253
254 static void
smtp_client_state(struct smtp_client * proto,int newstate)255 smtp_client_state(struct smtp_client *proto, int newstate)
256 {
257 struct smtp_rcpt *rcpt;
258 char ibuf[LINE_MAX], obuf[LINE_MAX];
259 size_t n;
260 int oldstate;
261
262 if (proto->reply)
263 proto->reply[0] = '\0';
264
265 again:
266 oldstate = proto->state;
267 proto->state = newstate;
268
269 log_trace(TRACE_SMTPCLT, "%p: %s -> %s", proto,
270 strstate[oldstate],
271 strstate[newstate]);
272
273 /* don't try this at home! */
274 #define smtp_client_state(_s, _st) do { newstate = _st; goto again; } while (0)
275
276 switch (proto->state) {
277 case STATE_BANNER:
278 io_set_read(proto->io);
279 break;
280
281 case STATE_EHLO:
282 smtp_client_sendcmd(proto, "EHLO %s", proto->params.helo);
283 break;
284
285 case STATE_HELO:
286 smtp_client_sendcmd(proto, "HELO %s", proto->params.helo);
287 break;
288
289 case STATE_LHLO:
290 smtp_client_sendcmd(proto, "LHLO %s", proto->params.helo);
291 break;
292
293 case STATE_STARTTLS:
294 if (proto->params.tls_req == TLS_NO || proto->flags & FLAG_TLS)
295 smtp_client_state(proto, STATE_AUTH);
296 else if (proto->ext & SMTP_EXT_STARTTLS)
297 smtp_client_sendcmd(proto, "STARTTLS");
298 else if (proto->params.tls_req == TLS_FORCE)
299 smtp_client_cancel(proto, FAIL_IMPL,
300 "TLS not supported by remote host");
301 else
302 smtp_client_state(proto, STATE_AUTH);
303 break;
304
305 case STATE_AUTH:
306 if (!proto->params.auth_user)
307 smtp_client_state(proto, STATE_READY);
308 else if ((proto->flags & FLAG_TLS) == 0)
309 smtp_client_cancel(proto, FAIL_IMPL,
310 "Authentication requires TLS");
311 else if ((proto->ext & SMTP_EXT_AUTH) == 0)
312 smtp_client_cancel(proto, FAIL_IMPL,
313 "AUTH not supported by remote host");
314 else if (proto->ext & SMTP_EXT_AUTH_PLAIN)
315 smtp_client_state(proto, STATE_AUTH_PLAIN);
316 else if (proto->ext & SMTP_EXT_AUTH_LOGIN)
317 smtp_client_state(proto, STATE_AUTH_LOGIN);
318 else
319 smtp_client_cancel(proto, FAIL_IMPL,
320 "No supported AUTH method");
321 break;
322
323 case STATE_AUTH_PLAIN:
324 (void)strlcpy(ibuf, "-", sizeof(ibuf));
325 (void)strlcat(ibuf, proto->params.auth_user, sizeof(ibuf));
326 if (strlcat(ibuf, ":", sizeof(ibuf)) >= sizeof(ibuf)) {
327 errno = EMSGSIZE;
328 smtp_client_cancel(proto, FAIL_INTERNAL,
329 "credentials too large");
330 break;
331 }
332 n = strlcat(ibuf, proto->params.auth_pass, sizeof(ibuf));
333 if (n >= sizeof(ibuf)) {
334 errno = EMSGSIZE;
335 smtp_client_cancel(proto, FAIL_INTERNAL,
336 "credentials too large");
337 break;
338 }
339 *strchr(ibuf, ':') = '\0';
340 ibuf[0] = '\0';
341 if (base64_encode(ibuf, n, obuf, sizeof(obuf)) == -1) {
342 errno = EMSGSIZE;
343 smtp_client_cancel(proto, FAIL_INTERNAL,
344 "credentials too large");
345 break;
346 }
347 smtp_client_sendcmd(proto, "AUTH PLAIN %s", obuf);
348 explicit_bzero(ibuf, sizeof ibuf);
349 explicit_bzero(obuf, sizeof obuf);
350 break;
351
352 case STATE_AUTH_LOGIN:
353 smtp_client_sendcmd(proto, "AUTH LOGIN");
354 break;
355
356 case STATE_AUTH_LOGIN_USER:
357 if (base64_encode(proto->params.auth_user,
358 strlen(proto->params.auth_user), obuf,
359 sizeof(obuf)) == -1) {
360 errno = EMSGSIZE;
361 smtp_client_cancel(proto, FAIL_INTERNAL,
362 "credentials too large");
363 break;
364 }
365 smtp_client_sendcmd(proto, "%s", obuf);
366 explicit_bzero(obuf, sizeof obuf);
367 break;
368
369 case STATE_AUTH_LOGIN_PASS:
370 if (base64_encode(proto->params.auth_pass,
371 strlen(proto->params.auth_pass), obuf,
372 sizeof(obuf)) == -1) {
373 errno = EMSGSIZE;
374 smtp_client_cancel(proto, FAIL_INTERNAL,
375 "credentials too large");
376 break;
377 }
378 smtp_client_sendcmd(proto, "%s", obuf);
379 explicit_bzero(obuf, sizeof obuf);
380 break;
381
382 case STATE_READY:
383 smtp_ready(proto->tag, proto);
384 break;
385
386 case STATE_MAIL:
387 if (proto->ext & SMTP_EXT_DSN)
388 smtp_client_sendcmd(proto, "MAIL FROM:<%s>%s%s%s%s",
389 proto->mail->from,
390 proto->mail->dsn_ret ? " RET=" : "",
391 proto->mail->dsn_ret ? proto->mail->dsn_ret : "",
392 proto->mail->dsn_envid ? " ENVID=" : "",
393 proto->mail->dsn_envid ? proto->mail->dsn_envid : "");
394 else
395 smtp_client_sendcmd(proto, "MAIL FROM:<%s>",
396 proto->mail->from);
397 break;
398
399 case STATE_RCPT:
400 if (proto->rcptidx == proto->mail->rcptcount) {
401 smtp_client_state(proto, STATE_DATA);
402 break;
403 }
404 rcpt = &proto->mail->rcpt[proto->rcptidx];
405 if (proto->ext & SMTP_EXT_DSN)
406 smtp_client_sendcmd(proto, "RCPT TO:<%s>%s%s%s%s",
407 rcpt->to,
408 rcpt->dsn_notify ? " NOTIFY=" : "",
409 rcpt->dsn_notify ? rcpt->dsn_notify : "",
410 rcpt->dsn_orcpt ? " ORCPT=" : "",
411 rcpt->dsn_orcpt ? rcpt->dsn_orcpt : "");
412 else
413 smtp_client_sendcmd(proto, "RCPT TO:<%s>", rcpt->to);
414 break;
415
416 case STATE_DATA:
417 if (proto->rcptok == 0) {
418 smtp_client_mail_abort(proto);
419 smtp_client_state(proto, STATE_RSET);
420 }
421 else
422 smtp_client_sendcmd(proto, "DATA");
423 break;
424
425 case STATE_BODY:
426 fseek(proto->mail->fp, 0, SEEK_SET);
427 smtp_client_sendbody(proto);
428 break;
429
430 case STATE_EOM:
431 smtp_client_sendcmd(proto, ".");
432 break;
433
434 case STATE_RSET:
435 smtp_client_sendcmd(proto, "RSET");
436 break;
437
438 case STATE_QUIT:
439 smtp_client_sendcmd(proto, "QUIT");
440 break;
441
442 default:
443 fatalx("%s: bad state %d", __func__, proto->state);
444 }
445 #undef smtp_client_state
446 }
447
448 /*
449 * Handle a response to an SMTP command
450 */
451 static void
smtp_client_response(struct smtp_client * proto,const char * line)452 smtp_client_response(struct smtp_client *proto, const char *line)
453 {
454 struct smtp_rcpt *rcpt;
455 int i, seen;
456
457 switch (proto->state) {
458 case STATE_BANNER:
459 if (line[0] != '2')
460 smtp_client_abort(proto, FAIL_RESP, line);
461 else if (proto->params.lmtp)
462 smtp_client_state(proto, STATE_LHLO);
463 else
464 smtp_client_state(proto, STATE_EHLO);
465 break;
466
467 case STATE_EHLO:
468 if (line[0] != '2') {
469 /*
470 * Either rejected or not implemented. If we want to
471 * use EHLO extensions, report an SMTP error.
472 * Otherwise, fallback to using HELO.
473 */
474 if ((proto->params.tls_req == TLS_FORCE) ||
475 (proto->params.auth_user))
476 smtp_client_cancel(proto, FAIL_RESP, line);
477 else
478 smtp_client_state(proto, STATE_HELO);
479 break;
480 }
481 smtp_client_state(proto, STATE_STARTTLS);
482 break;
483
484 case STATE_HELO:
485 if (line[0] != '2')
486 smtp_client_cancel(proto, FAIL_RESP, line);
487 else
488 smtp_client_state(proto, STATE_READY);
489 break;
490
491 case STATE_LHLO:
492 if (line[0] != '2')
493 smtp_client_cancel(proto, FAIL_RESP, line);
494 else
495 smtp_client_state(proto, STATE_READY);
496 break;
497
498 case STATE_STARTTLS:
499 if (line[0] != '2') {
500 if ((proto->params.tls_req == TLS_FORCE) ||
501 (proto->params.auth_user)) {
502 smtp_client_cancel(proto, FAIL_RESP, line);
503 break;
504 }
505 smtp_client_state(proto, STATE_AUTH);
506 }
507 else
508 smtp_require_tls(proto->tag, proto);
509 break;
510
511 case STATE_AUTH_PLAIN:
512 if (line[0] != '2')
513 smtp_client_cancel(proto, FAIL_RESP, line);
514 else
515 smtp_client_state(proto, STATE_READY);
516 break;
517
518 case STATE_AUTH_LOGIN:
519 if (strncmp(line, "334 ", 4))
520 smtp_client_cancel(proto, FAIL_RESP, line);
521 else
522 smtp_client_state(proto, STATE_AUTH_LOGIN_USER);
523 break;
524
525 case STATE_AUTH_LOGIN_USER:
526 if (strncmp(line, "334 ", 4))
527 smtp_client_cancel(proto, FAIL_RESP, line);
528 else
529 smtp_client_state(proto, STATE_AUTH_LOGIN_PASS);
530 break;
531
532 case STATE_AUTH_LOGIN_PASS:
533 if (line[0] != '2')
534 smtp_client_cancel(proto, FAIL_RESP, line);
535 else
536 smtp_client_state(proto, STATE_READY);
537 break;
538
539 case STATE_MAIL:
540 if (line[0] != '2') {
541 smtp_client_mail_status(proto, line);
542 smtp_client_state(proto, STATE_RSET);
543 }
544 else
545 smtp_client_state(proto, STATE_RCPT);
546 break;
547
548 case STATE_RCPT:
549 rcpt = &proto->mail->rcpt[proto->rcptidx++];
550 if (line[0] != '2')
551 smtp_client_rcpt_status(proto, rcpt, line);
552 else {
553 proto->rcptok++;
554 smtp_client_state(proto, STATE_RCPT);
555 }
556 break;
557
558 case STATE_DATA:
559 if (line[0] != '2' && line[0] != '3') {
560 smtp_client_mail_status(proto, line);
561 smtp_client_state(proto, STATE_RSET);
562 }
563 else
564 smtp_client_state(proto, STATE_BODY);
565 break;
566
567 case STATE_EOM:
568 if (proto->params.lmtp) {
569 /*
570 * LMTP reports a status of each accepted RCPT.
571 * Report status for the first pending RCPT and read
572 * more lines if another rcpt needs a status.
573 */
574 for (i = 0, seen = 0; i < proto->mail->rcptcount; i++) {
575 rcpt = &proto->mail->rcpt[i];
576 if (rcpt->done)
577 continue;
578 if (seen) {
579 io_set_read(proto->io);
580 return;
581 }
582 smtp_client_rcpt_status(proto,
583 &proto->mail->rcpt[i], line);
584 seen = 1;
585 }
586 }
587 smtp_client_mail_status(proto, line);
588 smtp_client_state(proto, STATE_READY);
589 break;
590
591 case STATE_RSET:
592 if (line[0] != '2')
593 smtp_client_cancel(proto, FAIL_RESP, line);
594 else
595 smtp_client_state(proto, STATE_READY);
596 break;
597
598 case STATE_QUIT:
599 smtp_client_free(proto);
600 break;
601
602 default:
603 fatalx("%s: bad state %d", __func__, proto->state);
604 }
605 }
606
607 static void
smtp_client_io(struct io * io,int evt,void * arg)608 smtp_client_io(struct io *io, int evt, void *arg)
609 {
610 struct smtp_client *proto = arg;
611
612 log_trace(TRACE_IO, "%p: %s %s", proto, io_strevent(evt), io_strio(io));
613
614 switch (evt) {
615 case IO_CONNECTED:
616 if (proto->params.tls_req == TLS_SMTPS) {
617 io_set_write(io);
618 smtp_require_tls(proto->tag, proto);
619 }
620 else
621 smtp_client_state(proto, STATE_BANNER);
622 break;
623
624 case IO_TLSREADY:
625 proto->flags |= FLAG_TLS;
626 if (proto->state == STATE_INIT)
627 smtp_client_state(proto, STATE_BANNER);
628 else {
629 /* Clear extensions before re-issueing an EHLO command. */
630 proto->ext = 0;
631 smtp_client_state(proto, STATE_EHLO);
632 }
633 break;
634
635 case IO_DATAIN:
636 while (smtp_client_readline(proto))
637 ;
638 break;
639
640 case IO_LOWAT:
641 if (proto->state == STATE_BODY)
642 smtp_client_sendbody(proto);
643 else
644 io_set_read(io);
645 break;
646
647 case IO_TIMEOUT:
648 errno = ETIMEDOUT;
649 smtp_client_abort(proto, FAIL_CONN, "Connection timeout");
650 break;
651
652 case IO_ERROR:
653 smtp_client_abort(proto, FAIL_CONN, io_error(io));
654 break;
655
656 case IO_DISCONNECTED:
657 smtp_client_abort(proto, FAIL_CONN, io_error(io));
658 break;
659
660 default:
661 fatalx("%s: bad event %d", __func__, evt);
662 }
663 }
664
665 /*
666 * return 1 if a new line is expected.
667 */
668 static int
smtp_client_readline(struct smtp_client * proto)669 smtp_client_readline(struct smtp_client *proto)
670 {
671 const char *e;
672 size_t len;
673 char *line, *msg, *p;
674 int cont;
675
676 line = io_getline(proto->io, &len);
677 if (line == NULL) {
678 if (io_datalen(proto->io) >= proto->params.linemax)
679 smtp_client_abort(proto, FAIL_PROTO, "Line too long");
680 return 0;
681 }
682
683 /* Strip trailing '\r' */
684 if (len && line[len - 1] == '\r')
685 line[--len] = '\0';
686
687 log_trace(TRACE_SMTPCLT, "%p: <<< %s", proto, line);
688
689 /* Validate SMTP */
690 if (len > 3) {
691 msg = line + 4;
692 cont = (line[3] == '-');
693 } else if (len == 3) {
694 msg = line + 3;
695 cont = 0;
696 } else {
697 smtp_client_abort(proto, FAIL_PROTO, "Response too short");
698 return 0;
699 }
700
701 /* Validate reply code. */
702 if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
703 !isdigit((unsigned char)line[2])) {
704 smtp_client_abort(proto, FAIL_PROTO, "Invalid reply code");
705 return 0;
706 }
707
708 /* Validate reply message. */
709 for (p = msg; *p; p++)
710 if (!isprint((unsigned char)*p)) {
711 smtp_client_abort(proto, FAIL_PROTO,
712 "Non-printable characters in response");
713 return 0;
714 }
715
716 /* Read extensions. */
717 if (proto->state == STATE_EHLO) {
718 if (strcmp(msg, "STARTTLS") == 0)
719 proto->ext |= SMTP_EXT_STARTTLS;
720 else if (strncmp(msg, "AUTH ", 5) == 0) {
721 proto->ext |= SMTP_EXT_AUTH;
722 if ((p = strstr(msg, " PLAIN")) &&
723 (*(p+6) == '\0' || *(p+6) == ' '))
724 proto->ext |= SMTP_EXT_AUTH_PLAIN;
725 if ((p = strstr(msg, " LOGIN")) &&
726 (*(p+6) == '\0' || *(p+6) == ' '))
727 proto->ext |= SMTP_EXT_AUTH_LOGIN;
728 }
729 else if (strcmp(msg, "PIPELINING") == 0)
730 proto->ext |= SMTP_EXT_PIPELINING;
731 else if (strcmp(msg, "DSN") == 0)
732 proto->ext |= SMTP_EXT_DSN;
733 else if (strncmp(msg, "SIZE ", 5) == 0) {
734 proto->ext_size = strtonum(msg + 5, 0, SIZE_T_MAX, &e);
735 if (e == NULL)
736 proto->ext |= SMTP_EXT_SIZE;
737 }
738 }
739
740 if (smtp_client_replycat(proto, line) == -1) {
741 smtp_client_abort(proto, FAIL_INTERNAL, NULL);
742 return 0;
743 }
744
745 if (cont)
746 return 1;
747
748 if (io_datalen(proto->io)) {
749 /*
750 * There should be no pending data after a response is read,
751 * except for the multiple status lines after a LMTP message.
752 * It can also happen with pipelineing, but we don't do that
753 * for now.
754 */
755 if (!(proto->params.lmtp && proto->state == STATE_EOM)) {
756 smtp_client_abort(proto, FAIL_PROTO, "Trailing data");
757 return 0;
758 }
759 }
760
761 io_set_write(proto->io);
762 smtp_client_response(proto, proto->reply);
763 return 0;
764 }
765
766 /*
767 * Concatenate the given response line.
768 */
769 static int
smtp_client_replycat(struct smtp_client * proto,const char * line)770 smtp_client_replycat(struct smtp_client *proto, const char *line)
771 {
772 size_t len;
773 char *tmp;
774 int first;
775
776 if (proto->reply && proto->reply[0]) {
777 /*
778 * If the line is the continuation of an multi-line response,
779 * skip the status and ESC parts. First, skip the status, then
780 * skip the separator amd ESC if found.
781 */
782 first = 0;
783 line += 3;
784 if (line[0]) {
785 line += 1;
786 if (isdigit((unsigned char)line[0]) && line[1] == '.' &&
787 isdigit((unsigned char)line[2]) && line[3] == '.' &&
788 isdigit((unsigned char)line[4]) &&
789 isspace((unsigned char)line[5]))
790 line += 5;
791 }
792 } else
793 first = 1;
794
795 if (proto->reply) {
796 len = strlcat(proto->reply, line, proto->replysz);
797 if (len < proto->replysz)
798 return 0;
799 }
800 else
801 len = strlen(line);
802
803 if (len > proto->params.ibufmax) {
804 errno = EMSGSIZE;
805 return -1;
806 }
807
808 /* Allocate by multiples of 2^8 */
809 len += (len % 256) ? (256 - (len % 256)) : 0;
810
811 tmp = realloc(proto->reply, len);
812 if (tmp == NULL)
813 return -1;
814 if (proto->reply == NULL)
815 tmp[0] = '\0';
816
817 proto->reply = tmp;
818 proto->replysz = len;
819 (void)strlcat(proto->reply, line, proto->replysz);
820
821 /* Replace the separator with a space for the first line. */
822 if (first && proto->reply[3])
823 proto->reply[3] = ' ';
824
825 return 0;
826 }
827
828 static void
smtp_client_sendbody(struct smtp_client * proto)829 smtp_client_sendbody(struct smtp_client *proto)
830 {
831 ssize_t len;
832 size_t sz = 0, total, w;
833 char *ln = NULL;
834 int n;
835
836 total = io_queued(proto->io);
837 w = 0;
838
839 while (total < proto->params.obufmax) {
840 if ((len = getline(&ln, &sz, proto->mail->fp)) == -1)
841 break;
842 if (ln[len - 1] == '\n')
843 ln[len - 1] = '\0';
844 n = io_printf(proto->io, "%s%s\r\n", *ln == '.'?".":"", ln);
845 if (n == -1) {
846 free(ln);
847 smtp_client_abort(proto, FAIL_INTERNAL, NULL);
848 return;
849 }
850 total += n;
851 w += n;
852 }
853 free(ln);
854
855 if (ferror(proto->mail->fp)) {
856 smtp_client_abort(proto, FAIL_INTERNAL, "Cannot read message");
857 return;
858 }
859
860 log_trace(TRACE_SMTPCLT, "%p: >>> [...%zd bytes...]", proto, w);
861
862 if (feof(proto->mail->fp))
863 smtp_client_state(proto, STATE_EOM);
864 }
865
866 static void
smtp_client_sendcmd(struct smtp_client * proto,char * fmt,...)867 smtp_client_sendcmd(struct smtp_client *proto, char *fmt, ...)
868 {
869 va_list ap;
870 char *p;
871 int len;
872
873 va_start(ap, fmt);
874 len = vasprintf(&p, fmt, ap);
875 va_end(ap);
876
877 if (len == -1) {
878 smtp_client_abort(proto, FAIL_INTERNAL, NULL);
879 return;
880 }
881
882 log_trace(TRACE_SMTPCLT, "mta: %p: >>> %s", proto, p);
883
884 len = io_printf(proto->io, "%s\r\n", p);
885 free(p);
886
887 if (len == -1)
888 smtp_client_abort(proto, FAIL_INTERNAL, NULL);
889 }
890
891 static void
smtp_client_mail_status(struct smtp_client * proto,const char * status)892 smtp_client_mail_status(struct smtp_client *proto, const char *status)
893 {
894 int i;
895
896 for (i = 0; i < proto->mail->rcptcount; i++)
897 smtp_client_rcpt_status(proto, &proto->mail->rcpt[i], status);
898
899 smtp_done(proto->tag, proto, proto->mail);
900 proto->mail = NULL;
901 }
902
903 static void
smtp_client_mail_abort(struct smtp_client * proto)904 smtp_client_mail_abort(struct smtp_client *proto)
905 {
906 smtp_done(proto->tag, proto, proto->mail);
907 proto->mail = NULL;
908 }
909
910 static void
smtp_client_rcpt_status(struct smtp_client * proto,struct smtp_rcpt * rcpt,const char * line)911 smtp_client_rcpt_status(struct smtp_client *proto, struct smtp_rcpt *rcpt, const char *line)
912 {
913 struct smtp_status status;
914
915 if (rcpt->done)
916 return;
917
918 rcpt->done = 1;
919 status.rcpt = rcpt;
920 status.cmd = strstate[proto->state];
921 status.status = line;
922 smtp_status(proto->tag, proto, &status);
923 }
924