summaryrefslogtreecommitdiffstats
path: root/src/smtp/smtp_sasl_glue.c
blob: ef8e8c4423c4acae49c22249f47362d0390c223a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
/*++
/* NAME
/*	smtp_sasl_glue 3
/* SUMMARY
/*	Postfix SASL interface for SMTP client
/* SYNOPSIS
/*	#include smtp_sasl.h
/*
/*	void	smtp_sasl_initialize()
/*
/*	void	smtp_sasl_connect(session)
/*	SMTP_SESSION *session;
/*
/*	void	smtp_sasl_start(session, sasl_opts_name, sasl_opts_val)
/*	SMTP_SESSION *session;
/*
/*	int     smtp_sasl_passwd_lookup(session)
/*	SMTP_SESSION *session;
/*
/*	int	smtp_sasl_authenticate(session, why)
/*	SMTP_SESSION *session;
/*	DSN_BUF *why;
/*
/*	void	smtp_sasl_cleanup(session)
/*	SMTP_SESSION *session;
/*
/*	void	smtp_sasl_passivate(session, buf)
/*	SMTP_SESSION *session;
/*	VSTRING	*buf;
/*
/*	int	smtp_sasl_activate(session, buf)
/*	SMTP_SESSION *session;
/*	char	*buf;
/* DESCRIPTION
/*	smtp_sasl_initialize() initializes the SASL library. This
/*	routine must be called once at process startup, before any
/*	chroot operations.
/*
/*	smtp_sasl_connect() performs per-session initialization. This
/*	routine must be called once at the start of each connection.
/*
/*	smtp_sasl_start() performs per-session initialization. This
/*	routine must be called once per session before doing any SASL
/*	authentication. The sasl_opts_name and sasl_opts_val parameters are
/*	the postfix configuration parameters setting the security
/*	policy of the SASL authentication.
/*
/*	smtp_sasl_passwd_lookup() looks up the username/password
/*	for the current SMTP server. The result is zero in case
/*	of failure, a long jump in case of error.
/*
/*	smtp_sasl_authenticate() implements the SASL authentication
/*	dialog. The result is < 0 in case of protocol failure, zero in
/*	case of unsuccessful authentication, > 0 in case of success.
/*	The why argument is updated with a reason for failure.
/*	This routine must be called only when smtp_sasl_passwd_lookup()
/*	succeeds.
/*
/*	smtp_sasl_cleanup() cleans up. It must be called at the
/*	end of every SMTP session that uses SASL authentication.
/*	This routine is a noop for non-SASL sessions.
/*
/*	smtp_sasl_passivate() appends flattened SASL attributes to the
/*	specified buffer. The SASL attributes are not destroyed.
/*
/*	smtp_sasl_activate() restores SASL attributes from the
/*	specified buffer. The buffer is modified. A result < 0
/*	means there was an error.
/*
/*	Arguments:
/* .IP session
/*	Session context.
/* .IP mech_list
/*	String of SASL mechanisms (separated by blanks)
/* DIAGNOSTICS
/*	All errors are fatal.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Original author:
/*	Till Franke
/*	SuSE Rhein/Main AG
/*	65760 Eschborn, Germany
/*
/*	Adopted by:
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

 /*
  * System library.
  */
#include <sys_defs.h>
#include <stdlib.h>
#include <string.h>

 /*
  * Utility library
  */
#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <split_at.h>

 /*
  * Global library
  */
#include <mail_params.h>
#include <string_list.h>
#include <maps.h>
#include <mail_addr_find.h>
#include <smtp_stream.h>

 /*
  * XSASL library.
  */
#include <xsasl.h>

 /*
  * Application-specific
  */
#include "smtp.h"
#include "smtp_sasl.h"
#include "smtp_sasl_auth_cache.h"

#ifdef USE_SASL_AUTH

 /*
  * Per-host login/password information.
  */
static MAPS *smtp_sasl_passwd_map;

 /*
  * Supported SASL mechanisms.
  */
