static void queue_envelope_cache_update(struct envelope *e) { struct envelope *cached; if ((cached = tree_get(&evpcache_tree, e->id)) == NULL) { queue_envelope_cache_add(e); stat_increment("queue.evpcache.update.missed", 1); } else { TAILQ_REMOVE(&evpcache_list, cached, entry); *cached = *e; TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); stat_increment("queue.evpcache.update.hit", 1); } }
static int queue_ram_envelope_create(uint32_t msgid, const char *buf, size_t len, uint64_t *evpid) { struct qr_envelope *evp; struct qr_message *msg; if ((msg = get_message(msgid)) == NULL) return (0); do { *evpid = queue_generate_evpid(msgid); } while (tree_check(&msg->envelopes, *evpid)); evp = calloc(1, sizeof *evp); if (evp == NULL) { log_warn("warn: queue-ram: calloc"); return (0); } evp->len = len; evp->buf = malloc(len); if (evp->buf == NULL) { log_warn("warn: queue-ram: malloc"); free(evp); return (0); } memmove(evp->buf, buf, len); tree_xset(&msg->envelopes, *evpid, evp); stat_increment("queue.ram.envelope.size", len); return (1); }
static int queue_ram_envelope_update(uint64_t evpid, const char *buf, size_t len) { struct qr_envelope *evp; struct qr_message *msg; void *tmp; if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) return (0); if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { log_warn("warn: queue-ram: not found"); return (0); } tmp = malloc(len); if (tmp == NULL) { log_warn("warn: queue-ram: malloc"); return (0); } memmove(tmp, buf, len); free(evp->buf); evp->len = len; evp->buf = tmp; stat_decrement("queue.ram.envelope.size", evp->len); stat_increment("queue.ram.envelope.size", len); return (1); }
/* ARGSUSED */ void control_accept(int listenfd, short event, void *arg) { int connfd; socklen_t len; struct sockaddr_un sun; struct ctl_conn *c; len = sizeof(sun); if ((connfd = accept(listenfd, (struct sockaddr *)&sun, &len)) == -1) { if (errno == EINTR || errno == ECONNABORTED) return; fatal("control_accept: accept"); } session_socket_blockmode(connfd, BM_NONBLOCK); if ((c = calloc(1, sizeof(*c))) == NULL) fatal(NULL); imsg_init(&c->iev.ibuf, connfd); c->iev.handler = control_dispatch_ext; c->iev.events = EV_READ; event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, c->iev.handler, NULL); event_add(&c->iev.ev, NULL); TAILQ_INSERT_TAIL(&ctl_conns, c, entry); if (stat_increment(STATS_CONTROL_SESSION) >= env->sc_maxconn) { log_warnx("ctl client limit hit, disabling new connections"); event_del(&control_state.ev); } }
static void session_read_data(struct session *s, char *line) { size_t datalen; size_t len; size_t i; if (strcmp(line, ".") == 0) { s->s_datalen = ftell(s->datafp); if (! safe_fclose(s->datafp)) s->s_dstatus |= DS_TEMPFAILURE; s->datafp = NULL; if (s->s_dstatus & DS_PERMFAILURE) { session_respond(s, "554 5.0.0 Transaction failed"); session_enter_state(s, S_HELO); } else if (s->s_dstatus & DS_TEMPFAILURE) { session_respond(s, "421 4.0.0 Temporary failure"); session_enter_state(s, S_QUIT); stat_increment("smtp.tempfail", 1); } else { session_imsg(s, PROC_QUEUE, IMSG_QUEUE_COMMIT_MESSAGE, 0, 0, -1, &s->s_msg, sizeof(s->s_msg)); session_enter_state(s, S_DONE); } return; } /* Don't waste resources on message if it's going to bin anyway. */ if (s->s_dstatus & (DS_PERMFAILURE|DS_TEMPFAILURE)) return; /* "If the first character is a period and there are other characters * on the line, the first character is deleted." [4.5.2] */ if (*line == '.') line++; len = strlen(line); /* If size of data overflows a size_t or exceeds max size allowed * for a message, set permanent failure. */ datalen = ftell(s->datafp); if (SIZE_MAX - datalen < len + 1 || datalen + len + 1 > env->sc_maxsize) { s->s_dstatus |= DS_PERMFAILURE; return; } if (! (s->s_flags & F_8BITMIME)) { for (i = 0; i < len; ++i) if (line[i] & 0x80) line[i] = line[i] & 0x7f; } if (fprintf(s->datafp, "%s\n", line) != (int)len + 1) s->s_dstatus |= DS_TEMPFAILURE; }
static void session_line(struct session *s, char *line, size_t len) { struct submit_status ss; if (s->s_state != S_DATACONTENT) log_trace(TRACE_SMTP, "smtp: %p: <<< %s", s, line); switch (s->s_state) { case S_AUTH_INIT: if (s->s_dstatus & DS_TEMPFAILURE) goto tempfail; session_rfc4954_auth_plain(s, line); break; case S_AUTH_USERNAME: case S_AUTH_PASSWORD: if (s->s_dstatus & DS_TEMPFAILURE) goto tempfail; session_rfc4954_auth_login(s, line); break; case S_GREETED: case S_HELO: case S_MAIL: case S_RCPT: if (s->s_dstatus & DS_TEMPFAILURE) goto tempfail; session_command(s, line); break; case S_DATACONTENT: if (env->filtermask & FILTER_DATALINE) { bzero(&ss, sizeof(ss)); ss.id = s->s_id; if (strlcpy(ss.u.dataline, line, sizeof(ss.u.dataline)) >= sizeof(ss.u.dataline)) fatal("session_line: data truncation"); session_imsg(s, PROC_MFA, IMSG_MFA_DATALINE, 0, 0, -1, &ss, sizeof(ss)); } else { /* no filtering */ session_read_data(s, line); } break; default: log_debug("session_read: %i", s->s_state); fatalx("session_read: unexpected state"); } return; tempfail: session_respond(s, "421 4.0.0 Service temporarily unavailable"); stat_increment("smtp.tempfail", 1); session_enter_state(s, S_QUIT); }
void mta_session(struct mta_relay *relay, struct mta_route *route) { struct mta_session *s; struct timeval tv; mta_session_init(); s = xcalloc(1, sizeof *s, "mta_session"); s->id = generate_uid(); s->relay = relay; s->route = route; s->io.sock = -1; if (relay->flags & RELAY_SSL && relay->flags & RELAY_AUTH) s->flags |= MTA_USE_AUTH; if (relay->pki_name) s->flags |= MTA_USE_CERT; if (relay->flags & RELAY_LMTP) s->flags |= MTA_LMTP; switch (relay->flags & (RELAY_SSL|RELAY_TLS_OPTIONAL)) { case RELAY_SSL: s->flags |= MTA_FORCE_ANYSSL; s->flags |= MTA_WANT_SECURE; break; case RELAY_SMTPS: s->flags |= MTA_FORCE_SMTPS; s->flags |= MTA_WANT_SECURE; break; case RELAY_STARTTLS: s->flags |= MTA_FORCE_TLS; s->flags |= MTA_WANT_SECURE; break; case RELAY_TLS_OPTIONAL: /* do not force anything, try tls then smtp */ break; default: s->flags |= MTA_FORCE_PLAIN; } log_debug("debug: mta: %p: spawned for relay %s", s, mta_relay_to_text(relay)); stat_increment("mta.session", 1); if (route->dst->ptrname || route->dst->lastptrquery) { /* We want to delay the connection since to always notify * the relay asynchronously. */ tv.tv_sec = 0; tv.tv_usec = 0; evtimer_set(&s->io.ev, mta_start, s); evtimer_add(&s->io.ev, &tv); } else if (waitq_wait(&route->dst->ptrname, mta_on_ptr, s)) { dns_query_ptr(s->id, s->route->dst->sa); tree_xset(&wait_ptr, s->id, s); s->flags |= MTA_WAIT; } }
int queue_envelope_load(uint64_t evpid, struct envelope *ep) { const char *e; char evpbuf[sizeof(struct envelope)]; size_t evplen; struct envelope *cached; if ((env->sc_queue_flags & QUEUE_EVPCACHE) && (cached = tree_get(&evpcache_tree, evpid))) { *ep = *cached; stat_increment("queue.evpcache.load.hit", 1); return (1); } ep->id = evpid; profile_enter("queue_envelope_load"); evplen = handler_envelope_load(ep->id, evpbuf, sizeof evpbuf); profile_leave(); log_trace(TRACE_QUEUE, "queue-backend: queue_envelope_load(%016"PRIx64") -> %zu", evpid, evplen); if (evplen == 0) return (0); if (queue_envelope_load_buffer(ep, evpbuf, evplen)) { if ((e = envelope_validate(ep)) == NULL) { ep->id = evpid; if (env->sc_queue_flags & QUEUE_EVPCACHE) { queue_envelope_cache_add(ep); stat_increment("queue.evpcache.load.missed", 1); } return (1); } log_debug("debug: invalid envelope %016" PRIx64 ": %s", ep->id, e); } (void)queue_message_corrupt(evpid_to_msgid(evpid)); return (0); }
static struct mta_route * mta_route_for(struct envelope *e) { struct ssl ssl; struct mta_route key, *route; bzero(&key, sizeof key); key.flags = e->agent.mta.relay.flags; if (e->agent.mta.relay.flags & ROUTE_BACKUP) { key.hostname = e->dest.domain; key.backupname = e->agent.mta.relay.hostname; } else if (e->agent.mta.relay.hostname[0]) { key.hostname = e->agent.mta.relay.hostname; key.flags |= ROUTE_MX; } else key.hostname = e->dest.domain; key.port = e->agent.mta.relay.port; key.cert = e->agent.mta.relay.cert; if (!key.cert[0]) key.cert = NULL; key.auth = e->agent.mta.relay.authmap; if (!key.auth[0]) key.auth = NULL; if ((route = SPLAY_FIND(mta_route_tree, &routes, &key)) == NULL) { route = xcalloc(1, sizeof *route, "mta_route"); TAILQ_INIT(&route->tasks); route->id = generate_uid(); route->flags = key.flags; route->hostname = xstrdup(key.hostname, "mta: hostname"); route->backupname = key.backupname ? xstrdup(key.backupname, "mta: backupname") : NULL; route->port = key.port; route->cert = key.cert ? xstrdup(key.cert, "mta: cert") : NULL; route->auth = key.auth ? xstrdup(key.auth, "mta: auth") : NULL; if (route->cert) { strlcpy(ssl.ssl_name, route->cert, sizeof(ssl.ssl_name)); route->ssl = SPLAY_FIND(ssltree, env->sc_ssl, &ssl); } SPLAY_INSERT(mta_route_tree, &routes, route); route->maxconn = MTA_MAXCONN; route->maxmail = MTA_MAXMAIL; route->maxrcpt = MTA_MAXRCPT; log_trace(TRACE_MTA, "mta: new %s", mta_route_to_text(route)); stat_increment("mta.route", 1); } else { log_trace(TRACE_MTA, "mta: reusing %s", mta_route_to_text(route)); } return (route); }
static void queue_envelope_cache_add(struct envelope *e) { struct envelope *cached; while (tree_count(&evpcache_tree) >= env->sc_queue_evpcache_size) queue_envelope_cache_del(TAILQ_LAST(&evpcache_list, evplst)->id); cached = xcalloc(1, sizeof *cached, "queue_envelope_cache_add"); *cached = *e; TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); tree_xset(&evpcache_tree, e->id, cached); stat_increment("queue.evpcache.size", 1); }
static int queue_ram_message_commit(uint32_t msgid, const char *path) { struct qr_message *msg; struct stat sb; size_t n; FILE *f; int ret; if ((msg = tree_get(&messages, msgid)) == NULL) { log_warnx("warn: queue-ram: msgid not found"); return (0); } f = fopen(path, "rb"); if (f == NULL) { log_warn("warn: queue-ram: fopen: %s", path); return (0); } if (fstat(fileno(f), &sb) == -1) { log_warn("warn: queue-ram: fstat"); fclose(f); return (0); } msg->len = sb.st_size; msg->buf = malloc(msg->len); if (msg->buf == NULL) { log_warn("warn: queue-ram: malloc"); fclose(f); return (0); } ret = 0; n = fread(msg->buf, 1, msg->len, f); if (ferror(f)) log_warn("warn: queue-ram: fread"); else if ((off_t)n != sb.st_size) log_warnx("warn: queue-ram: bad read"); else { ret = 1; stat_increment("queue.ram.message.size", msg->len); } fclose(f); return (ret); }
static void queue_bounce(struct envelope *e, struct delivery_bounce *d) { struct envelope b; b = *e; b.type = D_BOUNCE; b.agent.bounce = *d; b.retry = 0; b.lasttry = 0; b.creation = time(NULL); b.expire = 3600 * 24 * 7; if (e->dsn_notify & DSN_NEVER) return; if (b.id == 0) log_warnx("warn: queue_bounce: evpid=0"); if (evpid_to_msgid(b.id) == 0) log_warnx("warn: queue_bounce: msgid=0, evpid=%016"PRIx64, b.id); if (e->type == D_BOUNCE) { log_warnx("warn: queue: double bounce!"); } else if (e->sender.user[0] == '\0') { log_warnx("warn: queue: no return path!"); } else if (!queue_envelope_create(&b)) { log_warnx("warn: queue: cannot bounce!"); } else { log_debug("debug: queue: bouncing evp:%016" PRIx64 " as evp:%016" PRIx64, e->id, b.id); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); m_add_envelope(p_scheduler, &b); m_close(p_scheduler); m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1); m_add_msgid(p_scheduler, evpid_to_msgid(b.id)); m_close(p_scheduler); stat_increment("queue.bounce", 1); } }
void mta_session(struct mta_route *route) { struct mta_session *session; session = xcalloc(1, sizeof *session, "mta_session"); session->id = generate_uid(); session->route = route; session->state = MTA_INIT; session->io.sock = -1; tree_xset(&sessions, session->id, session); TAILQ_INIT(&session->hosts); if (route->flags & ROUTE_MX) session->flags |= MTA_FORCE_MX; if (route->flags & ROUTE_SSL && route->flags & ROUTE_AUTH) session->flags |= MTA_USE_AUTH; if (route->cert) session->flags |= MTA_USE_CERT; switch (route->flags & ROUTE_SSL) { case ROUTE_SSL: session->flags |= MTA_FORCE_ANYSSL; break; case ROUTE_SMTPS: session->flags |= MTA_FORCE_SMTPS; break; case ROUTE_STARTTLS: /* STARTTLS is tried by default */ break; default: session->flags |= MTA_ALLOW_PLAIN; } log_debug("mta: %p: spawned for %s", session, mta_route_to_text(route)); stat_increment("mta.session", 1); mta_enter_state(session, MTA_INIT); }
void mta_imsg(struct imsgev *iev, struct imsg *imsg) { struct mta_route *route; struct mta_batch2 *batch; struct mta_task *task; struct envelope *e; struct ssl *ssl; uint64_t id; if (iev->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_BATCH_CREATE: id = *(uint64_t*)(imsg->data); batch = xmalloc(sizeof *batch, "mta_batch"); batch->id = id; tree_init(&batch->tasks); tree_xset(&batches, batch->id, batch); log_trace(TRACE_MTA, "mta: batch:%016" PRIx64 " created", batch->id); return; case IMSG_BATCH_APPEND: e = xmemdup(imsg->data, sizeof *e, "mta:envelope"); route = mta_route_for(e); batch = tree_xget(&batches, e->batch_id); if ((task = tree_get(&batch->tasks, route->id)) == NULL) { log_trace(TRACE_MTA, "mta: new task for %s", mta_route_to_text(route)); task = xmalloc(sizeof *task, "mta_task"); TAILQ_INIT(&task->envelopes); task->route = route; tree_xset(&batch->tasks, route->id, task); task->msgid = evpid_to_msgid(e->id); task->sender = e->sender; route->refcount += 1; } /* Technically, we could handle that by adding a msg * level, but the batch sent by the scheduler should * be valid. */ if (task->msgid != evpid_to_msgid(e->id)) errx(1, "msgid mismatch in batch"); /* XXX honour route->maxrcpt */ TAILQ_INSERT_TAIL(&task->envelopes, e, entry); stat_increment("mta.envelope", 1); log_debug("mta: received evp:%016" PRIx64 " for <%s@%s>", e->id, e->dest.user, e->dest.domain); return; case IMSG_BATCH_CLOSE: id = *(uint64_t*)(imsg->data); batch = tree_xpop(&batches, id); log_trace(TRACE_MTA, "mta: batch:%016" PRIx64 " closed", batch->id); /* for all tasks, queue them on there route */ while (tree_poproot(&batch->tasks, &id, (void**)&task)) { if (id != task->route->id) errx(1, "route id mismatch!"); task->route->refcount -= 1; task->route->ntask += 1; TAILQ_INSERT_TAIL(&task->route->tasks, task, entry); stat_increment("mta.task", 1); mta_route_drain(task->route); } free(batch); return; case IMSG_QUEUE_MESSAGE_FD: mta_session_imsg(iev, imsg); return; } } if (iev->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_SECRET: case IMSG_DNS_HOST: case IMSG_DNS_HOST_END: case IMSG_DNS_PTR: mta_session_imsg(iev, imsg); return; } } if (iev->proc == PROC_PARENT) { switch (imsg->hdr.type) { case IMSG_CONF_START: if (env->sc_flags & SMTPD_CONFIGURING) return; env->sc_flags |= SMTPD_CONFIGURING; env->sc_ssl = xcalloc(1, sizeof *env->sc_ssl, "mta:sc_ssl"); return; case IMSG_CONF_SSL: if (!(env->sc_flags & SMTPD_CONFIGURING)) return; ssl = xmemdup(imsg->data, sizeof *ssl, "mta:ssl"); ssl->ssl_cert = xstrdup((char*)imsg->data + sizeof *ssl, "mta:ssl_cert"); ssl->ssl_key = xstrdup((char*)imsg->data + sizeof *ssl + ssl->ssl_cert_len, "mta:ssl_key"); SPLAY_INSERT(ssltree, env->sc_ssl, ssl); return; case IMSG_CONF_END: if (!(env->sc_flags & SMTPD_CONFIGURING)) return; env->sc_flags &= ~SMTPD_CONFIGURING; return; case IMSG_CTL_VERBOSE: log_verbose(*(int *)imsg->data); return; } } errx(1, "mta_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); }
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 }
void session_respond(struct session *s, char *fmt, ...) { va_list ap; int n, delay; char buf[SMTP_LINE_MAX]; va_start(ap, fmt); n = vsnprintf(buf, sizeof buf, fmt, ap); va_end(ap); if (n == -1 || n >= SMTP_LINE_MAX) fatal("session_respond: line too long"); if (n < 4) fatal("session_respond: response too short"); log_trace(TRACE_SMTP, "smtp: %p: >>> %s", s, buf); iobuf_xfqueue(&s->s_iobuf, "session_respond", "%s\r\n", buf); /* * Log failures. Might be annoying in the long term, but it is a good * development aid for now. */ switch (buf[0]) { case '5': case '4': log_info("%08x: from=<%s@%s>, relay=%s [%s], stat=LocalError (%.*s)", evpid_to_msgid(s->s_msg.id), s->s_msg.sender.user, s->s_msg.sender.domain, s->s_hostname, ss_to_text(&s->s_ss), n, buf); break; } /* Detect multi-line response. */ switch (buf[3]) { case '-': return; case ' ': break; default: fatalx("session_respond: invalid response"); } /* * Deal with request flooding; avoid letting response rate keep up * with incoming request rate. */ s->s_nresp[s->s_state]++; if (s->s_state == S_RCPT) delay = 0; else if ((n = s->s_nresp[s->s_state] - FAST_RESPONSES) > 0) delay = MIN(1 << (n - 1), MAX_RESPONSE_DELAY); else delay = 0; if (delay > 0) { struct timeval tv = { delay, 0 }; io_pause(&s->s_io, IO_PAUSE_OUT); stat_increment("smtp.delays", 1); /* in case session_respond is called multiple times */ evtimer_del(&s->s_ev); evtimer_set(&s->s_ev, session_respond_delayed, s); evtimer_add(&s->s_ev, &tv); } }
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 int runner_process_batch(enum delivery_type type, u_int64_t evpid) { struct envelope evp; void *batch; int fd; batch = scheduler->batch(evpid); switch (type) { case D_BOUNCE: while (scheduler->fetch(batch, &evpid)) { if (! queue_envelope_load(evpid, &evp)) goto end; evp.lasttry = time(NULL); imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_SMTP_ENQUEUE, PROC_SMTP, 0, -1, &evp, sizeof evp); scheduler->schedule(evpid); } stat_increment(STATS_RUNNER); stat_increment(STATS_RUNNER_BOUNCES); break; case D_MDA: scheduler->fetch(batch, &evpid); if (! queue_envelope_load(evpid, &evp)) goto end; evp.lasttry = time(NULL); fd = queue_message_fd_r(evpid_to_msgid(evpid)); imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_MDA_SESS_NEW, PROC_MDA, 0, fd, &evp, sizeof evp); scheduler->schedule(evpid); stat_increment(STATS_RUNNER); stat_increment(STATS_MDA_SESSION); break; case D_MTA: { struct mta_batch mta_batch; /* FIXME */ if (! scheduler->fetch(batch, &evpid)) goto end; if (! queue_envelope_load(evpid, &evp)) goto end; bzero(&mta_batch, sizeof mta_batch); mta_batch.id = arc4random(); mta_batch.relay = evp.agent.mta.relay; imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_BATCH_CREATE, PROC_MTA, 0, -1, &mta_batch, sizeof mta_batch); while (scheduler->fetch(batch, &evpid)) { if (! queue_envelope_load(evpid, &evp)) goto end; evp.lasttry = time(NULL); /* FIXME */ evp.batch_id = mta_batch.id; imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_BATCH_APPEND, PROC_MTA, 0, -1, &evp, sizeof evp); scheduler->schedule(evpid); stat_increment(STATS_RUNNER); } imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_BATCH_CLOSE, PROC_MTA, 0, -1, &mta_batch, sizeof mta_batch); stat_increment(STATS_MTA_SESSION); break; } default: fatalx("runner_process_batchqueue: unknown type"); } end: scheduler->close(batch); return 1; }
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); }
Term* run_name_search(NameSearch* params) { stat_increment(NameSearch); if (is_null(¶ms->name) || string_equals(¶ms->name, "")) return NULL; Block* block = params->block; if (block == NULL) return NULL; int position = 0; if (is_symbol(¶ms->position) && as_symbol(¶ms->position) == s_last) position = block->length(); else position = as_int(¶ms->position); if (position > block->length()) position = block->length(); // Look for an exact match. for (int i = position - 1; i >= 0; i--) { stat_increment(NameSearchStep); Term* term = block->get(i); if (term == NULL) continue; if (equals(&term->nameValue, ¶ms->name) && fits_lookup_type(term, params->lookupType) && (params->ordinal == -1 || term->uniqueOrdinal == params->ordinal)) return term; // If this term exposes its names, then search inside the nested block. // (Deprecated, I think). if (term->nestedContents != NULL && exposes_nested_names(term)) { NameSearch nestedSearch; nestedSearch.block = term->nestedContents; set_value(&nestedSearch.name, ¶ms->name); set_symbol(&nestedSearch.position, s_last); nestedSearch.ordinal = -1; nestedSearch.lookupType = params->lookupType; nestedSearch.searchParent = false; Term* found = run_name_search(&nestedSearch); if (found != NULL) return found; } #if 0 // Check for an 'import' statement. If found, continue this search in the designated module. if (term->function == FUNCS.require && term->boolProp(s_Syntax_Import, false)) { Block* module = find_module_for_require_statement(term); if (module != NULL) { NameSearch moduleSearch; moduleSearch.block = module; set_value(&moduleSearch.name, ¶ms->name); set_symbol(&moduleSearch.position, s_last); moduleSearch.ordinal = -1; moduleSearch.lookupType = params->lookupType; moduleSearch.searchParent = false; Term* found = run_name_search(&moduleSearch); if (found != NULL) return found; } } #endif } // Did not find in the local block. Possibly continue this search upwards. if (!params->searchParent) return NULL; // Possibly take this search to the builtins block. if ((get_parent_block(block) == NULL) || is_module(block)) { NameSearch builtinsSearch; builtinsSearch.block = find_builtins_block(block); set_value(&builtinsSearch.name, ¶ms->name); set_symbol(&builtinsSearch.position, s_last); builtinsSearch.lookupType = params->lookupType; builtinsSearch.ordinal = -1; builtinsSearch.searchParent = false; return run_name_search(&builtinsSearch); } // Search parent // The choice of position is a little weird. For regular name searches, // we start at the parent term's position (ie, search all the terms that // came before the parent). // // For a LookupFunction search, start at the bottom of the branch. It's okay // for a term to use a function that occurs after the term. NameSearch parentSearch; Term* parentTerm = block->owningTerm; if (parentTerm == NULL) return NULL; parentSearch.block = parentTerm->owningBlock; if (params->lookupType == s_LookupFunction) set_symbol(&parentSearch.position, s_last); else set_int(&parentSearch.position, parentTerm->index + 1); set_value(&parentSearch.name, ¶ms->name); parentSearch.lookupType = params->lookupType; parentSearch.ordinal = -1; parentSearch.searchParent = true; return run_name_search(&parentSearch); }
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 }