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