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