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