1 /*	$NetBSD: smtp_sasl_glue.c,v 1.3 2020/03/18 19:05:20 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	smtp_sasl_glue 3
6 /* SUMMARY
7 /*	Postfix SASL interface for SMTP client
8 /* SYNOPSIS
9 /*	#include smtp_sasl.h
10 /*
11 /*	void	smtp_sasl_initialize()
12 /*
13 /*	void	smtp_sasl_connect(session)
14 /*	SMTP_SESSION *session;
15 /*
16 /*	void	smtp_sasl_start(session, sasl_opts_name, sasl_opts_val)
17 /*	SMTP_SESSION *session;
18 /*
19 /*	int     smtp_sasl_passwd_lookup(session)
20 /*	SMTP_SESSION *session;
21 /*
22 /*	int	smtp_sasl_authenticate(session, why)
23 /*	SMTP_SESSION *session;
24 /*	DSN_BUF *why;
25 /*
26 /*	void	smtp_sasl_cleanup(session)
27 /*	SMTP_SESSION *session;
28 /*
29 /*	void	smtp_sasl_passivate(session, buf)
30 /*	SMTP_SESSION *session;
31 /*	VSTRING	*buf;
32 /*
33 /*	int	smtp_sasl_activate(session, buf)
34 /*	SMTP_SESSION *session;
35 /*	char	*buf;
36 /* DESCRIPTION
37 /*	smtp_sasl_initialize() initializes the SASL library. This
38 /*	routine must be called once at process startup, before any
39 /*	chroot operations.
40 /*
41 /*	smtp_sasl_connect() performs per-session initialization. This
42 /*	routine must be called once at the start of each connection.
43 /*
44 /*	smtp_sasl_start() performs per-session initialization. This
45 /*	routine must be called once per session before doing any SASL
46 /*	authentication. The sasl_opts_name and sasl_opts_val parameters are
47 /*	the postfix configuration parameters setting the security
48 /*	policy of the SASL authentication.
49 /*
50 /*	smtp_sasl_passwd_lookup() looks up the username/password
51 /*	for the current SMTP server. The result is zero in case
52 /*	of failure, a long jump in case of error.
53 /*
54 /*	smtp_sasl_authenticate() implements the SASL authentication
55 /*	dialog. The result is < 0 in case of protocol failure, zero in
56 /*	case of unsuccessful authentication, > 0 in case of success.
57 /*	The why argument is updated with a reason for failure.
58 /*	This routine must be called only when smtp_sasl_passwd_lookup()
59 /*	succeeds.
60 /*
61 /*	smtp_sasl_cleanup() cleans up. It must be called at the
62 /*	end of every SMTP session that uses SASL authentication.
63 /*	This routine is a noop for non-SASL sessions.
64 /*
65 /*	smtp_sasl_passivate() appends flattened SASL attributes to the
66 /*	specified buffer. The SASL attributes are not destroyed.
67 /*
68 /*	smtp_sasl_activate() restores SASL attributes from the
69 /*	specified buffer. The buffer is modified. A result < 0
70 /*	means there was an error.
71 /*
72 /*	Arguments:
73 /* .IP session
74 /*	Session context.
75 /* .IP mech_list
76 /*	String of SASL mechanisms (separated by blanks)
77 /* DIAGNOSTICS
78 /*	All errors are fatal.
79 /* LICENSE
80 /* .ad
81 /* .fi
82 /*	The Secure Mailer license must be distributed with this software.
83 /* AUTHOR(S)
84 /*	Original author:
85 /*	Till Franke
86 /*	SuSE Rhein/Main AG
87 /*	65760 Eschborn, Germany
88 /*
89 /*	Adopted by:
90 /*	Wietse Venema
91 /*	IBM T.J. Watson Research
92 /*	P.O. Box 704
93 /*	Yorktown Heights, NY 10598, USA
94 /*
95 /*	Wietse Venema
96 /*	Google, Inc.
97 /*	111 8th Avenue
98 /*	New York, NY 10011, USA
99 /*--*/
100 
101  /*
102   * System library.
103   */
104 #include <sys_defs.h>
105 #include <stdlib.h>
106 #include <string.h>
107 
108  /*
109   * Utility library
110   */
111 #include <msg.h>
112 #include <mymalloc.h>
113 #include <stringops.h>
114 #include <split_at.h>
115 
116  /*
117   * Global library
118   */
119 #include <mail_params.h>
120 #include <string_list.h>
121 #include <maps.h>
122 #include <mail_addr_find.h>
123 #include <smtp_stream.h>
124 
125  /*
126   * XSASL library.
127   */
128 #include <xsasl.h>
129 
130  /*
131   * Application-specific
132   */
133 #include "smtp.h"
134 #include "smtp_sasl.h"
135 #include "smtp_sasl_auth_cache.h"
136 
137 #ifdef USE_SASL_AUTH
138 
139  /*
140   * Per-host login/password information.
141   */
142 static MAPS *smtp_sasl_passwd_map;
143 
144  /*
145   * Supported SASL mechanisms.
146   */
147 STRING_LIST *smtp_sasl_mechs;
148 
149  /*
150   * SASL implementation handle.
151   */
152 static XSASL_CLIENT_IMPL *smtp_sasl_impl;
153 
154  /*
155   * The 535 SASL authentication failure cache.
156   */
157 #ifdef HAVE_SASL_AUTH_CACHE
158 static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;
159 
160 #endif
161 
162 /* smtp_sasl_passwd_lookup - password lookup routine */
163 
smtp_sasl_passwd_lookup(SMTP_SESSION * session)164 int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
165 {
166     const char *myname = "smtp_sasl_passwd_lookup";
167     SMTP_STATE *state = session->state;
168     SMTP_ITERATOR *iter = session->iterator;
169     const char *value;
170     char   *passwd;
171 
172     /*
173      * Sanity check.
174      */
175     if (smtp_sasl_passwd_map == 0)
176 	msg_panic("%s: passwd map not initialized", myname);
177 
178     /*
179      * Look up the per-server password information. Try the hostname first,
180      * then try the destination.
181      *
182      * XXX Instead of using nexthop (the intended destination) we use dest
183      * (either the intended destination, or a fall-back destination).
184      *
185      * XXX SASL authentication currently depends on the host/domain but not on
186      * the TCP port. If the port is not :25, we should append it to the table
187      * lookup key. Code for this was briefly introduced into 2.2 snapshots,
188      * but didn't canonicalize the TCP port, and did not append the port to
189      * the MX hostname.
190      */
191     smtp_sasl_passwd_map->error = 0;
192     if ((smtp_mode
193 	 && var_smtp_sender_auth && state->request->sender[0]
194 	 && (value = mail_addr_find(smtp_sasl_passwd_map,
195 				 state->request->sender, (char **) 0)) != 0)
196 	|| (smtp_sasl_passwd_map->error == 0
197 	    && (value = maps_find(smtp_sasl_passwd_map,
198 				  STR(iter->host), 0)) != 0)
199 	|| (smtp_sasl_passwd_map->error == 0
200 	    && (value = maps_find(smtp_sasl_passwd_map,
201 				  STR(iter->dest), 0)) != 0)) {
202 	if (session->sasl_username)
203 	    myfree(session->sasl_username);
204 	session->sasl_username = mystrdup(value);
205 	passwd = split_at(session->sasl_username, ':');
206 	if (session->sasl_passwd)
207 	    myfree(session->sasl_passwd);
208 	session->sasl_passwd = mystrdup(passwd ? passwd : "");
209 	if (msg_verbose)
210 	    msg_info("%s: host `%s' user `%s' pass `%s'",
211 		     myname, STR(iter->host),
212 		     session->sasl_username, session->sasl_passwd);
213 	return (1);
214     } else if (smtp_sasl_passwd_map->error) {
215 	msg_warn("%s: %s lookup error",
216 		 state->request->queue_id, smtp_sasl_passwd_map->title);
217 	vstream_longjmp(session->stream, SMTP_ERR_DATA);
218     } else {
219 	if (msg_verbose)
220 	    msg_info("%s: no auth info found (sender=`%s', host=`%s')",
221 		     myname, state->request->sender, STR(iter->host));
222 	return (0);
223     }
224 }
225 
226 /* smtp_sasl_initialize - per-process initialization (pre jail) */
227 
smtp_sasl_initialize(void)228 void    smtp_sasl_initialize(void)
229 {
230 
231     /*
232      * Sanity check.
233      */
234     if (smtp_sasl_passwd_map || smtp_sasl_impl)
235 	msg_panic("smtp_sasl_initialize: repeated call");
236     if (*var_smtp_sasl_passwd == 0)
237 	msg_fatal("specify a password table via the `%s' configuration parameter",
238 		  VAR_LMTP_SMTP(SASL_PASSWD));
239 
240     /*
241      * Open the per-host password table and initialize the SASL library. Use
242      * shared locks for reading, just in case someone updates the table.
243      */
244     smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD),
245 				       var_smtp_sasl_passwd,
246 				       DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
247 				       | DICT_FLAG_UTF8_REQUEST);
248     if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
249 					    var_smtp_sasl_path)) == 0)
250 	msg_fatal("SASL library initialization");
251 
252     /*
253      * Initialize optional supported mechanism matchlist
254      */
255     if (*var_smtp_sasl_mechs)
256 	smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS,
257 					   MATCH_FLAG_NONE,
258 					   var_smtp_sasl_mechs);
259 
260     /*
261      * Initialize the 535 SASL authentication failure cache.
262      */
263     if (*var_smtp_sasl_auth_cache_name) {
264 #ifdef HAVE_SASL_AUTH_CACHE
265 	smtp_sasl_auth_cache =
266 	    smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name,
267 				      var_smtp_sasl_auth_cache_time);
268 #else
269 	msg_warn("not compiled with TLS support -- "
270 	    "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME));
271 #endif
272     }
273 }
274 
275 /* smtp_sasl_connect - per-session client initialization */
276 
smtp_sasl_connect(SMTP_SESSION * session)277 void    smtp_sasl_connect(SMTP_SESSION *session)
278 {
279 
280     /*
281      * This initialization happens whenever we instantiate an SMTP session
282      * object. We don't instantiate a SASL client until we actually need one.
283      */
284     session->sasl_mechanism_list = 0;
285     session->sasl_username = 0;
286     session->sasl_passwd = 0;
287     session->sasl_client = 0;
288     session->sasl_reply = 0;
289 }
290 
291 /* smtp_sasl_start - per-session SASL initialization */
292 
smtp_sasl_start(SMTP_SESSION * session,const char * sasl_opts_name,const char * sasl_opts_val)293 void    smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
294 			        const char *sasl_opts_val)
295 {
296     XSASL_CLIENT_CREATE_ARGS create_args;
297     SMTP_ITERATOR *iter = session->iterator;
298 
299     if (msg_verbose)
300 	msg_info("starting new SASL client");
301     if ((session->sasl_client =
302 	 XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
303 			     stream = session->stream,
304 			     service = var_procname,
305 			     server_name = STR(iter->host),
306 			     security_options = sasl_opts_val)) == 0)
307 	msg_fatal("SASL per-connection initialization failed");
308     session->sasl_reply = vstring_alloc(20);
309 }
310 
311 /* smtp_sasl_authenticate - run authentication protocol */
312 
smtp_sasl_authenticate(SMTP_SESSION * session,DSN_BUF * why)313 int     smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
314 {
315     const char *myname = "smtp_sasl_authenticate";
316     SMTP_ITERATOR *iter = session->iterator;
317     SMTP_RESP *resp;
318     const char *mechanism;
319     int     result;
320     char   *line;
321     int     steps = 0;
322 
323     /*
324      * Sanity check.
325      */
326     if (session->sasl_mechanism_list == 0)
327 	msg_panic("%s: no mechanism list", myname);
328 
329     if (msg_verbose)
330 	msg_info("%s: %s: SASL mechanisms %s",
331 		 myname, session->namaddrport, session->sasl_mechanism_list);
332 
333     /*
334      * Avoid repeated login failures after a recent 535 error.
335      */
336 #ifdef HAVE_SASL_AUTH_CACHE
337     if (smtp_sasl_auth_cache
338 	&& smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
339 	char   *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
340 	char   *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);
341 
342 	if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
343 	    resp_dsn[0] = '4';
344 	dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
345 		   STR(iter->host), var_procname, resp_str,
346 		   "SASL [CACHED] authentication failed; server %s said: %s",
347 		   STR(iter->host), resp_str);
348 	return (0);
349     }
350 #endif
351 
352     /*
353      * Start the client side authentication protocol.
354      */
355     result = xsasl_client_first(session->sasl_client,
356 				session->sasl_mechanism_list,
357 				session->sasl_username,
358 				session->sasl_passwd,
359 				&mechanism, session->sasl_reply);
360     if (result != XSASL_AUTH_OK) {
361 	dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
362 		   DSB_DTYPE_SASL, STR(session->sasl_reply),
363 		   "SASL authentication failed; "
364 		   "cannot authenticate to server %s: %s",
365 		   session->namaddr, STR(session->sasl_reply));
366 	return (-1);
367     }
368     /*-
369      * Send the AUTH command and the optional initial client response.
370      *
371      * https://tools.ietf.org/html/rfc4954#page-4
372      * Note that the AUTH command is still subject to the line length
373      * limitations defined in [SMTP].  If use of the initial response argument
374      * would cause the AUTH command to exceed this length, the client MUST NOT
375      * use the initial response parameter...
376      *
377      * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4
378      * The maximum total length of a command line including the command word
379      * and the <CRLF> is 512 octets.
380      *
381      * Defer the initial response if the resulting command exceeds the limit.
382      */
383     if (LEN(session->sasl_reply) > 0
384 	&& strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) {
385 	smtp_chat_cmd(session, "AUTH %s %s", mechanism,
386 		      STR(session->sasl_reply));
387 	VSTRING_RESET(session->sasl_reply);	/* no deferred initial reply */
388     } else {
389 	smtp_chat_cmd(session, "AUTH %s", mechanism);
390     }
391 
392     /*
393      * Step through the authentication protocol until the server tells us
394      * that we are done.  If session->sasl_reply is non-empty we have a
395      * deferred initial reply and expect an empty initial challenge from the
396      * server. If the server's initial challenge is non-empty we have a SASL
397      * protocol violation with both sides wanting to go first.
398      */
399     while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
400 
401 	/*
402 	 * Sanity check.
403 	 */
404 	if (++steps > 100) {
405 	    dsb_simple(why, "4.3.0", "SASL authentication failed; "
406 		       "authentication protocol loop with server %s",
407 		       session->namaddr);
408 	    return (-1);
409 	}
410 
411 	/*
412 	 * Process a server challenge.
413 	 */
414 	line = resp->str;
415 	(void) mystrtok(&line, "- \t\n");	/* skip over result code */
416 
417 	if (LEN(session->sasl_reply) > 0) {
418 
419 	    /*
420 	     * Deferred initial response, the server challenge must be empty.
421 	     * Cleared after actual transmission to the server.
422 	     */
423 	    if (*line) {
424 		dsb_update(why, "4.7.0", DSB_DEF_ACTION,
425 			   DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error",
426 			   "SASL authentication failed; non-empty initial "
427 			   "%s challenge from server %s: %s", mechanism,
428 			   session->namaddr, STR(session->sasl_reply));
429 		return (-1);
430 	    }
431 	} else {
432 	    result = xsasl_client_next(session->sasl_client, line,
433 				       session->sasl_reply);
434 	    if (result != XSASL_AUTH_OK) {
435 		dsb_update(why, "4.7.0", DSB_DEF_ACTION,	/* Fix 200512 */
436 		    DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
437 			   "SASL authentication failed; "
438 			   "cannot authenticate to server %s: %s",
439 			   session->namaddr, STR(session->sasl_reply));
440 		return (-1);			/* Fix 200512 */
441 	    }
442 	}
443 
444 	/*
445 	 * Send a client response.
446 	 */
447 	smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
448 	VSTRING_RESET(session->sasl_reply);	/* clear initial reply */
449     }
450 
451     /*
452      * We completed the authentication protocol.
453      */
454     if (resp->code / 100 != 2) {
455 #ifdef HAVE_SASL_AUTH_CACHE
456 	/* Update the 535 authentication failure cache. */
457 	if (smtp_sasl_auth_cache && resp->code == 535)
458 	    smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
459 #endif
460 	if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
461 	    STR(resp->dsn_buf)[0] = '4';
462 	dsb_update(why, resp->dsn, DSB_DEF_ACTION,
463 		   DSB_MTYPE_DNS, STR(iter->host),
464 		   var_procname, resp->str,
465 		   "SASL authentication failed; server %s said: %s",
466 		   session->namaddr, resp->str);
467 	return (0);
468     }
469     return (1);
470 }
471 
472 /* smtp_sasl_cleanup - per-session cleanup */
473 
smtp_sasl_cleanup(SMTP_SESSION * session)474 void    smtp_sasl_cleanup(SMTP_SESSION *session)
475 {
476     if (session->sasl_username) {
477 	myfree(session->sasl_username);
478 	session->sasl_username = 0;
479     }
480     if (session->sasl_passwd) {
481 	myfree(session->sasl_passwd);
482 	session->sasl_passwd = 0;
483     }
484     if (session->sasl_mechanism_list) {
485 	/* allocated in smtp_sasl_helo_auth */
486 	myfree(session->sasl_mechanism_list);
487 	session->sasl_mechanism_list = 0;
488     }
489     if (session->sasl_client) {
490 	if (msg_verbose)
491 	    msg_info("disposing SASL state information");
492 	xsasl_client_free(session->sasl_client);
493 	session->sasl_client = 0;
494     }
495     if (session->sasl_reply) {
496 	vstring_free(session->sasl_reply);
497 	session->sasl_reply = 0;
498     }
499 }
500 
501 /* smtp_sasl_passivate - append serialized SASL attributes */
502 
smtp_sasl_passivate(SMTP_SESSION * session,VSTRING * buf)503 void    smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
504 {
505 }
506 
507 /* smtp_sasl_activate - de-serialize SASL attributes */
508 
smtp_sasl_activate(SMTP_SESSION * session,char * buf)509 int     smtp_sasl_activate(SMTP_SESSION *session, char *buf)
510 {
511     return (0);
512 }
513 
514 #endif
515