STRING_LIST *smtp_sasl_mechs;

 /*
  * SASL implementation handle.
  */
static XSASL_CLIENT_IMPL *smtp_sasl_impl;

 /*
  * The 535 SASL authentication failure cache.
  */
#ifdef HAVE_SASL_AUTH_CACHE
static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache;

#endif

/* smtp_sasl_passwd_lookup - password lookup routine */

int     smtp_sasl_passwd_lookup(SMTP_SESSION *session)
{
    const char *myname = "smtp_sasl_passwd_lookup";
    SMTP_STATE *state = session->state;
    SMTP_ITERATOR *iter = session->iterator;
    const char *value;
    char   *passwd;

    /*
     * Sanity check.
     */
    if (smtp_sasl_passwd_map == 0)
	msg_panic("%s: passwd map not initialized", myname);

    /*
     * Look up the per-server password information. Try the hostname first,
     * then try the destination.
     * 
     * XXX Instead of using nexthop (the intended destination) we use dest
     * (either the intended destination, or a fall-back destination).
     * 
     * XXX SASL authentication currently depends on the host/domain but not on
     * the TCP port. If the port is not :25, we should append it to the table
     * lookup key. Code for this was briefly introduced into 2.2 snapshots,
     * but didn't canonicalize the TCP port, and did not append the port to
     * the MX hostname.
     */
    smtp_sasl_passwd_map->error = 0;
    if ((smtp_mode
	 && var_smtp_sender_auth && state->request->sender[0]
	 && (value = mail_addr_find(smtp_sasl_passwd_map,
				 state->request->sender, (char **) 0)) != 0)
	|| (smtp_sasl_passwd_map->error == 0
	    && (value = maps_find(smtp_sasl_passwd_map,
				  STR(iter->host), 0)) != 0)
	|| (smtp_sasl_passwd_map->error == 0
	    && (value = maps_find(smtp_sasl_passwd_map,
				  STR(iter->dest), 0)) != 0)) {
	if (session->sasl_username)
	    myfree(session->sasl_username);
	session->sasl_username = mystrdup(value);
	passwd = split_at(session->sasl_username, ':');
	if (session->sasl_passwd)
	    myfree(session->sasl_passwd);
	session->sasl_passwd = mystrdup(passwd ? passwd : "");
	if (msg_verbose)
	    msg_info("%s: host `%s' user `%s' pass `%s'",
		     myname, STR(iter->host),
		     session->sasl_username, session->sasl_passwd);
	return (1);
    } else if (smtp_sasl_passwd_map->error) {
	msg_warn("%s: %s lookup error",
		 state->request->queue_id, smtp_sasl_passwd_map->title);
	vstream_longjmp(session->stream, SMTP_ERR_DATA);
    } else {
	if (msg_verbose)
	    msg_info("%s: no auth info found (sender=`%s', host=`%s')",
		     myname, state->request->sender, STR(iter->host));
	return (0);
    }
}

/* smtp_sasl_initialize - per-process initialization (pre jail) */

void    smtp_sasl_initialize(void)
{

    /*
     * Sanity check.
     */
    if (smtp_sasl_passwd_map || smtp_sasl_impl)
	msg_panic("smtp_sasl_initialize: repeated call");
    if (*var_smtp_sasl_passwd == 0)
	msg_fatal("specify a password table via the `%s' configuration parameter",
		  VAR_LMTP_SMTP(SASL_PASSWD));

    /*
     * Open the per-host password table and initialize the SASL library. Use
     * shared locks for reading, just in case someone updates the table.
     */
    smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD),
				       var_smtp_sasl_passwd,
				       DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
				       | DICT_FLAG_UTF8_REQUEST);
    if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type,
					    var_smtp_sasl_path)) == 0)
	msg_fatal("SASL library initialization");

    /*
     * Initialize optional supported mechanism matchlist
     */
    if (*var_smtp_sasl_mechs)
	smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS,
					   MATCH_FLAG_NONE,
					   var_smtp_sasl_mechs);

    /*
     * Initialize the 535 SASL authentication failure cache.
     */
    if (*var_smtp_sasl_auth_cache_name) {
#ifdef HAVE_SASL_AUTH_CACHE
	smtp_sasl_auth_cache =
	    smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name,
				      var_smtp_sasl_auth_cache_time);
