void mta_session_imsg(struct mproc *p, struct imsg *imsg) { struct ca_vrfy_resp_msg *resp_ca_vrfy; struct ca_cert_resp_msg *resp_ca_cert; struct mta_session *s; struct mta_host *h; struct msg m; uint64_t reqid; const char *name; void *ssl; int dnserror, status; switch (imsg->hdr.type) { case IMSG_QUEUE_MESSAGE_FD: m_msg(&m, imsg); m_get_id(&m, &reqid); m_end(&m); s = mta_tree_pop(&wait_fd, reqid); if (s == NULL) { if (imsg->fd != -1) close(imsg->fd); return; } if (imsg->fd == -1) { log_debug("debug: mta: failed to obtain msg fd"); mta_flush_task(s, IMSG_DELIVERY_TEMPFAIL, "Could not get message fd", 0, 0); mta_enter_state(s, MTA_READY); io_reload(&s->io); return; } s->datafp = fdopen(imsg->fd, "r"); if (s->datafp == NULL) fatal("mta: fdopen"); if (mta_check_loop(s->datafp)) { log_debug("debug: mta: loop detected"); fclose(s->datafp); s->datafp = NULL; mta_flush_task(s, IMSG_DELIVERY_LOOP, "Loop detected", 0, 0); mta_enter_state(s, MTA_READY); } else { mta_enter_state(s, MTA_MAIL); } io_reload(&s->io); return; case IMSG_DNS_PTR: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_int(&m, &dnserror); if (dnserror) name = NULL; else m_get_string(&m, &name); m_end(&m); s = mta_tree_pop(&wait_ptr, reqid); if (s == NULL) return; h = s->route->dst; h->lastptrquery = time(NULL); if (name) h->ptrname = xstrdup(name, "mta: ptr"); waitq_run(&h->ptrname, h->ptrname); return; case IMSG_LKA_SSL_INIT: resp_ca_cert = imsg->data; s = mta_tree_pop(&wait_ssl_init, resp_ca_cert->reqid); if (s == NULL) return; if (resp_ca_cert->status == CA_FAIL) { if (s->relay->pki_name) { log_info("smtp-out: Disconnecting session %016"PRIx64 ": CA failure", s->id); mta_free(s); return; } else { ssl = ssl_mta_init(NULL, 0, NULL, 0); if (ssl == NULL) fatal("mta: ssl_mta_init"); io_start_tls(&s->io, ssl); return; } } resp_ca_cert = xmemdup(imsg->data, sizeof *resp_ca_cert, "mta:ca_cert"); resp_ca_cert->cert = xstrdup((char *)imsg->data + sizeof *resp_ca_cert, "mta:ca_cert"); resp_ca_cert->key = xstrdup((char *)imsg->data + sizeof *resp_ca_cert + resp_ca_cert->cert_len, "mta:ca_key"); ssl = ssl_mta_init(resp_ca_cert->cert, resp_ca_cert->cert_len, resp_ca_cert->key, resp_ca_cert->key_len); if (ssl == NULL) fatal("mta: ssl_mta_init"); io_start_tls(&s->io, ssl); memset(resp_ca_cert->cert, 0, resp_ca_cert->cert_len); memset(resp_ca_cert->key, 0, resp_ca_cert->key_len); free(resp_ca_cert->cert); free(resp_ca_cert->key); free(resp_ca_cert); return; case IMSG_LKA_SSL_VERIFY: resp_ca_vrfy = imsg->data; s = mta_tree_pop(&wait_ssl_verify, resp_ca_vrfy->reqid); if (s == NULL) return; if (resp_ca_vrfy->status == CA_OK) s->flags |= MTA_VERIFIED; else if (s->relay->flags & F_TLS_VERIFY) { errno = 0; mta_error(s, "SSL certificate check failed"); mta_free(s); return; } mta_io(&s->io, IO_TLSVERIFIED); io_resume(&s->io, IO_PAUSE_IN); io_reload(&s->io); return; case IMSG_LKA_HELO: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_int(&m, &status); if (status == LKA_OK) m_get_string(&m, &name); m_end(&m); s = mta_tree_pop(&wait_helo, reqid); if (s == NULL) return; if (status == LKA_OK) { s->helo = xstrdup(name, "mta_session_imsg"); mta_connect(s); } else { mta_source_error(s->relay, s->route, "Failed to retrieve helo string"); mta_free(s); } return; default: errx(1, "mta_session_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); } }
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); }
/* * Handle a response to an SMTP command */ static void mta_response(struct mta_session *s, char *line) { void *ssl; struct envelope *evp; switch (s->state) { case MTA_SMTP_BANNER: mta_enter_state(s, MTA_SMTP_EHLO); break; case MTA_SMTP_EHLO: if (line[0] != '2') { if ((s->flags & MTA_USE_AUTH) || !(s->flags & MTA_ALLOW_PLAIN)) { mta_route_error(s->route, line); mta_enter_state(s, MTA_DONE); return; } mta_enter_state(s, MTA_SMTP_HELO); return; } mta_enter_state(s, MTA_SMTP_STARTTLS); break; case MTA_SMTP_HELO: if (line[0] != '2') { mta_route_error(s->route, line); mta_enter_state(s, MTA_DONE); return; } mta_enter_state(s, MTA_SMTP_READY); break; case MTA_SMTP_STARTTLS: if (line[0] != '2') { if (s->flags & MTA_ALLOW_PLAIN) { mta_enter_state(s, MTA_SMTP_AUTH); return; } /* stop here if ssl can't be used */ mta_route_error(s->route, line); mta_enter_state(s, MTA_DONE); return; } ssl = ssl_mta_init(s->ssl); if (ssl == NULL) fatal("mta: ssl_mta_init"); s->is_reading = 0; io_set_write(&s->io); io_start_tls(&s->io, ssl); break; case MTA_SMTP_AUTH: if (line[0] != '2') { mta_route_error(s->route, line); mta_enter_state(s, MTA_DONE); return; } mta_enter_state(s, MTA_SMTP_READY); break; case MTA_SMTP_MAIL: if (line[0] != '2') { mta_status(s, 0, line); mta_enter_state(s, MTA_SMTP_RSET); return; } mta_enter_state(s, MTA_SMTP_RCPT); break; case MTA_SMTP_RCPT: evp = s->currevp; s->currevp = TAILQ_NEXT(s->currevp, entry); if (line[0] != '2') { mta_envelope_done(s->task, evp, line); if (TAILQ_EMPTY(&s->task->envelopes)) { free(s->task); s->task = NULL; stat_decrement("mta.task.running", 1); mta_enter_state(s, MTA_SMTP_RSET); break; } } if (s->currevp == NULL) mta_enter_state(s, MTA_SMTP_DATA); else mta_enter_state(s, MTA_SMTP_RCPT); break; case MTA_SMTP_DATA: if (line[0] != '2' && line[0] != '3') { mta_status(s, 0, line); mta_enter_state(s, MTA_SMTP_RSET); return; } mta_enter_state(s, MTA_SMTP_BODY); break; case MTA_SMTP_DONE: mta_status(s, 0, line); if (line[0] == '2') s->msgcount++; mta_enter_state(s, MTA_SMTP_READY); break; case MTA_SMTP_RSET: mta_enter_state(s, MTA_SMTP_READY); break; default: fatalx("mta_response() bad state"); } }
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()"); } }
void mta_session_imsg(struct imsgev *iev, struct imsg *imsg) { uint64_t id; struct mta_session *s; struct mta_host *host; struct secret *secret; struct dns *dns; const char *error; void *ptr; switch(imsg->hdr.type) { case IMSG_QUEUE_MESSAGE_FD: id = *(uint64_t*)(imsg->data); if (imsg->fd == -1) fatalx("mta: cannot obtain msgfd"); s = tree_xget(&sessions, id); s->datafp = fdopen(imsg->fd, "r"); if (s->datafp == NULL) fatal("mta: fdopen"); if (mta_check_loop(s->datafp)) { log_debug("mta: loop detected"); fclose(s->datafp); s->datafp = NULL; mta_status(s, 0, "646 Loop detected"); mta_enter_state(s, MTA_SMTP_READY); } else { mta_enter_state(s, MTA_SMTP_MAIL); } return; case IMSG_LKA_SECRET: /* LKA responded to AUTH lookup. */ secret = imsg->data; s = tree_xget(&sessions, secret->id); s->secret = xstrdup(secret->secret, "mta: secret"); if (s->secret[0] == '\0') { mta_route_error(s->route, "secrets lookup failed"); mta_enter_state(s, MTA_DONE); } else mta_enter_state(s, MTA_MX); return; case IMSG_DNS_HOST: dns = imsg->data; s = tree_xget(&sessions, dns->id); host = xcalloc(1, sizeof *host, "mta: host"); host->sa = dns->ss; TAILQ_INSERT_TAIL(&s->hosts, host, entry); return; case IMSG_DNS_HOST_END: /* LKA responded to DNS lookup. */ dns = imsg->data; s = tree_xget(&sessions, dns->id); if (!dns->error) { mta_enter_state(s, MTA_CONNECT); return; } if (dns->error == DNS_RETRY) error = "100 MX lookup failed temporarily"; else if (dns->error == DNS_EINVAL) error = "600 Invalid domain name"; else if (dns->error == DNS_ENONAME) error = "600 Domain does not exist"; else if (dns->error == DNS_ENOTFOUND) error = "600 No MX address found for domain"; else error = "100 Weird error"; mta_route_error(s->route, error); mta_enter_state(s, MTA_CONNECT); return; case IMSG_DNS_PTR: dns = imsg->data; s = tree_xget(&sessions, dns->id); host = TAILQ_FIRST(&s->hosts); if (dns->error) strlcpy(host->fqdn, "<unknown>", sizeof host->fqdn); else strlcpy(host->fqdn, dns->host, sizeof host->fqdn); log_debug("mta: %p: connected to %s", s, host->fqdn); /* check if we need to start tls now... */ if (((s->flags & MTA_FORCE_ANYSSL) && host->used == 1) || (s->flags & MTA_FORCE_SMTPS)) { log_debug("mta: %p: trying smtps (ssl=%p)...", s, s->ssl); if ((ptr = ssl_mta_init(s->ssl)) == NULL) fatalx("mta: ssl_mta_init"); io_start_tls(&s->io, ptr); } else { mta_enter_state(s, MTA_SMTP_BANNER); } break; default: errx(1, "mta_session_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); } }