xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtpd/smtpd_sasl_glue.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
1 /*	$NetBSD: smtpd_sasl_glue.c,v 1.5 2023/12/23 20:30:45 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 
127 /* System library. */
128 
129 #include <sys_defs.h>
130 #include <stdlib.h>
131 #include <string.h>
132 
133 /* Utility library. */
134 
135 #include <msg.h>
136 #include <mymalloc.h>
137 #include <stringops.h>
138 
139 /* Global library. */
140 
141 #include <mail_params.h>
142 #include <sasl_mech_filter.h>
143 #include <string_list.h>
144 
145 /* XSASL library. */
146 
147 #include <xsasl.h>
148 
149 /* Application-specific. */
150 
151 #include "smtpd.h"
152 #include "smtpd_sasl_glue.h"
153 #include "smtpd_chat.h"
154 
155 #ifdef USE_SASL_AUTH
156 
157  /*
158   * SASL mechanism filter.
159   */
160 static STRING_LIST *smtpd_sasl_mech_filter;
161 
162 /*
163  * Silly little macros.
164  */
165 #define STR(s)	vstring_str(s)
166 
167  /*
168   * SASL server implementation handle.
169   */
170 static XSASL_SERVER_IMPL *smtpd_sasl_impl;
171 
172 /* smtpd_sasl_initialize - per-process initialization */
173 
smtpd_sasl_initialize(void)174 void    smtpd_sasl_initialize(void)
175 {
176 
177     /*
178      * Sanity check.
179      */
180     if (smtpd_sasl_impl)
181 	msg_panic("smtpd_sasl_initialize: repeated call");
182 
183     /*
184      * Initialize the SASL library.
185      */
186     if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type,
187 					     var_smtpd_sasl_path)) == 0)
188 	msg_fatal("SASL per-process initialization failed");
189 
190     /*
191      * Initialize the SASL mechanism filter.
192      */
193     smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER,
194 					      MATCH_FLAG_NONE,
195 					      var_smtpd_sasl_mech_filter);
196 }
197 
198 /* smtpd_sasl_activate - per-connection initialization */
199 
smtpd_sasl_activate(SMTPD_STATE * state,const char * sasl_opts_name,const char * sasl_opts_val)200 void    smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name,
201 			            const char *sasl_opts_val)
202 {
203     const char *mechanism_list;
204     const char *filtered_mechanism_list;
205     XSASL_SERVER_CREATE_ARGS create_args;
206     int     tls_flag;
207 
208     /*
209      * Sanity check.
210      */
211     if (smtpd_sasl_is_active(state))
212 	msg_panic("smtpd_sasl_activate: already active");
213 
214     /*
215      * Initialize SASL-specific state variables. Use long-lived storage for
216      * base 64 conversion results, rather than local variables, to avoid
217      * memory leaks when a read or write routine returns abnormally after
218      * timeout or I/O error.
219      */
220     state->sasl_reply = vstring_alloc(20);
221     state->sasl_mechanism_list = 0;
222 
223     /*
224      * Set up a new server context for this connection.
225      */
226 #ifdef USE_TLS
227     tls_flag = state->tls_context != 0;
228 #else
229     tls_flag = 0;
230 #endif
231 #define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "")
232 #define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0)
233 
234     if ((state->sasl_server =
235 	 XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args,
236 			     stream = state->client,
237 			     addr_family = state->addr_family,
238 			     server_addr = ADDR_OR_EMPTY(state->dest_addr,
239 						       SERVER_ADDR_UNKNOWN),
240 			     server_port = ADDR_OR_EMPTY(state->dest_port,
241 						       SERVER_PORT_UNKNOWN),
242 			     client_addr = ADDR_OR_EMPTY(state->addr,
243 						       CLIENT_ADDR_UNKNOWN),
244 			     client_port = ADDR_OR_EMPTY(state->port,
245 						       CLIENT_PORT_UNKNOWN),
246 			     service = var_smtpd_sasl_service,
247 			   user_realm = REALM_OR_NULL(var_smtpd_sasl_realm),
248 			     security_options = sasl_opts_val,
249 			     tls_flag = tls_flag)) == 0)
250 	msg_fatal("SASL per-connection initialization failed");
251 
252     /*
253      * Get the list of authentication mechanisms.
254      */
255     if ((mechanism_list =
256 	 xsasl_server_get_mechanism_list(state->sasl_server)) == 0)
257 	msg_fatal("no SASL authentication mechanisms");
258     filtered_mechanism_list =
259 	sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list);
260     if (*filtered_mechanism_list == 0)
261 	msg_fatal("%s discards all mechanisms in '%s'",
262 		  VAR_SMTPD_SASL_MECH_FILTER, mechanism_list);
263     state->sasl_mechanism_list = mystrdup(filtered_mechanism_list);
264 }
265 
266 /* smtpd_sasl_state_init - initialize state to allow extern authentication. */
267 
smtpd_sasl_state_init(SMTPD_STATE * state)268 void    smtpd_sasl_state_init(SMTPD_STATE *state)
269 {
270     /* Initialization to support external authentication (e.g., XCLIENT). */
271     state->sasl_username = 0;
272     state->sasl_method = 0;
273     state->sasl_sender = 0;
274 }
275 
276 /* smtpd_sasl_deactivate - per-connection cleanup */
277 
smtpd_sasl_deactivate(SMTPD_STATE * state)278 void    smtpd_sasl_deactivate(SMTPD_STATE *state)
279 {
280     if (state->sasl_reply) {
281 	vstring_free(state->sasl_reply);
282 	state->sasl_reply = 0;
283     }
284     if (state->sasl_mechanism_list) {
285 	myfree(state->sasl_mechanism_list);
286 	state->sasl_mechanism_list = 0;
287     }
288     if (state->sasl_username) {
289 	myfree(state->sasl_username);
290 	state->sasl_username = 0;
291     }
292     if (state->sasl_method) {
293 	myfree(state->sasl_method);
294 	state->sasl_method = 0;
295     }
296     if (state->sasl_sender) {
297 	myfree(state->sasl_sender);
298 	state->sasl_sender = 0;
299     }
300     if (state->sasl_server) {
301 	xsasl_server_free(state->sasl_server);
302 	state->sasl_server = 0;
303     }
304 }
305 
306 /* smtpd_sasl_authenticate - per-session authentication */
307 
smtpd_sasl_authenticate(SMTPD_STATE * state,const char * sasl_method,const char * init_response)308 int     smtpd_sasl_authenticate(SMTPD_STATE *state,
309 				        const char *sasl_method,
310 				        const char *init_response)
311 {
312     int     status;
313     const char *sasl_username;
314 
315     /*
316      * SASL authentication protocol start-up. Process any initial client
317      * response that was sent along in the AUTH command.
318      */
319     for (status = xsasl_server_first(state->sasl_server, sasl_method,
320 				     init_response, state->sasl_reply);
321 	 status == XSASL_AUTH_MORE;
322 	 status = xsasl_server_next(state->sasl_server, STR(state->buffer),
323 				    state->sasl_reply)) {
324 
325 	/*
326 	 * Send a server challenge.
327 	 */
328 	smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply));
329 
330 	/*
331 	 * Receive the client response. "*" means that the client gives up.
332 	 */
333 	if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) {
334 	    smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded");
335 	    return (-1);
336 	}
337 	if (strcmp(STR(state->buffer), "*") == 0) {
338 	    msg_warn("%s: SASL %s authentication aborted",
339 		     state->namaddr, sasl_method);
340 	    smtpd_chat_reply(state, "501 5.7.0 Authentication aborted");
341 	    return (-1);
342 	}
343     }
344     if (status != XSASL_AUTH_DONE) {
345 	sasl_username = xsasl_server_get_username(state->sasl_server);
346 	msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s",
347 		 state->namaddr, sasl_method, *STR(state->sasl_reply) ?
348 		 STR(state->sasl_reply) : "(reason unavailable)",
349 		 sasl_username ? sasl_username : "(unavailable)");
350 	/* RFC 4954 Section 6. */
351 	if (status == XSASL_AUTH_TEMP)
352 	    smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s",
353 			     STR(state->sasl_reply));
354 	else
355 	    smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s",
356 			     STR(state->sasl_reply));
357 	return (-1);
358     }
359     /* RFC 4954 Section 6. */
360     smtpd_chat_reply(state, "235 2.7.0 Authentication successful");
361     if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0)
362 	msg_panic("cannot look up the authenticated SASL username");
363     state->sasl_username = mystrdup(sasl_username);
364     printable(state->sasl_username, '?');
365     state->sasl_method = mystrdup(sasl_method);
366     printable(state->sasl_method, '?');
367 
368     return (0);
369 }
370 
371 /* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */
372 
smtpd_sasl_logout(SMTPD_STATE * state)373 void    smtpd_sasl_logout(SMTPD_STATE *state)
374 {
375     if (state->sasl_username) {
376 	myfree(state->sasl_username);
377 	state->sasl_username = 0;
378     }
379     if (state->sasl_method) {
380 	myfree(state->sasl_method);
381 	state->sasl_method = 0;
382     }
383 }
384 
385 /* smtpd_sasl_login - set login information */
386 
smtpd_sasl_login(SMTPD_STATE * state,const char * sasl_username,const char * sasl_method)387 void    smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username,
388 			         const char *sasl_method)
389 {
390     if (state->sasl_username)
391 	myfree(state->sasl_username);
392     state->sasl_username = mystrdup(sasl_username);
393     if (state->sasl_method)
394 	myfree(state->sasl_method);
395     state->sasl_method = mystrdup(sasl_method);
396 }
397 
398 #endif
399