#else
	msg_warn("not compiled with TLS support -- "
	    "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME));
#endif
    }
}

/* smtp_sasl_connect - per-session client initialization */

void    smtp_sasl_connect(SMTP_SESSION *session)
{

    /*
     * This initialization happens whenever we instantiate an SMTP session
     * object. We don't instantiate a SASL client until we actually need one.
     */
    session->sasl_mechanism_list = 0;
    session->sasl_username = 0;
    session->sasl_passwd = 0;
    session->sasl_client = 0;
    session->sasl_reply = 0;
}

/* smtp_sasl_start - per-session SASL initialization */

void    smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name,
			        const char *sasl_opts_val)
{
    XSASL_CLIENT_CREATE_ARGS create_args;
    SMTP_ITERATOR *iter = session->iterator;

    if (msg_verbose)
	msg_info("starting new SASL client");
    if ((session->sasl_client =
	 XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args,
			     stream = session->stream,
			     service = var_procname,
			     server_name = STR(iter->host),
			     security_options = sasl_opts_val)) == 0)
	msg_fatal("SASL per-connection initialization failed");
    session->sasl_reply = vstring_alloc(20);
}

/* smtp_sasl_authenticate - run authentication protocol */

int     smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
{
    const char *myname = "smtp_sasl_authenticate";
    SMTP_ITERATOR *iter = session->iterator;
    SMTP_RESP *resp;
    const char *mechanism;
    int     result;
    char   *line;
    int     steps = 0;

    /*
     * Sanity check.
     */
    if (session->sasl_mechanism_list == 0)
	msg_panic("%s: no mechanism list", myname);

    if (msg_verbose)
	msg_info("%s: %s: SASL mechanisms %s",
		 myname, session->namaddrport, session->sasl_mechanism_list);

    /*
     * Avoid repeated login failures after a recent 535 error.
     */
#ifdef HAVE_SASL_AUTH_CACHE
    if (smtp_sasl_auth_cache
	&& smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
	char   *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
	char   *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);

	if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
	    resp_dsn[0] = '4';
	dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
		   STR(iter->host), var_procname, resp_str,
		   "SASL [CACHED] authentication failed; server %s said: %s",
		   STR(iter->host), resp_str);
	return (0);
    }
