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