diff -ur src/lmtp.tls.orig/Makefile.in src/lmtp.tls/Makefile.in --- src/lmtp.tls.orig/Makefile.in 2003-07-06 18:47:30.000000000 -0600 +++ src/lmtp.tls/Makefile.in 2003-07-06 18:46:55.000000000 -0600 @@ -13,7 +13,7 @@ DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) TESTPROG= -PROG = lmtp +PROG = lmtp.tls INC_DIR = ../../include LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a diff -ur src/postconf.tls.orig/Makefile.in src/postconf.tls/Makefile.in --- src/postconf.tls.orig/Makefile.in 2003-07-06 18:47:30.000000000 -0600 +++ src/postconf.tls/Makefile.in 2003-07-06 18:46:55.000000000 -0600 @@ -11,7 +11,7 @@ TESTPROG= MAKES = bool_table.h bool_vars.h int_table.h int_vars.h str_table.h \ str_vars.h time_table.h time_vars.h raw_table.h raw_vars.h -PROG = postconf +PROG = postconf.tls SAMPLES = ../../conf/main.cf.default INC_DIR = ../../include LIBS = ../../lib/libglobal.a ../../lib/libutil.a diff -ur src/smtp.tls.orig/Makefile.in src/smtp.tls/Makefile.in --- src/smtp.tls.orig/Makefile.in 2003-07-06 18:47:30.000000000 -0600 +++ src/smtp.tls/Makefile.in 2003-07-06 18:46:55.000000000 -0600 @@ -13,9 +13,9 @@ DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) TESTPROG= smtp_unalias -PROG = smtp +PROG = smtp.tls INC_DIR = ../../include -LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a +LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a ../../lib/pfixtls.o .c.o:; $(CC) $(CFLAGS) -c $*.c @@ -84,6 +84,7 @@ smtp.o: ../../include/iostuff.h smtp.o: ../../include/attr.h smtp.o: ../../include/mail_server.h +smtp.o: ../../include/pfixtls.h smtp.o: smtp.h smtp.o: smtp_sasl.h smtp_addr.o: smtp_addr.c @@ -103,6 +104,7 @@ smtp_addr.o: ../../include/argv.h smtp_addr.o: ../../include/deliver_request.h smtp_addr.o: ../../include/recipient_list.h +smtp_addr.o: ../../include/pfixtls.h smtp_addr.o: smtp_addr.h smtp_chat.o: smtp_chat.c smtp_chat.o: ../../include/sys_defs.h @@ -123,6 +125,7 @@ smtp_chat.o: ../../include/cleanup_user.h smtp_chat.o: ../../include/mail_error.h smtp_chat.o: ../../include/name_mask.h +smtp_chat.o: ../../include/pfixtls.h smtp_chat.o: smtp.h smtp_connect.o: smtp_connect.c smtp_connect.o: ../../include/sys_defs.h @@ -144,6 +147,7 @@ smtp_connect.o: ../../include/argv.h smtp_connect.o: ../../include/deliver_request.h smtp_connect.o: ../../include/recipient_list.h +smtp_connetc.o: ../../include/pfixtls.h smtp_connect.o: smtp_addr.h smtp_proto.o: smtp_proto.c smtp_proto.o: ../../include/sys_defs.h @@ -169,6 +173,7 @@ smtp_proto.o: ../../include/rec_type.h smtp_proto.o: ../../include/off_cvt.h smtp_proto.o: ../../include/mark_corrupt.h +smtp_proto.o: ../../include/pfixtls.h smtp_proto.o: ../../include/quote_821_local.h smtp_proto.o: ../../include/quote_flags.h smtp_proto.o: ../../include/mail_proto.h @@ -220,9 +225,12 @@ smtp_session.o: ../../include/stringops.h smtp_session.o: ../../include/vstring.h smtp_session.o: smtp.h +smtp_session.o: ../../include/mail_params.h +smtp_session.o: ../../include/pfixtls.h smtp_session.o: ../../include/argv.h smtp_session.o: ../../include/deliver_request.h smtp_session.o: ../../include/recipient_list.h +smtp_session.o: ../../include/maps.h smtp_state.o: smtp_state.c smtp_state.o: ../../include/sys_defs.h smtp_state.o: ../../include/mymalloc.h @@ -236,6 +244,7 @@ smtp_state.o: ../../include/argv.h smtp_state.o: ../../include/deliver_request.h smtp_state.o: ../../include/recipient_list.h +smtp_state.o: ../../include/pfixtls.h smtp_state.o: smtp_sasl.h smtp_trouble.o: smtp_trouble.c smtp_trouble.o: ../../include/sys_defs.h @@ -255,6 +264,7 @@ smtp_trouble.o: ../../include/name_mask.h smtp_trouble.o: smtp.h smtp_trouble.o: ../../include/argv.h +smtp_trouble.o: ../../include/pfixtls.h smtp_unalias.o: smtp_unalias.c smtp_unalias.o: ../../include/sys_defs.h smtp_unalias.o: ../../include/htable.h @@ -267,3 +277,4 @@ smtp_unalias.o: ../../include/argv.h smtp_unalias.o: ../../include/deliver_request.h smtp_unalias.o: ../../include/recipient_list.h +smtp_unalias.o: ../../include/pfixtls.h diff -ur src/smtp.tls.orig/smtp.c src/smtp.tls/smtp.c --- src/smtp.tls.orig/smtp.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtp.tls/smtp.c 2003-07-06 18:46:55.000000000 -0600 @@ -253,6 +253,7 @@ #include #include #include +#include /* Single server skeleton. */ @@ -267,8 +268,15 @@ * Tunable parameters. These have compiled-in defaults that can be overruled * by settings in the global Postfix configuration file. */ +int var_smtp_use_tls; +int var_smtp_enforce_tls; +int var_smtp_tls_enforce_peername; +char *var_smtp_tls_per_site; +int var_smtp_tls_scert_vd; +int var_smtp_tls_note_starttls_offer; int var_smtp_conn_tmout; int var_smtp_helo_tmout; +int var_smtp_starttls_tmout; int var_smtp_mail_tmout; int var_smtp_rcpt_tmout; int var_smtp_data0_tmout; @@ -442,6 +450,14 @@ msg_warn("%s is true, but SASL support is not compiled in", VAR_SMTP_SASL_ENABLE); #endif + /* + * Initialize the TLS data before entering the chroot jail + */ +#ifdef HAS_SSL + if (var_smtp_use_tls || var_smtp_enforce_tls || var_smtp_tls_per_site[0]) + pfixtls_init_clientengine(var_smtp_tls_scert_vd); + smtp_tls_list_init(); +#endif } /* pre_accept - see if tables have changed */ @@ -474,6 +490,7 @@ VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, VAR_FALLBACK_RELAY, DEF_FALLBACK_RELAY, &var_fallback_relay, 0, 0, VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, @@ -494,6 +511,7 @@ VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, 0, }; static CONFIG_INT_TABLE int_table[] = { @@ -501,6 +519,10 @@ 0, }; static CONFIG_BOOL_TABLE bool_table[] = { + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, VAR_SMTP_SKIP_4XX, DEF_SMTP_SKIP_4XX, &var_smtp_skip_4xx_greeting, VAR_SMTP_SKIP_5XX, DEF_SMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, VAR_IGN_MX_LOOKUP_ERR, DEF_IGN_MX_LOOKUP_ERR, &var_ign_mx_lookup_err, diff -ur src/smtp.tls.orig/smtp.h src/smtp.tls/smtp.h --- src/smtp.tls.orig/smtp.h 2003-07-06 18:47:30.000000000 -0600 +++ src/smtp.tls/smtp.h 2003-07-06 18:46:55.000000000 -0600 @@ -27,6 +27,7 @@ * Global library. */ #include +#include /* * State information associated with each SMTP delivery. We're bundling the @@ -83,9 +84,14 @@ char *addr; /* mail exchanger */ char *namaddr; /* mail exchanger */ int best; /* most preferred host */ + int tls_use_tls; /* can do TLS */ + int tls_enforce_tls; /* must do TLS */ + int tls_enforce_peername; /* cert must match */ + tls_info_t tls_info; /* TLS connection state */ } SMTP_SESSION; -extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, char *, char *); +extern void smtp_tls_list_init(void); +extern SMTP_SESSION *smtp_session_alloc(char *, VSTREAM *, char *, char *); extern void smtp_session_free(SMTP_SESSION *); /* diff -ur src/smtp.tls.orig/smtp_connect.c src/smtp.tls/smtp_connect.c --- src/smtp.tls.orig/smtp_connect.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtp.tls/smtp_connect.c 2003-07-06 18:46:55.000000000 -0600 @@ -117,6 +117,7 @@ #include #include +#include /* DNS library. */ @@ -263,7 +264,7 @@ vstream_fclose(stream); return (0); } - return (smtp_session_alloc(stream, addr->name, hbuf)); + return (smtp_session_alloc(dest, stream, addr->name, hbuf)); } /* smtp_connect_host - direct connection to host */ diff -ur src/smtp.tls.orig/smtp_proto.c src/smtp.tls/smtp_proto.c --- src/smtp.tls.orig/smtp_proto.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtp.tls/smtp_proto.c 2003-07-06 18:46:55.000000000 -0600 @@ -103,6 +103,7 @@ #include #include #include +#include /* Application-specific. */ @@ -170,6 +171,8 @@ char *words; char *word; int n; + int oldfeatures; + int rval; /* * Prepare for disaster. @@ -232,7 +235,8 @@ translit(resp->str, "\n", " "))); return (0); } - + if (var_smtp_always_ehlo) + state->features |= SMTP_FEATURE_ESMTP; /* * Pick up some useful features offered by the SMTP server. XXX Until we * have a portable routine to convert from string to off_t with proper @@ -244,6 +248,7 @@ * MicroSoft implemented AUTH based on an old draft. */ lines = resp->str; + oldfeatures = state->features; /* remember */ while ((words = mystrtok(&lines, "\n")) != 0) { if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) { if (strcasecmp(word, "8BITMIME") == 0) @@ -260,6 +265,8 @@ state->size_limit = off_cvt_string(word); } } + else if (strcasecmp(word, "STARTTLS") == 0) + state->features |= SMTP_FEATURE_STARTTLS; #ifdef USE_SASL_AUTH else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0) smtp_sasl_helo_auth(state, words); @@ -277,6 +284,128 @@ msg_info("server features: 0x%x size %.0f", state->features, (double) state->size_limit); +#ifdef HAS_SSL + if ((state->features & SMTP_FEATURE_STARTTLS) && + (var_smtp_tls_note_starttls_offer) && + (!(session->tls_enforce_tls || session->tls_use_tls))) + msg_info("Host offered STARTTLS: [%s]", session->host); + if ((session->tls_enforce_tls) && + !(state->features & SMTP_FEATURE_STARTTLS)) + { + /* + * We are enforced to use TLS but it is not offered, so we will give + * up on this host. We won't even try STARTTLS, because we could + * receive a "500 command unrecognized" which would bounce the + * message. We instead want to delay until STARTTLS becomes + * available. + */ + return (smtp_site_fail(state, 450, "Could not start TLS: not offered")); + } + if ((session->tls_enforce_tls) && !pfixtls_clientengine) { + /* + * We would like to start client TLS, but our own TLS-engine is + * not running. + */ + return (smtp_site_fail(state, 450, + "Could not start TLS: our TLS-engine not running")); + } + if ((state->features & SMTP_FEATURE_STARTTLS) && + ((session->tls_use_tls && pfixtls_clientengine) || + (session->tls_enforce_tls))) { + /* + * Try to use the TLS feature + */ + smtp_chat_cmd(state, "STARTTLS"); + if ((resp = smtp_chat_resp(state))->code / 100 != 2) { + state->features &= ~SMTP_FEATURE_STARTTLS; + /* + * At this point a political decision is necessary. If we + * enforce usage of tls, we have to close the connection + * now. + */ + if (session->tls_enforce_tls) + return (smtp_site_fail(state, resp->code, + "host %s refused to start TLS: %s", + session->host, + translit(resp->str, "\n", " "))); + } else { + if (rval = pfixtls_start_clienttls(session->stream, + var_smtp_starttls_tmout, + session->tls_enforce_peername, + session->host, + &(session->tls_info))) + return (smtp_site_fail(state, 450, + "Could not start TLS: client failure")); + + + /* + * Now the connection is established and maybe we do have a + * validated cert with a CommonName in it. + * In enforce_peername state, the handshake would already have + * been terminated so the check here is for logging only! + */ + if (session->tls_info.peer_CN != NULL) { + if (!session->tls_info.peer_verified) { + msg_info("Peer certificate could not be verified"); + if (session->tls_enforce_tls) { + pfixtls_stop_clienttls(session->stream, + var_smtp_starttls_tmout, 1, + &(session->tls_info)); + return(smtp_site_fail(state, 450, "TLS-failure: Could not verify certificate")); + } + } + } else if (session->tls_enforce_tls) { + pfixtls_stop_clienttls(session->stream, + var_smtp_starttls_tmout, 1, + &(session->tls_info)); + return (smtp_site_fail(state, 450, "TLS-failure: Cannot verify hostname")); + } + + /* + * At this point we have to re-negotiate the "EHLO" to reget + * the feature-list + */ + state->features = oldfeatures; +#ifdef USE_SASL_AUTH + if (state->sasl_mechanism_list) { + myfree(state->sasl_mechanism_list); + state->sasl_mechanism_list = 0; + } +#endif + if (state->features & SMTP_FEATURE_ESMTP) { + smtp_chat_cmd(state, "EHLO %s", var_myhostname); + if ((resp = smtp_chat_resp(state))->code / 100 != 2) + state->features &= ~SMTP_FEATURE_ESMTP; + } + lines = resp->str; + (void) mystrtok(&lines, "\n"); + while ((words = mystrtok(&lines, "\n")) != 0) { + if (mystrtok(&words, "- ") && + (word = mystrtok(&words, " \t=")) != 0) { + if (strcasecmp(word, "8BITMIME") == 0) + state->features |= SMTP_FEATURE_8BITMIME; + else if (strcasecmp(word, "PIPELINING") == 0) + state->features |= SMTP_FEATURE_PIPELINING; + else if (strcasecmp(word, "SIZE") == 0) + state->features |= SMTP_FEATURE_SIZE; + else if (strcasecmp(word, "STARTTLS") == 0) + state->features |= SMTP_FEATURE_STARTTLS; +#ifdef USE_SASL_AUTH + else if (var_smtp_sasl_enable && + strcasecmp(word, "AUTH") == 0) + smtp_sasl_helo_auth(state, words); +#endif + } + } + /* + * Actually, at this point STARTTLS should not be offered + * anymore, so we could check for a protocol violation, but + * what should we do then? + */ + + } + } +#endif #ifdef USE_SASL_AUTH if (var_smtp_sasl_enable && (state->features & SMTP_FEATURE_AUTH)) return (smtp_sasl_helo_login(state)); diff -ur src/smtp.tls.orig/smtp_session.c src/smtp.tls/smtp_session.c --- src/smtp.tls.orig/smtp_session.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtp.tls/smtp_session.c 2003-07-06 18:46:55.000000000 -0600 @@ -42,15 +42,42 @@ #include #include +#include +#include +#include + /* Application-specific. */ #include "smtp.h" +#ifdef HAS_SSL +/* static lists */ +static MAPS *tls_per_site; + +/* smtp_tls_list_init - initialize lists */ + +void smtp_tls_list_init(void) +{ + tls_per_site = maps_create(VAR_SMTP_TLS_PER_SITE, var_smtp_tls_per_site, + DICT_FLAG_LOCK); +} +#endif + /* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */ -SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, char *host, char *addr) +SMTP_SESSION *smtp_session_alloc(char *dest, VSTREAM *stream, char *host, char *addr) { SMTP_SESSION *session; + const char *lookup; + char *lookup_key; + int host_dont_use = 0; + int host_use = 0; + int host_enforce = 0; + int host_enforce_peername = 0; + int recipient_dont_use = 0; + int recipient_use = 0; + int recipient_enforce = 0; + int recipient_enforce_peername = 0; session = (SMTP_SESSION *) mymalloc(sizeof(*session)); session->stream = stream; @@ -58,6 +85,61 @@ session->addr = mystrdup(addr); session->namaddr = concatenate(host, "[", addr, "]", (char *) 0); session->best = 1; + session->tls_use_tls = session->tls_enforce_tls = 0; + session->tls_enforce_peername = 0; +#ifdef HAS_SSL + lookup_key = lowercase(mystrdup(host)); + if (lookup = maps_find(tls_per_site, lookup_key, 0)) { + if (!strcasecmp(lookup, "NONE")) + host_dont_use = 1; + else if (!strcasecmp(lookup, "MAY")) + host_use = 1; + else if (!strcasecmp(lookup, "MUST")) + host_enforce = host_enforce_peername = 1; + else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) + host_enforce = 1; + else + msg_warn("Unknown TLS state for receiving host %s: '%s', using default policy", session->host, lookup); + } + myfree(lookup_key); + lookup_key = lowercase(mystrdup(dest)); + if (lookup = maps_find(tls_per_site, dest, 0)) { + if (!strcasecmp(lookup, "NONE")) + recipient_dont_use = 1; + else if (!strcasecmp(lookup, "MAY")) + recipient_use = 1; + else if (!strcasecmp(lookup, "MUST")) + recipient_enforce = recipient_enforce_peername = 1; + else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) + recipient_enforce = 1; + else + msg_warn("Unknown TLS state for recipient domain %s: '%s', using default policy", dest, lookup); + } + myfree(lookup_key); + + if ((var_smtp_enforce_tls && !host_dont_use && !recipient_dont_use) || host_enforce || + recipient_enforce) + session->tls_enforce_tls = session->tls_use_tls = 1; + + /* + * Set up peername checking. We want to make sure that a MUST* entry in + * the tls_per_site table always has precedence. MUST always must lead to + * a peername check, MUST_NOPEERMATCH must always disable it. Only when + * no explicit setting has been found, the default will be used. + * There is the case left, that both "host" and "recipient" settings + * conflict. In this case, the "host" setting wins. + */ + if (host_enforce && host_enforce_peername) + session->tls_enforce_peername = 1; + else if (recipient_enforce && recipient_enforce_peername) + session->tls_enforce_peername = 1; + else if (var_smtp_enforce_tls && var_smtp_tls_enforce_peername) + session->tls_enforce_peername = 1; + + else if ((var_smtp_use_tls && !host_dont_use && !recipient_dont_use) || host_use || recipient_use) + session->tls_use_tls = 1; +#endif + session->tls_info = tls_info_zero; return (session); } @@ -65,6 +147,11 @@ void smtp_session_free(SMTP_SESSION *session) { +#ifdef HAS_SSL + vstream_fflush(session->stream); + pfixtls_stop_clienttls(session->stream, var_smtp_starttls_tmout, 0, + &(session->tls_info)); +#endif vstream_fclose(session->stream); myfree(session->host); myfree(session->addr); diff -ur src/smtpd.tls.orig/Makefile.in src/smtpd.tls/Makefile.in --- src/smtpd.tls.orig/Makefile.in 2003-07-06 18:47:30.000000000 -0600 +++ src/smtpd.tls/Makefile.in 2003-07-06 18:46:55.000000000 -0600 @@ -12,9 +12,9 @@ DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) TESTPROG= smtpd_token smtpd_check -PROG = smtpd +PROG = smtpd.tls INC_DIR = ../../include -LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a +LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a ../../lib/pfixtls.o .c.o:; $(CC) $(CFLAGS) -c $*.c @@ -149,6 +149,7 @@ smtpd.o: ../../include/namadr_list.h smtpd.o: ../../include/input_transp.h smtpd.o: ../../include/mail_server.h +smtpd.o: ../../include/pfixtls.h smtpd.o: smtpd_token.h smtpd.o: smtpd.h smtpd.o: smtpd_check.h @@ -178,6 +179,7 @@ smtpd_chat.o: ../../include/cleanup_user.h smtpd_chat.o: ../../include/mail_error.h smtpd_chat.o: ../../include/name_mask.h +smtpd_chat.o: ../../include/pfixtls.h smtpd_chat.o: smtpd.h smtpd_chat.o: ../../include/mail_stream.h smtpd_chat.o: smtpd_chat.h @@ -230,6 +232,7 @@ smtpd_check.o: ../../include/input_transp.h smtpd_check.o: smtpd.h smtpd_check.o: ../../include/mail_stream.h +smtpd_check.o: ../../include/pfixtls.h smtpd_check.o: smtpd_sasl_glue.h smtpd_check.o: smtpd_check.h smtpd_peer.o: smtpd_peer.c @@ -241,6 +244,7 @@ smtpd_peer.o: ../../include/vstring.h smtpd_peer.o: ../../include/vbuf.h smtpd_peer.o: smtpd.h +smtpd_peer.o: ../../include/pfixtls.h smtpd_peer.o: ../../include/vstream.h smtpd_peer.o: ../../include/argv.h smtpd_peer.o: ../../include/mail_stream.h @@ -319,6 +323,7 @@ smtpd_state.o: ../../include/vstring.h smtpd_state.o: ../../include/argv.h smtpd_state.o: ../../include/mail_stream.h +smtpd_state.o: ../../include/pfixtls.h smtpd_state.o: smtpd_chat.h smtpd_state.o: smtpd_sasl_glue.h smtpd_token.o: smtpd_token.c @@ -328,3 +333,4 @@ smtpd_token.o: smtpd_token.h smtpd_token.o: ../../include/vstring.h smtpd_token.o: ../../include/vbuf.h +smtpd_token.o: ../../include/pfixtls.h diff -ur src/smtpd.tls.orig/smtpd.c src/smtpd.tls/smtpd.c --- src/smtpd.tls.orig/smtpd.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtpd.tls/smtpd.c 2003-07-06 18:47:58.000000000 -0600 @@ -410,6 +410,7 @@ #include #include #include +#include #include #include #include @@ -437,8 +438,18 @@ * mail system stays in control even when a malicious client sends an * unreasonable number of recipients (qmail-dos-2). */ +int var_smtpd_starttls_tmout; +int var_smtpd_tls_wrappermode; +int var_smtpd_use_tls; +int var_smtpd_enforce_tls; +int var_smtpd_tls_auth_only; +int var_smtpd_tls_ask_ccert; +int var_smtpd_tls_req_ccert; +int var_smtpd_tls_ccert_vd; +int var_smtpd_tls_received_header; int var_smtpd_rcpt_limit; int var_smtpd_tmout; +char *var_relay_ccerts; int var_smtpd_soft_erlim; int var_smtpd_hard_erlim; int var_queue_minfree; /* XXX use off_t */ @@ -619,11 +630,21 @@ if (var_disable_vrfy_cmd == 0) smtpd_chat_reply(state, "250-VRFY"); smtpd_chat_reply(state, "250-ETRN"); +#ifdef HAS_SSL + if ((state->tls_use_tls || state->tls_enforce_tls) && (!state->tls_active)) + smtpd_chat_reply(state, "250-STARTTLS"); +#endif #ifdef USE_SASL_AUTH if (var_smtpd_sasl_enable) { +#ifdef HAS_SSL + if (!state->tls_auth_only || state->tls_active) { +#endif smtpd_chat_reply(state, "250-AUTH %s", state->sasl_mechanism_list); if (var_broken_auth_clients) smtpd_chat_reply(state, "250-AUTH=%s", state->sasl_mechanism_list); +#ifdef HAS_SSL + } +#endif } #endif if (namadr_list_match(verp_clients, state->name, state->addr)) @@ -1106,12 +1127,77 @@ state->rcpt_count = 0; } +/* CN_sanitize - make sure, the CN-string is well behaved */ + +static void CN_sanitize(char *CNstring) +{ + int i; + int len; + int parencount; + + /* + * The information included in the CN (CommonName) of the peer and its + * issuer can be included into the Received: header line. The characters + * allowed as well as comment nesting are limited by RFC822. + */ + + len = strlen(CNstring); + /* + * The Received: header can only contain characters. Make sure that only + * acceptable characters are printed. Maybe we could allow more, but + * not everything makes sense inside a CommonName. + */ + for (i = 0; i < len; i++) + if (!((CNstring[i] >= 'A') && (CNstring[i] <='Z')) && + !((CNstring[i] >= 'a') && (CNstring[i] <='z')) && + !((CNstring[i] >= '0') && (CNstring[i] <='9')) && + (CNstring[i] != '(') && (CNstring[i] != ')') && + (CNstring[i] != '[') && (CNstring[i] != ']') && + (CNstring[i] != '{') && (CNstring[i] != '}') && + (CNstring[i] != '<') && (CNstring[i] != '>') && + (CNstring[i] != '?') && (CNstring[i] != '!') && + (CNstring[i] != ';') && (CNstring[i] != ':') && + (CNstring[i] != '"') && (CNstring[i] != '\'') && + (CNstring[i] != '/') && (CNstring[i] != '|') && + (CNstring[i] != '+') && (CNstring[i] != '&') && + (CNstring[i] != '~') && (CNstring[i] != '@') && + (CNstring[i] != '#') && (CNstring[i] != '$') && + (CNstring[i] != '%') && (CNstring[i] != '&') && + (CNstring[i] != '^') && (CNstring[i] != '*') && + (CNstring[i] != '_') && (CNstring[i] != '-') && + (CNstring[i] != '.') && (CNstring[i] != ' ')) + CNstring[i] = '?'; + + /* + * This information will go into the Received: header inside a comment. + * Since comments can be nested, parentheses '(' and ')' must match. + */ + parencount = 0; + for (i = 0; i < len; i++) { + if (CNstring[i] == '(') + parencount++; + else if (CNstring[i] == ')') + parencount--; + } + /* + * The necessary condition is violated. Do YOU know, where to correct? + * I don't know, so I will practically remove all parentheses. + */ + if (parencount != 0) { + for (i = 0; i < len; i++) + if ((CNstring[i] == '(') || (CNstring[i] == ')')) + CNstring[i] = '/'; + } +} + /* data_cmd - process DATA command */ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) { char *err; char *start; + char *peer_CN; + char *issuer_CN; int len; int curr_rec_type; int prev_rec_type; @@ -1180,6 +1266,35 @@ "Received: from %s (%s [%s])", state->helo_name ? state->helo_name : state->name, state->name, state->addr); + if (var_smtpd_tls_received_header && state->tls_active) { + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(using %s with cipher %s (%d/%d bits))", + state->tls_info.protocol, state->tls_info.cipher_name, + state->tls_info.cipher_usebits, + state->tls_info.cipher_algbits); + if (state->tls_info.peer_CN) { + peer_CN = mystrdup(state->tls_info.peer_CN); + CN_sanitize(peer_CN); + issuer_CN = mystrdup(state->tls_info.issuer_CN); + CN_sanitize(issuer_CN); + if (state->tls_info.peer_verified) + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(Client CN \"%s\", Issuer \"%s\" (verified OK))", + peer_CN, issuer_CN); + else + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(Client CN \"%s\", Issuer \"%s\" (not verified))", + peer_CN, issuer_CN); + myfree(issuer_CN); + myfree(peer_CN); + } + else if (var_smtpd_tls_ask_ccert) + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(Client did not present a certificate)"); + else + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(No client certificate requested)"); + } if (state->rcpt_count == 1 && state->recipient) { out_fprintf(out_stream, REC_TYPE_NORM, state->cleanup ? "\tby %s (%s) with %s id %s" : @@ -1549,6 +1664,77 @@ } } +static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + char *err; + +#ifdef HAS_SSL + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Syntax: STARTTLS"); + return (-1); + } + if (state->tls_active != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "554 Error: TLS already active"); + return (-1); + } + if (state->tls_use_tls == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 Error: command not implemented"); + return (-1); + } + if (!pfixtls_serverengine) { + smtpd_chat_reply(state, "454 TLS not available due to temporary reason"); + return (0); + } + smtpd_chat_reply(state, "220 Ready to start TLS"); + vstream_fflush(state->client); + /* + * When deciding about continuing the handshake, we will stop when a + * client certificate was _required_ and none was presented or the + * verification failed. This however does only make sense when TLS is + * enforced. Otherwise we would happily perform perform the SMTP + * transaction without any STARTTLS at all! So only have the handshake + * fail when TLS is also enforced. + */ + if (pfixtls_start_servertls(state->client, var_smtpd_starttls_tmout, + state->name, state->addr, &(state->tls_info), + (var_smtpd_tls_req_ccert && state->tls_enforce_tls))) { + /* + * Typically the connection is hanging at this point, so + * we should try to shut it down by force! Unfortunately this + * problem is not addressed in postfix! + */ + return (-1); + } + state->tls_active = 1; + helo_reset(state); + mail_reset(state); + rcpt_reset(state); + return (0); +#else + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 Error: command not implemented"); + return (-1); +#endif +} + +static void tls_reset(SMTPD_STATE *state) +{ + int failure = 0; + + if (state->reason && state->where && strcmp(state->where, SMTPD_AFTER_DOT)) + failure = 1; +#ifdef HAS_SSL + vstream_fflush(state->client); + if (state->tls_active) + pfixtls_stop_servertls(state->client, var_smtpd_starttls_tmout, + failure, &(state->tls_info)); +#endif + state->tls_active = 0; +} + /* * The table of all SMTP commands that we know. Set the junk limit flag on * any command that can be repeated an arbitrary number of times without @@ -1567,6 +1753,10 @@ "HELO", helo_cmd, SMTPD_CMD_FLAG_LIMIT, "EHLO", ehlo_cmd, SMTPD_CMD_FLAG_LIMIT, +#ifdef HAS_SSL + "STARTTLS", starttls_cmd, 0, +#endif + #ifdef USE_SASL_AUTH "AUTH", smtpd_sasl_auth_cmd, 0, #endif @@ -1687,9 +1877,28 @@ state->error_count++; continue; } + if (state->tls_enforce_tls && + !state->tls_active && + cmdp->action != starttls_cmd && + cmdp->action != noop_cmd && + cmdp->action != ehlo_cmd && + cmdp->action != quit_cmd) { + smtpd_chat_reply(state, + "530 Must issue a STARTTLS command first"); + state->error_count++; + continue; + } state->where = cmdp->name; - if (cmdp->action(state, argc, argv) != 0) + if (cmdp->action(state, argc, argv) != 0) { state->error_count++; + /* + * Die after TLS negotiation failure, as there is no + * stable way to recover from a possible mixture of + * TLS and SMTP protocol from the client. + */ + if (cmdp->action == starttls_cmd) + break; + } if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT) && state->junk_cmds++ > var_smtpd_junk_cmd_limit) state->error_count++; @@ -1715,6 +1924,7 @@ * Cleanup whatever information the client gave us during the SMTP * dialog. */ + tls_reset(state); helo_reset(state); #ifdef USE_SASL_AUTH if (var_smtpd_sasl_enable) @@ -1749,6 +1959,43 @@ smtpd_state_init(&state, stream); msg_info("connect from %s[%s]", state.name, state.addr); +#ifdef HAS_SSL + if (SMTPD_STAND_ALONE((&state))) { + state.tls_use_tls = 0; + state.tls_enforce_tls = 0; + state.tls_auth_only = 0; + } + else { + state.tls_use_tls = var_smtpd_use_tls | var_smtpd_enforce_tls; + state.tls_enforce_tls = var_smtpd_enforce_tls; + if (var_smtpd_tls_wrappermode) { + /* + * TLS has been set to wrapper mode, meaning that we run on a + * seperate port and we must switch to TLS layer before actually + * performing the SMTP protocol. This implies enforce-mode. + */ + state.tls_use_tls = state.tls_enforce_tls = 1; + if (pfixtls_start_servertls(state.client, var_smtpd_starttls_tmout, + state.name, state.addr, &state.tls_info, + var_smtpd_tls_req_ccert)) { + /* + * Typically the connection is hanging at this point, so + * we should try to shut it down by force! Unfortunately this + * problem is not addressed in postfix! + */ + return; + } + state.tls_active = 1; + } + if (var_smtpd_tls_auth_only || state.tls_enforce_tls) + state.tls_auth_only = 1; + } +#else + state.tls_use_tls = 0; + state.tls_enforce_tls = 0; + state.tls_auth_only = 0; +#endif + /* * XXX non_blocking() aborts upon error. */ @@ -1791,7 +2038,6 @@ static void pre_jail_init(char *unused_name, char **unused_argv) { - /* * Initialize blacklist/etc. patterns before entering the chroot jail, in * case they specify a filename pattern. @@ -1809,6 +2055,12 @@ msg_warn("%s is true, but SASL support is not compiled in", VAR_SMTPD_SASL_ENABLE); #endif + +#ifdef HAS_SSL + if (var_smtpd_use_tls || var_smtpd_enforce_tls || var_smtpd_tls_wrappermode) + pfixtls_init_serverengine(var_smtpd_tls_ccert_vd, + var_smtpd_tls_ask_ccert); +#endif } /* post_jail_init - post-jail initialization */ @@ -1844,6 +2096,7 @@ VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code, 0, 0, VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0, VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0, + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, VAR_SMTPD_HIST_THRSH, DEF_SMTPD_HIST_THRSH, &var_smtpd_hist_thrsh, 1, 0, VAR_UNV_FROM_CODE, DEF_UNV_FROM_CODE, &var_unv_from_code, 0, 0, VAR_UNV_RCPT_CODE, DEF_UNV_RCPT_CODE, &var_unv_rcpt_code, 0, 0, @@ -1869,6 +2122,13 @@ VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd, VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route, VAR_SMTPD_SASL_ENABLE, DEF_SMTPD_SASL_ENABLE, &var_smtpd_sasl_enable, + VAR_SMTPD_TLS_WRAPPER, DEF_SMTPD_TLS_WRAPPER, &var_smtpd_tls_wrappermode, + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_AUTH_ONLY, DEF_SMTPD_TLS_AUTH_ONLY, &var_smtpd_tls_auth_only, + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header, VAR_BROKEN_AUTH_CLNTS, DEF_BROKEN_AUTH_CLNTS, &var_broken_auth_clients, VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, 0, @@ -1897,6 +2157,7 @@ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, 0, 0, VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, + VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_relay_ccerts, 0, 0, VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0, VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0, VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, @@ -1927,3 +2188,4 @@ MAIL_SERVER_POST_INIT, post_jail_init, 0); } + diff -ur src/smtpd.tls.orig/smtpd.h src/smtpd.tls/smtpd.h --- src/smtpd.tls.orig/smtpd.h 2003-07-06 18:47:30.000000000 -0600 +++ src/smtpd.tls/smtpd.h 2003-07-06 18:46:55.000000000 -0600 @@ -32,6 +32,7 @@ * Global library. */ #include +#include /* * Variables that keep track of conversation state. There is only one SMTP @@ -73,6 +74,11 @@ int recursion; off_t msg_size; int junk_cmds; + int tls_active; + int tls_use_tls; + int tls_enforce_tls; + int tls_auth_only; + tls_info_t tls_info; #ifdef USE_SASL_AUTH #if SASL_VERSION_MAJOR >= 2 const char *sasl_mechanism_list; diff -ur src/smtpd.tls.orig/smtpd_check.c src/smtpd.tls/smtpd_check.c --- src/smtpd.tls.orig/smtpd_check.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtpd.tls/smtpd_check.c 2003-07-06 18:46:55.000000000 -0600 @@ -382,6 +383,9 @@ static DOMAIN_LIST *relay_domains; static NAMADR_LIST *mynetworks; static NAMADR_LIST *perm_mx_networks; +#ifdef HAS_SSL +static MAPS *relay_ccerts; +#endif /* * How to do parent domain wildcard matching, if any. @@ -645,6 +649,10 @@ perm_mx_networks = namadr_list_init(match_parent_style(VAR_PERM_MX_NETWORKS), var_perm_mx_networks); +#ifdef HAS_SSL + relay_ccerts = maps_create(VAR_RELAY_CCERTS, var_relay_ccerts, + DICT_FLAG_LOCK); +#endif /* * Pre-parse and pre-open the recipient maps. @@ -1142,6 +1150,36 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient); +/* permit_tls_clientcerts - OK/DUNNO for message relaying */ + +#ifdef HAS_SSL +static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs) +{ + char *low_name; + const char *found; + + if (state->tls_info.peer_verified && permit_all_certs) { + if (msg_verbose) + msg_info("Relaying allowed for all verified client certificates"); + return(SMTPD_CHECK_OK); + } + + if (state->tls_info.peer_verified && state->tls_info.peer_fingerprint) { + low_name = lowercase(mystrdup(state->tls_info.peer_fingerprint)); + found = maps_find(relay_ccerts, low_name, DICT_FLAG_FIXED); + myfree(low_name); + if (found) { + if (msg_verbose) + msg_info("Relaying allowed for certified client: %s", found); + return (SMTPD_CHECK_OK); + } else if (msg_verbose) + msg_info("relay_clientcerts: No match for fingerprint '%s'", + state->tls_info.peer_fingerprint); + } + return (SMTPD_CHECK_DUNNO); +} +#endif + /* check_relay_domains - OK/FAIL for message relaying */ static int check_relay_domains(SMTPD_STATE *state, char *recipient, @@ -2911,6 +2949,12 @@ #else msg_warn("restriction `%s' ignored: no SASL support", name); #endif +#ifdef HAS_SSL + } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 1); + } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 0); +#endif } else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) { if (state->recipient) status = reject_unknown_address(state, state->recipient, @@ -3534,6 +3578,7 @@ char *var_etrn_checks = ""; char *var_data_checks = ""; char *var_relay_domains = ""; +char *var_relay_ccerts = ""; char *var_mynetworks = ""; char *var_notify_classes = ""; diff -ur src/smtpd.tls.orig/smtpd_sasl_proto.c src/smtpd.tls/smtpd_sasl_proto.c --- src/smtpd.tls.orig/smtpd_sasl_proto.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtpd.tls/smtpd_sasl_proto.c 2003-07-06 18:46:55.000000000 -0600 @@ -128,6 +128,13 @@ smtpd_chat_reply(state, "503 Error: authentication not enabled"); return (-1); } +#ifdef HAS_SSL + if (state->tls_auth_only && !state->tls_active) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "538 Encryption required for requested authentication mechanism"); + return (-1); + } +#endif if (state->sasl_username) { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "503 Error: already authenticated"); diff -ur src/smtpd.tls.orig/smtpd_state.c src/smtpd.tls/smtpd_state.c --- src/smtpd.tls.orig/smtpd_state.c 2003-07-06 18:47:30.000000000 -0600 +++ src/smtpd.tls/smtpd_state.c 2003-07-06 18:46:55.000000000 -0600 @@ -86,6 +86,11 @@ state->reason = 0; state->sender = 0; state->recipient = 0; + state->tls_active = 0; + state->tls_use_tls = 0; + state->tls_enforce_tls = 0; + state->tls_info = tls_info_zero; + state->tls_auth_only = 0; state->etrn_name = 0; state->protocol = MAIL_PROTO_SMTP; state->where = SMTPD_AFTER_CONNECT;