/*++ /* NAME /* smtp_sasl 3 /* SUMMARY /* Postfix SASL interface for SMTP client /* SYNOPSIS /* #include smtp_sasl.h /* /* void smtp_sasl_initialize() /* /* void smtp_sasl_connect(session) /* SMTP_SESSION *session; /* /* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val) /* SMTP_SESSION *session; /* /* int smtp_sasl_passwd_lookup(session) /* SMTP_SESSION *session; /* /* int smtp_sasl_authenticate(session, why) /* SMTP_SESSION *session; /* VSTRING *why; /* /* void smtp_sasl_cleanup(session) /* SMTP_SESSION *session; /* /* void smtp_sasl_passivate(session, buf) /* SMTP_SESSION *session; /* VSTRING *buf; /* /* int smtp_sasl_activate(session, buf) /* SMTP_SESSION *session; /* char *buf; /* DESCRIPTION /* smtp_sasl_initialize() initializes the SASL library. This /* routine must be called once at process startup, before any /* chroot operations. /* /* smtp_sasl_connect() performs per-session initialization. This /* routine must be called once at the start of each connection. /* /* smtp_sasl_start() performs per-session initialization. This /* routine must be called once per session before doing any SASL /* authentication. The sasl_opts_name and sasl_opts_val parameters are /* the postfix configuration parameters setting the security /* policy of the SASL authentication. /* /* smtp_sasl_passwd_lookup() looks up the username/password /* for the current SMTP server. The result is zero in case /* of failure. /* /* smtp_sasl_authenticate() implements the SASL authentication /* dialog. The result is < 0 in case of protocol failure, zero in /* case of unsuccessful authentication, > 0 in case of success. /* The why argument is updated with a reason for failure. /* This routine must be called only when smtp_sasl_passwd_lookup() /* suceeds. /* /* smtp_sasl_cleanup() cleans up. It must be called at the /* end of every SMTP session that uses SASL authentication. /* This routine is a noop for non-SASL sessions. /* /* smtp_sasl_passivate() appends flattened SASL attributes to the /* specified buffer. The SASL attributes are not destroyed. /* /* smtp_sasl_activate() restores SASL attributes from the /* specified buffer. The buffer is modified. A result < 0 /* means there was an error. /* /* Arguments: /* .IP session /* Session context. /* .IP mech_list /* String of SASL mechanisms (separated by blanks) /* DIAGNOSTICS /* All errors are fatal. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Original author: /* Till Franke /* SuSE Rhein/Main AG /* 65760 Eschborn, Germany /* /* Adopted by: /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* * System library. */ #include #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* * Utility library */ #include #include #include #include #include /* * Global library */ #include #include #include /* * Application-specific */ #include "smtp.h" #include "smtp_sasl.h" #ifdef USE_SASL_AUTH /* * Authentication security options. */ static NAME_MASK smtp_sasl_sec_mask[] = { "noplaintext", SASL_SEC_NOPLAINTEXT, "noactive", SASL_SEC_NOACTIVE, "nodictionary", SASL_SEC_NODICTIONARY, "noanonymous", SASL_SEC_NOANONYMOUS, #if SASL_VERSION_MAJOR >= 2 "mutual_auth", SASL_SEC_MUTUAL_AUTH, #endif 0, }; /* * Silly little macros. */ #define STR(x) vstring_str(x) /* * Macros to handle API differences between SASLv1 and SASLv2. Specifics: * * The SASL_LOG_* constants were renamed in SASLv2. * * SASLv2's sasl_client_new takes two new parameters to specify local and * remote IP addresses for auth mechs that use them. * * SASLv2's sasl_client_start function no longer takes the secret parameter. * * SASLv2's sasl_decode64 function takes an extra parameter for the length of * the output buffer. * * The other major change is that SASLv2 now takes more responsibility for * deallocating memory that it allocates internally. Thus, some of the * function parameters are now 'const', to make sure we don't try to free * them too. This is dealt with in the code later on. */ #if SASL_VERSION_MAJOR < 2 /* SASL version 1.x */ #define SASL_LOG_WARN SASL_LOG_WARNING #define SASL_LOG_NOTE SASL_LOG_INFO #define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ sasl_client_new(srv, fqdn, prompt, secflags, pconn) #define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech) #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ sasl_decode64(in, inlen, out, outlen) #endif #if SASL_VERSION_MAJOR >= 2 /* SASL version > 2.x */ #define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn) #define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ sasl_client_start(conn, mechlst, prompt, clout, cllen, mech) #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ sasl_decode64(in, inlen, out, outmaxlen, outlen) #endif /* * Per-host login/password information. */ static MAPS *smtp_sasl_passwd_map; /* * Supported SASL mechanisms. */ STRING_LIST *smtp_sasl_mechs; /* smtp_sasl_log - logging call-back routine */ static int smtp_sasl_log(void *unused_context, int priority, const char *message) { switch (priority) { case SASL_LOG_ERR: /* unusual errors */ case SASL_LOG_WARN: /* non-fatal warnings */ msg_warn("SASL authentication problem: %s", message); break; case SASL_LOG_NOTE: /* other info */ if (msg_verbose) msg_info("SASL authentication info: %s", message); break; #if SASL_VERSION_MAJOR >= 2 case SASL_LOG_FAIL: /* authentication failures */ msg_warn("SASL authentication failure: %s", message); #endif } return (SASL_OK); } /* smtp_sasl_get_user - username lookup call-back routine */ static int smtp_sasl_get_user(void *context, int unused_id, const char **result, unsigned *len) { char *myname = "smtp_sasl_get_user"; SMTP_SESSION *session = (SMTP_SESSION *) context; if (msg_verbose) msg_info("%s: %s", myname, session->sasl_username); /* * Sanity check. */ if (session->sasl_passwd == 0) msg_panic("%s: no username looked up", myname); *result = session->sasl_username; if (len) *len = strlen(session->sasl_username); return (SASL_OK); } /* smtp_sasl_get_passwd - password lookup call-back routine */ static int smtp_sasl_get_passwd(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret) { char *myname = "smtp_sasl_get_passwd"; SMTP_SESSION *session = (SMTP_SESSION *) context; int len; if (msg_verbose) msg_info("%s: %s", myname, session->sasl_passwd); /* * Sanity check. */ if (!conn || !psecret || id != SASL_CB_PASS) return (SASL_BADPARAM); if (session->sasl_passwd == 0) msg_panic("%s: no password looked up", myname); /* * Convert the password into a counted string. */ len = strlen(session->sasl_passwd); if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0) return (SASL_NOMEM); (*psecret)->len = len; memcpy((*psecret)->data, session->sasl_passwd, len + 1); return (SASL_OK); } /* smtp_sasl_passwd_lookup - password lookup routine */ int smtp_sasl_passwd_lookup(SMTP_SESSION *session) { char *myname = "smtp_sasl_passwd_lookup"; const char *value; char *passwd; /* * Sanity check. */ if (smtp_sasl_passwd_map == 0) msg_panic("%s: passwd map not initialized", myname); /* * Look up the per-server password information. Try the hostname first, * then try the destination. * * XXX Instead of using nexthop (the intended destination) we use dest * (either the intended destination, or a fall-back destination). * * XXX SASL authentication currently depends on the host/domain but not on * the TCP port. If the port is not :25, we should append it to the table * lookup key. Code for this was briefly introduced into 2.2 snapshots, * but didn't canonicalize the TCP port, and did not append the port to * the MX hostname. */ if ((value = maps_find(smtp_sasl_passwd_map, session->host, 0)) != 0 || (value = maps_find(smtp_sasl_passwd_map, session->dest, 0)) != 0) { session->sasl_username = mystrdup(value); passwd = split_at(session->sasl_username, ':'); session->sasl_passwd = mystrdup(passwd ? passwd : ""); if (msg_verbose) msg_info("%s: host `%s' user `%s' pass `%s'", myname, session->host, session->sasl_username, session->sasl_passwd); return (1); } else { if (msg_verbose) msg_info("%s: host `%s' no auth info found", myname, session->host); return (0); } } /* smtp_sasl_initialize - per-process initialization (pre jail) */ void smtp_sasl_initialize(void) { /* * Global callbacks. These have no per-session context. */ static sasl_callback_t callbacks[] = { {SASL_CB_LOG, &smtp_sasl_log, 0}, {SASL_CB_LIST_END, 0, 0} }; /* * Sanity check. */ if (smtp_sasl_passwd_map) msg_panic("smtp_sasl_initialize: repeated call"); if (*var_smtp_sasl_passwd == 0) msg_fatal("specify a password table via the `%s' configuration parameter", VAR_SMTP_SASL_PASSWD); /* * Open the per-host password table and initialize the SASL library. Use * shared locks for reading, just in case someone updates the table. */ smtp_sasl_passwd_map = maps_create("smtp_sasl_passwd", var_smtp_sasl_passwd, DICT_FLAG_LOCK); if (sasl_client_init(callbacks) != SASL_OK) msg_fatal("SASL library initialization"); /* * Initialize optional supported mechanism matchlist */ if (*var_smtp_sasl_mechs) smtp_sasl_mechs = string_list_init(MATCH_FLAG_NONE, var_smtp_sasl_mechs); } /* smtp_sasl_connect - per-session client initialization */ void smtp_sasl_connect(SMTP_SESSION *session) { session->sasl_mechanism_list = 0; session->sasl_username = 0; session->sasl_passwd = 0; session->sasl_conn = 0; session->sasl_encoded = 0; session->sasl_decoded = 0; session->sasl_callbacks = 0; } /* smtp_sasl_start - per-session SASL initialization */ void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name, const char *sasl_opts_val) { static sasl_callback_t callbacks[] = { {SASL_CB_USER, &smtp_sasl_get_user, 0}, {SASL_CB_AUTHNAME, &smtp_sasl_get_user, 0}, {SASL_CB_PASS, &smtp_sasl_get_passwd, 0}, {SASL_CB_LIST_END, 0, 0} }; sasl_callback_t *cp; sasl_security_properties_t sec_props; if (msg_verbose) msg_info("starting new SASL client"); /* * Per-session initialization. Provide each session with its own callback * context. */ #define NULL_SECFLAGS 0 session->sasl_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks)); memcpy((char *) session->sasl_callbacks, callbacks, sizeof(callbacks)); for (cp = session->sasl_callbacks; cp->id != SASL_CB_LIST_END; cp++) cp->context = (void *) session; #define NULL_SERVER_ADDR ((char *) 0) #define NULL_CLIENT_ADDR ((char *) 0) if (SASL_CLIENT_NEW("smtp", session->host, NULL_CLIENT_ADDR, NULL_SERVER_ADDR, session->sasl_callbacks, NULL_SECFLAGS, (sasl_conn_t **) &session->sasl_conn) != SASL_OK) msg_fatal("per-session SASL client initialization"); /* * Per-session security properties. XXX This routine is not sufficiently * documented. What is the purpose of all this? */ memset(&sec_props, 0L, sizeof(sec_props)); sec_props.min_ssf = 0; sec_props.max_ssf = 1; /* don't allow real SASL * security layer */ sec_props.security_flags = name_mask(sasl_opts_name, smtp_sasl_sec_mask, sasl_opts_val); sec_props.maxbufsize = 0; sec_props.property_names = 0; sec_props.property_values = 0; if (sasl_setprop(session->sasl_conn, SASL_SEC_PROPS, &sec_props) != SASL_OK) msg_fatal("set per-session SASL security properties"); /* * We use long-lived conversion buffers rather than local variables in * order to avoid memory leaks in case of read/write timeout or I/O * error. */ session->sasl_encoded = vstring_alloc(10); session->sasl_decoded = vstring_alloc(10); } /* smtp_sasl_authenticate - run authentication protocol */ int smtp_sasl_authenticate(SMTP_SESSION *session, VSTRING *why) { char *myname = "smtp_sasl_authenticate"; unsigned enc_length; unsigned enc_length_out; #if SASL_VERSION_MAJOR >= 2 const char *clientout; #else char *clientout; #endif unsigned clientoutlen; unsigned serverinlen; SMTP_RESP *resp; const char *mechanism; int result; char *line; #define NO_SASL_SECRET 0 #define NO_SASL_INTERACTION 0 #define NO_SASL_LANGLIST ((const char *) 0) #define NO_SASL_OUTLANG ((const char **) 0) if (msg_verbose) msg_info("%s: %s: SASL mechanisms %s", myname, session->namaddr, session->sasl_mechanism_list); /* * Start the client side authentication protocol. */ result = SASL_CLIENT_START((sasl_conn_t *) session->sasl_conn, session->sasl_mechanism_list, NO_SASL_SECRET, NO_SASL_INTERACTION, &clientout, &clientoutlen, &mechanism); if (result != SASL_OK && result != SASL_CONTINUE) { vstring_sprintf(why, "cannot SASL authenticate to server %s: %s", session->namaddr, sasl_errstring(result, NO_SASL_LANGLIST, NO_SASL_OUTLANG)); return (-1); } /* * Send the AUTH command and the optional initial client response. * sasl_encode64() produces four bytes for each complete or incomplete * triple of input bytes. Allocate an extra byte for string termination. */ #define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4) if (clientoutlen > 0) { if (msg_verbose) msg_info("%s: %s: uncoded initial reply: %.*s", myname, session->namaddr, (int) clientoutlen, clientout); enc_length = ENCODE64_LENGTH(clientoutlen) + 1; VSTRING_SPACE(session->sasl_encoded, enc_length); if (sasl_encode64(clientout, clientoutlen, STR(session->sasl_encoded), enc_length, &enc_length_out) != SASL_OK) msg_panic("%s: sasl_encode64 botch", myname); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(clientout); #endif smtp_chat_cmd(session, "AUTH %s %s", mechanism, STR(session->sasl_encoded)); } else { smtp_chat_cmd(session, "AUTH %s", mechanism); } /* * Step through the authentication protocol until the server tells us * that we are done. */ while ((resp = smtp_chat_resp(session))->code / 100 == 3) { /* * Process a server challenge. */ line = resp->str; (void) mystrtok(&line, "- \t\n"); /* skip over result code */ serverinlen = strlen(line); VSTRING_SPACE(session->sasl_decoded, serverinlen); if (SASL_DECODE64(line, serverinlen, STR(session->sasl_decoded), serverinlen, &enc_length) != SASL_OK) { vstring_sprintf(why, "malformed SASL challenge from server %s", session->namaddr); return (-1); } if (msg_verbose) msg_info("%s: %s: decoded challenge: %.*s", myname, session->namaddr, (int) enc_length, STR(session->sasl_decoded)); result = sasl_client_step((sasl_conn_t *) session->sasl_conn, STR(session->sasl_decoded), enc_length, NO_SASL_INTERACTION, &clientout, &clientoutlen); if (result != SASL_OK && result != SASL_CONTINUE) msg_warn("SASL authentication failed to server %s: %s", session->namaddr, sasl_errstring(result, NO_SASL_LANGLIST, NO_SASL_OUTLANG)); /* * Send a client response. */ if (clientoutlen > 0) { if (msg_verbose) msg_info("%s: %s: uncoded client response %.*s", myname, session->namaddr, (int) clientoutlen, clientout); enc_length = ENCODE64_LENGTH(clientoutlen) + 1; VSTRING_SPACE(session->sasl_encoded, enc_length); if (sasl_encode64(clientout, clientoutlen, STR(session->sasl_encoded), enc_length, &enc_length_out) != SASL_OK) msg_panic("%s: sasl_encode64 botch", myname); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(clientout); #endif } else { vstring_strcat(session->sasl_encoded, ""); } smtp_chat_cmd(session, "%s", STR(session->sasl_encoded)); } /* * We completed the authentication protocol. */ if (resp->code / 100 != 2) { vstring_sprintf(why, "SASL authentication failed; server %s said: %s", session->namaddr, resp->str); return (0); } return (1); } /* smtp_sasl_cleanup - per-session cleanup */ void smtp_sasl_cleanup(SMTP_SESSION *session) { if (session->sasl_username) { myfree(session->sasl_username); session->sasl_username = 0; } if (session->sasl_passwd) { myfree(session->sasl_passwd); session->sasl_passwd = 0; } if (session->sasl_mechanism_list) { /* allocated in smtp_sasl_helo_auth */ myfree(session->sasl_mechanism_list); session->sasl_mechanism_list = 0; } if (session->sasl_conn) { if (msg_verbose) msg_info("disposing SASL state information"); sasl_dispose(&session->sasl_conn); } if (session->sasl_callbacks) { myfree((char *) session->sasl_callbacks); session->sasl_callbacks = 0; } if (session->sasl_encoded) { vstring_free(session->sasl_encoded); session->sasl_encoded = 0; } if (session->sasl_decoded) { vstring_free(session->sasl_decoded); session->sasl_decoded = 0; } } /* smtp_sasl_passivate - append serialized SASL attributes */ void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf) { } /* smtp_sasl_activate - de-serialize SASL attributes */ int smtp_sasl_activate(SMTP_SESSION *session, char *buf) { return (0); } #endif