xref: /netbsd-src/external/ibm-public/postfix/dist/src/smtp/smtp_session.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
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