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