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