Home | History | Annotate | Line # | Download | only in smtpd
      1 /*	$NetBSD: smtpd_sasl_glue.c,v 1.6 2025/02/25 19:15:50 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	smtpd_sasl_glue 3
      6 /* SUMMARY
      7 /*	Postfix SMTP server, SASL support interface
      8 /* SYNOPSIS
      9 /*	#include "smtpd_sasl_glue.h"
     10 /*
     11 /*	void	smtpd_sasl_state_init(state)
     12 /*	SMTPD_STATE *state;
     13 /*
     14 /*	void    smtpd_sasl_initialize()
     15 /*
     16 /*	void	smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val)
     17 /*	SMTPD_STATE *state;
     18 /*	const char *sasl_opts_name;
     19 /*	const char *sasl_opts_val;
     20 /*
     21 /*	char	*smtpd_sasl_authenticate(state, sasl_method, init_response)
     22 /*	SMTPD_STATE *state;
     23 /*	const char *sasl_method;
     24 /*	const char *init_response;
     25 /*
     26 /*	void	smtpd_sasl_logout(state)
     27 /*	SMTPD_STATE *state;
     28 /*
     29 /*	void	smtpd_sasl_login(state, sasl_username, sasl_method)
     30 /*	SMTPD_STATE *state;
     31 /*	const char *sasl_username;
     32 /*	const char *sasl_method;
     33 /*
     34 /*	void	smtpd_sasl_deactivate(state)
     35 /*	SMTPD_STATE *state;
     36 /*
     37 /*	int	smtpd_sasl_is_active(state)
     38 /*	SMTPD_STATE *state;
     39 /*
     40 /*	int	smtpd_sasl_set_inactive(state)
     41 /*	SMTPD_STATE *state;
     42 /* DESCRIPTION
     43 /*	This module encapsulates most of the detail specific to SASL
     44 /*	authentication.
     45 /*
     46 /*	smtpd_sasl_state_init() performs minimal server state
     47 /*	initialization to support external authentication (e.g.,
     48 /*	XCLIENT) without having to enable SASL in main.cf. This
     49 /*	should always be called at process startup.
     50 /*
     51 /*	smtpd_sasl_initialize() initializes the SASL library. This
     52 /*	routine should be called once at process start-up. It may
     53 /*	need access to the file system for run-time loading of
     54 /*	plug-in modules. There is no corresponding cleanup routine.
     55 /*
     56 /*	smtpd_sasl_activate() performs per-connection initialization.
     57 /*	This routine should be called once at the start of every
     58 /*	connection. The sasl_opts_name and sasl_opts_val parameters
     59 /*	are the postfix configuration parameters setting the security
     60 /*	policy of the SASL authentication.
     61 /*
     62 /*	smtpd_sasl_authenticate() implements the authentication
     63 /*	dialog.  The result is zero in case of success, -1 in case
     64 /*	of failure. smtpd_sasl_authenticate() updates the following
     65 /*	state structure members:
     66 /* .IP sasl_method
     67 /*	The authentication method that was successfully applied.
     68 /*	This member is a null pointer in the absence of successful
     69 /*	authentication.
     70 /* .IP sasl_username
     71 /*	The username that was successfully authenticated.
     72 /*	This member is a null pointer in the absence of successful
     73 /*	authentication.
     74 /* .PP
     75 /*	smtpd_sasl_login() records the result of successful external
     76 /*	authentication, i.e. without invoking smtpd_sasl_authenticate(),
     77 /*	but produces an otherwise equivalent result.
     78 /*
     79 /*	smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate().
     80 /*	This routine exists for the sake of symmetry.
     81 /*
     82 /*	smtpd_sasl_deactivate() performs per-connection cleanup.
     83 /*	This routine should be called at the end of every connection.
     84 /*
     85 /*	smtpd_sasl_is_active() is a predicate that returns true
     86 /*	if the SMTP server session state is between smtpd_sasl_activate()
     87 /*	and smtpd_sasl_deactivate().
     88 /*
     89 /*	smtpd_sasl_set_inactive() initializes the SMTP session
     90 /*	state before the first smtpd_sasl_activate() call.
     91 /*
     92 /*	Arguments:
     93 /* .IP state
     94 /*	SMTP session context.
     95 /* .IP sasl_opts_name
     96 /*	Security options parameter name.
     97 /* .IP sasl_opts_val
     98 /*	Security options parameter value.
     99 /* .IP sasl_method
    100 /*	A SASL mechanism name
    101 /* .IP init_reply
    102 /*	An optional initial client response.
    103 /* DIAGNOSTICS
    104 /*	All errors are fatal.
    105 /* LICENSE
    106 /* .ad
    107 /* .fi
    108 /*	The Secure Mailer license must be distributed with this software.
    109 /* AUTHOR(S)
    110 /*	Initial implementation by:
    111 /*	Till Franke
    112 /*	SuSE Rhein/Main AG
    113 /*	65760 Eschborn, Germany
    114 /*
    115 /*	Adopted by:
    116 /*	Wietse Venema
    117 /*	IBM T.J. Watson Research
    118 /*	P.O. Box 704
    119 /*	Yorktown Heights, NY 10598, USA
    120 /*
    121 /*	Wietse Venema
    122 /*	Google, Inc.
    123 /*	111 8th Avenue
    124 /*	New York, NY 10011, USA
    125 /*
    126 /*	Wietse Venema
    127 /*	porcupine.org
    128 /*	Amawalk, NY 10501, USA
    129 /*--*/
    130 
    131 /* System library. */
    132 
    133 #include <sys_defs.h>
    134 #include <stdlib.h>
    135 #include <string.h>
    136 
    137 /* Utility library. */
    138 
    139 #include <msg.h>
    140 #include <mymalloc.h>
    141 #include <stringops.h>
    142 
    143 /* Global library. */
    144 
    145 #include <mail_params.h>
    146 #include <sasl_mech_filter.h>
    147 #include <string_list.h>
    148 
    149 /* XSASL library. */
    150 
    151 #include <xsasl.h>
    152 
    153 /* Application-specific. */
    154 
    155 #include "smtpd.h"
    156 #include "smtpd_sasl_glue.h"
    157 #include "smtpd_chat.h"
    158 
    159 #ifdef USE_SASL_AUTH
    160 
    161  /*
    162   * SASL mechanism filter.
    163   */
    164 static STRING_LIST *smtpd_sasl_mech_filter;
    165 
    166 /*
    167  * Silly little macros.
    168  */
    169 #define STR(s)	vstring_str(s)
    170 
    171  /*
    172   * SASL server implementation handle.
    173   */
    174 static XSASL_SERVER_IMPL *smtpd_sasl_impl;
    175 
    176 /* smtpd_sasl_initialize - per-process initialization */
    177 
    178 void    smtpd_sasl_initialize(void)
    179 {
    180 
    181     /*
    182      * Sanity check.
    183      */
    184     if (smtpd_sasl_impl)
    185 	msg_panic("smtpd_sasl_initialize: repeated call");
    186 
    187     /*
    188      * Initialize the SASL library.
    189      */
    190     if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type,
    191 					     var_smtpd_sasl_path)) == 0)
    192 	msg_fatal("SASL per-process initialization failed");
    193 
    194     /*
    195      * Initialize the SASL mechanism filter.
    196      */
    197     smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER,
    198 					      MATCH_FLAG_NONE,
    199 					      var_smtpd_sasl_mech_filter);
    200 }
    201 
    202 /* smtpd_sasl_activate - per-connection initialization */
    203 
    204 void    smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name,
    205 			            const char *sasl_opts_val)
    206 {
    207     const char *mechanism_list;
    208     const char *filtered_mechanism_list;
    209     XSASL_SERVER_CREATE_ARGS create_args;
    210     int     tls_flag;
    211 
    212     /*
    213      * Sanity check.
    214      */
    215     if (smtpd_sasl_is_active(state))
    216 	msg_panic("smtpd_sasl_activate: already active");
    217 
    218     /*
    219      * Initialize SASL-specific state variables. Use long-lived storage for
    220      * base 64 conversion results, rather than local variables, to avoid
    221      * memory leaks when a read or write routine returns abnormally after
    222      * timeout or I/O error.
    223      */
    224     state->sasl_reply = vstring_alloc(20);
    225     state->sasl_mechanism_list = 0;
    226 
    227     /*
    228      * Set up a new server context for this connection.
    229      */
    230 #ifdef USE_TLS
    231     tls_flag = state->tls_context != 0;
    232 #else
    233     tls_flag = 0;
    234 #endif
    235 #define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "")
    236 #define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0)
    237 
    238     if ((state->sasl_server =
    239 	 XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args,
    240 			     stream = state->client,
    241 			     addr_family = state->addr_family,
    242 			     server_addr = ADDR_OR_EMPTY(state->dest_addr,
    243 						       SERVER_ADDR_UNKNOWN),
    244 			     server_port = ADDR_OR_EMPTY(state->dest_port,
    245 						       SERVER_PORT_UNKNOWN),
    246 			     client_addr = ADDR_OR_EMPTY(state->addr,
    247 						       CLIENT_ADDR_UNKNOWN),
    248 			     client_port = ADDR_OR_EMPTY(state->port,
    249 						       CLIENT_PORT_UNKNOWN),
    250 			     service = var_smtpd_sasl_service,
    251 			   user_realm = REALM_OR_NULL(var_smtpd_sasl_realm),
    252 			     security_options = sasl_opts_val,
    253 			     tls_flag = tls_flag)) == 0)
    254 	msg_fatal("SASL per-connection initialization failed");
    255 
    256     /*
    257      * Get the list of authentication mechanisms.
    258      */
    259     if ((mechanism_list =
    260 	 xsasl_server_get_mechanism_list(state->sasl_server)) == 0)
    261 	msg_fatal("no SASL authentication mechanisms");
    262     filtered_mechanism_list =
    263 	sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list);
    264     if (*filtered_mechanism_list == 0)
    265 	msg_fatal("%s discards all mechanisms in '%s'",
    266 		  VAR_SMTPD_SASL_MECH_FILTER, mechanism_list);
    267     state->sasl_mechanism_list = mystrdup(filtered_mechanism_list);
    268 }
    269 
    270 /* smtpd_sasl_state_init - initialize state to allow extern authentication. */
    271 
    272 void    smtpd_sasl_state_init(SMTPD_STATE *state)
    273 {
    274     /* Initialization to support external authentication (e.g., XCLIENT). */
    275     state->sasl_username = 0;
    276     state->sasl_method = 0;
    277     state->sasl_sender = 0;
    278 }
    279 
    280 /* smtpd_sasl_deactivate - per-connection cleanup */
    281 
    282 void    smtpd_sasl_deactivate(SMTPD_STATE *state)
    283 {
    284     if (state->sasl_reply) {
    285 	vstring_free(state->sasl_reply);
    286 	state->sasl_reply = 0;
    287     }
    288     if (state->sasl_mechanism_list) {
    289 	myfree(state->sasl_mechanism_list);
    290 	state->sasl_mechanism_list = 0;
    291     }
    292     if (state->sasl_username) {
    293 	myfree(state->sasl_username);
    294 	state->sasl_username = 0;
    295     }
    296     if (state->sasl_method) {
    297 	myfree(state->sasl_method);
    298 	state->sasl_method = 0;
    299     }
    300     if (state->sasl_sender) {
    301 	myfree(state->sasl_sender);
    302 	state->sasl_sender = 0;
    303     }
    304     if (state->sasl_server) {
    305 	xsasl_server_free(state->sasl_server);
    306 	state->sasl_server = 0;
    307     }
    308 }
    309 
    310 /* smtpd_sasl_authenticate - per-session authentication */
    311 
    312 int     smtpd_sasl_authenticate(SMTPD_STATE *state,
    313 				        const char *sasl_method,
    314 				        const char *init_response)
    315 {
    316     int     status;
    317     const char *sasl_username;
    318 
    319     /*
    320      * SASL authentication protocol start-up. Process any initial client
    321      * response that was sent along in the AUTH command.
    322      */
    323     for (status = xsasl_server_first(state->sasl_server, sasl_method,
    324 				     init_response, state->sasl_reply);
    325 	 status == XSASL_AUTH_MORE;
    326 	 status = xsasl_server_next(state->sasl_server, STR(state->buffer),
    327 				    state->sasl_reply)) {
    328 
    329 	/*
    330 	 * Send a server challenge.
    331 	 */
    332 	smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply));
    333 
    334 	/*
    335 	 * Receive the client response. "*" means that the client gives up.
    336 	 */
    337 	if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
    338 	    smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
    339 	    return (-1);
    340 	}
    341 	if (strcmp(STR(state->buffer), "*") == 0) {
    342 	    msg_warn("%s: SASL %s authentication aborted",
    343 		     state->namaddr, sasl_method);
    344 	    smtpd_chat_reply(state, "501 5.7.0 Authentication aborted");
    345 	    return (-1);
    346 	}
    347     }
    348     if (status != XSASL_AUTH_DONE) {
    349 	const char *reason = (*STR(state->sasl_reply) ? STR(state->sasl_reply) :
    350 			      "(reason unavailable)");
    351 
    352 	sasl_username = xsasl_server_get_username(state->sasl_server);
    353 	msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
    354 		 state->namaddr, sasl_method, reason,
    355 		 sasl_username ? sasl_username : "(unavailable)");
    356 	/* RFC 4954 Section 6. */
    357 	if (status == XSASL_AUTH_TEMP)
    358 	    smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
    359 			     reason);
    360 	else
    361 	    smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
    362 			     reason);
    363 	return (-1);
    364     }
    365     /* RFC 4954 Section 6. */
    366     smtpd_chat_reply(state, "235 2.7.0 Authentication successful");
    367     if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0)
    368 	msg_panic("cannot look up the authenticated SASL username");
    369     state->sasl_username = mystrdup(sasl_username);
    370     printable(state->sasl_username, '?');
    371     state->sasl_method = mystrdup(sasl_method);
    372     printable(state->sasl_method, '?');
    373 
    374     return (0);
    375 }
    376 
    377 /* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */
    378 
    379 void    smtpd_sasl_logout(SMTPD_STATE *state)
    380 {
    381     if (state->sasl_username) {
    382 	myfree(state->sasl_username);
    383 	state->sasl_username = 0;
    384     }
    385     if (state->sasl_method) {
    386 	myfree(state->sasl_method);
    387 	state->sasl_method = 0;
    388     }
    389 }
    390 
    391 /* smtpd_sasl_login - set login information */
    392 
    393 void    smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username,
    394 			         const char *sasl_method)
    395 {
    396     if (state->sasl_username)
    397 	myfree(state->sasl_username);
    398     state->sasl_username = mystrdup(sasl_username);
    399     if (state->sasl_method)
    400 	myfree(state->sasl_method);
    401     state->sasl_method = mystrdup(sasl_method);
    402 }
    403 
    404 #endif
    405