1/* $NetBSD: smtp_sasl_glue.c,v 1.3 2020/03/18 19:05:20 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 */ 142static MAPS *smtp_sasl_passwd_map; 143 144 /* 145 * Supported SASL mechanisms. 146 */ 147STRING_LIST *smtp_sasl_mechs; 148 149 /* 150 * SASL implementation handle. 151 */ 152static XSASL_CLIENT_IMPL *smtp_sasl_impl; 153 154 /* 155 * The 535 SASL authentication failure cache. 156 */ 157#ifdef HAVE_SASL_AUTH_CACHE 158static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache; 159 160#endif 161 162/* smtp_sasl_passwd_lookup - password lookup routine */ 163 164int 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 passwd = split_at(session->sasl_username, ':'); 206 if (session->sasl_passwd) 207 myfree(session->sasl_passwd); 208 session->sasl_passwd = mystrdup(passwd ? passwd : ""); 209 if (msg_verbose) 210 msg_info("%s: host `%s' user `%s' pass `%s'", 211 myname, STR(iter->host), 212 session->sasl_username, session->sasl_passwd); 213 return (1); 214 } else if (smtp_sasl_passwd_map->error) { 215 msg_warn("%s: %s lookup error", 216 state->request->queue_id, smtp_sasl_passwd_map->title); 217 vstream_longjmp(session->stream, SMTP_ERR_DATA); 218 } else { 219 if (msg_verbose) 220 msg_info("%s: no auth info found (sender=`%s', host=`%s')", 221 myname, state->request->sender, STR(iter->host)); 222 return (0); 223 } 224} 225 226/* smtp_sasl_initialize - per-process initialization (pre jail) */ 227 228void smtp_sasl_initialize(void) 229{ 230 231 /* 232 * Sanity check. 233 */ 234 if (smtp_sasl_passwd_map || smtp_sasl_impl) 235 msg_panic("smtp_sasl_initialize: repeated call"); 236 if (*var_smtp_sasl_passwd == 0) 237 msg_fatal("specify a password table via the `%s' configuration parameter", 238 VAR_LMTP_SMTP(SASL_PASSWD)); 239 240 /* 241 * Open the per-host password table and initialize the SASL library. Use 242 * shared locks for reading, just in case someone updates the table. 243 */ 244 smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD), 245 var_smtp_sasl_passwd, 246 DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX 247 | DICT_FLAG_UTF8_REQUEST); 248 if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type, 249 var_smtp_sasl_path)) == 0) 250 msg_fatal("SASL library initialization"); 251 252 /* 253 * Initialize optional supported mechanism matchlist 254 */ 255 if (*var_smtp_sasl_mechs) 256 smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS, 257 MATCH_FLAG_NONE, 258 var_smtp_sasl_mechs); 259 260 /* 261 * Initialize the 535 SASL authentication failure cache. 262 */ 263 if (*var_smtp_sasl_auth_cache_name) { 264#ifdef HAVE_SASL_AUTH_CACHE 265 smtp_sasl_auth_cache = 266 smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name, 267 var_smtp_sasl_auth_cache_time); 268#else 269 msg_warn("not compiled with TLS support -- " 270 "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME)); 271#endif 272 } 273} 274 275/* smtp_sasl_connect - per-session client initialization */ 276 277void smtp_sasl_connect(SMTP_SESSION *session) 278{ 279 280 /* 281 * This initialization happens whenever we instantiate an SMTP session 282 * object. We don't instantiate a SASL client until we actually need one. 283 */ 284 session->sasl_mechanism_list = 0; 285 session->sasl_username = 0; 286 session->sasl_passwd = 0; 287 session->sasl_client = 0; 288 session->sasl_reply = 0; 289} 290 291/* smtp_sasl_start - per-session SASL initialization */ 292 293void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name, 294 const char *sasl_opts_val) 295{ 296 XSASL_CLIENT_CREATE_ARGS create_args; 297 SMTP_ITERATOR *iter = session->iterator; 298 299 if (msg_verbose) 300 msg_info("starting new SASL client"); 301 if ((session->sasl_client = 302 XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args, 303 stream = session->stream, 304 service = var_procname, 305 server_name = STR(iter->host), 306 security_options = sasl_opts_val)) == 0) 307 msg_fatal("SASL per-connection initialization failed"); 308 session->sasl_reply = vstring_alloc(20); 309} 310 311/* smtp_sasl_authenticate - run authentication protocol */ 312 313int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) 314{ 315 const char *myname = "smtp_sasl_authenticate"; 316 SMTP_ITERATOR *iter = session->iterator; 317 SMTP_RESP *resp; 318 const char *mechanism; 319 int result; 320 char *line; 321 int steps = 0; 322 323 /* 324 * Sanity check. 325 */ 326 if (session->sasl_mechanism_list == 0) 327 msg_panic("%s: no mechanism list", myname); 328 329 if (msg_verbose) 330 msg_info("%s: %s: SASL mechanisms %s", 331 myname, session->namaddrport, session->sasl_mechanism_list); 332 333 /* 334 * Avoid repeated login failures after a recent 535 error. 335 */ 336#ifdef HAVE_SASL_AUTH_CACHE 337 if (smtp_sasl_auth_cache 338 && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) { 339 char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache); 340 char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache); 341 342 if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5') 343 resp_dsn[0] = '4'; 344 dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, 345 STR(iter->host), var_procname, resp_str, 346 "SASL [CACHED] authentication failed; server %s said: %s", 347 STR(iter->host), resp_str); 348 return (0); 349 } 350#endif 351 352 /* 353 * Start the client side authentication protocol. 354 */ 355 result = xsasl_client_first(session->sasl_client, 356 session->sasl_mechanism_list, 357 session->sasl_username, 358 session->sasl_passwd, 359 &mechanism, session->sasl_reply); 360 if (result != XSASL_AUTH_OK) { 361 dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA, 362 DSB_DTYPE_SASL, STR(session->sasl_reply), 363 "SASL authentication failed; " 364 "cannot authenticate to server %s: %s", 365 session->namaddr, STR(session->sasl_reply)); 366 return (-1); 367 } 368 /*- 369 * Send the AUTH command and the optional initial client response. 370 * 371 * https://tools.ietf.org/html/rfc4954#page-4 372 * Note that the AUTH command is still subject to the line length 373 * limitations defined in [SMTP]. If use of the initial response argument 374 * would cause the AUTH command to exceed this length, the client MUST NOT 375 * use the initial response parameter... 376 * 377 * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4 378 * The maximum total length of a command line including the command word 379 * and the <CRLF> is 512 octets. 380 * 381 * Defer the initial response if the resulting command exceeds the limit. 382 */ 383 if (LEN(session->sasl_reply) > 0 384 && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) { 385 smtp_chat_cmd(session, "AUTH %s %s", mechanism, 386 STR(session->sasl_reply)); 387 VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */ 388 } else { 389 smtp_chat_cmd(session, "AUTH %s", mechanism); 390 } 391 392 /* 393 * Step through the authentication protocol until the server tells us 394 * that we are done. If session->sasl_reply is non-empty we have a 395 * deferred initial reply and expect an empty initial challenge from the 396 * server. If the server's initial challenge is non-empty we have a SASL 397 * protocol violation with both sides wanting to go first. 398 */ 399 while ((resp = smtp_chat_resp(session))->code / 100 == 3) { 400 401 /* 402 * Sanity check. 403 */ 404 if (++steps > 100) { 405 dsb_simple(why, "4.3.0", "SASL authentication failed; " 406 "authentication protocol loop with server %s", 407 session->namaddr); 408 return (-1); 409 } 410 411 /* 412 * Process a server challenge. 413 */ 414 line = resp->str; 415 (void) mystrtok(&line, "- \t\n"); /* skip over result code */ 416 417 if (LEN(session->sasl_reply) > 0) { 418 419 /* 420 * Deferred initial response, the server challenge must be empty. 421 * Cleared after actual transmission to the server. 422 */ 423 if (*line) { 424 dsb_update(why, "4.7.0", DSB_DEF_ACTION, 425 DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error", 426 "SASL authentication failed; non-empty initial " 427 "%s challenge from server %s: %s", mechanism, 428 session->namaddr, STR(session->sasl_reply)); 429 return (-1); 430 } 431 } else { 432 result = xsasl_client_next(session->sasl_client, line, 433 session->sasl_reply); 434 if (result != XSASL_AUTH_OK) { 435 dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ 436 DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), 437 "SASL authentication failed; " 438 "cannot authenticate to server %s: %s", 439 session->namaddr, STR(session->sasl_reply)); 440 return (-1); /* Fix 200512 */ 441 } 442 } 443 444 /* 445 * Send a client response. 446 */ 447 smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); 448 VSTRING_RESET(session->sasl_reply); /* clear initial reply */ 449 } 450 451 /* 452 * We completed the authentication protocol. 453 */ 454 if (resp->code / 100 != 2) { 455#ifdef HAVE_SASL_AUTH_CACHE 456 /* Update the 535 authentication failure cache. */ 457 if (smtp_sasl_auth_cache && resp->code == 535) 458 smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp); 459#endif 460 if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5) 461 STR(resp->dsn_buf)[0] = '4'; 462 dsb_update(why, resp->dsn, DSB_DEF_ACTION, 463 DSB_MTYPE_DNS, STR(iter->host), 464 var_procname, resp->str, 465 "SASL authentication failed; server %s said: %s", 466 session->namaddr, resp->str); 467 return (0); 468 } 469 return (1); 470} 471 472/* smtp_sasl_cleanup - per-session cleanup */ 473 474void smtp_sasl_cleanup(SMTP_SESSION *session) 475{ 476 if (session->sasl_username) { 477 myfree(session->sasl_username); 478 session->sasl_username = 0; 479 } 480 if (session->sasl_passwd) { 481 myfree(session->sasl_passwd); 482 session->sasl_passwd = 0; 483 } 484 if (session->sasl_mechanism_list) { 485 /* allocated in smtp_sasl_helo_auth */ 486 myfree(session->sasl_mechanism_list); 487 session->sasl_mechanism_list = 0; 488 } 489 if (session->sasl_client) { 490 if (msg_verbose) 491 msg_info("disposing SASL state information"); 492 xsasl_client_free(session->sasl_client); 493 session->sasl_client = 0; 494 } 495 if (session->sasl_reply) { 496 vstring_free(session->sasl_reply); 497 session->sasl_reply = 0; 498 } 499} 500 501/* smtp_sasl_passivate - append serialized SASL attributes */ 502 503void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf) 504{ 505} 506 507/* smtp_sasl_activate - de-serialize SASL attributes */ 508 509int smtp_sasl_activate(SMTP_SESSION *session, char *buf) 510{ 511 return (0); 512} 513 514#endif 515