/*++ /* NAME /* xsasl_cyrus_server 3 /* SUMMARY /* Cyrus SASL server-side plug-in /* SYNOPSIS /* #include /* /* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info) /* const char *server_type; /* const char *path_info; /* DESCRIPTION /* This module implements the Cyrus SASL server-side authentication /* plug-in. /* /* xsasl_cyrus_server_init() initializes the Cyrus SASL library and /* returns an implementation handle that can be used to generate /* SASL server instances. /* /* Arguments: /* .IP server_type /* The server type (cyrus). This argument is ignored, but it /* could be used when one implementation provides multiple /* variants. /* .IP path_info /* The base name of the SASL server configuration file (example: /* smtpd becomes /usr/lib/sasl2/smtpd.conf). /* DIAGNOSTICS /* Fatal: out of memory. /* /* Panic: interface violation. /* /* Other: the routines log a warning and return an error result /* as specified in xsasl_server(3). /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Initial implementation by: /* 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 /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #include #include #include /* Utility library. */ #include #include #include #include /* Global library. */ #include /* Application-specific. */ #include #include #include #if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) #include #include /* * Silly little macros. */ #define STR(s) vstring_str(s) /* * Macros to handle API differences between SASLv1 and SASLv2. Specifics: * * The SASL_LOG_* constants were renamed in SASLv2. * * SASLv2's sasl_server_new takes two new parameters to specify local and * remote IP addresses for auth mechs that use them. * * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr * 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_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn) #define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err) #define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err) #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ sasl_decode64(in, inlen, out, outlen) typedef char *MECHANISM_TYPE; typedef unsigned MECHANISM_COUNT_TYPE; typedef char *SERVEROUT_TYPE; typedef void *VOID_SERVEROUT_TYPE; #endif #if SASL_VERSION_MAJOR >= 2 /* SASL version > 2.x */ #define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) #define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen) #define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ sasl_server_step(conn, clin, clinlen, srvout, srvoutlen) #define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ sasl_decode64(in, inlen, out, outmaxlen, outlen) typedef const char *MECHANISM_TYPE; typedef int MECHANISM_COUNT_TYPE; typedef const char *SERVEROUT_TYPE; typedef const void *VOID_SERVEROUT_TYPE; #endif #ifndef NO_IP_CYRUS_SASL_AUTH #define USE_IP_CYRUS_SASL_AUTH #endif /* * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER * object. */ typedef struct { XSASL_SERVER xsasl; /* generic members, must be first */ VSTREAM *stream; /* client-server connection */ sasl_conn_t *sasl_conn; /* SASL context */ VSTRING *decoded; /* decoded challenge or response */ char *username; /* authenticated user */ char *mechanism_list; /* applicable mechanisms */ } XSASL_CYRUS_SERVER; /* * Forward declarations. */ static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *); static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *, XSASL_SERVER_CREATE_ARGS *); static void xsasl_cyrus_server_free(XSASL_SERVER *); static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *, const char *, VSTRING *); static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *); static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *); static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *); static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *); /* * SASL callback interface structure. These call-backs have no per-session * context. */ #define NO_CALLBACK_CONTEXT 0 static sasl_callback_t callbacks[] = { {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT}, {SASL_CB_LIST_END, 0, 0} }; /* xsasl_cyrus_server_init - create implementation handle */ XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, const char *path_info) { const char *myname = "xsasl_cyrus_server_init"; XSASL_SERVER_IMPL *xp; int sasl_status; #if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) int sasl_major; int sasl_minor; int sasl_step; /* * DLL hell guard. */ sasl_version_info((const char **) 0, (const char **) 0, &sasl_major, &sasl_minor, &sasl_step, (int *) 0); if (sasl_major != SASL_VERSION_MAJOR #if 0 || sasl_minor != SASL_VERSION_MINOR || sasl_step != SASL_VERSION_STEP #endif ) { msg_warn("incorrect SASL library version. " "Postfix was built with include files from version %d.%d.%d, " "but the run-time library version is %d.%d.%d", SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, sasl_major, sasl_minor, sasl_step); return (0); } #endif if (*var_cyrus_conf_path) { #ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ if (sasl_set_path(SASL_PATH_TYPE_CONFIG, var_cyrus_conf_path) != SASL_OK) msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", var_cyrus_conf_path); #else msg_warn("%s is not empty, but setting the Cyrus SASL configuration " "path is not supported with SASL library version %d.%d.%d", VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP); #endif } /* * Initialize the library: load SASL plug-in routines, etc. */ if (msg_verbose) msg_info("%s: SASL config file is %s.conf", myname, path_info); if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { msg_warn("SASL per-process initialization failed: %s", xsasl_cyrus_strerror(sasl_status)); return (0); } /* * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it * with our own methods or data. */ xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); xp->create = xsasl_cyrus_server_create; xp->done = xsasl_cyrus_server_done; return (xp); } /* xsasl_cyrus_server_done - dispose of implementation */ static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl) { myfree((void *) impl); sasl_done(); } /* xsasl_cyrus_server_create - create server instance */ static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, XSASL_SERVER_CREATE_ARGS *args) { const char *myname = "xsasl_cyrus_server_create"; char *server_addr_port = 0; char *client_addr_port = 0; sasl_conn_t *sasl_conn = 0; XSASL_CYRUS_SERVER *server = 0; int sasl_status; if (msg_verbose) msg_info("%s: SASL service=%s, realm=%s", myname, args->service, args->user_realm ? args->user_realm : "(null)"); /* * The optimizer will eliminate code duplication and/or dead code. */ #define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ do { \ if (server) { \ xsasl_cyrus_server_free(&server->xsasl); \ } else { \ if (sasl_conn) \ sasl_dispose(&sasl_conn); \ } \ XSASL_CYRUS_SERVER_CREATE_RETURN(x); \ } while (0) #define XSASL_CYRUS_SERVER_CREATE_RETURN(x) \ do { \ if (server_addr_port) \ myfree(server_addr_port); \ if (client_addr_port) \ myfree(client_addr_port); \ return (x); \ } while (0) /* * Set up a new server context. */ #define NO_SECURITY_LAYERS (0) #define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) #define NO_AUTH_REALM ((char *) 0) #if SASL_VERSION_MAJOR >= 2 && defined(USE_IP_CYRUS_SASL_AUTH) /* * Get IP address and port of local and remote endpoints for SASL. Some * implementation supports "[ipv6addr]:port" and "ipv4addr:port" (e.g., * https://illumos.org/man/3sasl/sasl_server_new), They still support the * historical "address;port" syntax, so we stick with that for now. */ server_addr_port = (*args->server_addr && *args->server_port ? concatenate(args->server_addr, ";", args->server_port, (char *) 0) : 0); client_addr_port = (*args->client_addr && *args->client_port ? concatenate(args->client_addr, ";", args->client_port, (char *) 0) : 0); #else /* * Don't give any IP address information to SASL. */ #endif if ((sasl_status = SASL_SERVER_NEW(args->service, var_myhostname, args->user_realm ? args->user_realm : NO_AUTH_REALM, server_addr_port, client_addr_port, NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, &sasl_conn)) != SASL_OK) { msg_warn("SASL per-connection server initialization: %s", xsasl_cyrus_strerror(sasl_status)); XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); } /* * Extend the XSASL_SERVER object with our own data. We use long-lived * conversion buffers rather than local variables to avoid memory leaks * in case of read/write timeout or I/O error. */ server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server)); server->xsasl.free = xsasl_cyrus_server_free; server->xsasl.first = xsasl_cyrus_server_first; server->xsasl.next = xsasl_cyrus_server_next; server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; server->xsasl.get_username = xsasl_cyrus_server_get_username; server->stream = args->stream; server->sasl_conn = sasl_conn; server->decoded = vstring_alloc(20); server->username = 0; server->mechanism_list = 0; if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) != XSASL_AUTH_OK) XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); XSASL_CYRUS_SERVER_CREATE_RETURN(&server->xsasl); } /* xsasl_cyrus_server_set_security - set security properties */ static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, const char *sasl_opts_val) { XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; sasl_security_properties_t sec_props; int sasl_status; /* * Security options. Some information can be found in the sasl.h include * file. */ memset(&sec_props, 0, sizeof(sec_props)); sec_props.min_ssf = 0; sec_props.max_ssf = 0; /* don't allow real SASL * security layer */ if (*sasl_opts_val == 0) { sec_props.security_flags = 0; } else { sec_props.security_flags = xsasl_cyrus_security_parse_opts(sasl_opts_val); if (sec_props.security_flags == 0) { msg_warn("bad per-session SASL security properties"); return (XSASL_AUTH_FAIL); } } sec_props.maxbufsize = 0; sec_props.property_names = 0; sec_props.property_values = 0; if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, &sec_props)) != SASL_OK) { msg_warn("SASL per-connection security setup; %s", xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FAIL); } return (XSASL_AUTH_OK); } /* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */ static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) { const char *myname = "xsasl_cyrus_server_get_mechanism_list"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; MECHANISM_TYPE mechanism_list; MECHANISM_COUNT_TYPE mechanism_count; int sasl_status; /* * Get the list of authentication mechanisms. */ #define UNSUPPORTED_USER ((char *) 0) #define IGNORE_MECHANISM_LEN ((unsigned *) 0) if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, "", " ", "", &mechanism_list, IGNORE_MECHANISM_LEN, &mechanism_count)) != SASL_OK) { msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); return (0); } if (mechanism_count <= 0) { msg_warn("%s: no applicable SASL mechanisms", myname); return (0); } server->mechanism_list = mystrdup(mechanism_list); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(mechanism_list); #endif return (server->mechanism_list); } /* xsasl_cyrus_server_free - destroy server instance */ static void xsasl_cyrus_server_free(XSASL_SERVER *xp) { XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; sasl_dispose(&server->sasl_conn); vstring_free(server->decoded); if (server->username) myfree(server->username); if (server->mechanism_list) myfree(server->mechanism_list); myfree((void *) server); } /* xsasl_cyrus_server_auth_response - encode server first/next response */ static int xsasl_cyrus_server_auth_response(int sasl_status, SERVEROUT_TYPE serverout, unsigned serveroutlen, VSTRING *reply) { const char *myname = "xsasl_cyrus_server_auth_response"; unsigned enc_length; unsigned enc_length_out; /* * Encode the server first/next non-error response; otherwise return the * unencoded error text that corresponds to the SASL error status. * * Regarding the hairy expression below: output from sasl_encode64() comes * in multiples of four bytes for each triple of input bytes, plus four * bytes for any incomplete last triple, plus one byte for the null * terminator. */ if (sasl_status == SASL_OK) { vstring_strcpy(reply, ""); return (XSASL_AUTH_DONE); } else if (sasl_status == SASL_CONTINUE) { if (msg_verbose) msg_info("%s: uncoded server challenge: %.*s", myname, (int) serveroutlen, serverout); enc_length = ((serveroutlen + 2) / 3) * 4 + 1; VSTRING_RESET(reply); /* Fix 200512 */ VSTRING_SPACE(reply, enc_length); if ((sasl_status = sasl_encode64(serverout, serveroutlen, STR(reply), vstring_avail(reply), &enc_length_out)) != SASL_OK) msg_panic("%s: sasl_encode64 botch: %s", myname, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_MORE); } else { if (sasl_status == SASL_NOUSER) /* privacy */ sasl_status = SASL_BADAUTH; vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); switch (sasl_status) { case SASL_FAIL: case SASL_NOMEM: case SASL_TRYAGAIN: case SASL_UNAVAIL: return XSASL_AUTH_TEMP; default: return (XSASL_AUTH_FAIL); } } } /* xsasl_cyrus_server_first - per-session authentication */ int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, const char *init_response, VSTRING *reply) { const char *myname = "xsasl_cyrus_server_first"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; char *dec_buffer; unsigned dec_length; unsigned reply_len; unsigned serveroutlen; int sasl_status; SERVEROUT_TYPE serverout = 0; int xsasl_status; #if SASL_VERSION_MAJOR < 2 const char *errstr = 0; #endif #define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) if (msg_verbose) msg_info("%s: sasl_method %s%s%s", myname, sasl_method, IFELSE(init_response, ", init_response ", ""), IFELSE(init_response, init_response, "")); /* * SASL authentication protocol start-up. Process any initial client * response that was sent along in the AUTH command. */ if (init_response) { reply_len = strlen(init_response); VSTRING_RESET(server->decoded); /* Fix 200512 */ VSTRING_SPACE(server->decoded, reply_len); if ((sasl_status = SASL_DECODE64(init_response, reply_len, dec_buffer = STR(server->decoded), vstring_avail(server->decoded), &dec_length)) != SASL_OK) { vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FORM); } if (msg_verbose) msg_info("%s: decoded initial response %s", myname, dec_buffer); } else { dec_buffer = 0; dec_length = 0; } sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, dec_length, &serverout, &serveroutlen, &errstr); xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, serveroutlen, reply); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(serverout); #endif return (xsasl_status); } /* xsasl_cyrus_server_next - continue authentication */ static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, VSTRING *reply) { const char *myname = "xsasl_cyrus_server_next"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; unsigned dec_length; unsigned request_len; unsigned serveroutlen; int sasl_status; SERVEROUT_TYPE serverout = 0; int xsasl_status; #if SASL_VERSION_MAJOR < 2 const char *errstr = 0; #endif request_len = strlen(request); VSTRING_RESET(server->decoded); /* Fix 200512 */ VSTRING_SPACE(server->decoded, request_len); if ((sasl_status = SASL_DECODE64(request, request_len, STR(server->decoded), vstring_avail(server->decoded), &dec_length)) != SASL_OK) { vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); return (XSASL_AUTH_FORM); } if (msg_verbose) msg_info("%s: decoded response: %.*s", myname, (int) dec_length, STR(server->decoded)); sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), dec_length, &serverout, &serveroutlen, &errstr); xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, serveroutlen, reply); #if SASL_VERSION_MAJOR < 2 /* SASL version 1 doesn't free memory that it allocates. */ free(serverout); #endif return (xsasl_status); } /* xsasl_cyrus_server_get_username - get authenticated username */ static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) { const char *myname = "xsasl_cyrus_server_get_username"; XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; VOID_SERVEROUT_TYPE serverout = 0; int sasl_status; /* * XXX Do not free(serverout). */ if (server->username) myfree(server->username); sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); if (sasl_status != SASL_OK || serverout == 0) { server->username = 0; } else { server->username = mystrdup(serverout); printable(server->username, '?'); } return (server->username); } #endif