1 /* $NetBSD: smtp_session.c,v 1.1.1.1 2009/06/23 10:08:54 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* smtp_session 3 6 /* SUMMARY 7 /* SMTP_SESSION structure management 8 /* SYNOPSIS 9 /* #include "smtp.h" 10 /* 11 /* SMTP_SESSION *smtp_session_alloc(stream, dest, host, addr, 12 /* port, start, flags) 13 /* VSTREAM *stream; 14 /* char *dest; 15 /* char *host; 16 /* char *addr; 17 /* unsigned port; 18 /* time_t start; 19 /* int flags; 20 /* 21 /* void smtp_session_free(session) 22 /* SMTP_SESSION *session; 23 /* 24 /* int smtp_session_passivate(session, dest_prop, endp_prop) 25 /* SMTP_SESSION *session; 26 /* VSTRING *dest_prop; 27 /* VSTRING *endp_prop; 28 /* 29 /* SMTP_SESSION *smtp_session_activate(fd, dest_prop, endp_prop) 30 /* int fd; 31 /* VSTRING *dest_prop; 32 /* VSTRING *endp_prop; 33 /* DESCRIPTION 34 /* smtp_session_alloc() allocates memory for an SMTP_SESSION structure 35 /* and initializes it with the given stream and destination, host name 36 /* and address information. The host name and address strings are 37 /* copied. The port is in network byte order. 38 /* When TLS is enabled, smtp_session_alloc() looks up the 39 /* per-site TLS policies for TLS enforcement and certificate 40 /* verification. The resulting policy is stored into the 41 /* SMTP_SESSION object. 42 /* 43 /* smtp_session_free() destroys an SMTP_SESSION structure and its 44 /* members, making memory available for reuse. It will handle the 45 /* case of a null stream and will assume it was given a different 46 /* purpose. 47 /* 48 /* smtp_session_passivate() flattens an SMTP session so that 49 /* it can be cached. The SMTP_SESSION structure is destroyed. 50 /* 51 /* smtp_session_activate() inflates a flattened SMTP session 52 /* so that it can be used. The input is modified. 53 /* 54 /* Arguments: 55 /* .IP stream 56 /* A full-duplex stream. 57 /* .IP dest 58 /* The unmodified next-hop or fall-back destination including 59 /* the optional [] and including the optional port or service. 60 /* .IP host 61 /* The name of the host that we are connected to. 62 /* .IP addr 63 /* The address of the host that we are connected to. 64 /* .IP port 65 /* The remote port, network byte order. 66 /* .IP start 67 /* The time when this connection was opened. 68 /* .IP flags 69 /* Zero or more of the following: 70 /* .RS 71 /* .IP SMTP_MISC_FLAG_CONN_LOAD 72 /* Enable re-use of cached SMTP or LMTP connections. 73 /* .IP SMTP_MISC_FLAG_CONN_STORE 74 /* Enable saving of cached SMTP or LMTP connections. 75 /* .RE 76 /* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE. 77 /* .IP dest_prop 78 /* Destination specific session properties: the server is the 79 /* best MX host for the current logical destination. 80 /* .IP endp_prop 81 /* Endpoint specific session properties: all the features 82 /* advertised by the remote server. 83 /* LICENSE 84 /* .ad 85 /* .fi 86 /* The Secure Mailer license must be distributed with this software. 87 /* AUTHOR(S) 88 /* Wietse Venema 89 /* IBM T.J. Watson Research 90 /* P.O. Box 704 91 /* Yorktown Heights, NY 10598, USA 92 /* 93 /* TLS support originally by: 94 /* Lutz Jaenicke 95 /* BTU Cottbus 96 /* Allgemeine Elektrotechnik 97 /* Universitaetsplatz 3-4 98 /* D-03044 Cottbus, Germany 99 /*--*/ 100 101 /* System library. */ 102 103 #include <sys_defs.h> 104 #include <stdlib.h> 105 #include <string.h> 106 #include <netinet/in.h> 107 108 #ifdef STRCASECMP_IN_STRINGS_H 109 #include <strings.h> 110 #endif 111 112 /* Utility library. */ 113 114 #include <msg.h> 115 #include <mymalloc.h> 116 #include <vstring.h> 117 #include <vstream.h> 118 #include <stringops.h> 119 #include <valid_hostname.h> 120 #include <name_code.h> 121 122 /* Global library. */ 123 124 #include <mime_state.h> 125 #include <debug_peer.h> 126 #include <mail_params.h> 127 #include <maps.h> 128 129 /* Application-specific. */ 130 131 #include "smtp.h" 132 #include "smtp_sasl.h" 133 134 #ifdef USE_TLS 135 136 static MAPS *tls_policy; /* lookup table(s) */ 137 static MAPS *tls_per_site; /* lookup table(s) */ 138 139 /* smtp_tls_list_init - initialize per-site policy lists */ 140 141 void smtp_tls_list_init(void) 142 { 143 if (*var_smtp_tls_policy) { 144 tls_policy = maps_create(VAR_SMTP_TLS_POLICY, var_smtp_tls_policy, 145 DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); 146 if (*var_smtp_tls_per_site) 147 msg_warn("%s ignored when %s is not empty.", 148 VAR_SMTP_TLS_PER_SITE, VAR_SMTP_TLS_POLICY); 149 return; 150 } 151 if (*var_smtp_tls_per_site) { 152 tls_per_site = maps_create(VAR_SMTP_TLS_PER_SITE, var_smtp_tls_per_site, 153 DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); 154 } 155 } 156 157 /* policy_name - printable tls policy level */ 158 159 static const char *policy_name(int tls_level) 160 { 161 const char *name = str_tls_level(tls_level); 162 163 if (name == 0) 164 name = "unknown"; 165 return name; 166 } 167 168 /* tls_site_lookup - look up per-site TLS security level */ 169 170 static void tls_site_lookup(int *site_level, const char *site_name, 171 const char *site_class) 172 { 173 const char *lookup; 174 175 /* 176 * Look up a non-default policy. In case of multiple lookup results, the 177 * precedence order is a permutation of the TLS enforcement level order: 178 * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more 179 * specific policy including NONE, otherwise we choose the stronger 180 * enforcement level. 181 */ 182 if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { 183 if (!strcasecmp(lookup, "NONE")) { 184 /* NONE overrides MAY or NOTFOUND. */ 185 if (*site_level <= TLS_LEV_MAY) 186 *site_level = TLS_LEV_NONE; 187 } else if (!strcasecmp(lookup, "MAY")) { 188 /* MAY overrides NOTFOUND but not NONE. */ 189 if (*site_level < TLS_LEV_NONE) 190 *site_level = TLS_LEV_MAY; 191 } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { 192 if (*site_level < TLS_LEV_ENCRYPT) 193 *site_level = TLS_LEV_ENCRYPT; 194 } else if (!strcasecmp(lookup, "MUST")) { 195 if (*site_level < TLS_LEV_VERIFY) 196 *site_level = TLS_LEV_VERIFY; 197 } else { 198 msg_warn("Table %s: ignoring unknown TLS policy '%s' for %s %s", 199 var_smtp_tls_per_site, lookup, site_class, site_name); 200 } 201 } 202 } 203 204 /* tls_policy_lookup_one - look up destination TLS policy */ 205 206 static int tls_policy_lookup_one(SMTP_SESSION *session, int *site_level, 207 const char *site_name, 208 const char *site_class) 209 { 210 const char *lookup; 211 char *policy; 212 char *saved_policy; 213 char *tok; 214 const char *err; 215 char *name; 216 char *val; 217 static VSTRING *cbuf; 218 219 #undef FREE_RETURN 220 #define FREE_RETURN(x) do { myfree(saved_policy); return (x); } while (0) 221 222 if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) 223 return (0); 224 225 if (cbuf == 0) 226 cbuf = vstring_alloc(10); 227 228 #define WHERE \ 229 vstring_str(vstring_sprintf(cbuf, "TLS policy table, %s \"%s\"", \ 230 site_class, site_name)) 231 232 saved_policy = policy = mystrdup(lookup); 233 234 if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { 235 msg_warn("%s: invalid empty policy", WHERE); 236 *site_level = TLS_LEV_INVALID; 237 FREE_RETURN(1); /* No further lookups */ 238 } 239 *site_level = tls_level_lookup(tok); 240 if (*site_level == TLS_LEV_INVALID) { 241 /* tls_level_lookup() logs no warning. */ 242 msg_warn("%s: invalid security level \"%s\"", WHERE, tok); 243 FREE_RETURN(1); /* No further lookups */ 244 } 245 246 /* 247 * Warn about ignored attributes when TLS is disabled. 248 */ 249 if (*site_level < TLS_LEV_MAY) { 250 while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) 251 msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", 252 WHERE, tok); 253 FREE_RETURN(1); 254 } 255 256 /* 257 * Errors in attributes may have security consequences, don't ignore 258 * errors that can degrade security. 259 */ 260 while ((tok = mystrtok(&policy, "\t\n\r ,")) != 0) { 261 if ((err = split_nameval(tok, &name, &val)) != 0) { 262 *site_level = TLS_LEV_INVALID; 263 msg_warn("%s: malformed attribute/value pair \"%s\": %s", 264 WHERE, tok, err); 265 break; 266 } 267 /* Only one instance per policy. */ 268 if (!strcasecmp(name, "ciphers")) { 269 if (*val == 0) { 270 msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); 271 *site_level = TLS_LEV_INVALID; 272 break; 273 } 274 if (session->tls_grade) { 275 msg_warn("%s: attribute \"%s\" is specified multiple times", 276 WHERE, name); 277 *site_level = TLS_LEV_INVALID; 278 break; 279 } 280 session->tls_grade = mystrdup(val); 281 continue; 282 } 283 /* Only one instance per policy. */ 284 if (!strcasecmp(name, "protocols")) { 285 if (session->tls_protocols) { 286 msg_warn("%s: attribute \"%s\" is specified multiple times", 287 WHERE, name); 288 *site_level = TLS_LEV_INVALID; 289 break; 290 } 291 session->tls_protocols = mystrdup(val); 292 continue; 293 } 294 /* Multiple instance(s) per policy. */ 295 if (!strcasecmp(name, "match")) { 296 char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; 297 298 if (*site_level <= TLS_LEV_ENCRYPT) { 299 msg_warn("%s: attribute \"%s\" invalid at security level \"%s\"", 300 WHERE, name, policy_name(*site_level)); 301 *site_level = TLS_LEV_INVALID; 302 break; 303 } 304 if (*val == 0) { 305 msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); 306 *site_level = TLS_LEV_INVALID; 307 break; 308 } 309 if (session->tls_matchargv == 0) 310 session->tls_matchargv = argv_split(val, delim); 311 else 312 argv_split_append(session->tls_matchargv, val, delim); 313 continue; 314 } 315 /* Only one instance per policy. */ 316 if (!strcasecmp(name, "exclude")) { 317 if (session->tls_exclusions) { 318 msg_warn("%s: attribute \"%s\" is specified multiple times", 319 WHERE, name); 320 *site_level = TLS_LEV_INVALID; 321 break; 322 } 323 session->tls_exclusions = vstring_strcpy(vstring_alloc(10), val); 324 continue; 325 } else { 326 msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); 327 *site_level = TLS_LEV_INVALID; 328 break; 329 } 330 } 331 FREE_RETURN(1); 332 } 333 334 /* tls_policy_lookup - look up destination TLS policy */ 335 336 static void tls_policy_lookup(SMTP_SESSION *session, int *site_level, 337 const char *site_name, 338 const char *site_class) 339 { 340 341 /* 342 * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These 343 * are never the domain part of localpart@domain, rather they are 344 * explicit nexthops from transport:nexthop, and match only the 345 * corresponding policy. Parent domain matching (below) applies only to 346 * sub-domains of the recipient domain. 347 */ 348 if (!valid_hostname(site_name, DONT_GRIPE)) { 349 tls_policy_lookup_one(session, site_level, site_name, site_class); 350 return; 351 } 352 353 /* 354 * XXX For clarity consider using ``do { .. } while'', instead of using 355 * ``while { .. }'' with loop control at the bottom. 356 */ 357 while (1) { 358 /* Try the given domain */ 359 if (tls_policy_lookup_one(session, site_level, site_name, site_class)) 360 return; 361 /* Re-try with parent domain */ 362 if ((site_name = strchr(site_name + 1, '.')) == 0) 363 return; 364 } 365 } 366 367 /* set_cipher_grade - Set cipher grade and exclusions */ 368 369 static void set_cipher_grade(SMTP_SESSION *session) 370 { 371 const char *mand_exclude = ""; 372 const char *also_exclude = ""; 373 374 /* 375 * Use main.cf cipher level if no per-destination value specified. With 376 * mandatory encryption at least encrypt, and with mandatory verification 377 * at least authenticate! 378 */ 379 switch (session->tls_level) { 380 case TLS_LEV_INVALID: 381 case TLS_LEV_NONE: 382 return; 383 384 case TLS_LEV_MAY: 385 if (session->tls_grade == 0) 386 session->tls_grade = mystrdup(var_smtp_tls_ciph); 387 break; 388 389 case TLS_LEV_ENCRYPT: 390 if (session->tls_grade == 0) 391 session->tls_grade = mystrdup(var_smtp_tls_mand_ciph); 392 mand_exclude = var_smtp_tls_mand_excl; 393 also_exclude = "eNULL"; 394 break; 395 396 case TLS_LEV_FPRINT: 397 case TLS_LEV_VERIFY: 398 case TLS_LEV_SECURE: 399 if (session->tls_grade == 0) 400 session->tls_grade = mystrdup(var_smtp_tls_mand_ciph); 401 mand_exclude = var_smtp_tls_mand_excl; 402 also_exclude = "aNULL"; 403 break; 404 } 405 406 #define ADD_EXCLUDE(vstr, str) \ 407 do { \ 408 if (*(str)) \ 409 vstring_sprintf_append((vstr), "%s%s", \ 410 VSTRING_LEN(vstr) ? " " : "", (str)); \ 411 } while (0) 412 413 /* 414 * The "exclude" policy table attribute overrides main.cf exclusion 415 * lists. 416 */ 417 if (session->tls_exclusions == 0) { 418 session->tls_exclusions = vstring_alloc(10); 419 ADD_EXCLUDE(session->tls_exclusions, var_smtp_tls_excl_ciph); 420 ADD_EXCLUDE(session->tls_exclusions, mand_exclude); 421 } 422 ADD_EXCLUDE(session->tls_exclusions, also_exclude); 423 } 424 425 /* session_tls_init - session TLS parameters */ 426 427 static void session_tls_init(SMTP_SESSION *session, const char *dest, 428 const char *host, int flags) 429 { 430 const char *myname = "session_tls_init"; 431 int global_level; 432 int site_level; 433 434 /* 435 * Initialize all TLS related session properties. 436 */ 437 session->tls_context = 0; 438 session->tls_nexthop = 0; 439 session->tls_level = TLS_LEV_NONE; 440 session->tls_retry_plain = 0; 441 session->tls_protocols = 0; 442 session->tls_grade = 0; 443 session->tls_exclusions = 0; 444 session->tls_matchargv = 0; 445 446 /* 447 * Compute the global TLS policy. This is the default policy level when 448 * no per-site policy exists. It also is used to override a wild-card 449 * per-site policy. 450 */ 451 if (*var_smtp_tls_level) { 452 /* Require that var_smtp_tls_level is sanitized upon startup. */ 453 global_level = tls_level_lookup(var_smtp_tls_level); 454 if (global_level == TLS_LEV_INVALID) 455 msg_panic("%s: invalid TLS security level: \"%s\"", 456 myname, var_smtp_tls_level); 457 } else if (var_smtp_enforce_tls) { 458 global_level = var_smtp_tls_enforce_peername ? 459 TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; 460 } else { 461 global_level = var_smtp_use_tls ? 462 TLS_LEV_MAY : TLS_LEV_NONE; 463 } 464 if (msg_verbose) 465 msg_info("%s TLS level: %s", "global", policy_name(global_level)); 466 467 /* 468 * Compute the per-site TLS enforcement level. For compatibility with the 469 * original TLS patch, this algorithm is gives equal precedence to host 470 * and next-hop policies. 471 */ 472 site_level = TLS_LEV_NOTFOUND; 473 474 if (tls_policy) { 475 tls_policy_lookup(session, &site_level, dest, "next-hop destination"); 476 } else if (tls_per_site) { 477 tls_site_lookup(&site_level, dest, "next-hop destination"); 478 if (strcasecmp(dest, host) != 0) 479 tls_site_lookup(&site_level, host, "server hostname"); 480 if (msg_verbose) 481 msg_info("%s TLS level: %s", "site", policy_name(site_level)); 482 483 /* 484 * Override a wild-card per-site policy with a more specific global 485 * policy. 486 * 487 * With the original TLS patch, 1) a per-site ENCRYPT could not override 488 * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy 489 * produced inconsistent results: it changed a global VERIFY into 490 * NONE, while producing MAY with all weaker global policy settings. 491 * 492 * With the current implementation, a combined per-site (NONE+MAY) 493 * consistently overrides global policy with NONE, and global policy 494 * can override only a per-site MAY wildcard. That is, specific 495 * policies consistently override wildcard policies, and 496 * (non-wildcard) per-site policies consistently override global 497 * policies. 498 */ 499 if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY) 500 site_level = global_level; 501 } 502 if (site_level == TLS_LEV_NOTFOUND) 503 session->tls_level = global_level; 504 else 505 session->tls_level = site_level; 506 507 /* 508 * Use main.cf protocols setting if not set in per-destination table. 509 */ 510 if (session->tls_level > TLS_LEV_NONE && session->tls_protocols == 0) 511 session->tls_protocols = 512 mystrdup((session->tls_level == TLS_LEV_MAY) ? 513 var_smtp_tls_proto : var_smtp_tls_mand_proto); 514 515 /* 516 * Compute cipher grade (if set in per-destination table, else 517 * set_cipher() uses main.cf settings) and security level dependent 518 * cipher exclusion list. 519 */ 520 set_cipher_grade(session); 521 522 /* 523 * Use main.cf cert_match setting if not set in per-destination table. 524 */ 525 if (session->tls_matchargv == 0) { 526 switch (session->tls_level) { 527 case TLS_LEV_INVALID: 528 case TLS_LEV_NONE: 529 case TLS_LEV_MAY: 530 case TLS_LEV_ENCRYPT: 531 break; 532 case TLS_LEV_FPRINT: 533 session->tls_matchargv = 534 argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |"); 535 break; 536 case TLS_LEV_VERIFY: 537 session->tls_matchargv = 538 argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :"); 539 break; 540 case TLS_LEV_SECURE: 541 session->tls_matchargv = 542 argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :"); 543 break; 544 default: 545 msg_panic("unexpected TLS security level: %d", 546 session->tls_level); 547 } 548 } 549 if (msg_verbose && (tls_policy || tls_per_site)) 550 msg_info("%s TLS level: %s", "effective", 551 policy_name(session->tls_level)); 552 } 553 554 #endif 555 556 /* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */ 557 558 SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, const char *dest, 559 const char *host, const char *addr, 560 unsigned port, time_t start, 561 int flags) 562 { 563 SMTP_SESSION *session; 564 565 session = (SMTP_SESSION *) mymalloc(sizeof(*session)); 566 session->stream = stream; 567 session->dest = mystrdup(dest); 568 session->host = mystrdup(host); 569 session->addr = mystrdup(addr); 570 session->namaddr = concatenate(host, "[", addr, "]", (char *) 0); 571 session->helo = 0; 572 session->port = port; 573 session->features = 0; 574 575 session->size_limit = 0; 576 session->error_mask = 0; 577 session->buffer = vstring_alloc(100); 578 session->scratch = vstring_alloc(100); 579 session->scratch2 = vstring_alloc(100); 580 smtp_chat_init(session); 581 session->mime_state = 0; 582 583 if (session->port) { 584 vstring_sprintf(session->buffer, "%s:%d", 585 session->namaddr, ntohs(session->port)); 586 session->namaddrport = mystrdup(STR(session->buffer)); 587 } else 588 session->namaddrport = mystrdup(session->namaddr); 589 590 session->send_proto_helo = 0; 591 592 if (flags & SMTP_MISC_FLAG_CONN_STORE) 593 CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time); 594 else 595 DONT_CACHE_THIS_SESSION; 596 session->reuse_count = 0; 597 USE_NEWBORN_SESSION; /* He's not dead Jim! */ 598 599 #ifdef USE_SASL_AUTH 600 smtp_sasl_connect(session); 601 #endif 602 603 /* 604 * Need to pass the session as a parameter when the new-style per-nexthop 605 * policies can specify not only security level thresholds, but also how 606 * security levels are defined. 607 */ 608 #ifdef USE_TLS 609 session_tls_init(session, dest, host, flags); 610 #endif 611 session->state = 0; 612 debug_peer_check(host, addr); 613 return (session); 614 } 615 616 /* smtp_session_free - destroy SMTP_SESSION structure and contents */ 617 618 void smtp_session_free(SMTP_SESSION *session) 619 { 620 #ifdef USE_TLS 621 if (session->stream) { 622 vstream_fflush(session->stream); 623 if (session->tls_context) 624 tls_client_stop(smtp_tls_ctx, session->stream, 625 var_smtp_starttls_tmout, 0, session->tls_context); 626 } 627 if (session->tls_protocols) 628 myfree(session->tls_protocols); 629 if (session->tls_grade) 630 myfree(session->tls_grade); 631 if (session->tls_exclusions) 632 vstring_free(session->tls_exclusions); 633 if (session->tls_matchargv) 634 argv_free(session->tls_matchargv); 635 #endif 636 if (session->stream) 637 vstream_fclose(session->stream); 638 myfree(session->dest); 639 myfree(session->host); 640 myfree(session->addr); 641 myfree(session->namaddr); 642 myfree(session->namaddrport); 643 if (session->helo) 644 myfree(session->helo); 645 646 vstring_free(session->buffer); 647 vstring_free(session->scratch); 648 vstring_free(session->scratch2); 649 650 if (session->history) 651 smtp_chat_reset(session); 652 if (session->mime_state) 653 mime_state_free(session->mime_state); 654 655 #ifdef USE_SASL_AUTH 656 smtp_sasl_cleanup(session); 657 #endif 658 659 debug_peer_restore(); 660 myfree((char *) session); 661 } 662 663 /* smtp_session_passivate - passivate an SMTP_SESSION object */ 664 665 int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop, 666 VSTRING *endp_prop) 667 { 668 int fd; 669 670 /* 671 * Encode the local-to-physical binding properties: whether or not this 672 * server is best MX host for the next-hop or fall-back logical 673 * destination (this information is needed for loop handling in 674 * smtp_proto()). 675 * 676 * XXX It would be nice to have a VSTRING to VSTREAM adapter so that we can 677 * serialize the properties with attr_print() instead of using ad-hoc, 678 * non-reusable, code and hard-coded format strings. 679 */ 680 vstring_sprintf(dest_prop, "%u", 681 session->features & SMTP_FEATURE_DESTINATION_MASK); 682 683 /* 684 * Encode the physical endpoint properties: all the session properties 685 * except for "session from cache", "best MX", or "RSET failure". 686 * 687 * XXX It would be nice to have a VSTRING to VSTREAM adapter so that we can 688 * serialize the properties with attr_print() instead of using obscure 689 * hard-coded format strings. 690 * 691 * XXX Should also record an absolute time when a session must be closed, 692 * how many non-delivering mail transactions there were during this 693 * session, and perhaps other statistics, so that we don't reuse a 694 * session too much. 695 * 696 * XXX Be sure to use unsigned types in the format string. Sign characters 697 * would be rejected by the alldig() test on the reading end. 698 */ 699 vstring_sprintf(endp_prop, "%u\n%s\n%s\n%s\n%u\n%u\n%lu", 700 session->reuse_count, 701 session->dest, session->host, 702 session->addr, session->port, 703 session->features & SMTP_FEATURE_ENDPOINT_MASK, 704 (long) session->expire_time); 705 706 /* 707 * Append the passivated SASL attributes. 708 */ 709 #ifdef notdef 710 if (smtp_sasl_enable) 711 smtp_sasl_passivate(endp_prop, session); 712 #endif 713 714 /* 715 * Salvage the underlying file descriptor, and destroy the session 716 * object. 717 */ 718 fd = vstream_fileno(session->stream); 719 vstream_fdclose(session->stream); 720 session->stream = 0; 721 smtp_session_free(session); 722 723 return (fd); 724 } 725 726 /* smtp_session_activate - re-activate a passivated SMTP_SESSION object */ 727 728 SMTP_SESSION *smtp_session_activate(int fd, VSTRING *dest_prop, 729 VSTRING *endp_prop) 730 { 731 const char *myname = "smtp_session_activate"; 732 SMTP_SESSION *session; 733 char *dest_props; 734 char *endp_props; 735 const char *prop; 736 const char *dest; 737 const char *host; 738 const char *addr; 739 unsigned port; 740 unsigned features; /* server features */ 741 time_t expire_time; /* session re-use expiration time */ 742 unsigned reuse_count; /* # times reused */ 743 744 /* 745 * XXX it would be nice to have a VSTRING to VSTREAM adapter so that we 746 * can de-serialize the properties with attr_scan(), instead of using 747 * ad-hoc, non-reusable code. 748 * 749 * XXX As a preliminary solution we use mystrtok(), but that function is not 750 * suitable for zero-length fields. 751 */ 752 endp_props = STR(endp_prop); 753 if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) { 754 msg_warn("%s: bad cached session reuse count property", myname); 755 return (0); 756 } 757 reuse_count = atoi(prop); 758 if ((dest = mystrtok(&endp_props, "\n")) == 0) { 759 msg_warn("%s: missing cached session destination property", myname); 760 return (0); 761 } 762 if ((host = mystrtok(&endp_props, "\n")) == 0) { 763 msg_warn("%s: missing cached session hostname property", myname); 764 return (0); 765 } 766 if ((addr = mystrtok(&endp_props, "\n")) == 0) { 767 msg_warn("%s: missing cached session address property", myname); 768 return (0); 769 } 770 if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) { 771 msg_warn("%s: bad cached session port property", myname); 772 return (0); 773 } 774 port = atoi(prop); 775 776 if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) { 777 msg_warn("%s: bad cached session features property", myname); 778 return (0); 779 } 780 features = atoi(prop); 781 782 if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) { 783 msg_warn("%s: bad cached session expiration time property", myname); 784 return (0); 785 } 786 #ifdef MISSING_STRTOUL 787 expire_time = strtol(prop, 0, 10); 788 #else 789 expire_time = strtoul(prop, 0, 10); 790 #endif 791 792 if (dest_prop && VSTRING_LEN(dest_prop)) { 793 dest_props = STR(dest_prop); 794 if ((prop = mystrtok(&dest_props, "\n")) == 0 || !alldig(prop)) { 795 msg_warn("%s: bad cached destination features property", myname); 796 return (0); 797 } 798 features |= atoi(prop); 799 } 800 801 /* 802 * Allright, bundle up what we have sofar. 803 */ 804 #define NO_FLAGS 0 805 806 session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), dest, host, 807 addr, port, (time_t) 0, NO_FLAGS); 808 session->features = (features | SMTP_FEATURE_FROM_CACHE); 809 CACHE_THIS_SESSION_UNTIL(expire_time); 810 session->reuse_count = ++reuse_count; 811 812 if (msg_verbose) 813 msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, " 814 "ttl=%ld, reuse=%d", 815 myname, dest, host, addr, ntohs(port), features, 816 (long) (expire_time - time((time_t *) 0)), reuse_count); 817 818 /* 819 * Re-activate the SASL attributes. 820 */ 821 #ifdef notdef 822 if (smtp_sasl_enable && smtp_sasl_activate(session, endp_props) < 0) { 823 vstream_fdclose(session->stream); 824 session->stream = 0; 825 smtp_session_free(session); 826 return (0); 827 } 828 #endif 829 830 return (session); 831 } 832