static void mta_io(struct io *io, int evt) { struct mta_session *s = io->arg; char *line, *msg, *p; size_t len; const char *error; int cont; X509 *x; log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch (evt) { case IO_CONNECTED: log_info("smtp-out: Connected on session %016"PRIx64, s->id); if (s->use_smtps) { io_set_write(io); mta_start_tls(s); } else { mta_enter_state(s, MTA_BANNER); io_set_read(io); } break; case IO_TLSREADY: log_info("smtp-out: Started TLS on session %016"PRIx64": %s", s->id, ssl_to_text(s->io.ssl)); s->flags |= MTA_TLS; if (mta_verify_certificate(s)) { io_pause(&s->io, IO_PAUSE_IN); break; } case IO_TLSVERIFIED: x = SSL_get_peer_certificate(s->io.ssl); if (x) { log_info("smtp-out: Server certificate verification %s " "on session %016"PRIx64, (s->flags & MTA_VERIFIED) ? "succeeded" : "failed", s->id); X509_free(x); } if (s->use_smtps) { mta_enter_state(s, MTA_BANNER); io_set_read(io); } else mta_enter_state(s, MTA_EHLO); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->iobuf, &len); if (line == NULL) { if (iobuf_len(&s->iobuf) >= SMTPD_MAXLINESIZE) { mta_error(s, "Input too long"); mta_free(s); return; } iobuf_normalize(&s->iobuf); break; } log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); if ((error = parse_smtp_response(line, len, &msg, &cont))) { mta_error(s, "Bad response: %s", error); mta_free(s); return; } /* read extensions */ if (s->state == MTA_EHLO) { if (strcmp(msg, "STARTTLS") == 0) s->ext |= MTA_EXT_STARTTLS; else if (strncmp(msg, "AUTH ", 5) == 0) { s->ext |= MTA_EXT_AUTH; if ((p = strstr(msg, " PLAIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_PLAIN; if ((p = strstr(msg, " LOGIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_LOGIN; } else if (strcmp(msg, "PIPELINING") == 0) s->ext |= MTA_EXT_PIPELINING; else if (strcmp(msg, "DSN") == 0) s->ext |= MTA_EXT_DSN; } if (cont) goto nextline; if (s->state == MTA_QUIT) { log_info("smtp-out: Closing session %016"PRIx64 ": %zu message%s sent.", s->id, s->msgcount, (s->msgcount > 1) ? "s" : ""); mta_free(s); return; } io_set_write(io); mta_response(s, line); if (s->flags & MTA_FREE) { mta_free(s); return; } iobuf_normalize(&s->iobuf); if (iobuf_len(&s->iobuf)) { log_debug("debug: mta: remaining data in input buffer"); mta_error(s, "Remote host sent too much data"); if (s->flags & MTA_WAIT) s->flags |= MTA_FREE; else mta_free(s); } break; case IO_LOWAT: if (s->state == MTA_BODY) { mta_enter_state(s, MTA_BODY); if (s->flags & MTA_FREE) { mta_free(s); return; } } if (iobuf_queued(&s->iobuf) == 0) io_set_read(io); break; case IO_TIMEOUT: log_debug("debug: mta: %p: connection timeout", s); mta_error(s, "Connection timeout"); if (!s->ready) mta_connect(s); else mta_free(s); break; case IO_ERROR: log_debug("debug: mta: %p: IO error: %s", s, io->error); mta_error(s, "IO Error: %s", io->error); if (!s->ready) mta_connect(s); else mta_free(s); break; case IO_DISCONNECTED: log_debug("debug: mta: %p: disconnected in state %s", s, mta_strstate(s->state)); mta_error(s, "Connection closed unexpectedly"); if (!s->ready) mta_connect(s); else mta_free(s); break; default: fatalx("mta_io() bad event"); } }
/* * Handle a response to an SMTP command */ static void mta_response(struct mta_session *s, char *line) { struct mta_envelope *e; struct sockaddr_storage ss; struct sockaddr *sa; const char *domain; socklen_t sa_len; char buf[SMTPD_MAXLINESIZE]; int delivery; switch (s->state) { case MTA_BANNER: if (s->flags & MTA_LMTP) mta_enter_state(s, MTA_LHLO); else mta_enter_state(s, MTA_EHLO); break; case MTA_EHLO: if (line[0] != '2') { /* rejected at ehlo state */ if ((s->flags & MTA_USE_AUTH) || (s->flags & MTA_WANT_SECURE)) { mta_error(s, "EHLO rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_HELO); return; } if (!(s->flags & MTA_FORCE_PLAIN)) mta_enter_state(s, MTA_STARTTLS); else mta_enter_state(s, MTA_READY); break; case MTA_HELO: if (line[0] != '2') { mta_error(s, "HELO rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_READY); break; case MTA_LHLO: if (line[0] != '2') { mta_error(s, "LHLO rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_READY); break; case MTA_STARTTLS: if (line[0] != '2') { if (!(s->flags & MTA_WANT_SECURE)) { mta_enter_state(s, MTA_AUTH); return; } /* XXX mark that the MX doesn't support STARTTLS */ mta_error(s, "STARTTLS rejected: %s", line); s->flags |= MTA_FREE; return; } mta_start_tls(s); break; case MTA_AUTH_PLAIN: if (line[0] != '2') { mta_error(s, "AUTH rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_READY); break; case MTA_AUTH_LOGIN: if (strncmp(line, "334 ", 4) != 0) { mta_error(s, "AUTH rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_AUTH_LOGIN_USER); break; case MTA_AUTH_LOGIN_USER: if (strncmp(line, "334 ", 4) != 0) { mta_error(s, "AUTH rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_AUTH_LOGIN_PASS); break; case MTA_AUTH_LOGIN_PASS: if (line[0] != '2') { mta_error(s, "AUTH rejected: %s", line); s->flags |= MTA_FREE; return; } mta_enter_state(s, MTA_READY); break; case MTA_MAIL: if (line[0] != '2') { if (line[0] == '5') delivery = IMSG_DELIVERY_PERMFAIL; else delivery = IMSG_DELIVERY_TEMPFAIL; mta_flush_task(s, delivery, line, 0, 0); mta_enter_state(s, MTA_RSET); return; } mta_enter_state(s, MTA_RCPT); break; case MTA_RCPT: e = s->currevp; /* remove envelope from hosttat cache if there */ if ((domain = strchr(e->dest, '@')) != NULL) { domain++; mta_hoststat_uncache(domain, e->id); } s->currevp = TAILQ_NEXT(s->currevp, entry); if (line[0] == '2') { s->failures = 0; /* * this host is up, reschedule envelopes that * were cached for reschedule. */ if (domain) mta_hoststat_reschedule(domain); } else { if (line[0] == '5') delivery = IMSG_DELIVERY_PERMFAIL; else delivery = IMSG_DELIVERY_TEMPFAIL; s->failures++; /* remove failed envelope from task list */ TAILQ_REMOVE(&s->task->envelopes, e, entry); stat_decrement("mta.envelope", 1); /* log right away */ snprintf(buf, sizeof(buf), "%s", mta_host_to_text(s->route->dst)); e->session = s->id; /* XXX */ /* * getsockname() can only fail with ENOBUFS here * best effort, don't log source ... */ sa_len = sizeof(ss); sa = (struct sockaddr *)&ss; if (getsockname(s->io.sock, sa, &sa_len) < 0) mta_delivery_log(e, NULL, buf, delivery, line); else mta_delivery_log(e, sa_to_text(sa), buf, delivery, line); if (domain) mta_hoststat_update(domain + 1, e->status); mta_delivery_notify(e); if (s->relay->limits->max_failures_per_session && s->failures == s->relay->limits->max_failures_per_session) { mta_flush_task(s, IMSG_DELIVERY_TEMPFAIL, "Too many consecutive errors, closing connection", 0, 1); mta_enter_state(s, MTA_QUIT); break; } /* * if no more envelopes, flush failed queue */ if (TAILQ_EMPTY(&s->task->envelopes)) { mta_flush_task(s, IMSG_DELIVERY_OK, "No envelope", 0, 0); mta_enter_state(s, MTA_RSET); break; } } if (s->currevp == NULL) mta_enter_state(s, MTA_DATA); else mta_enter_state(s, MTA_RCPT); break; case MTA_DATA: if (line[0] == '2' || line[0] == '3') { mta_enter_state(s, MTA_BODY); break; } if (line[0] == '5') delivery = IMSG_DELIVERY_PERMFAIL; else delivery = IMSG_DELIVERY_TEMPFAIL; mta_flush_task(s, delivery, line, 0, 0); mta_enter_state(s, MTA_RSET); break; case MTA_LMTP_EOM: case MTA_EOM: if (line[0] == '2') { delivery = IMSG_DELIVERY_OK; s->msgtried = 0; s->msgcount++; } else if (line[0] == '5') delivery = IMSG_DELIVERY_PERMFAIL; else delivery = IMSG_DELIVERY_TEMPFAIL; mta_flush_task(s, delivery, line, (s->flags & MTA_LMTP) ? 1 : 0, 0); if (s->task) { s->rcptcount--; mta_enter_state(s, MTA_LMTP_EOM); } else { s->rcptcount = 0; if (s->relay->limits->sessdelay_transaction) { log_debug("debug: mta: waiting for %llds before next transaction", (long long int)s->relay->limits->sessdelay_transaction); s->hangon = s->relay->limits->sessdelay_transaction -1; s->flags |= MTA_HANGON; runq_schedule(hangon, time(NULL) + s->relay->limits->sessdelay_transaction, NULL, s); } else mta_enter_state(s, MTA_READY); } break; case MTA_RSET: s->rcptcount = 0; if (s->relay->limits->sessdelay_transaction) { log_debug("debug: mta: waiting for %llds after reset", (long long int)s->relay->limits->sessdelay_transaction); s->hangon = s->relay->limits->sessdelay_transaction -1; s->flags |= MTA_HANGON; runq_schedule(hangon, time(NULL) + s->relay->limits->sessdelay_transaction, NULL, s); } else mta_enter_state(s, MTA_READY); break; default: fatalx("mta_response() bad state"); } }
static void mta_io(struct io *io, int evt) { struct mta_session *s = io->arg; char *line, *msg, *p; size_t len; const char *error; int cont; X509 *x; log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch (evt) { case IO_CONNECTED: log_info("smtp-out: Connected on session %016"PRIx64, s->id); if (s->use_smtps) { io_set_write(io); mta_start_tls(s); } else { mta_enter_state(s, MTA_BANNER); io_set_read(io); } break; case IO_TLSREADY: log_info("smtp-out: Started TLS on session %016"PRIx64": %s", s->id, ssl_to_text(s->io.ssl)); s->flags |= MTA_TLS; if (mta_verify_certificate(s)) { io_pause(&s->io, IO_PAUSE_IN); break; } case IO_TLSVERIFIED: x = SSL_get_peer_certificate(s->io.ssl); if (x) { log_info("smtp-out: Server certificate verification %s " "on session %016"PRIx64, (s->flags & MTA_VERIFIED) ? "succeeded" : "failed", s->id); X509_free(x); } if (s->use_smtps) { mta_enter_state(s, MTA_BANNER); io_set_read(io); } else mta_enter_state(s, MTA_EHLO); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->iobuf, &len); if (line == NULL) { if (iobuf_len(&s->iobuf) >= LINE_MAX) { mta_error(s, "Input too long"); mta_free(s); return; } iobuf_normalize(&s->iobuf); break; } log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); if ((error = parse_smtp_response(line, len, &msg, &cont))) { mta_error(s, "Bad response: %s", error); mta_free(s); return; } /* read extensions */ if (s->state == MTA_EHLO) { if (strcmp(msg, "STARTTLS") == 0) s->ext |= MTA_EXT_STARTTLS; else if (strncmp(msg, "AUTH ", 5) == 0) { s->ext |= MTA_EXT_AUTH; if ((p = strstr(msg, " PLAIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_PLAIN; if ((p = strstr(msg, " LOGIN")) && (*(p+6) == '\0' || *(p+6) == ' ')) s->ext |= MTA_EXT_AUTH_LOGIN; } else if (strcmp(msg, "PIPELINING") == 0) s->ext |= MTA_EXT_PIPELINING; else if (strcmp(msg, "DSN") == 0) s->ext |= MTA_EXT_DSN; } /* continuation reply, we parse out the repeating statuses and ESC */ if (cont) { if (s->replybuf[0] == '\0') (void)strlcat(s->replybuf, line, sizeof s->replybuf); else { line = line + 4; if (isdigit((int)*line) && *(line + 1) == '.' && isdigit((int)*line+2) && *(line + 3) == '.' && isdigit((int)*line+4) && isspace((int)*(line + 5))) (void)strlcat(s->replybuf, line+5, sizeof s->replybuf); else (void)strlcat(s->replybuf, line, sizeof s->replybuf); } goto nextline; } /* last line of a reply, check if we're on a continuation to parse out status and ESC. * if we overflow reply buffer or are not on continuation, log entire last line. */ if (s->replybuf[0] != '\0') { p = line + 4; if (isdigit((int)*p) && *(p + 1) == '.' && isdigit((int)*p+2) && *(p + 3) == '.' && isdigit((int)*p+4) && isspace((int)*(p + 5))) p += 5; if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf) (void)strlcpy(s->replybuf, line, sizeof s->replybuf); } else (void)strlcpy(s->replybuf, line, sizeof s->replybuf); if (s->state == MTA_QUIT) { log_info("smtp-out: Closing session %016"PRIx64 ": %zu message%s sent.", s->id, s->msgcount, (s->msgcount > 1) ? "s" : ""); mta_free(s); return; } io_set_write(io); mta_response(s, s->replybuf); if (s->flags & MTA_FREE) { mta_free(s); return; } if (s->flags & MTA_RECONN) { s->flags &= ~MTA_RECONN; mta_connect(s); return; } iobuf_normalize(&s->iobuf); if (iobuf_len(&s->iobuf)) { log_debug("debug: mta: remaining data in input buffer"); mta_error(s, "Remote host sent too much data"); if (s->flags & MTA_WAIT) s->flags |= MTA_FREE; else mta_free(s); } break; case IO_LOWAT: if (s->state == MTA_BODY) { mta_enter_state(s, MTA_BODY); if (s->flags & MTA_FREE) { mta_free(s); return; } } if (iobuf_queued(&s->iobuf) == 0) io_set_read(io); break; case IO_TIMEOUT: log_debug("debug: mta: %p: connection timeout", s); mta_error(s, "Connection timeout"); if (!s->ready) mta_connect(s); else mta_free(s); break; case IO_ERROR: log_debug("debug: mta: %p: IO error: %s", s, io->error); if (!s->ready) { mta_error(s, "IO Error: %s", io->error); mta_connect(s); break; } else if (!(s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL))) { /* error in non-strict SSL negotiation, downgrade to plain */ if (s->flags & MTA_TLS) { log_info("smtp-out: Error on session %016"PRIx64 ": opportunistic TLS failed, " "downgrading to plain", s->id); s->flags &= ~MTA_TLS; s->flags |= MTA_DOWNGRADE_PLAIN; mta_connect(s); break; } } mta_error(s, "IO Error: %s", io->error); mta_free(s); break; case IO_TLSERROR: log_debug("debug: mta: %p: TLS IO error: %s", s, io->error); if (!(s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL))) { /* error in non-strict SSL negotiation, downgrade to plain */ log_info("smtp-out: TLS Error on session %016"PRIx64 ": TLS failed, " "downgrading to plain", s->id); s->flags &= ~MTA_TLS; s->flags |= MTA_DOWNGRADE_PLAIN; mta_connect(s); break; } mta_error(s, "IO Error: %s", io->error); mta_free(s); break; case IO_DISCONNECTED: log_debug("debug: mta: %p: disconnected in state %s", s, mta_strstate(s->state)); mta_error(s, "Connection closed unexpectedly"); if (!s->ready) mta_connect(s); else mta_free(s); break; default: fatalx("mta_io() bad event"); } }