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 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 }