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