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