xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtp/smtp_sasl_glue.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: smtp_sasl_glue.c,v 1.2 2017/02/14 01:16:48 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 
96  /*
97   * System library.
98   */
99 #include <sys_defs.h>
100 #include <stdlib.h>
101 #include <string.h>
102 
103  /*
104   * Utility library
105   */
106 #include <msg.h>
107 #include <mymalloc.h>
108 #include <stringops.h>
109 #include <split_at.h>
110 
111  /*
112   * Global library
113   */
114 #include <mail_params.h>
115 #include <string_list.h>
116 #include <maps.h>
117 #include <mail_addr_find.h>
118 #include <smtp_stream.h>
119 
120  /*
121   * XSASL library.
122   */
123 #include <xsasl.h>
124 
125  /*
126   * Application-specific
127   */
128 #include "smtp.h"
129 #include "smtp_sasl.h"
130 #include "smtp_sasl_auth_cache.h"
131 
132 #ifdef USE_SASL_AUTH
133 
134  /*
135   * Per-host login/password information.
136   */
137 static MAPS *smtp_sasl_passwd_map;
138 
139  /*
140   * Supported SASL mechanisms.
141   */
142 STRING_LIST *smtp_sasl_mechs;
143 
144  /*
145   * SASL implementation handle.
146   */
147 static XSASL_CLIENT_IMPL *smtp_sasl_impl;
148 
149  /*
150   * The 535 SASL authentication failure cache.
151   */
152 #ifdef HAVE_SASL_AUTH_CACHE
153 static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;
154 
155 #endif
156 
157 /* smtp_sasl_passwd_lookup - password lookup routine */
158 
159 int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
160 {
161     const char *myname = "smtp_sasl_passwd_lookup";
162     SMTP_STATE *state = session->state;
163     SMTP_ITERATOR *iter = session->iterator;
164     const char *value;
165     char   *passwd;
166 
167     /*
168      * Sanity check.
169      */
170     if (smtp_sasl_passwd_map == 0)
171 	msg_panic("%s: passwd map not initialized", myname);
172 
173     /*
174      * Look up the per-server password information. Try the hostname first,
175      * then try the destination.
176      *
177      * XXX Instead of using nexthop (the intended destination) we use dest
178      * (either the intended destination, or a fall-back destination).
179      *
180      * XXX SASL authentication currently depends on the host/domain but not on
181      * the TCP port. If the port is not :25, we should append it to the table
182      * lookup key. Code for this was briefly introduced into 2.2 snapshots,
183      * but didn't canonicalize the TCP port, and did not append the port to
184      * the MX hostname.
185      */
186     smtp_sasl_passwd_map->error = 0;
187     if ((smtp_mode
188 	 && var_smtp_sender_auth && state->request->sender[0]
189 	 && (value = mail_addr_find(smtp_sasl_passwd_map,
190 				 state->request->sender, (char **) 0)) != 0)
191 	|| (smtp_sasl_passwd_map->error == 0
192 	    && (value = maps_find(smtp_sasl_passwd_map,
193 				  STR(iter->host), 0)) != 0)
194 	|| (smtp_sasl_passwd_map->error == 0
195 	    && (value = maps_find(smtp_sasl_passwd_map,
196 				  STR(iter->dest), 0)) != 0)) {
197 	if (session->sasl_username)
198 	    myfree(session->sasl_username);
199 	session->sasl_username = mystrdup(value);
200 	passwd = split_at(session->sasl_username, ':');
201 	if (session->sasl_passwd)
202 	    myfree(session->sasl_passwd);
203 	session->sasl_passwd = mystrdup(passwd ? passwd : "");
204 	if (msg_verbose)
205 	    msg_info("%s: host `%s' user `%s' pass `%s'",
206 		     myname, STR(iter->host),
207 		     session->sasl_username, session->sasl_passwd);
208 	return (1);
209     } else if (smtp_sasl_passwd_map->error) {
210 	msg_warn("%s: %s lookup error",
211 		 state->request->queue_id, smtp_sasl_passwd_map->title);
212 	vstream_longjmp(session->stream, SMTP_ERR_DATA);
213     } else {
214 	if (msg_verbose)
215 	    msg_info("%s: no auth info found (sender=`%s', host=`%s')",
216 		     myname, state->request->sender, STR(iter->host));
217 	return (0);
218     }
219 }
220 
221 /* smtp_sasl_initialize - per-process initialization (pre jail) */
222 
223 void    smtp_sasl_initialize(void)
224 {
225 
226     /*
227      * Sanity check.
228      */
229     if (smtp_sasl_passwd_map || smtp_sasl_impl)
230 	msg_panic("smtp_sasl_initialize: repeated call");
231     if (*var_smtp_sasl_passwd == 0)
232 	msg_fatal("specify a password table via the `%s' configuration parameter",
233 		  VAR_LMTP_SMTP(SASL_PASSWD));
234 
235     /*
236      * Open the per-host password table and initialize the SASL library. Use
237      * shared locks for reading, just in case someone updates the table.
238      */
239     smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD),
240 				       var_smtp_sasl_passwd,
241 				       DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
242 				       | DICT_FLAG_UTF8_REQUEST);
243     if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
244 					    var_smtp_sasl_path)) == 0)
245 	msg_fatal("SASL library initialization");
246 
247     /*
248      * Initialize optional supported mechanism matchlist
249      */
250     if (*var_smtp_sasl_mechs)
251 	smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS,
252 					   MATCH_FLAG_NONE,
253 					   var_smtp_sasl_mechs);
254 
255     /*
256      * Initialize the 535 SASL authentication failure cache.
257      */
258     if (*var_smtp_sasl_auth_cache_name) {
259 #ifdef HAVE_SASL_AUTH_CACHE
260 	smtp_sasl_auth_cache =
261 	    smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name,
262 				      var_smtp_sasl_auth_cache_time);
263 #else
264 	msg_warn("not compiled with TLS support -- "
265 	    "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME));
266 #endif
267     }
268 }
269 
270 /* smtp_sasl_connect - per-session client initialization */
271 
272 void    smtp_sasl_connect(SMTP_SESSION *session)
273 {
274 
275     /*
276      * This initialization happens whenever we instantiate an SMTP session
277      * object. We don't instantiate a SASL client until we actually need one.
278      */
279     session->sasl_mechanism_list = 0;
280     session->sasl_username = 0;
281     session->sasl_passwd = 0;
282     session->sasl_client = 0;
283     session->sasl_reply = 0;
284 }
285 
286 /* smtp_sasl_start - per-session SASL initialization */
287 
288 void    smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
289 			        const char *sasl_opts_val)
290 {
291     XSASL_CLIENT_CREATE_ARGS create_args;
292     SMTP_ITERATOR *iter = session->iterator;
293 
294     if (msg_verbose)
295 	msg_info("starting new SASL client");
296     if ((session->sasl_client =
297 	 XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
298 			     stream = session->stream,
299 			     service = var_procname,
300 			     server_name = STR(iter->host),
301 			     security_options = sasl_opts_val)) == 0)
302 	msg_fatal("SASL per-connection initialization failed");
303     session->sasl_reply = vstring_alloc(20);
304 }
305 
306 /* smtp_sasl_authenticate - run authentication protocol */
307 
308 int     smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
309 {
310     const char *myname = "smtp_sasl_authenticate";
311     SMTP_ITERATOR *iter = session->iterator;
312     SMTP_RESP *resp;
313     const char *mechanism;
314     int     result;
315     char   *line;
316     int     steps = 0;
317 
318     /*
319      * Sanity check.
320      */
321     if (session->sasl_mechanism_list == 0)
322 	msg_panic("%s: no mechanism list", myname);
323 
324     if (msg_verbose)
325 	msg_info("%s: %s: SASL mechanisms %s",
326 		 myname, session->namaddrport, session->sasl_mechanism_list);
327 
328     /*
329      * Avoid repeated login failures after a recent 535 error.
330      */
331 #ifdef HAVE_SASL_AUTH_CACHE
332     if (smtp_sasl_auth_cache
333 	&& smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
334 	char   *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
335 	char   *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);
336 
337 	if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
338 	    resp_dsn[0] = '4';
339 	dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
340 		   STR(iter->host), var_procname, resp_str,
341 		   "SASL [CACHED] authentication failed; server %s said: %s",
342 		   STR(iter->host), resp_str);
343 	return (0);
344     }
345 #endif
346 
347     /*
348      * Start the client side authentication protocol.
349      */
350     result = xsasl_client_first(session->sasl_client,
351 				session->sasl_mechanism_list,
352 				session->sasl_username,
353 				session->sasl_passwd,
354 				&mechanism, session->sasl_reply);
355     if (result != XSASL_AUTH_OK) {
356 	dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
357 		   DSB_DTYPE_SASL, STR(session->sasl_reply),
358 		   "SASL authentication failed; "
359 		   "cannot authenticate to server %s: %s",
360 		   session->namaddr, STR(session->sasl_reply));
361 	return (-1);
362     }
363 
364     /*
365      * Send the AUTH command and the optional initial client response.
366      * sasl_encode64() produces four bytes for each complete or incomplete
367      * triple of input bytes. Allocate an extra byte for string termination.
368      */
369     if (LEN(session->sasl_reply) > 0) {
370 	smtp_chat_cmd(session, "AUTH %s %s", mechanism,
371 		      STR(session->sasl_reply));
372     } else {
373 	smtp_chat_cmd(session, "AUTH %s", mechanism);
374     }
375 
376     /*
377      * Step through the authentication protocol until the server tells us
378      * that we are done.
379      */
380     while ((resp = smtp_chat_resp(session))->code / 100 == 3) {
381 
382 	/*
383 	 * Sanity check.
384 	 */
385 	if (++steps > 100) {
386 	    dsb_simple(why, "4.3.0", "SASL authentication failed; "
387 		       "authentication protocol loop with server %s",
388 		       session->namaddr);
389 	    return (-1);
390 	}
391 
392 	/*
393 	 * Process a server challenge.
394 	 */
395 	line = resp->str;
396 	(void) mystrtok(&line, "- \t\n");	/* skip over result code */
397 	result = xsasl_client_next(session->sasl_client, line,
398 				   session->sasl_reply);
399 	if (result != XSASL_AUTH_OK) {
400 	    dsb_update(why, "4.7.0", DSB_DEF_ACTION,	/* Fix 200512 */
401 		    DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
402 		       "SASL authentication failed; "
403 		       "cannot authenticate to server %s: %s",
404 		       session->namaddr, STR(session->sasl_reply));
405 	    return (-1);			/* Fix 200512 */
406 	}
407 
408 	/*
409 	 * Send a client response.
410 	 */
411 	smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
412     }
413 
414     /*
415      * We completed the authentication protocol.
416      */
417     if (resp->code / 100 != 2) {
418 #ifdef HAVE_SASL_AUTH_CACHE
419 	/* Update the 535 authentication failure cache. */
420 	if (smtp_sasl_auth_cache && resp->code == 535)
421 	    smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
422 #endif
423 	if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
424 	    STR(resp->dsn_buf)[0] = '4';
425 	dsb_update(why, resp->dsn, DSB_DEF_ACTION,
426 		   DSB_MTYPE_DNS, STR(iter->host),
427 		   var_procname, resp->str,
428 		   "SASL authentication failed; server %s said: %s",
429 		   session->namaddr, resp->str);
430 	return (0);
431     }
432     return (1);
433 }
434 
435 /* smtp_sasl_cleanup - per-session cleanup */
436 
437 void    smtp_sasl_cleanup(SMTP_SESSION *session)
438 {
439     if (session->sasl_username) {
440 	myfree(session->sasl_username);
441 	session->sasl_username = 0;
442     }
443     if (session->sasl_passwd) {
444 	myfree(session->sasl_passwd);
445 	session->sasl_passwd = 0;
446     }
447     if (session->sasl_mechanism_list) {
448 	/* allocated in smtp_sasl_helo_auth */
449 	myfree(session->sasl_mechanism_list);
450 	session->sasl_mechanism_list = 0;
451     }
452     if (session->sasl_client) {
453 	if (msg_verbose)
454 	    msg_info("disposing SASL state information");
455 	xsasl_client_free(session->sasl_client);
456 	session->sasl_client = 0;
457     }
458     if (session->sasl_reply) {
459 	vstring_free(session->sasl_reply);
460 	session->sasl_reply = 0;
461     }
462 }
463 
464 /* smtp_sasl_passivate - append serialized SASL attributes */
465 
466 void    smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
467 {
468 }
469 
470 /* smtp_sasl_activate - de-serialize SASL attributes */
471 
472 int     smtp_sasl_activate(SMTP_SESSION *session, char *buf)
473 {
474     return (0);
475 }
476 
477 #endif
478