static void mta_enter_state(struct mta_session *s, int newstate) { struct mta_envelope *e; size_t envid_sz; int oldstate; ssize_t q; char ibuf[SMTPD_MAXLINESIZE]; char obuf[SMTPD_MAXLINESIZE]; int offset; again: oldstate = s->state; log_trace(TRACE_MTA, "mta: %p: %s -> %s", s, mta_strstate(oldstate), mta_strstate(newstate)); s->state = newstate; /* don't try this at home! */ #define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while (0) switch (s->state) { case MTA_INIT: case MTA_BANNER: break; case MTA_EHLO: s->ext = 0; mta_send(s, "EHLO %s", s->helo); break; case MTA_HELO: s->ext = 0; mta_send(s, "HELO %s", s->helo); break; case MTA_LHLO: s->ext = 0; mta_send(s, "LHLO %s", s->helo); break; case MTA_STARTTLS: if (s->flags & MTA_TLS) /* already started */ mta_enter_state(s, MTA_AUTH); else if ((s->ext & MTA_EXT_STARTTLS) == 0) { if (s->flags & MTA_FORCE_TLS || s->flags & MTA_WANT_SECURE) { mta_error(s, "TLS required but not supported by remote host"); mta_connect(s); } else /* server doesn't support starttls, do not use it */ mta_enter_state(s, MTA_AUTH); } else mta_send(s, "STARTTLS"); break; case MTA_AUTH: if (s->relay->secret && s->flags & MTA_TLS) { if (s->ext & MTA_EXT_AUTH) { if (s->ext & MTA_EXT_AUTH_PLAIN) { mta_enter_state(s, MTA_AUTH_PLAIN); break; } if (s->ext & MTA_EXT_AUTH_LOGIN) { mta_enter_state(s, MTA_AUTH_LOGIN); break; } log_debug("debug: mta: %p: no supported AUTH method on session", s); mta_error(s, "no supported AUTH method"); } else { log_debug("debug: mta: %p: AUTH not advertised on session", s); mta_error(s, "AUTH not advertised"); } } else if (s->relay->secret) { log_debug("debug: mta: %p: not using AUTH on non-TLS " "session", s); mta_error(s, "Refuse to AUTH over unsecure channel"); mta_connect(s); } else { mta_enter_state(s, MTA_READY); } break; case MTA_AUTH_PLAIN: mta_send(s, "AUTH PLAIN %s", s->relay->secret); break; case MTA_AUTH_LOGIN: mta_send(s, "AUTH LOGIN"); break; case MTA_AUTH_LOGIN_USER: memset(ibuf, 0, sizeof ibuf); if (base64_decode(s->relay->secret, (unsigned char *)ibuf, sizeof(ibuf)-1) == -1) { log_debug("debug: mta: %p: credentials too large on session", s); mta_error(s, "Credentials too large"); break; } memset(obuf, 0, sizeof obuf); base64_encode((unsigned char *)ibuf + 1, strlen(ibuf + 1), obuf, sizeof obuf); mta_send(s, "%s", obuf); memset(ibuf, 0, sizeof ibuf); memset(obuf, 0, sizeof obuf); break; case MTA_AUTH_LOGIN_PASS: memset(ibuf, 0, sizeof ibuf); if (base64_decode(s->relay->secret, (unsigned char *)ibuf,\ sizeof(ibuf)-1) == -1) { log_debug("debug: mta: %p: credentials too large on session", s); mta_error(s, "Credentials too large"); break; } offset = strlen(ibuf+1)+2; memset(obuf, 0, sizeof obuf); base64_encode((unsigned char *)ibuf + offset, strlen(ibuf + offset), obuf, sizeof obuf); mta_send(s, "%s", obuf); memset(ibuf, 0, sizeof ibuf); memset(obuf, 0, sizeof obuf); break; case MTA_READY: /* Ready to send a new mail */ if (s->ready == 0) { s->ready = 1; s->relay->nconn_ready += 1; mta_route_ok(s->relay, s->route); } if (s->msgtried >= MAX_TRYBEFOREDISABLE) { log_info("smtp-out: Remote host seems to reject all mails on session %016"PRIx64, s->id); mta_route_down(s->relay, s->route); mta_enter_state(s, MTA_QUIT); break; } if (s->msgcount >= s->relay->limits->max_mail_per_session) { log_debug("debug: mta: " "%p: cannot send more message to relay %s", s, mta_relay_to_text(s->relay)); mta_enter_state(s, MTA_QUIT); break; } s->task = mta_route_next_task(s->relay, s->route); if (s->task == NULL) { log_debug("debug: mta: %p: no task for relay %s", s, mta_relay_to_text(s->relay)); if (s->relay->nconn > 1 || s->hangon >= s->relay->limits->sessdelay_keepalive) { mta_enter_state(s, MTA_QUIT); break; } log_debug("mta: debug: last connection: hanging on for %llds", (long long)(s->relay->limits->sessdelay_keepalive - s->hangon)); s->flags |= MTA_HANGON; runq_schedule(hangon, time(NULL) + 1, NULL, s); break; } log_debug("debug: mta: %p: handling next task for relay %s", s, mta_relay_to_text(s->relay)); stat_increment("mta.task.running", 1); m_create(p_queue, IMSG_QUEUE_MESSAGE_FD, 0, 0, -1); m_add_id(p_queue, s->id); m_add_msgid(p_queue, s->task->msgid); m_close(p_queue); tree_xset(&wait_fd, s->id, s); s->flags |= MTA_WAIT; break; case MTA_MAIL: if (s->currevp == NULL) s->currevp = TAILQ_FIRST(&s->task->envelopes); e = s->currevp; s->hangon = 0; s->msgtried++; envid_sz = strlen(e->dsn_envid); if (s->ext & MTA_EXT_DSN) { mta_send(s, "MAIL FROM:<%s> %s%s %s%s", s->task->sender, e->dsn_ret ? "RET=" : "", e->dsn_ret ? dsn_strret(e->dsn_ret) : "", envid_sz ? "ENVID=" : "", envid_sz ? e->dsn_envid : ""); } else mta_send(s, "MAIL FROM:<%s>", s->task->sender); break; case MTA_RCPT: if (s->currevp == NULL) s->currevp = TAILQ_FIRST(&s->task->envelopes); e = s->currevp; if (s->ext & MTA_EXT_DSN) { mta_send(s, "RCPT TO:<%s> %s%s %s%s", e->dest, e->dsn_notify ? "NOTIFY=" : "", e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "", e->dsn_orcpt ? "ORCPT=" : "", e->dsn_orcpt ? e->dsn_orcpt : ""); } else mta_send(s, "RCPT TO:<%s>", e->dest); s->rcptcount++; break; case MTA_DATA: fseek(s->datafp, 0, SEEK_SET); mta_send(s, "DATA"); break; case MTA_BODY: if (s->datafp == NULL) { log_trace(TRACE_MTA, "mta: %p: end-of-file", s); mta_enter_state(s, MTA_EOM); break; } if ((q = mta_queue_data(s)) == -1) { s->flags |= MTA_FREE; break; } if (q == 0) { mta_enter_state(s, MTA_BODY); break; } log_trace(TRACE_MTA, "mta: %p: >>> [...%zi bytes...]", s, q); break; case MTA_EOM: mta_send(s, "."); break; case MTA_LMTP_EOM: /* LMTP reports status of each delivery, so enable read */ io_set_read(&s->io); break; case MTA_RSET: if (s->datafp) { fclose(s->datafp); s->datafp = NULL; } mta_send(s, "RSET"); break; case MTA_QUIT: mta_send(s, "QUIT"); break; default: fatalx("mta_enter_state: unknown state"); } #undef mta_enter_state }
static void filter_dispatch(struct mproc *p, struct imsg *imsg) { struct filter_session *s; struct filter_connect q_connect; struct mailaddr maddr; struct msg m; const char *line, *name; uint32_t v, datalen; uint64_t id, qid; int status, type; int fds[2], fdin, fdout; log_trace(TRACE_FILTERS, "filter-api:%s imsg %s", filter_name, filterimsg_to_str(imsg->hdr.type)); switch (imsg->hdr.type) { case IMSG_FILTER_REGISTER: m_msg(&m, imsg); m_get_u32(&m, &v); m_get_string(&m, &name); filter_name = strdup(name); m_end(&m); if (v != FILTER_API_VERSION) { log_warnx("warn: filter-api:%s API mismatch", filter_name); fatalx("filter-api: exiting"); } m_create(p, IMSG_FILTER_REGISTER, 0, 0, -1); m_add_int(p, fi.hooks); m_add_int(p, fi.flags); m_close(p); break; case IMSG_FILTER_EVENT: m_msg(&m, imsg); m_get_id(&m, &id); m_get_int(&m, &type); m_end(&m); switch (type) { case EVENT_CONNECT: s = xcalloc(1, sizeof(*s), "filter_dispatch"); s->id = id; s->pipe.iev.sock = -1; s->pipe.oev.sock = -1; tree_xset(&sessions, id, s); break; case EVENT_DISCONNECT: filter_dispatch_disconnect(id); s = tree_xpop(&sessions, id); free(s); break; case EVENT_RESET: filter_dispatch_reset(id); break; case EVENT_COMMIT: filter_dispatch_commit(id); break; case EVENT_ROLLBACK: filter_dispatch_rollback(id); break; default: log_warnx("warn: filter-api:%s bad event %d", filter_name, type); fatalx("filter-api: exiting"); } break; case IMSG_FILTER_QUERY: m_msg(&m, imsg); m_get_id(&m, &id); m_get_id(&m, &qid); m_get_int(&m, &type); switch(type) { case QUERY_CONNECT: m_get_sockaddr(&m, (struct sockaddr*)&q_connect.local); m_get_sockaddr(&m, (struct sockaddr*)&q_connect.remote); m_get_string(&m, &q_connect.hostname); m_end(&m); filter_register_query(id, qid, type); filter_dispatch_connect(id, &q_connect); break; case QUERY_HELO: m_get_string(&m, &line); m_end(&m); filter_register_query(id, qid, type); filter_dispatch_helo(id, line); break; case QUERY_MAIL: m_get_mailaddr(&m, &maddr); m_end(&m); filter_register_query(id, qid, type); filter_dispatch_mail(id, &maddr); break; case QUERY_RCPT: m_get_mailaddr(&m, &maddr); m_end(&m); filter_register_query(id, qid, type); filter_dispatch_rcpt(id, &maddr); break; case QUERY_DATA: m_end(&m); filter_register_query(id, qid, type); filter_dispatch_data(id); break; case QUERY_EOM: m_get_u32(&m, &datalen); m_end(&m); filter_register_query(id, qid, type); filter_dispatch_eom(id, datalen); break; default: log_warnx("warn: filter-api:%s bad query %d", filter_name, type); fatalx("filter-api: exiting"); } break; case IMSG_FILTER_PIPE: m_msg(&m, imsg); m_get_id(&m, &id); m_end(&m); fdout = imsg->fd; fdin = -1; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) == -1) { log_warn("warn: filter-api:%s socketpair", filter_name); close(fdout); } else { s = tree_xget(&sessions, id); s->pipe.eom_called = 0; s->pipe.error = 0; s->pipe.idatalen = 0; s->pipe.odatalen = 0; iobuf_init(&s->pipe.obuf, 0, 0); io_init(&s->pipe.oev, fdout, s, filter_io_out, &s->pipe.obuf); io_set_write(&s->pipe.oev); iobuf_init(&s->pipe.ibuf, 0, 0); io_init(&s->pipe.iev, fds[0], s, filter_io_in, &s->pipe.ibuf); io_set_read(&s->pipe.iev); fdin = fds[1]; } log_trace(TRACE_FILTERS, "filter-api:%s %016"PRIx64" tx pipe %d -> %d", filter_name, id, fdin, fdout); m_create(&fi.p, IMSG_FILTER_PIPE, 0, 0, fdin); m_add_id(&fi.p, id); m_close(&fi.p); break; } }
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"); } }
void session_pickup(struct session *s, struct submit_status *ss) { void *ssl; s->s_flags &= ~F_WAITIMSG; if ((ss != NULL && ss->code == 421) || (s->s_dstatus & DS_TEMPFAILURE)) { stat_increment("smtp.tempfail", 1); session_respond(s, "421 Service temporarily unavailable"); session_enter_state(s, S_QUIT); io_reload(&s->s_io); return; } switch (s->s_state) { case S_CONNECTED: session_enter_state(s, S_INIT); s->s_msg.session_id = s->s_id; s->s_msg.ss = s->s_ss; session_imsg(s, PROC_MFA, IMSG_MFA_CONNECT, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); break; case S_INIT: if (ss->code != 250) { session_destroy(s, "rejected by filter"); return; } if (s->s_l->flags & F_SMTPS) { ssl = ssl_smtp_init(s->s_l->ssl_ctx); io_set_read(&s->s_io); io_start_tls(&s->s_io, ssl); return; } session_respond(s, SMTPD_BANNER, env->sc_hostname); session_enter_state(s, S_GREETED); break; case S_AUTH_FINALIZE: if (s->s_flags & F_AUTHENTICATED) session_respond(s, "235 Authentication succeeded"); else session_respond(s, "535 Authentication failed"); session_enter_state(s, S_HELO); break; case S_RSET: session_respond(s, "250 2.0.0 Reset state"); session_enter_state(s, S_HELO); break; case S_HELO: if (ss->code != 250) { session_enter_state(s, S_GREETED); session_respond(s, "%d Helo rejected", ss->code); break; } session_respond(s, "250%c%s Hello %s [%s], pleased to meet you", (s->s_flags & F_EHLO) ? '-' : ' ', env->sc_hostname, s->s_msg.helo, ss_to_text(&s->s_ss)); if (s->s_flags & F_EHLO) { /* unconditionnal extensions go first */ session_respond(s, "250-8BITMIME"); session_respond(s, "250-ENHANCEDSTATUSCODES"); /* XXX - we also want to support reading SIZE from MAIL parameters */ session_respond(s, "250-SIZE %zu", env->sc_maxsize); if (ADVERTISE_TLS(s)) session_respond(s, "250-STARTTLS"); if (ADVERTISE_AUTH(s)) session_respond(s, "250-AUTH PLAIN LOGIN"); session_respond(s, "250 HELP"); } break; case S_MAIL_MFA: if (ss->code != 250) { session_enter_state(s, S_HELO); session_respond(s, "%d Sender rejected", ss->code); break; } session_enter_state(s, S_MAIL_QUEUE); s->s_msg.sender = ss->u.maddr; session_imsg(s, PROC_QUEUE, IMSG_QUEUE_CREATE_MESSAGE, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); break; case S_MAIL_QUEUE: session_enter_state(s, S_MAIL); session_respond(s, "%d 2.1.0 Sender ok", ss->code); break; case S_RCPT_MFA: /* recipient was not accepted */ if (ss->code != 250) { /* We do not have a valid recipient, downgrade state */ if (s->rcptcount == 0) session_enter_state(s, S_MAIL); else session_enter_state(s, S_RCPT); session_respond(s, "%d 5.0.0 Recipient rejected: %s@%s", ss->code, s->s_msg.rcpt.user, s->s_msg.rcpt.domain); break; } session_enter_state(s, S_RCPT); s->rcptcount++; s->s_msg.dest = ss->u.maddr; session_respond(s, "%d 2.0.0 Recipient ok", ss->code); break; case S_DATA_QUEUE: session_enter_state(s, S_DATACONTENT); session_respond(s, "354 Enter mail, end with \".\" on a line by" " itself"); fprintf(s->datafp, "Received: from %s (%s [%s])\n", s->s_msg.helo, s->s_hostname, ss_to_text(&s->s_ss)); fprintf(s->datafp, "\tby %s (OpenSMTPD) with %sSMTP id %08x", env->sc_hostname, s->s_flags & F_EHLO ? "E" : "", evpid_to_msgid(s->s_msg.id)); if (s->s_flags & F_SECURE) { fprintf(s->datafp, "\n\t(version=%s cipher=%s bits=%d)", SSL_get_cipher_version(s->s_io.ssl), SSL_get_cipher_name(s->s_io.ssl), SSL_get_cipher_bits(s->s_io.ssl, NULL)); } if (s->rcptcount == 1) fprintf(s->datafp, "\n\tfor <%s@%s>; ", s->s_msg.rcpt.user, s->s_msg.rcpt.domain); else fprintf(s->datafp, ";\n\t"); fprintf(s->datafp, "%s\n", time_to_text(time(NULL))); break; case S_DATACONTENT: if (ss->code != 250) s->s_dstatus |= DS_PERMFAILURE; session_read_data(s, ss->u.dataline); break; case S_DONE: session_respond(s, "250 2.0.0 %08x Message accepted for delivery", evpid_to_msgid(s->s_msg.id)); log_info("%08x: from=<%s%s%s>, size=%ld, nrcpts=%zu, proto=%s, " "relay=%s [%s]", evpid_to_msgid(s->s_msg.id), s->s_msg.sender.user, s->s_msg.sender.user[0] == '\0' ? "" : "@", s->s_msg.sender.domain, s->s_datalen, s->rcptcount, s->s_flags & F_EHLO ? "ESMTP" : "SMTP", s->s_hostname, ss_to_text(&s->s_ss)); session_enter_state(s, S_HELO); s->s_msg.id = 0; bzero(&s->s_nresp, sizeof(s->s_nresp)); break; default: fatal("session_pickup: unknown state"); } io_reload(&s->s_io); }
void session_io(struct io *io, int evt) { struct session *s = io->arg; void *ssl; char *line; size_t len; log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch(evt) { case IO_TLSREADY: s->s_flags |= F_SECURE; if (s->s_l->flags & F_SMTPS) stat_increment("smtp.smtps", 1); if (s->s_l->flags & F_STARTTLS) stat_increment("smtp.tls", 1); if (s->s_state == S_INIT) { io_set_write(&s->s_io); session_respond(s, SMTPD_BANNER, env->sc_hostname); } session_enter_state(s, S_GREETED); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->s_iobuf, &len); if ((line == NULL && iobuf_len(&s->s_iobuf) >= SMTP_LINE_MAX) || (line && len >= SMTP_LINE_MAX)) { session_respond(s, "500 5.0.0 Line too long"); session_enter_state(s, S_QUIT); io_set_write(io); return; } if (line == NULL) { iobuf_normalize(&s->s_iobuf); return; } if (s->s_state == S_DATACONTENT && strcmp(line, ".")) { /* more data to come */ session_line(s, line, len); goto nextline; } /* pipelining not supported */ if (iobuf_len(&s->s_iobuf)) { session_respond(s, "500 5.0.0 Pipelining not supported"); session_enter_state(s, S_QUIT); io_set_write(io); return; } session_line(s, line, len); iobuf_normalize(&s->s_iobuf); io_set_write(io); break; case IO_LOWAT: if (s->s_state == S_QUIT) { session_destroy(s, "done"); break; } io_set_read(io); /* wait for the client to start tls */ if (s->s_state == S_TLS) { ssl = ssl_smtp_init(s->s_l->ssl_ctx); io_start_tls(io, ssl); } break; case IO_TIMEOUT: session_destroy(s, "timeout"); break; case IO_DISCONNECTED: session_destroy(s, "disconnected"); break; case IO_ERROR: session_destroy(s, "error"); break; default: fatal("session_io()"); } }
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"); } }
static void mta_io(struct io *io, int evt) { struct mta_session *s = io->arg; char *line, *msg; size_t len; struct mta_host *host; const char *error; int cont; log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), io_strio(io)); switch (evt) { case IO_CONNECTED: s->is_reading = 0; io_set_timeout(io, 300000); io_set_write(io); host = TAILQ_FIRST(&s->hosts); dns_query_ptr(&host->sa, s->id); break; case IO_TLSREADY: s->flags |= MTA_TLS; if (s->state == MTA_CONNECT) /* smtps */ mta_enter_state(s, MTA_SMTP_BANNER); else mta_enter_state(s, MTA_SMTP_EHLO); break; case IO_DATAIN: nextline: line = iobuf_getline(&s->iobuf, &len); if (line == NULL) { if (iobuf_len(&s->iobuf) >= SMTP_LINE_MAX) { mta_status(s, 1, "150 Input too long"); mta_enter_state(s, MTA_DONE); 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_status(s, 1, "150 Bad response: %s", error); mta_enter_state(s, MTA_DONE); return; } /* read extensions */ if (s->state == MTA_SMTP_EHLO) { if (strcmp(msg, "STARTTLS") == 0) s->ext |= MTA_EXT_STARTTLS; else if (strncmp(msg, "AUTH", 4) == 0) s->ext |= MTA_EXT_AUTH; else if (strcmp(msg, "PIPELINING") == 0) s->ext |= MTA_EXT_PIPELINING; } if (cont) goto nextline; if (s->state == MTA_SMTP_QUIT) { mta_enter_state(s, MTA_DONE); return; } mta_response(s, line); iobuf_normalize(&s->iobuf); break; case IO_LOWAT: if (s->state == MTA_SMTP_BODY) mta_enter_state(s, MTA_SMTP_BODY); if (iobuf_queued(&s->iobuf) == 0) { s->is_reading = 1; io_set_read(io); } break; case IO_TIMEOUT: log_debug("mta: %p: connection timeout", s); if (!s->ready) { mta_enter_state(s, MTA_CONNECT); break; } mta_status(s, 1, "150 connection timeout"); mta_enter_state(s, MTA_DONE); break; case IO_ERROR: log_debug("mta: %p: IO error: %s", s, strerror(errno)); if (!s->ready) { mta_enter_state(s, MTA_CONNECT); break; } mta_status(s, 1, "150 IO error"); mta_enter_state(s, MTA_DONE); break; case IO_DISCONNECTED: log_debug("mta: %p: disconnected in state %s", s, mta_strstate(s->state)); if (!s->ready) { mta_enter_state(s, MTA_CONNECT); break; } mta_status(s, 1, "150 connection closed unexpectedly"); mta_enter_state(s, MTA_DONE); break; default: fatalx("mta_io() bad event"); } }
static void mta_enter_state(struct mta_session *s, int newstate) { int oldstate; struct secret secret; struct mta_route *route; struct mta_host *host; struct sockaddr *sa; int max_reuse; ssize_t q; #ifdef VALGRIND bzero(&batch, sizeof(batch)); #endif again: oldstate = s->state; log_trace(TRACE_MTA, "mta: %p: %s -> %s", s, mta_strstate(oldstate), mta_strstate(newstate)); s->state = newstate; /* don't try this at home! */ #define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while(0) switch (s->state) { case MTA_INIT: if (s->route->auth) mta_enter_state(s, MTA_SECRET); else mta_enter_state(s, MTA_MX); break; case MTA_DATA: /* * Obtain message body fd. */ imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_FD, s->task->msgid, 0, -1, &s->id, sizeof(s->id)); break; case MTA_SECRET: /* * Lookup AUTH secret. */ bzero(&secret, sizeof(secret)); secret.id = s->id; strlcpy(secret.mapname, s->route->auth, sizeof(secret.mapname)); strlcpy(secret.host, s->route->hostname, sizeof(secret.host)); imsg_compose_event(env->sc_ievs[PROC_LKA], IMSG_LKA_SECRET, 0, 0, -1, &secret, sizeof(secret)); break; case MTA_MX: /* * Lookup MX record. */ if (s->flags & MTA_FORCE_MX) /* XXX */ dns_query_host(s->route->hostname, s->route->port, s->id); else dns_query_mx(s->route->hostname, s->route->backupname, 0, s->id); break; case MTA_CONNECT: /* * Connect to the MX. */ /* cleanup previous connection if any */ iobuf_clear(&s->iobuf); io_clear(&s->io); if (s->flags & MTA_FORCE_ANYSSL) max_reuse = 2; else max_reuse = 1; /* pick next mx */ while ((host = TAILQ_FIRST(&s->hosts))) { if (host->used == max_reuse) { TAILQ_REMOVE(&s->hosts, host, entry); free(host); continue; } host->used++; log_debug("mta: %p: connecting to %s...", s, ss_to_text(&host->sa)); sa = (struct sockaddr *)&host->sa; if (s->route->port) sa_set_port(sa, s->route->port); else if ((s->flags & MTA_FORCE_ANYSSL) && host->used == 1) sa_set_port(sa, 465); else if (s->flags & MTA_FORCE_SMTPS) sa_set_port(sa, 465); else sa_set_port(sa, 25); iobuf_xinit(&s->iobuf, 0, 0, "mta_enter_state"); io_init(&s->io, -1, s, mta_io, &s->iobuf); io_set_timeout(&s->io, 10000); if (io_connect(&s->io, sa, NULL) == -1) { log_debug("mta: %p: connection failed: %s", s, strerror(errno)); iobuf_clear(&s->iobuf); /* * This error is most likely a "no route", * so there is no need to try the same * relay again. */ TAILQ_REMOVE(&s->hosts, host, entry); free(host); continue; } return; } /* tried them all? */ mta_route_error(s->route, "150 Can not connect to MX"); mta_enter_state(s, MTA_DONE); break; case MTA_DONE: /* * Kill the mta session. */ log_debug("mta: %p: session done", s); io_clear(&s->io); iobuf_clear(&s->iobuf); if (s->task) fatalx("current task should have been deleted already"); if (s->datafp) fclose(s->datafp); s->datafp = NULL; while ((host = TAILQ_FIRST(&s->hosts))) { TAILQ_REMOVE(&s->hosts, host, entry); free(host); } route = s->route; tree_xpop(&sessions, s->id); free(s); stat_decrement("mta.session", 1); mta_route_collect(route); break; case MTA_SMTP_BANNER: /* just wait for banner */ s->is_reading = 1; io_set_read(&s->io); break; case MTA_SMTP_EHLO: s->ext = 0; mta_send(s, "EHLO %s", env->sc_hostname); break; case MTA_SMTP_HELO: s->ext = 0; mta_send(s, "HELO %s", env->sc_hostname); break; case MTA_SMTP_STARTTLS: if (s->flags & MTA_TLS) /* already started */ mta_enter_state(s, MTA_SMTP_AUTH); else if ((s->ext & MTA_EXT_STARTTLS) == 0) /* server doesn't support starttls, do not use it */ mta_enter_state(s, MTA_SMTP_AUTH); else mta_send(s, "STARTTLS"); break; case MTA_SMTP_AUTH: if (s->secret && s->flags & MTA_TLS) mta_send(s, "AUTH PLAIN %s", s->secret); else if (s->secret) { log_debug("mta: %p: not using AUTH on non-TLS session", s); mta_enter_state(s, MTA_CONNECT); } else { mta_enter_state(s, MTA_SMTP_READY); } break; case MTA_SMTP_READY: /* ready to send a new mail */ if (s->ready == 0) { s->ready = 1; mta_route_ok(s->route); } if (s->msgcount >= s->route->maxmail) { log_debug("mta: %p: cannot send more message to %s", s, mta_route_to_text(s->route)); mta_enter_state(s, MTA_SMTP_QUIT); } else if ((s->task = TAILQ_FIRST(&s->route->tasks))) { log_debug("mta: %p: handling next task for %s", s, mta_route_to_text(s->route)); TAILQ_REMOVE(&s->route->tasks, s->task, entry); s->route->ntask -= 1; s->task->session = s; stat_decrement("mta.task", 1); stat_increment("mta.task.running", 1); mta_enter_state(s, MTA_DATA); } else { log_debug("mta: %p: no pending task for %s", s, mta_route_to_text(s->route)); /* XXX stay open for a while? */ mta_enter_state(s, MTA_SMTP_QUIT); } break; case MTA_SMTP_MAIL: if (s->task->sender.user[0] && s->task->sender.domain[0]) mta_send(s, "MAIL FROM: <%s@%s>", s->task->sender.user, s->task->sender.domain); else mta_send(s, "MAIL FROM: <>"); break; case MTA_SMTP_RCPT: if (s->currevp == NULL) s->currevp = TAILQ_FIRST(&s->task->envelopes); mta_send(s, "RCPT TO: <%s@%s>", s->currevp->dest.user, s->currevp->dest.domain); break; case MTA_SMTP_DATA: fseek(s->datafp, 0, SEEK_SET); mta_send(s, "DATA"); break; case MTA_SMTP_BODY: if (s->datafp == NULL) { log_trace(TRACE_MTA, "mta: %p: end-of-file", s); mta_enter_state(s, MTA_SMTP_DONE); break; } if ((q = mta_queue_data(s)) == -1) { mta_enter_state(s, MTA_DONE); break; } log_trace(TRACE_MTA, "mta: %p: >>> [...%zi bytes...]", s, q); break; case MTA_SMTP_DONE: mta_send(s, "."); break; case MTA_SMTP_QUIT: mta_send(s, "QUIT"); break; case MTA_SMTP_RSET: mta_send(s, "RSET"); break; default: fatalx("mta_enter_state: unknown state"); } #undef mta_enter_state }