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