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