#endif

    /*
     * Start the client side authentication protocol.
     */
    result = xsasl_client_first(session->sasl_client,
				session->sasl_mechanism_list,
				session->sasl_username,
				session->sasl_passwd,
				&mechanism, session->sasl_reply);
    if (result != XSASL_AUTH_OK) {
	dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
		   DSB_DTYPE_SASL, STR(session->sasl_reply),
		   "SASL authentication failed; "
		   "cannot authenticate to server %s: %s",
		   session->namaddr, STR(session->sasl_reply));
	return (-1);
    }
    /*-
     * Send the AUTH command and the optional initial client response.
     *
     * https://tools.ietf.org/html/rfc4954#page-4
     * Note that the AUTH command is still subject to the line length
     * limitations defined in [SMTP].  If use of the initial response argument
     * would cause the AUTH command to exceed this length, the client MUST NOT
     * use the initial response parameter...
     *
     * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4
     * The maximum total length of a command line including the command word
     * and the <CRLF> is 512 octets.
     *
     * Defer the initial response if the resulting command exceeds the limit.
     */
    if (LEN(session->sasl_reply) > 0
	&& strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) {
	smtp_chat_cmd(session, "AUTH %s %s", mechanism,
		      STR(session->sasl_reply));
	VSTRING_RESET(session->sasl_reply);	/* no deferred initial reply */
    } else {
	smtp_chat_cmd(session, "AUTH %s", mechanism);
    }

    /*
     * Step through the authentication protocol until the server tells us
     * that we are done.  If session->sasl_reply is non-empty we have a
     * deferred initial reply and expect an empty initial challenge from the
     * server. If the server's initial challenge is non-empty we have a SASL
     * protocol violation with both sides wanting to go first.
     */
    while ((resp = smtp_chat_resp(session))->code / 100 == 3) {

	/*
	 * Sanity check.
	 */
	if (++steps > 100) {
	    dsb_simple(why, "4.3.0", "SASL authentication failed; "
		       "authentication protocol loop with server %s",
		       session->namaddr);
	    return (-1);
	}

	/*
	 * Process a server challenge.
	 */
	line = resp->str;
	(void) mystrtok(&line, "- \t\n");	/* skip over result code */

	if (LEN(session->sasl_reply) > 0) {

	    /*
	     * Deferred initial response, the server challenge must be empty.
	     * Cleared after actual transmission to the server.
	     */
	    if (*line) {
		dsb_update(why, "4.7.0", DSB_DEF_ACTION,
			   DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error",
			   "SASL authentication failed; non-empty initial "
			   "%s challenge from server %s: %s", mechanism,
			   session->namaddr, STR(session->sasl_reply));
		return (-1);
	    }
	} else {
	    result = xsasl_client_next(session->sasl_client, line,
				       session->sasl_reply);
	    if (result != XSASL_AUTH_OK) {
		dsb_update(why, "4.7.0", DSB_DEF_ACTION,	/* Fix 200512 */
		    DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
			   "SASL authentication failed; "
			   "cannot authenticate to server %s: %s",
			   session->namaddr, STR(session->sasl_reply));
		return (-1);			/* Fix 200512 */
	    }
	}

	/*
	 * Send a client response.
	 */
	smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
	VSTRING_RESET(session->sasl_reply);	/* clear initial reply */
    }

    /*
     * We completed the authentication protocol.
     */
    if (resp->code / 100 != 2) {
#ifdef HAVE_SASL_AUTH_CACHE
	/* Update the 535 authentication failure cache. */
	if (smtp_sasl_auth_cache && resp->code == 535)
	    smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
#endif
	if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
	    STR(resp->dsn_buf)[0] = '4';
	dsb_update(why, resp->dsn, DSB_DEF_ACTION,
		   DSB_MTYPE_DNS, STR(iter->host),
		   var_procname, resp->str,
		   "SASL authentication failed; server %s said: %s",
		   session->namaddr, resp->str);
	return (0);
    }
    return (1);
}

/* smtp_sasl_cleanup - per-session cleanup */

void    smtp_sasl_cleanup(SMTP_SESSION *session)
{
    if (session->sasl_username) {
	myfree(session->sasl_username);
	session->sasl_username = 0;
    }
    if (session->sasl_passwd) {
	myfree(session->sasl_passwd);
	session->sasl_passwd = 0;
    }
    if (session->sasl_mechanism_list) {
	/* allocated in smtp_sasl_helo_auth */
	myfree(session->sasl_mechanism_list);
	session->sasl_mechanism_list = 0;
    }
    if (session->sasl_client) {
	if (msg_verbose)
	    msg_info("disposing SASL state information");
	xsasl_client_free(session->sasl_client);
	session->sasl_client = 0;
    }
    if (session->sasl_reply) {
	vstring_free(session->sasl_reply);
	session->sasl_reply = 0;
    }
}

/* smtp_sasl_passivate - append serialized SASL attributes */

void    smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf)
{
}

/* smtp_sasl_activate - de-serialize SASL attributes */

int     smtp_sasl_activate(SMTP_SESSION *session, char *buf)
{
    return (0);
}

#endif