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