static void mta_start_tls(struct mta_session *s) { struct ca_cert_req_msg req_ca_cert; const char *certname; if (s->relay->pki_name) certname = s->relay->pki_name; else certname = s->helo; req_ca_cert.reqid = s->id; strlcpy(req_ca_cert.name, certname, sizeof req_ca_cert.name); m_compose(p_lka, IMSG_LKA_SSL_INIT, 0, 0, -1, &req_ca_cert, sizeof(req_ca_cert)); tree_xset(&wait_ssl_init, s->id, s); s->flags |= MTA_WAIT; return; }
static void queue_msgid_walk(int fd, short event, void *arg) { struct envelope evp; struct timeval tv; struct msg_walkinfo *wi = arg; int r; r = queue_message_walk(&evp, wi->msgid, &wi->done, &wi->data); if (r == -1) { if (wi->n_evp) { m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, 0, 0, -1); m_add_msgid(p_scheduler, wi->msgid); m_close(p_scheduler); } m_compose(p_control, IMSG_CTL_DISCOVER_MSGID, wi->peerid, 0, -1, &wi->n_evp, sizeof wi->n_evp); evtimer_del(&wi->ev); free(wi); return; } if (r) { m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, 0, 0, -1); m_add_envelope(p_scheduler, &evp); m_close(p_scheduler); wi->n_evp += 1; } tv.tv_sec = 0; tv.tv_usec = 10; evtimer_set(&wi->ev, queue_msgid_walk, wi); evtimer_add(&wi->ev, &tv); }
static void queue_imsg(struct mproc *p, struct imsg *imsg) { struct delivery_bounce bounce; struct bounce_req_msg *req_bounce; struct envelope evp; struct msg m; const char *reason; uint64_t reqid, evpid, holdq; uint32_t msgid; time_t nexttry; int fd, mta_ext, ret, v, flags, code; memset(&bounce, 0, sizeof(struct delivery_bounce)); if (p->proc == PROC_PONY) { switch (imsg->hdr.type) { case IMSG_SMTP_MESSAGE_CREATE: m_msg(&m, imsg); m_get_id(&m, &reqid); m_end(&m); ret = queue_message_create(&msgid); m_create(p, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); m_add_id(p, reqid); if (ret == 0) m_add_int(p, 0); else { m_add_int(p, 1); m_add_msgid(p, msgid); } m_close(p); return; case IMSG_SMTP_MESSAGE_ROLLBACK: m_msg(&m, imsg); m_get_msgid(&m, &msgid); m_end(&m); queue_message_delete(msgid); m_create(p_scheduler, IMSG_QUEUE_MESSAGE_ROLLBACK, 0, 0, -1); m_add_msgid(p_scheduler, msgid); m_close(p_scheduler); return; case IMSG_SMTP_MESSAGE_COMMIT: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_msgid(&m, &msgid); m_end(&m); ret = queue_message_commit(msgid); m_create(p, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); m_add_id(p, reqid); m_add_int(p, (ret == 0) ? 0 : 1); m_close(p); if (ret) { m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1); m_add_msgid(p_scheduler, msgid); m_close(p_scheduler); } return; case IMSG_SMTP_MESSAGE_OPEN: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_msgid(&m, &msgid); m_end(&m); fd = queue_message_fd_rw(msgid); m_create(p, IMSG_SMTP_MESSAGE_OPEN, 0, 0, fd); m_add_id(p, reqid); m_add_int(p, (fd == -1) ? 0 : 1); m_close(p); return; case IMSG_QUEUE_SMTP_SESSION: bounce_fd(imsg->fd); return; } } if (p->proc == PROC_LKA) { switch (imsg->hdr.type) { case IMSG_LKA_ENVELOPE_SUBMIT: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_envelope(&m, &evp); m_end(&m); if (evp.id == 0) log_warnx("warn: imsg_queue_submit_envelope: evpid=0"); if (evpid_to_msgid(evp.id) == 0) log_warnx("warn: imsg_queue_submit_envelope: msgid=0, " "evpid=%016"PRIx64, evp.id); ret = queue_envelope_create(&evp); m_create(p_pony, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); m_add_id(p_pony, reqid); if (ret == 0) m_add_int(p_pony, 0); else { m_add_int(p_pony, 1); m_add_evpid(p_pony, evp.id); } m_close(p_pony); if (ret) { m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); m_add_envelope(p_scheduler, &evp); m_close(p_scheduler); } return; case IMSG_LKA_ENVELOPE_COMMIT: m_msg(&m, imsg); m_get_id(&m, &reqid); m_end(&m); m_create(p_pony, IMSG_QUEUE_ENVELOPE_COMMIT, 0, 0, -1); m_add_id(p_pony, reqid); m_add_int(p_pony, 1); m_close(p_pony); return; } } if (p->proc == PROC_SCHEDULER) { switch (imsg->hdr.type) { case IMSG_SCHED_ENVELOPE_REMOVE: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_end(&m); /* already removed by scheduler */ if (queue_envelope_load(evpid, &evp) == 0) return; queue_log(&evp, "Remove", "Removed by administrator"); queue_envelope_delete(evpid); return; case IMSG_SCHED_ENVELOPE_EXPIRE: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_end(&m); /* already removed by scheduler*/ if (queue_envelope_load(evpid, &evp) == 0) return; bounce.type = B_ERROR; envelope_set_errormsg(&evp, "Envelope expired"); envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); envelope_set_esc_code(&evp, ESC_DELIVERY_TIME_EXPIRED); queue_bounce(&evp, &bounce); queue_log(&evp, "Expire", "Envelope expired"); queue_envelope_delete(evpid); return; case IMSG_SCHED_ENVELOPE_BOUNCE: req_bounce = imsg->data; evpid = req_bounce->evpid; if (queue_envelope_load(evpid, &evp) == 0) { log_warnx("queue: bounce: failed to load envelope"); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_add_u32(p_scheduler, 0); /* not in-flight */ m_close(p_scheduler); return; } queue_bounce(&evp, &req_bounce->bounce); evp.lastbounce = req_bounce->timestamp; if (!queue_envelope_update(&evp)) log_warnx("warn: could not update envelope %016"PRIx64, evpid); return; case IMSG_SCHED_ENVELOPE_DELIVER: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) { log_warnx("queue: deliver: failed to load envelope"); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_add_u32(p_scheduler, 1); /* in-flight */ m_close(p_scheduler); return; } evp.lasttry = time(NULL); m_create(p_pony, IMSG_QUEUE_DELIVER, 0, 0, -1); m_add_envelope(p_pony, &evp); m_close(p_pony); return; case IMSG_SCHED_ENVELOPE_INJECT: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_end(&m); bounce_add(evpid); return; case IMSG_SCHED_ENVELOPE_TRANSFER: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) { log_warnx("queue: failed to load envelope"); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_add_u32(p_scheduler, 1); /* in-flight */ m_close(p_scheduler); return; } evp.lasttry = time(NULL); m_create(p_pony, IMSG_QUEUE_TRANSFER, 0, 0, -1); m_add_envelope(p_pony, &evp); m_close(p_pony); return; case IMSG_CTL_LIST_ENVELOPES: if (imsg->hdr.len == sizeof imsg->hdr) { m_forward(p_control, imsg); return; } m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_get_int(&m, &flags); m_get_time(&m, &nexttry); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) return; /* Envelope is gone, drop it */ /* * XXX consistency: The envelope might already be on * its way back to the scheduler. We need to detect * this properly and report that state. */ evp.flags |= flags; /* In the past if running or runnable */ evp.nexttry = nexttry; if (flags & EF_INFLIGHT) { /* * Not exactly correct but pretty close: The * value is not recorded on the envelope unless * a tempfail occurs. */ evp.lasttry = nexttry; } m_compose(p_control, IMSG_CTL_LIST_ENVELOPES, imsg->hdr.peerid, 0, -1, &evp, sizeof evp); return; } } if (p->proc == PROC_PONY) { switch (imsg->hdr.type) { case IMSG_MDA_OPEN_MESSAGE: case IMSG_MTA_OPEN_MESSAGE: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_msgid(&m, &msgid); m_end(&m); fd = queue_message_fd_r(msgid); m_create(p, imsg->hdr.type, 0, 0, fd); m_add_id(p, reqid); m_close(p); return; case IMSG_MDA_DELIVERY_OK: case IMSG_MTA_DELIVERY_OK: m_msg(&m, imsg); m_get_evpid(&m, &evpid); if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK) m_get_int(&m, &mta_ext); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) { log_warn("queue: dsn: failed to load envelope"); return; } if (evp.dsn_notify & DSN_SUCCESS) { bounce.type = B_DSN; bounce.dsn_ret = evp.dsn_ret; if (imsg->hdr.type == IMSG_MDA_DELIVERY_OK) queue_bounce(&evp, &bounce); else if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK && (mta_ext & MTA_EXT_DSN) == 0) { bounce.mta_without_dsn = 1; queue_bounce(&evp, &bounce); } } queue_envelope_delete(evpid); m_create(p_scheduler, IMSG_QUEUE_DELIVERY_OK, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_close(p_scheduler); return; case IMSG_MDA_DELIVERY_TEMPFAIL: case IMSG_MTA_DELIVERY_TEMPFAIL: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_get_string(&m, &reason); m_get_int(&m, &code); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) { log_warnx("queue: tempfail: failed to load envelope"); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_add_u32(p_scheduler, 1); /* in-flight */ m_close(p_scheduler); return; } envelope_set_errormsg(&evp, "%s", reason); envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); envelope_set_esc_code(&evp, code); evp.retry++; if (!queue_envelope_update(&evp)) log_warnx("warn: could not update envelope %016"PRIx64, evpid); m_create(p_scheduler, IMSG_QUEUE_DELIVERY_TEMPFAIL, 0, 0, -1); m_add_envelope(p_scheduler, &evp); m_close(p_scheduler); return; case IMSG_MDA_DELIVERY_PERMFAIL: case IMSG_MTA_DELIVERY_PERMFAIL: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_get_string(&m, &reason); m_get_int(&m, &code); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) { log_warnx("queue: permfail: failed to load envelope"); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_add_u32(p_scheduler, 1); /* in-flight */ m_close(p_scheduler); return; } bounce.type = B_ERROR; envelope_set_errormsg(&evp, "%s", reason); envelope_set_esc_class(&evp, ESC_STATUS_PERMFAIL); envelope_set_esc_code(&evp, code); queue_bounce(&evp, &bounce); queue_envelope_delete(evpid); m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_close(p_scheduler); return; case IMSG_MDA_DELIVERY_LOOP: case IMSG_MTA_DELIVERY_LOOP: m_msg(&m, imsg); m_get_evpid(&m, &evpid); m_end(&m); if (queue_envelope_load(evpid, &evp) == 0) { log_warnx("queue: loop: failed to load envelope"); m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); m_add_evpid(p_scheduler, evpid); m_add_u32(p_scheduler, 1); /* in-flight */ m_close(p_scheduler); return; } envelope_set_errormsg(&evp, "%s", "Loop detected"); envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); envelope_set_esc_code(&evp, ESC_ROUTING_LOOP_DETECTED); bounce.type = B_ERROR; queue_bounce(&evp, &bounce); queue_envelope_delete(evp.id); m_create(p_scheduler, IMSG_QUEUE_DELIVERY_LOOP, 0, 0, -1); m_add_evpid(p_scheduler, evp.id); m_close(p_scheduler); return; case IMSG_MTA_DELIVERY_HOLD: case IMSG_MDA_DELIVERY_HOLD: imsg->hdr.type = IMSG_QUEUE_HOLDQ_HOLD; m_forward(p_scheduler, imsg); return; case IMSG_MTA_SCHEDULE: imsg->hdr.type = IMSG_QUEUE_ENVELOPE_SCHEDULE; m_forward(p_scheduler, imsg); return; case IMSG_MTA_HOLDQ_RELEASE: case IMSG_MDA_HOLDQ_RELEASE: m_msg(&m, imsg); m_get_id(&m, &holdq); m_get_int(&m, &v); m_end(&m); m_create(p_scheduler, IMSG_QUEUE_HOLDQ_RELEASE, 0, 0, -1); if (imsg->hdr.type == IMSG_MTA_HOLDQ_RELEASE) m_add_int(p_scheduler, D_MTA); else m_add_int(p_scheduler, D_MDA); m_add_id(p_scheduler, holdq); m_add_int(p_scheduler, v); m_close(p_scheduler); return; } } if (p->proc == PROC_CONTROL) { switch (imsg->hdr.type) { case IMSG_CTL_PAUSE_MDA: case IMSG_CTL_PAUSE_MTA: case IMSG_CTL_RESUME_MDA: case IMSG_CTL_RESUME_MTA: m_forward(p_scheduler, imsg); return; } } if (p->proc == PROC_PARENT) { switch (imsg->hdr.type) { case IMSG_CTL_VERBOSE: m_msg(&m, imsg); m_get_int(&m, &v); m_end(&m); log_verbose(v); m_forward(p_scheduler, imsg); return; case IMSG_CTL_PROFILE: m_msg(&m, imsg); m_get_int(&m, &v); m_end(&m); profiling = v; return; } } errx(1, "queue_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); }
static void control_imsg(struct mproc *p, struct imsg *imsg) { struct ctl_conn *c; struct stat_value val; struct msg m; const char *key; const void *data; size_t sz; if (p->proc == PROC_PONY) { switch (imsg->hdr.type) { case IMSG_CTL_SMTP_SESSION: c = tree_get(&ctl_conns, imsg->hdr.peerid); if (c == NULL) return; m_compose(&c->mproc, IMSG_CTL_OK, 0, 0, imsg->fd, NULL, 0); return; } } if (p->proc == PROC_SCHEDULER) { switch (imsg->hdr.type) { case IMSG_CTL_OK: case IMSG_CTL_FAIL: case IMSG_CTL_LIST_MESSAGES: c = tree_get(&ctl_conns, imsg->hdr.peerid); if (c == NULL) return; imsg->hdr.peerid = 0; m_forward(&c->mproc, imsg); return; } } if (p->proc == PROC_QUEUE) { switch (imsg->hdr.type) { case IMSG_CTL_LIST_ENVELOPES: case IMSG_CTL_DISCOVER_EVPID: case IMSG_CTL_DISCOVER_MSGID: case IMSG_CTL_UNCORRUPT_MSGID: c = tree_get(&ctl_conns, imsg->hdr.peerid); if (c == NULL) return; m_forward(&c->mproc, imsg); return; } } if (p->proc == PROC_PONY) { switch (imsg->hdr.type) { case IMSG_CTL_OK: case IMSG_CTL_FAIL: case IMSG_CTL_MTA_SHOW_HOSTS: case IMSG_CTL_MTA_SHOW_RELAYS: case IMSG_CTL_MTA_SHOW_ROUTES: case IMSG_CTL_MTA_SHOW_HOSTSTATS: case IMSG_CTL_MTA_SHOW_BLOCK: c = tree_get(&ctl_conns, imsg->hdr.peerid); if (c == NULL) return; imsg->hdr.peerid = 0; m_forward(&c->mproc, imsg); return; } } switch (imsg->hdr.type) { case IMSG_STAT_INCREMENT: m_msg(&m, imsg); m_get_string(&m, &key); m_get_data(&m, &data, &sz); m_end(&m); memmove(&val, data, sz); if (stat_backend) stat_backend->increment(key, val.u.counter); control_digest_update(key, val.u.counter, 1); return; case IMSG_STAT_DECREMENT: m_msg(&m, imsg); m_get_string(&m, &key); m_get_data(&m, &data, &sz); m_end(&m); memmove(&val, data, sz); if (stat_backend) stat_backend->decrement(key, val.u.counter); control_digest_update(key, val.u.counter, 0); return; case IMSG_STAT_SET: m_msg(&m, imsg); m_get_string(&m, &key); m_get_data(&m, &data, &sz); m_end(&m); memmove(&val, data, sz); if (stat_backend) stat_backend->set(key, &val); return; } errx(1, "control_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); }
/* ARGSUSED */ static void control_dispatch_ext(struct mproc *p, struct imsg *imsg) { struct sockaddr_storage ss; struct ctl_conn *c; int v; struct stat_kv *kvp; char *key; struct stat_value val; size_t len; uint64_t evpid; uint32_t msgid; c = p->data; if (imsg == NULL) { control_close(c); return; } if (imsg->hdr.peerid != IMSG_VERSION) { m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0); return; } switch (imsg->hdr.type) { case IMSG_CTL_SMTP_SESSION: if (env->sc_flags & (SMTPD_SMTP_PAUSED | SMTPD_EXITING)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } m_compose(p_pony, IMSG_CTL_SMTP_SESSION, c->id, 0, -1, &c->euid, sizeof(c->euid)); return; case IMSG_CTL_GET_DIGEST: if (c->euid) goto badcred; digest.timestamp = time(NULL); m_compose(p, IMSG_CTL_GET_DIGEST, 0, 0, -1, &digest, sizeof digest); return; case IMSG_CTL_GET_STATS: if (c->euid) goto badcred; kvp = imsg->data; if (! stat_backend->iter(&kvp->iter, &key, &val)) kvp->iter = NULL; else { (void)strlcpy(kvp->key, key, sizeof kvp->key); kvp->val = val; } m_compose(p, IMSG_CTL_GET_STATS, 0, 0, -1, kvp, sizeof *kvp); return; case IMSG_CTL_SHUTDOWN: /* NEEDS_FIX */ log_debug("debug: received shutdown request"); if (c->euid) goto badcred; if (env->sc_flags & SMTPD_EXITING) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } env->sc_flags |= SMTPD_EXITING; m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); m_compose(p_parent, IMSG_CTL_SHUTDOWN, 0, 0, -1, NULL, 0); return; case IMSG_CTL_VERBOSE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); verbose = v; log_verbose(verbose); control_broadcast_verbose(IMSG_CTL_VERBOSE, verbose); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_TRACE_ENABLE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); verbose |= v; log_verbose(verbose); control_broadcast_verbose(IMSG_CTL_VERBOSE, verbose); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_TRACE_DISABLE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); verbose &= ~v; log_verbose(verbose); control_broadcast_verbose(IMSG_CTL_VERBOSE, verbose); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PROFILE_ENABLE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); profiling |= v; control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PROFILE_DISABLE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); profiling &= ~v; control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PAUSE_EVP: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_PAUSE_MDA: if (c->euid) goto badcred; if (env->sc_flags & SMTPD_MDA_PAUSED) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mda paused"); env->sc_flags |= SMTPD_MDA_PAUSED; m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PAUSE_MTA: if (c->euid) goto badcred; if (env->sc_flags & SMTPD_MTA_PAUSED) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mta paused"); env->sc_flags |= SMTPD_MTA_PAUSED; m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PAUSE_SMTP: if (c->euid) goto badcred; if (env->sc_flags & SMTPD_SMTP_PAUSED) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: smtp paused"); env->sc_flags |= SMTPD_SMTP_PAUSED; m_compose(p_pony, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_EVP: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_RESUME_MDA: if (c->euid) goto badcred; if (! (env->sc_flags & SMTPD_MDA_PAUSED)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mda resumed"); env->sc_flags &= ~SMTPD_MDA_PAUSED; m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_MTA: if (c->euid) goto badcred; if (!(env->sc_flags & SMTPD_MTA_PAUSED)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mta resumed"); env->sc_flags &= ~SMTPD_MTA_PAUSED; m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_SMTP: if (c->euid) goto badcred; if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: smtp resumed"); env->sc_flags &= ~SMTPD_SMTP_PAUSED; m_forward(p_pony, imsg); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_ROUTE: if (c->euid) goto badcred; m_forward(p_pony, imsg); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_LIST_MESSAGES: if (c->euid) goto badcred; m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1, imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); return; case IMSG_CTL_LIST_ENVELOPES: if (c->euid) goto badcred; m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1, imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); return; case IMSG_CTL_MTA_SHOW_HOSTS: case IMSG_CTL_MTA_SHOW_RELAYS: case IMSG_CTL_MTA_SHOW_ROUTES: case IMSG_CTL_MTA_SHOW_HOSTSTATS: case IMSG_CTL_MTA_SHOW_BLOCK: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_pony, imsg); return; case IMSG_CTL_SHOW_STATUS: if (c->euid) goto badcred; m_compose(p, IMSG_CTL_SHOW_STATUS, 0, 0, -1, &env->sc_flags, sizeof(env->sc_flags)); return; case IMSG_CTL_MTA_BLOCK: case IMSG_CTL_MTA_UNBLOCK: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE <= sizeof(ss)) goto invalid; memmove(&ss, imsg->data, sizeof(ss)); m_create(p_pony, imsg->hdr.type, c->id, 0, -1); m_add_sockaddr(p_pony, (struct sockaddr *)&ss); m_add_string(p_pony, (char *)imsg->data + sizeof(ss)); m_close(p_pony); return; case IMSG_CTL_SCHEDULE: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_REMOVE: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_UPDATE_TABLE: if (c->euid) goto badcred; /* table name too long */ len = strlen(imsg->data); if (len >= LINE_MAX) goto invalid; m_forward(p_lka, imsg); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_DISCOVER_EVPID: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof evpid) goto invalid; memmove(&evpid, imsg->data, sizeof evpid); m_create(p_queue, imsg->hdr.type, c->id, 0, -1); m_add_evpid(p_queue, evpid); m_close(p_queue); return; case IMSG_CTL_DISCOVER_MSGID: case IMSG_CTL_UNCORRUPT_MSGID: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof msgid) goto invalid; memmove(&msgid, imsg->data, sizeof msgid); m_create(p_queue, imsg->hdr.type, c->id, 0, -1); m_add_msgid(p_queue, msgid); m_close(p_queue); return; default: log_debug("debug: control_dispatch_ext: " "error handling %s imsg", imsg_to_str(imsg->hdr.type)); return; } badcred: invalid: m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); }
static int mta_verify_certificate(struct mta_session *s) { struct ca_vrfy_req_msg req_ca_vrfy; struct iovec iov[2]; X509 *x; STACK_OF(X509) *xchain; int i; const char *pkiname; x = SSL_get_peer_certificate(s->io.ssl); if (x == NULL) return 0; xchain = SSL_get_peer_cert_chain(s->io.ssl); /* * Client provided a certificate and possibly a certificate chain. * SMTP can't verify because it does not have the information that * it needs, instead it will pass the certificate and chain to the * lookup process and wait for a reply. * */ tree_xset(&wait_ssl_verify, s->id, s); s->flags |= MTA_WAIT; /* Send the client certificate */ memset(&req_ca_vrfy, 0, sizeof req_ca_vrfy); if (s->relay->pki_name) pkiname = s->relay->pki_name; else pkiname = s->helo; if (strlcpy(req_ca_vrfy.pkiname, pkiname, sizeof req_ca_vrfy.pkiname) >= sizeof req_ca_vrfy.pkiname) return 0; req_ca_vrfy.reqid = s->id; req_ca_vrfy.cert_len = i2d_X509(x, &req_ca_vrfy.cert); if (xchain) req_ca_vrfy.n_chain = sk_X509_num(xchain); iov[0].iov_base = &req_ca_vrfy; iov[0].iov_len = sizeof(req_ca_vrfy); iov[1].iov_base = req_ca_vrfy.cert; iov[1].iov_len = req_ca_vrfy.cert_len; m_composev(p_lka, IMSG_LKA_SSL_VERIFY_CERT, 0, 0, -1, iov, nitems(iov)); free(req_ca_vrfy.cert); X509_free(x); if (xchain) { /* Send the chain, one cert at a time */ for (i = 0; i < sk_X509_num(xchain); ++i) { memset(&req_ca_vrfy, 0, sizeof req_ca_vrfy); req_ca_vrfy.reqid = s->id; x = sk_X509_value(xchain, i); req_ca_vrfy.cert_len = i2d_X509(x, &req_ca_vrfy.cert); iov[0].iov_base = &req_ca_vrfy; iov[0].iov_len = sizeof(req_ca_vrfy); iov[1].iov_base = req_ca_vrfy.cert; iov[1].iov_len = req_ca_vrfy.cert_len; m_composev(p_lka, IMSG_LKA_SSL_VERIFY_CHAIN, 0, 0, -1, iov, nitems(iov)); free(req_ca_vrfy.cert); } } /* Tell lookup process that it can start verifying, we're done */ memset(&req_ca_vrfy, 0, sizeof req_ca_vrfy); req_ca_vrfy.reqid = s->id; m_compose(p_lka, IMSG_LKA_SSL_VERIFY, 0, 0, -1, &req_ca_vrfy, sizeof req_ca_vrfy); return 1; }
static void lka_imsg(struct mproc *p, struct imsg *imsg) { struct rule *rule; struct table *table; void *tmp; int ret; const char *key, *val; struct ssl *ssl; struct iovec iov[3]; static struct dict *ssl_dict; static struct dict *tables_dict; static struct table *table_last; static struct ca_vrfy_req_msg *req_ca_vrfy_smtp = NULL; static struct ca_vrfy_req_msg *req_ca_vrfy_mta = NULL; struct ca_vrfy_req_msg *req_ca_vrfy_chain; struct ca_vrfy_resp_msg resp_ca_vrfy; struct ca_cert_req_msg *req_ca_cert; struct ca_cert_resp_msg resp_ca_cert; struct sockaddr_storage ss; struct userinfo userinfo; struct addrname addrname; struct envelope evp; struct msg m; union lookup lk; char buf[SMTPD_MAXLINESIZE]; const char *tablename, *username, *password, *label; uint64_t reqid; size_t i; int v; if (imsg->hdr.type == IMSG_DNS_HOST || imsg->hdr.type == IMSG_DNS_PTR || imsg->hdr.type == IMSG_DNS_MX || imsg->hdr.type == IMSG_DNS_MX_PREFERENCE) { dns_imsg(p, imsg); return; } if (p->proc == PROC_SMTP) { switch (imsg->hdr.type) { case IMSG_LKA_EXPAND_RCPT: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_envelope(&m, &evp); m_end(&m); lka_session(reqid, &evp); return; case IMSG_LKA_SSL_INIT: req_ca_cert = imsg->data; resp_ca_cert.reqid = req_ca_cert->reqid; ssl = dict_get(env->sc_ssl_dict, req_ca_cert->name); if (ssl == NULL) { resp_ca_cert.status = CA_FAIL; m_compose(p, IMSG_LKA_SSL_INIT, 0, 0, -1, &resp_ca_cert, sizeof(resp_ca_cert)); return; } resp_ca_cert.status = CA_OK; resp_ca_cert.cert_len = ssl->ssl_cert_len; resp_ca_cert.key_len = ssl->ssl_key_len; iov[0].iov_base = &resp_ca_cert; iov[0].iov_len = sizeof(resp_ca_cert); iov[1].iov_base = ssl->ssl_cert; iov[1].iov_len = ssl->ssl_cert_len; iov[2].iov_base = ssl->ssl_key; iov[2].iov_len = ssl->ssl_key_len; m_composev(p, IMSG_LKA_SSL_INIT, 0, 0, -1, iov, nitems(iov)); return; case IMSG_LKA_SSL_VERIFY_CERT: req_ca_vrfy_smtp = xmemdup(imsg->data, sizeof *req_ca_vrfy_smtp, "lka:ca_vrfy"); if (req_ca_vrfy_smtp == NULL) fatal(NULL); req_ca_vrfy_smtp->cert = xmemdup((char *)imsg->data + sizeof *req_ca_vrfy_smtp, req_ca_vrfy_smtp->cert_len, "lka:ca_vrfy"); req_ca_vrfy_smtp->chain_cert = xcalloc(req_ca_vrfy_smtp->n_chain, sizeof (unsigned char *), "lka:ca_vrfy"); req_ca_vrfy_smtp->chain_cert_len = xcalloc(req_ca_vrfy_smtp->n_chain, sizeof (off_t), "lka:ca_vrfy"); return; case IMSG_LKA_SSL_VERIFY_CHAIN: if (req_ca_vrfy_smtp == NULL) fatalx("lka:ca_vrfy: chain without a certificate"); req_ca_vrfy_chain = imsg->data; req_ca_vrfy_smtp->chain_cert[req_ca_vrfy_smtp->chain_offset] = xmemdup((char *)imsg->data + sizeof *req_ca_vrfy_chain, req_ca_vrfy_chain->cert_len, "lka:ca_vrfy"); req_ca_vrfy_smtp->chain_cert_len[req_ca_vrfy_smtp->chain_offset] = req_ca_vrfy_chain->cert_len; req_ca_vrfy_smtp->chain_offset++; return; case IMSG_LKA_SSL_VERIFY: if (req_ca_vrfy_smtp == NULL) fatalx("lka:ca_vrfy: verify without a certificate"); resp_ca_vrfy.reqid = req_ca_vrfy_smtp->reqid; if (! lka_X509_verify(req_ca_vrfy_smtp, CA_FILE, NULL)) resp_ca_vrfy.status = CA_FAIL; else resp_ca_vrfy.status = CA_OK; m_compose(p, IMSG_LKA_SSL_VERIFY, 0, 0, -1, &resp_ca_vrfy, sizeof resp_ca_vrfy); for (i = 0; i < req_ca_vrfy_smtp->n_chain; ++i) free(req_ca_vrfy_smtp->chain_cert[i]); free(req_ca_vrfy_smtp->chain_cert); free(req_ca_vrfy_smtp->chain_cert_len); free(req_ca_vrfy_smtp->cert); free(req_ca_vrfy_smtp); return; case IMSG_LKA_AUTHENTICATE: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_string(&m, &tablename); m_get_string(&m, &username); m_get_string(&m, &password); m_end(&m); if (!tablename[0]) { m_create(p_parent, IMSG_LKA_AUTHENTICATE, 0, 0, -1); m_add_id(p_parent, reqid); m_add_string(p_parent, username); m_add_string(p_parent, password); m_close(p_parent); return; } ret = lka_authenticate(tablename, username, password); m_create(p, IMSG_LKA_AUTHENTICATE, 0, 0, -1); m_add_id(p, reqid); m_add_int(p, ret); m_close(p); return; } } if (p->proc == PROC_MDA) { switch (imsg->hdr.type) { case IMSG_LKA_USERINFO: m_msg(&m, imsg); m_get_string(&m, &tablename); m_get_string(&m, &username); m_end(&m); ret = lka_userinfo(tablename, username, &userinfo); m_create(p, IMSG_LKA_USERINFO, 0, 0, -1); m_add_string(p, tablename); m_add_string(p, username); m_add_int(p, ret); if (ret == LKA_OK) m_add_data(p, &userinfo, sizeof(userinfo)); m_close(p); return; } } if (p->proc == PROC_MTA) { switch (imsg->hdr.type) { case IMSG_LKA_SSL_INIT: req_ca_cert = imsg->data; resp_ca_cert.reqid = req_ca_cert->reqid; ssl = dict_get(env->sc_ssl_dict, req_ca_cert->name); if (ssl == NULL) { resp_ca_cert.status = CA_FAIL; m_compose(p, IMSG_LKA_SSL_INIT, 0, 0, -1, &resp_ca_cert, sizeof(resp_ca_cert)); return; } resp_ca_cert.status = CA_OK; resp_ca_cert.cert_len = ssl->ssl_cert_len; resp_ca_cert.key_len = ssl->ssl_key_len; iov[0].iov_base = &resp_ca_cert; iov[0].iov_len = sizeof(resp_ca_cert); iov[1].iov_base = ssl->ssl_cert; iov[1].iov_len = ssl->ssl_cert_len; iov[2].iov_base = ssl->ssl_key; iov[2].iov_len = ssl->ssl_key_len; m_composev(p, IMSG_LKA_SSL_INIT, 0, 0, -1, iov, nitems(iov)); return; case IMSG_LKA_SSL_VERIFY_CERT: req_ca_vrfy_mta = xmemdup(imsg->data, sizeof *req_ca_vrfy_mta, "lka:ca_vrfy"); if (req_ca_vrfy_mta == NULL) fatal(NULL); req_ca_vrfy_mta->cert = xmemdup((char *)imsg->data + sizeof *req_ca_vrfy_mta, req_ca_vrfy_mta->cert_len, "lka:ca_vrfy"); req_ca_vrfy_mta->chain_cert = xcalloc(req_ca_vrfy_mta->n_chain, sizeof (unsigned char *), "lka:ca_vrfy"); req_ca_vrfy_mta->chain_cert_len = xcalloc(req_ca_vrfy_mta->n_chain, sizeof (off_t), "lka:ca_vrfy"); return; case IMSG_LKA_SSL_VERIFY_CHAIN: if (req_ca_vrfy_mta == NULL) fatalx("lka:ca_vrfy: verify without a certificate"); req_ca_vrfy_chain = imsg->data; req_ca_vrfy_mta->chain_cert[req_ca_vrfy_mta->chain_offset] = xmemdup((char *)imsg->data + sizeof *req_ca_vrfy_chain, req_ca_vrfy_chain->cert_len, "lka:ca_vrfy"); req_ca_vrfy_mta->chain_cert_len[req_ca_vrfy_mta->chain_offset] = req_ca_vrfy_chain->cert_len; req_ca_vrfy_mta->chain_offset++; return; case IMSG_LKA_SSL_VERIFY: if (req_ca_vrfy_mta == NULL) fatalx("lka:ca_vrfy: verify without a certificate"); resp_ca_vrfy.reqid = req_ca_vrfy_mta->reqid; if (! lka_X509_verify(req_ca_vrfy_mta, CA_FILE, NULL)) resp_ca_vrfy.status = CA_FAIL; else resp_ca_vrfy.status = CA_OK; m_compose(p, IMSG_LKA_SSL_VERIFY, 0, 0, -1, &resp_ca_vrfy, sizeof resp_ca_vrfy); for (i = 0; i < req_ca_vrfy_mta->n_chain; ++i) free(req_ca_vrfy_mta->chain_cert[i]); free(req_ca_vrfy_mta->chain_cert); free(req_ca_vrfy_mta->chain_cert_len); free(req_ca_vrfy_mta->cert); free(req_ca_vrfy_mta); return; case IMSG_LKA_SECRET: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_string(&m, &tablename); m_get_string(&m, &label); m_end(&m); lka_credentials(tablename, label, buf, sizeof(buf)); m_create(p, IMSG_LKA_SECRET, 0, 0, -1); m_add_id(p, reqid); m_add_string(p, buf); m_close(p); return; case IMSG_LKA_SOURCE: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_string(&m, &tablename); table = table_find(tablename, NULL); m_create(p, IMSG_LKA_SOURCE, 0, 0, -1); m_add_id(p, reqid); if (table == NULL) { log_warn("warn: source address table %s missing", tablename); m_add_int(p, LKA_TEMPFAIL); } else { ret = table_fetch(table, K_SOURCE, &lk); if (ret == -1) m_add_int(p, LKA_TEMPFAIL); else if (ret == 0) m_add_int(p, LKA_PERMFAIL); else { m_add_int(p, LKA_OK); m_add_sockaddr(p, (struct sockaddr *)&lk.source.addr); } } m_close(p); return; case IMSG_LKA_HELO: m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_string(&m, &tablename); m_get_sockaddr(&m, (struct sockaddr *)&ss); m_end(&m); ret = lka_addrname(tablename, (struct sockaddr*)&ss, &addrname); m_create(p, IMSG_LKA_HELO, 0, 0, -1); m_add_id(p, reqid); m_add_int(p, ret); if (ret == LKA_OK) m_add_string(p, addrname.name); m_close(p); return; } } if (p->proc == PROC_PARENT) { switch (imsg->hdr.type) { case IMSG_CONF_START: env->sc_rules_reload = xcalloc(1, sizeof *env->sc_rules, "lka:sc_rules_reload"); tables_dict = xcalloc(1, sizeof *tables_dict, "lka:tables_dict"); ssl_dict = calloc(1, sizeof *ssl_dict); if (ssl_dict == NULL) fatal(NULL); dict_init(ssl_dict); dict_init(tables_dict); TAILQ_INIT(env->sc_rules_reload); return; case IMSG_CONF_SSL: ssl = calloc(1, sizeof *ssl); if (ssl == NULL) fatal(NULL); *ssl = *(struct ssl *)imsg->data; ssl->ssl_cert = xstrdup((char *)imsg->data + sizeof *ssl, "smtp:ssl_cert"); ssl->ssl_key = xstrdup((char *)imsg->data + sizeof *ssl + ssl->ssl_cert_len, "smtp:ssl_key"); if (ssl->ssl_dhparams_len) { ssl->ssl_dhparams = xstrdup((char *)imsg->data + sizeof *ssl + ssl->ssl_cert_len + ssl->ssl_key_len, "smtp:ssl_dhparams"); } if (ssl->ssl_ca_len) { ssl->ssl_ca = xstrdup((char *)imsg->data + sizeof *ssl + ssl->ssl_cert_len + ssl->ssl_key_len + ssl->ssl_dhparams_len, "smtp:ssl_ca"); } dict_set(ssl_dict, ssl->ssl_name, ssl); return; case IMSG_CONF_RULE: rule = xmemdup(imsg->data, sizeof *rule, "lka:rule"); TAILQ_INSERT_TAIL(env->sc_rules_reload, rule, r_entry); return; case IMSG_CONF_TABLE: table_last = table = xmemdup(imsg->data, sizeof *table, "lka:table"); dict_init(&table->t_dict); dict_set(tables_dict, table->t_name, table); return; case IMSG_CONF_RULE_SOURCE: rule = TAILQ_LAST(env->sc_rules_reload, rulelist); tmp = env->sc_tables_dict; env->sc_tables_dict = tables_dict; rule->r_sources = table_find(imsg->data, NULL); if (rule->r_sources == NULL) fatalx("lka: tables inconsistency"); env->sc_tables_dict = tmp; return; case IMSG_CONF_RULE_SENDER: rule = TAILQ_LAST(env->sc_rules_reload, rulelist); tmp = env->sc_tables_dict; env->sc_tables_dict = tables_dict; rule->r_senders = table_find(imsg->data, NULL); if (rule->r_senders == NULL) fatalx("lka: tables inconsistency"); env->sc_tables_dict = tmp; return; case IMSG_CONF_RULE_DESTINATION: rule = TAILQ_LAST(env->sc_rules_reload, rulelist); tmp = env->sc_tables_dict; env->sc_tables_dict = tables_dict; rule->r_destination = table_find(imsg->data, NULL); if (rule->r_destination == NULL) fatalx("lka: tables inconsistency"); env->sc_tables_dict = tmp; return; case IMSG_CONF_RULE_MAPPING: rule = TAILQ_LAST(env->sc_rules_reload, rulelist); tmp = env->sc_tables_dict; env->sc_tables_dict = tables_dict; rule->r_mapping = table_find(imsg->data, NULL); if (rule->r_mapping == NULL) fatalx("lka: tables inconsistency"); env->sc_tables_dict = tmp; return; case IMSG_CONF_RULE_USERS: rule = TAILQ_LAST(env->sc_rules_reload, rulelist); tmp = env->sc_tables_dict; env->sc_tables_dict = tables_dict; rule->r_userbase = table_find(imsg->data, NULL); if (rule->r_userbase == NULL) fatalx("lka: tables inconsistency"); env->sc_tables_dict = tmp; return; case IMSG_CONF_TABLE_CONTENT: table = table_last; if (table == NULL) fatalx("lka: tables inconsistency"); key = imsg->data; if (table->t_type == T_HASH) val = key + strlen(key) + 1; else val = NULL; dict_set(&table->t_dict, key, val ? xstrdup(val, "lka:dict_set") : NULL); return; case IMSG_CONF_END: if (env->sc_rules) purge_config(PURGE_RULES); if (env->sc_tables_dict) { table_close_all(); purge_config(PURGE_TABLES); } env->sc_rules = env->sc_rules_reload; env->sc_ssl_dict = ssl_dict; env->sc_tables_dict = tables_dict; if (verbose & TRACE_TABLES) table_dump_all(); table_open_all(); ssl_dict = NULL; table_last = NULL; tables_dict = NULL; /* Start fulfilling requests */ mproc_enable(p_mda); mproc_enable(p_mta); mproc_enable(p_smtp); return; case IMSG_CTL_VERBOSE: m_msg(&m, imsg); m_get_int(&m, &v); m_end(&m); log_verbose(v); return; case IMSG_CTL_PROFILE: m_msg(&m, imsg); m_get_int(&m, &v); m_end(&m); profiling = v; return; case IMSG_PARENT_FORWARD_OPEN: lka_session_forward_reply(imsg->data, imsg->fd); return; case IMSG_LKA_AUTHENTICATE: m_forward(p_smtp, imsg); return; } } if (p->proc == PROC_CONTROL) { switch (imsg->hdr.type) { case IMSG_LKA_UPDATE_TABLE: table = table_find(imsg->data, NULL); if (table == NULL) { log_warnx("warn: Lookup table not found: " "\"%s\"", (char *)imsg->data); return; } table_update(table); return; } } errx(1, "lka_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); }
static void lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) { struct forward_req fwreq; struct envelope ep; struct expandnode node; struct mailaddr maddr; int r; union lookup lk; if (xn->depth >= EXPAND_DEPTH) { log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep."); lks->error = LKA_PERMFAIL; return; } switch (xn->type) { case EXPAND_INVALID: case EXPAND_INCLUDE: fatalx("lka_expand: unexpected type"); break; case EXPAND_ADDRESS: log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s " "[depth=%d]", xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth); /* Pass the node through the ruleset */ ep = lks->envelope; ep.dest = xn->u.mailaddr; if (xn->parent) /* nodes with parent are forward addresses */ ep.flags |= EF_INTERNAL; rule = ruleset_match(&ep); if (rule == NULL || rule->r_decision == R_REJECT) { lks->error = (errno == EAGAIN) ? LKA_TEMPFAIL : LKA_PERMFAIL; break; } xn->mapping = rule->r_mapping; xn->userbase = rule->r_userbase; if (rule->r_action == A_RELAY || rule->r_action == A_RELAYVIA) { lka_submit(lks, rule, xn); } else if (rule->r_desttype == DEST_VDOM) { /* expand */ lks->expand.rule = rule; lks->expand.parent = xn; lks->expand.alias = 1; /* temporary replace the mailaddr with a copy where * we eventually strip the '+'-part before lookup. */ maddr = xn->u.mailaddr; mailaddr_to_username(&xn->u.mailaddr, maddr.user, sizeof maddr.user); r = aliases_virtual_get(&lks->expand, &maddr); if (r == -1) { lks->error = LKA_TEMPFAIL; log_trace(TRACE_EXPAND, "expand: lka_expand: " "error in virtual alias lookup"); } else if (r == 0) { lks->error = LKA_PERMFAIL; log_trace(TRACE_EXPAND, "expand: lka_expand: " "no aliases for virtual"); } } else { lks->expand.rule = rule; lks->expand.parent = xn; lks->expand.alias = 1; bzero(&node, sizeof node); node.type = EXPAND_USERNAME; mailaddr_to_username(&xn->u.mailaddr, node.u.user, sizeof node.u.user); node.mapping = rule->r_mapping; node.userbase = rule->r_userbase; expand_insert(&lks->expand, &node); } break; case EXPAND_USERNAME: log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s " "[depth=%d]", xn->u.user, xn->depth); if (xn->sameuser) { log_trace(TRACE_EXPAND, "expand: lka_expand: same " "user, submitting"); lka_submit(lks, rule, xn); break; } /* expand aliases with the given rule */ lks->expand.rule = rule; lks->expand.parent = xn; lks->expand.alias = 1; xn->mapping = rule->r_mapping; xn->userbase = rule->r_userbase; if (rule->r_mapping) { r = aliases_get(&lks->expand, xn->u.user); if (r == -1) { log_trace(TRACE_EXPAND, "expand: lka_expand: " "error in alias lookup"); lks->error = LKA_TEMPFAIL; } if (r) break; } /* A username should not exceed the size of a system user */ if (strlen(xn->u.user) >= sizeof fwreq.user) { log_trace(TRACE_EXPAND, "expand: lka_expand: " "user-part too long to be a system user"); lks->error = LKA_PERMFAIL; break; } r = table_lookup(rule->r_userbase, xn->u.user, K_USERINFO, &lk); if (r == -1) { log_trace(TRACE_EXPAND, "expand: lka_expand: " "backend error while searching user"); lks->error = LKA_TEMPFAIL; break; } if (r == 0) { log_trace(TRACE_EXPAND, "expand: lka_expand: " "user-part does not match system user"); lks->error = LKA_PERMFAIL; break; } /* no aliases found, query forward file */ lks->rule = rule; lks->node = xn; fwreq.id = lks->id; (void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user)); (void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory)); fwreq.uid = lk.userinfo.uid; fwreq.gid = lk.userinfo.gid; m_compose(p_parent, IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, &fwreq, sizeof(fwreq)); lks->flags |= F_WAITING; break; case EXPAND_FILENAME: log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s " "[depth=%d]", xn->u.buffer, xn->depth); lka_submit(lks, rule, xn); break; case EXPAND_ERROR: log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s " "[depth=%d]", xn->u.buffer, xn->depth); if (xn->u.buffer[0] == '4') lks->error = LKA_TEMPFAIL; else if (xn->u.buffer[0] == '5') lks->error = LKA_PERMFAIL; lks->errormsg = xn->u.buffer; break; case EXPAND_FILTER: log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s " "[depth=%d]", xn->u.buffer, xn->depth); lka_submit(lks, rule, xn); break; } }
/* ARGSUSED */ static void control_dispatch_ext(struct mproc *p, struct imsg *imsg) { struct ctl_conn *c; int v; struct stat_kv *kvp; char *key; struct stat_value val; size_t len; c = p->data; if (imsg == NULL) { control_close(c); return; } if (imsg->hdr.peerid != IMSG_VERSION) { m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0); return; } switch (imsg->hdr.type) { case IMSG_SMTP_ENQUEUE_FD: if (env->sc_flags & (SMTPD_SMTP_PAUSED | SMTPD_EXITING)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } m_compose(p_smtp, IMSG_SMTP_ENQUEUE_FD, c->id, 0, -1, &c->euid, sizeof(c->euid)); return; case IMSG_STATS: if (c->euid) goto badcred; m_compose(p, IMSG_STATS, 0, 0, -1, NULL, 0); return; case IMSG_DIGEST: if (c->euid) goto badcred; digest.timestamp = time(NULL); m_compose(p, IMSG_DIGEST, 0, 0, -1, &digest, sizeof digest); return; case IMSG_STATS_GET: if (c->euid) goto badcred; kvp = imsg->data; if (! stat_backend->iter(&kvp->iter, &key, &val)) kvp->iter = NULL; else { strlcpy(kvp->key, key, sizeof kvp->key); kvp->val = val; } m_compose(p, IMSG_STATS_GET, 0, 0, -1, kvp, sizeof *kvp); return; case IMSG_CTL_SHUTDOWN: /* NEEDS_FIX */ log_debug("debug: received shutdown request"); if (c->euid) goto badcred; if (env->sc_flags & SMTPD_EXITING) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } env->sc_flags |= SMTPD_EXITING; m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); m_compose(p_parent, IMSG_CTL_SHUTDOWN, 0, 0, -1, NULL, 0); return; case IMSG_CTL_VERBOSE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); verbose = v; log_verbose(verbose); m_create(p_parent, IMSG_CTL_VERBOSE, 0, 0, -1); m_add_int(p_parent, verbose); m_close(p_parent); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_TRACE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); verbose |= v; log_verbose(verbose); m_create(p_parent, IMSG_CTL_TRACE, 0, 0, -1); m_add_int(p_parent, v); m_close(p_parent); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_UNTRACE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); verbose &= ~v; log_verbose(verbose); m_create(p_parent, IMSG_CTL_UNTRACE, 0, 0, -1); m_add_int(p_parent, v); m_close(p_parent); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PROFILE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); profiling |= v; m_create(p_parent, IMSG_CTL_PROFILE, 0, 0, -1); m_add_int(p_parent, v); m_close(p_parent); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_UNPROFILE: if (c->euid) goto badcred; if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(verbose)) goto badcred; memcpy(&v, imsg->data, sizeof(v)); profiling &= ~v; m_create(p_parent, IMSG_CTL_UNPROFILE, 0, 0, -1); m_add_int(p_parent, v); m_close(p_parent); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PAUSE_EVP: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_PAUSE_MDA: if (c->euid) goto badcred; if (env->sc_flags & SMTPD_MDA_PAUSED) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mda paused"); env->sc_flags |= SMTPD_MDA_PAUSED; m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PAUSE_MTA: if (c->euid) goto badcred; if (env->sc_flags & SMTPD_MTA_PAUSED) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mta paused"); env->sc_flags |= SMTPD_MTA_PAUSED; m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_PAUSE_SMTP: if (c->euid) goto badcred; if (env->sc_flags & SMTPD_SMTP_PAUSED) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: smtp paused"); env->sc_flags |= SMTPD_SMTP_PAUSED; m_compose(p_smtp, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_EVP: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_RESUME_MDA: if (c->euid) goto badcred; if (! (env->sc_flags & SMTPD_MDA_PAUSED)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mda resumed"); env->sc_flags &= ~SMTPD_MDA_PAUSED; m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_MTA: if (c->euid) goto badcred; if (!(env->sc_flags & SMTPD_MTA_PAUSED)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: mta resumed"); env->sc_flags &= ~SMTPD_MTA_PAUSED; m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_SMTP: if (c->euid) goto badcred; if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) { m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); return; } log_info("info: smtp resumed"); env->sc_flags &= ~SMTPD_SMTP_PAUSED; m_forward(p_smtp, imsg); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_RESUME_ROUTE: if (c->euid) goto badcred; m_forward(p_mta, imsg); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; case IMSG_CTL_LIST_MESSAGES: if (c->euid) goto badcred; m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1, imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); return; case IMSG_CTL_LIST_ENVELOPES: if (c->euid) goto badcred; m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1, imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); return; case IMSG_CTL_MTA_SHOW_HOSTS: case IMSG_CTL_MTA_SHOW_RELAYS: case IMSG_CTL_MTA_SHOW_ROUTES: case IMSG_CTL_MTA_SHOW_HOSTSTATS: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_mta, imsg); return; case IMSG_CTL_SCHEDULE: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_CTL_REMOVE: if (c->euid) goto badcred; imsg->hdr.peerid = c->id; m_forward(p_scheduler, imsg); return; case IMSG_LKA_UPDATE_TABLE: if (c->euid) goto badcred; /* table name too long */ len = strlen(imsg->data); if (len >= SMTPD_MAXLINESIZE) goto invalid; m_forward(p_lka, imsg); m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); return; default: log_debug("debug: control_dispatch_ext: " "error handling %s imsg", imsg_to_str(imsg->hdr.type)); return; } badcred: invalid: m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); }
static int mta_verify_certificate(struct mta_session *s) { #define MAX_CERTS 16 #define MAX_CERT_LEN (MAX_IMSGSIZE - (IMSG_HEADER_SIZE + sizeof(req_ca_vrfy))) struct ca_vrfy_req_msg req_ca_vrfy; struct iovec iov[2]; X509 *x; STACK_OF(X509) *xchain; const char *name; unsigned char *cert_der[MAX_CERTS]; int cert_len[MAX_CERTS]; int i, cert_count, res; res = 0; memset(cert_der, 0, sizeof(cert_der)); memset(&req_ca_vrfy, 0, sizeof req_ca_vrfy); /* Send the client certificate */ if (s->relay->ca_name) { name = s->relay->ca_name; req_ca_vrfy.fallback = 0; } else { name = s->helo; req_ca_vrfy.fallback = 1; } if (strlcpy(req_ca_vrfy.name, name, sizeof req_ca_vrfy.name) >= sizeof req_ca_vrfy.name) return 0; x = SSL_get_peer_certificate(s->io.ssl); if (x == NULL) return 0; xchain = SSL_get_peer_cert_chain(s->io.ssl); /* * Client provided a certificate and possibly a certificate chain. * SMTP can't verify because it does not have the information that * it needs, instead it will pass the certificate and chain to the * lookup process and wait for a reply. * */ cert_len[0] = i2d_X509(x, &cert_der[0]); X509_free(x); if (cert_len[0] < 0) { log_warnx("warn: failed to encode certificate"); goto end; } log_debug("debug: certificate 0: len=%d", cert_len[0]); if (cert_len[0] > (int)MAX_CERT_LEN) { log_warnx("warn: certificate too long"); goto end; } if (xchain) { cert_count = sk_X509_num(xchain); log_debug("debug: certificate chain len: %d", cert_count); if (cert_count >= MAX_CERTS) { log_warnx("warn: certificate chain too long"); goto end; } } else cert_count = 0; for (i = 0; i < cert_count; ++i) { x = sk_X509_value(xchain, i); cert_len[i+1] = i2d_X509(x, &cert_der[i+1]); if (cert_len[i+1] < 0) { log_warnx("warn: failed to encode certificate"); goto end; } log_debug("debug: certificate %i: len=%d", i+1, cert_len[i+1]); if (cert_len[i+1] > (int)MAX_CERT_LEN) { log_warnx("warn: certificate too long"); goto end; } } tree_xset(&wait_ssl_verify, s->id, s); s->flags |= MTA_WAIT; /* Send the client certificate */ req_ca_vrfy.reqid = s->id; req_ca_vrfy.cert_len = cert_len[0]; req_ca_vrfy.n_chain = cert_count; iov[0].iov_base = &req_ca_vrfy; iov[0].iov_len = sizeof(req_ca_vrfy); iov[1].iov_base = cert_der[0]; iov[1].iov_len = cert_len[0]; m_composev(p_lka, IMSG_MTA_TLS_VERIFY_CERT, 0, 0, -1, iov, nitems(iov)); memset(&req_ca_vrfy, 0, sizeof req_ca_vrfy); req_ca_vrfy.reqid = s->id; /* Send the chain, one cert at a time */ for (i = 0; i < cert_count; ++i) { req_ca_vrfy.cert_len = cert_len[i+1]; iov[1].iov_base = cert_der[i+1]; iov[1].iov_len = cert_len[i+1]; m_composev(p_lka, IMSG_MTA_TLS_VERIFY_CHAIN, 0, 0, -1, iov, nitems(iov)); } /* Tell lookup process that it can start verifying, we're done */ memset(&req_ca_vrfy, 0, sizeof req_ca_vrfy); req_ca_vrfy.reqid = s->id; m_compose(p_lka, IMSG_MTA_TLS_VERIFY, 0, 0, -1, &req_ca_vrfy, sizeof req_ca_vrfy); res = 1; end: for (i = 0; i < MAX_CERTS; ++i) free(cert_der[i]); return res; }