static int global_tls_level(void) { static int l = TLS_LEV_NOTFOUND; if (l != TLS_LEV_NOTFOUND) return l; /* * Compute the global TLS policy. This is the default policy level when * no per-site policy exists. It also is used to override a wild-card * per-site policy. * * We require that the global level is valid on startup. */ if (*var_smtp_tls_level) { if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID) msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level); } else if (var_smtp_enforce_tls) l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; else l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE; if (msg_verbose) msg_info("%s TLS level: %s", "global", policy_name(l)); return l; }
static void pre_jail_init(char *unused_name, char **unused_argv) { TLS_SERVER_INIT_PROPS props; const char *cert_file; int have_server_cert; int no_server_cert_ok; int require_server_cert; /* * The code in this routine is pasted literally from smtpd(8). I am not * going to sanitize this because doing so surely will break things in * unexpected ways. */ if (*var_tlsp_tls_level) { switch (tls_level_lookup(var_tlsp_tls_level)) { default: msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level); /* NOTREACHED */ break; case TLS_LEV_SECURE: case TLS_LEV_VERIFY: case TLS_LEV_FPRINT: msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level); /* FALLTHROUGH */ case TLS_LEV_ENCRYPT: var_tlsp_enforce_tls = var_tlsp_use_tls = 1; break; case TLS_LEV_MAY: var_tlsp_enforce_tls = 0; var_tlsp_use_tls = 1; break; case TLS_LEV_NONE: var_tlsp_enforce_tls = var_tlsp_use_tls = 0; break; } } var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls; if (!var_tlsp_use_tls) { msg_warn("TLS service is requested, but disabled with %s or %s", VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS); return; } /* * Load TLS keys before dropping privileges. * * Can't use anonymous ciphers if we want client certificates. Must use * anonymous ciphers if we have no certificates. */ ask_client_cert = require_server_cert = (var_tlsp_tls_ask_ccert || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)); if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) { no_server_cert_ok = 1; cert_file = ""; } else { no_server_cert_ok = 0; cert_file = var_tlsp_tls_cert_file; } have_server_cert = (*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file); /* Some TLS configuration errors are not show stoppers. */ if (!have_server_cert && require_server_cert) msg_warn("Need a server cert to request client certs"); if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert) msg_warn("Can't require client certs unless TLS is required"); /* After a show-stopper error, log a warning. */ if (have_server_cert || (no_server_cert_ok && !require_server_cert)) /* * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. */ tlsp_server_ctx = TLS_SERVER_INIT(&props, log_param = VAR_TLSP_TLS_LOGLEVEL, log_level = var_tlsp_tls_loglevel, verifydepth = var_tlsp_tls_ccert_vd, cache_type = TLS_MGR_SCACHE_SMTPD, set_sessid = var_tlsp_tls_set_sessid, cert_file = cert_file, key_file = var_tlsp_tls_key_file, dcert_file = var_tlsp_tls_dcert_file, dkey_file = var_tlsp_tls_dkey_file, eccert_file = var_tlsp_tls_eccert_file, eckey_file = var_tlsp_tls_eckey_file, CAfile = var_tlsp_tls_CAfile, CApath = var_tlsp_tls_CApath, dh1024_param_file = var_tlsp_tls_dh1024_param_file, dh512_param_file = var_tlsp_tls_dh512_param_file, eecdh_grade = var_tlsp_tls_eecdh, protocols = var_tlsp_enforce_tls ? var_tlsp_tls_mand_proto : var_tlsp_tls_proto, ask_ccert = ask_client_cert, mdalg = var_tlsp_tls_fpt_dgst); else msg_warn("No server certs available. TLS can't be enabled"); /* * To maintain sanity, allow partial SSL_write() operations, and allow * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE * result. This is based on OpenSSL developers talking on a mailing list, * but is not supported by documentation. If this code stops working then * no-one can be held responsible. */ if (tlsp_server_ctx) SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); }
static void pre_init(char *unused_name, char **unused_argv) { int use_tls; static const NAME_CODE addr_pref_map[] = { INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6, INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4, INET_PROTO_NAME_ANY, 0, 0, -1, }; /* * Turn on per-peer debugging. */ debug_peer_init(); /* * SASL initialization. */ if (var_smtp_sasl_enable) #ifdef USE_SASL_AUTH smtp_sasl_initialize(); #else msg_warn("%s is true, but SASL support is not compiled in", VAR_SMTP_SASL_ENABLE); #endif if (*var_smtp_tls_level != 0) switch (tls_level_lookup(var_smtp_tls_level)) { case TLS_LEV_SECURE: case TLS_LEV_VERIFY: case TLS_LEV_FPRINT: case TLS_LEV_ENCRYPT: var_smtp_use_tls = var_smtp_enforce_tls = 1; break; case TLS_LEV_MAY: var_smtp_use_tls = 1; var_smtp_enforce_tls = 0; break; case TLS_LEV_NONE: var_smtp_use_tls = var_smtp_enforce_tls = 0; break; default: /* tls_level_lookup() logs no warning. */ /* session_tls_init() assumes that var_smtp_tls_level is sane. */ msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level); } use_tls = (var_smtp_use_tls || var_smtp_enforce_tls); /* * Initialize the TLS data before entering the chroot jail */ if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) { #ifdef USE_TLS TLS_CLIENT_INIT_PROPS props; int using_smtp = (strcmp(var_procname, "smtp") == 0); /* * We get stronger type safety and a cleaner interface by combining * the various parameters into a single tls_client_props structure. * * Large parameter lists are error-prone, so we emulate a language * feature that C does not have natively: named parameter lists. */ smtp_tls_ctx = TLS_CLIENT_INIT(&props, log_param = using_smtp ? VAR_SMTP_TLS_LOGLEVEL : VAR_LMTP_TLS_LOGLEVEL, log_level = var_smtp_tls_loglevel, verifydepth = var_smtp_tls_scert_vd, cache_type = using_smtp ? TLS_MGR_SCACHE_SMTP : TLS_MGR_SCACHE_LMTP, cert_file = var_smtp_tls_cert_file, key_file = var_smtp_tls_key_file, dcert_file = var_smtp_tls_dcert_file, dkey_file = var_smtp_tls_dkey_file, eccert_file = var_smtp_tls_eccert_file, eckey_file = var_smtp_tls_eckey_file, CAfile = var_smtp_tls_CAfile, CApath = var_smtp_tls_CApath, fpt_dgst = var_smtp_tls_fpt_dgst); smtp_tls_list_init(); #else msg_warn("TLS has been selected, but TLS support is not compiled in"); #endif } /* * Flush client. */ flush_init(); /* * Session cache domain list. */ if (*var_smtp_cache_dest) smtp_cache_dest = string_list_init(MATCH_FLAG_RETURN, var_smtp_cache_dest); /* * EHLO keyword filter. */ if (*var_smtp_ehlo_dis_maps) smtp_ehlo_dis_maps = maps_create(VAR_SMTP_EHLO_DIS_MAPS, var_smtp_ehlo_dis_maps, DICT_FLAG_LOCK); /* * PIX bug workarounds. */ if (*var_smtp_pix_bug_maps) smtp_pix_bug_maps = maps_create(VAR_SMTP_PIX_BUG_MAPS, var_smtp_pix_bug_maps, DICT_FLAG_LOCK); /* * Generic maps. */ if (*var_prop_extension) smtp_ext_prop_mask = ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); if (*var_smtp_generic_maps) smtp_generic_maps = maps_create(VAR_SMTP_GENERIC_MAPS, var_smtp_generic_maps, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); /* * Header/body checks. */ smtp_header_checks = hbc_header_checks_create( VAR_SMTP_HEAD_CHKS, var_smtp_head_chks, VAR_SMTP_MIME_CHKS, var_smtp_mime_chks, VAR_SMTP_NEST_CHKS, var_smtp_nest_chks, smtp_hbc_callbacks); smtp_body_checks = hbc_body_checks_create( VAR_SMTP_BODY_CHKS, var_smtp_body_chks, smtp_hbc_callbacks); /* * Server reply filter. */ if (*var_smtp_resp_filter) smtp_chat_resp_filter = dict_open(var_smtp_resp_filter, O_RDONLY, DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); /* * Address family preference. */ if (*var_smtp_addr_pref) { smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE, var_smtp_addr_pref); if (smtp_addr_pref < 0) msg_fatal("bad %s value: %s", VAR_SMTP_ADDR_PREF, var_smtp_addr_pref); } }
static void session_tls_init(SMTP_SESSION *session, const char *dest, const char *host, int flags) { const char *myname = "session_tls_init"; int global_level; int site_level; /* * Initialize all TLS related session properties. */ session->tls_context = 0; session->tls_nexthop = 0; session->tls_level = TLS_LEV_NONE; session->tls_retry_plain = 0; session->tls_protocols = 0; session->tls_grade = 0; session->tls_exclusions = 0; session->tls_matchargv = 0; /* * Compute the global TLS policy. This is the default policy level when * no per-site policy exists. It also is used to override a wild-card * per-site policy. */ if (*var_smtp_tls_level) { /* Require that var_smtp_tls_level is sanitized upon startup. */ global_level = tls_level_lookup(var_smtp_tls_level); if (global_level == TLS_LEV_INVALID) msg_panic("%s: invalid TLS security level: \"%s\"", myname, var_smtp_tls_level); } else if (var_smtp_enforce_tls) { global_level = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; } else { global_level = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE; } if (msg_verbose) msg_info("%s TLS level: %s", "global", policy_name(global_level)); /* * Compute the per-site TLS enforcement level. For compatibility with the * original TLS patch, this algorithm is gives equal precedence to host * and next-hop policies. */ site_level = TLS_LEV_NOTFOUND; if (tls_policy) { tls_policy_lookup(session, &site_level, dest, "next-hop destination"); } else if (tls_per_site) { tls_site_lookup(&site_level, dest, "next-hop destination"); if (strcasecmp(dest, host) != 0) tls_site_lookup(&site_level, host, "server hostname"); if (msg_verbose) msg_info("%s TLS level: %s", "site", policy_name(site_level)); /* * Override a wild-card per-site policy with a more specific global * policy. * * With the original TLS patch, 1) a per-site ENCRYPT could not override * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy * produced inconsistent results: it changed a global VERIFY into * NONE, while producing MAY with all weaker global policy settings. * * With the current implementation, a combined per-site (NONE+MAY) * consistently overrides global policy with NONE, and global policy * can override only a per-site MAY wildcard. That is, specific * policies consistently override wildcard policies, and * (non-wildcard) per-site policies consistently override global * policies. */ if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY) site_level = global_level; } if (site_level == TLS_LEV_NOTFOUND) session->tls_level = global_level; else session->tls_level = site_level; /* * Use main.cf protocols setting if not set in per-destination table. */ if (session->tls_level > TLS_LEV_NONE && session->tls_protocols == 0) session->tls_protocols = mystrdup((session->tls_level == TLS_LEV_MAY) ? var_smtp_tls_proto : var_smtp_tls_mand_proto); /* * Compute cipher grade (if set in per-destination table, else * set_cipher() uses main.cf settings) and security level dependent * cipher exclusion list. */ set_cipher_grade(session); /* * Use main.cf cert_match setting if not set in per-destination table. */ if (session->tls_matchargv == 0) { switch (session->tls_level) { case TLS_LEV_INVALID: case TLS_LEV_NONE: case TLS_LEV_MAY: case TLS_LEV_ENCRYPT: break; case TLS_LEV_FPRINT: session->tls_matchargv = argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |"); break; case TLS_LEV_VERIFY: session->tls_matchargv = argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :"); break; case TLS_LEV_SECURE: session->tls_matchargv = argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :"); break; default: msg_panic("unexpected TLS security level: %d", session->tls_level); } } if (msg_verbose && (tls_policy || tls_per_site)) msg_info("%s TLS level: %s", "effective", policy_name(session->tls_level)); }
static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0) if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_fatal("%s: %s lookup error for %s", session->state->request->queue_id, tls_policy->title, site_name); /* XXX session->stream has no longjmp context yet. */ } return (0); } if (cbuf == 0) cbuf = vstring_alloc(10); #define WHERE \ vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \ site_class, site_name)) saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); *site_level = TLS_LEV_INVALID; FREE_RETURN(1); /* No further lookups */ } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); FREE_RETURN(1); /* No further lookups */ } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN(1); } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { *site_level = TLS_LEV_INVALID; msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); break; } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (session->tls_protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_protocols = mystrdup(val); continue; } /* Multiple instance(s) per policy. */ if (!strcasecmp(name, "match")) { char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; if (*site_level <= TLS_LEV_ENCRYPT) { msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"", WHERE, name, policy_name(*site_level)); *site_level = TLS_LEV_INVALID; break; } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); *site_level = TLS_LEV_INVALID; break; } if (session->tls_matchargv == 0) session->tls_matchargv = argv_split(val, delim); else argv_split_append(session->tls_matchargv, val, delim); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (session->tls_exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); *site_level = TLS_LEV_INVALID; break; } session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } else { msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); *site_level = TLS_LEV_INVALID; break; } } FREE_RETURN(1); }
static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, const char *site_class) { const char *lookup; char *policy; char *saved_policy; char *tok; const char *err; char *name; char *val; static VSTRING *cbuf; #undef FREE_RETURN #define FREE_RETURN do { myfree(saved_policy); return; } while (0) #define INVALID_RETURN(why, levelp) do { \ MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) #define WHERE \ STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ tls_policy->title, site_class, site_name)) if (cbuf == 0) cbuf = vstring_alloc(10); if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_warn("%s: policy table lookup error", WHERE); MARK_INVALID(tls->why, site_level); } return; } saved_policy = policy = mystrdup(lookup); if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); INVALID_RETURN(tls->why, site_level); } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); INVALID_RETURN(tls->why, site_level); } /* * Warn about ignored attributes when TLS is disabled. */ if (*site_level < TLS_LEV_MAY) { while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", WHERE, tok); FREE_RETURN; } /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. */ while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { if ((err = split_nameval(tok, &name, &val)) != 0) { msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); INVALID_RETURN(tls->why, site_level); } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (tls->grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->grade = mystrdup(val); continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "protocols")) { if (tls->protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->protocols = mystrdup(val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "match")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } switch (*site_level) { default: msg_warn("%s: attribute \"%s\" invalid at security level " "\"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); break; case TLS_LEV_FPRINT: if (!tls->dane) tls->dane = tls_dane_alloc(); tls_dane_add_ee_digests(tls->dane, var_smtp_tls_fpt_dgst, val, "|"); break; case TLS_LEV_VERIFY: case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = argv_split(val, ":"); else argv_split_append(tls->matchargv, val, ":"); break; } continue; } /* Only one instance per policy. */ if (!strcasecmp(name, "exclude")) { if (tls->exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); INVALID_RETURN(tls->why, site_level); } tls->exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "tafile")) { /* Only makes sense if we're using CA-based trust */ if (!TLS_MUST_PKIX(*site_level)) { msg_warn("%s: attribute \"%s\" invalid at security level" " \"%s\"", WHERE, name, policy_name(*site_level)); INVALID_RETURN(tls->why, site_level); } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); INVALID_RETURN(tls->why, site_level); } if (!tls->dane) tls->dane = tls_dane_alloc(); if (!tls_dane_load_trustfile(tls->dane, val)) { INVALID_RETURN(tls->why, site_level); } continue; } msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); INVALID_RETURN(tls->why, site_level); } FREE_RETURN; }