1/*++
2/* NAME
3/*	smtp_session 3
4/* SUMMARY
5/*	SMTP_SESSION structure management
6/* SYNOPSIS
7/*	#include "smtp.h"
8/*
9/*	SMTP_SESSION *smtp_session_alloc(stream, iter, start, flags)
10/*	VSTREAM	*stream;
11/*	SMTP_ITERATOR *iter;
12/*	time_t	start;
13/*	int	flags;
14/*
15/*	void	smtp_session_free(session)
16/*	SMTP_SESSION *session;
17/*
18/*	int	smtp_session_passivate(session, dest_prop, endp_prop)
19/*	SMTP_SESSION *session;
20/*	VSTRING	*dest_prop;
21/*	VSTRING	*endp_prop;
22/*
23/*	SMTP_SESSION *smtp_session_activate(fd, iter, dest_prop, endp_prop)
24/*	int	fd;
25/*	SMTP_ITERATOR *iter;
26/*	VSTRING	*dest_prop;
27/*	VSTRING	*endp_prop;
28/* DESCRIPTION
29/*	smtp_session_alloc() allocates memory for an SMTP_SESSION structure
30/*	and initializes it with the given stream and destination, host name
31/*	and address information.  The host name and address strings are
32/*	copied. The port is in network byte order.
33/*
34/*	smtp_session_free() destroys an SMTP_SESSION structure and its
35/*	members, making memory available for reuse. It will handle the
36/*	case of a null stream and will assume it was given a different
37/*	purpose.
38/*
39/*	smtp_session_passivate() flattens an SMTP session so that
40/*	it can be cached. The SMTP_SESSION structure is destroyed.
41/*
42/*	smtp_session_activate() inflates a flattened SMTP session
43/*	so that it can be used. The input property arguments are
44/*	modified.
45/*
46/*	Arguments:
47/* .IP stream
48/*	A full-duplex stream.
49/* .IP iter
50/*	The literal next-hop or fall-back destination including
51/*	the optional [] and including the :port or :service;
52/*	the name of the remote host;
53/*	the printable address of the remote host;
54/*	the remote port in network byte order.
55/* .IP start
56/*	The time when this connection was opened.
57/* .IP flags
58/*	Zero or more of the following:
59/* .RS
60/* .IP SMTP_MISC_FLAG_CONN_LOAD
61/*	Enable re-use of cached SMTP or LMTP connections.
62/* .IP SMTP_MISC_FLAG_CONN_STORE
63/*	Enable saving of cached SMTP or LMTP connections.
64/* .RE
65/*	SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE.
66/* .IP dest_prop
67/*	Destination specific session properties: the server is the
68/*	best MX host for the current logical destination, the dest,
69/*	host, and addr properties. When dest_prop is non-empty, it
70/*	overrides the iterator dest, host, and addr properties.  It
71/*	is the caller's responsibility to save the current nexthop
72/*	with SMTP_ITER_SAVE_DEST() and to restore it afterwards
73/*	with SMTP_ITER_RESTORE_DEST() before trying alternatives.
74/* .IP endp_prop
75/*	Endpoint specific session properties: all the features
76/*	advertised by the remote server.
77/* LICENSE
78/* .ad
79/* .fi
80/*	The Secure Mailer license must be distributed with this software.
81/* AUTHOR(S)
82/*	Wietse Venema
83/*	IBM T.J. Watson Research
84/*	P.O. Box 704
85/*	Yorktown Heights, NY 10598, USA
86/*
87/*	Viktor Dukhovni
88/*--*/
89
90/* System library. */
91
92#include <sys_defs.h>
93#include <stdlib.h>
94#include <string.h>
95#include <netinet/in.h>
96
97#ifdef STRCASECMP_IN_STRINGS_H
98#include <strings.h>
99#endif
100
101/* Utility library. */
102
103#include <msg.h>
104#include <mymalloc.h>
105#include <vstring.h>
106#include <vstream.h>
107#include <stringops.h>
108
109/* Global library. */
110
111#include <mime_state.h>
112#include <debug_peer.h>
113#include <mail_params.h>
114
115/* Application-specific. */
116
117#include "smtp.h"
118#include "smtp_sasl.h"
119
120/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */
121
122SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter,
123				         time_t start, int flags)
124{
125    SMTP_SESSION *session;
126    const char *host = STR(iter->host);
127    const char *addr = STR(iter->addr);
128    unsigned port = iter->port;
129
130    session = (SMTP_SESSION *) mymalloc(sizeof(*session));
131    session->stream = stream;
132    session->iterator = iter;
133    session->namaddr = concatenate(host, "[", addr, "]", (char *) 0);
134    session->helo = 0;
135    session->port = port;
136    session->features = 0;
137
138    session->size_limit = 0;
139    session->error_mask = 0;
140    session->buffer = vstring_alloc(100);
141    session->scratch = vstring_alloc(100);
142    session->scratch2 = vstring_alloc(100);
143    smtp_chat_init(session);
144    session->mime_state = 0;
145
146    if (session->port) {
147	vstring_sprintf(session->buffer, "%s:%d",
148			session->namaddr, ntohs(session->port));
149	session->namaddrport = mystrdup(STR(session->buffer));
150    } else
151	session->namaddrport = mystrdup(session->namaddr);
152
153    session->send_proto_helo = 0;
154
155    if (flags & SMTP_MISC_FLAG_CONN_STORE)
156	CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time);
157    else
158	DONT_CACHE_THIS_SESSION;
159    session->reuse_count = 0;
160    USE_NEWBORN_SESSION;			/* He's not dead Jim! */
161
162#ifdef USE_SASL_AUTH
163    smtp_sasl_connect(session);
164#endif
165
166#ifdef USE_TLS
167    session->tls_context = 0;
168    session->tls_retry_plain = 0;
169    session->tls_nexthop = 0;
170    session->tls = 0;				/* TEMPORARY */
171#endif
172    session->state = 0;
173    debug_peer_check(host, addr);
174    return (session);
175}
176
177/* smtp_session_free - destroy SMTP_SESSION structure and contents */
178
179void    smtp_session_free(SMTP_SESSION *session)
180{
181#ifdef USE_TLS
182    if (session->stream) {
183	vstream_fflush(session->stream);
184	if (session->tls_context)
185	    tls_client_stop(smtp_tls_ctx, session->stream,
186			  var_smtp_starttls_tmout, 0, session->tls_context);
187    }
188#endif
189    if (session->stream)
190	vstream_fclose(session->stream);
191    myfree(session->namaddr);
192    myfree(session->namaddrport);
193    if (session->helo)
194	myfree(session->helo);
195
196    vstring_free(session->buffer);
197    vstring_free(session->scratch);
198    vstring_free(session->scratch2);
199
200    if (session->history)
201	smtp_chat_reset(session);
202    if (session->mime_state)
203	mime_state_free(session->mime_state);
204
205#ifdef USE_SASL_AUTH
206    smtp_sasl_cleanup(session);
207#endif
208
209    debug_peer_restore();
210    myfree((char *) session);
211}
212
213/* smtp_session_passivate - passivate an SMTP_SESSION object */
214
215int     smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop,
216			               VSTRING *endp_prop)
217{
218    SMTP_ITERATOR *iter = session->iterator;
219    int     fd;
220
221    /*
222     * Encode the local-to-physical binding properties: whether or not this
223     * server is best MX host for the next-hop or fall-back logical
224     * destination (this information is needed for loop handling in
225     * smtp_proto()).
226     *
227     * XXX It would be nice to have a VSTRING to VSTREAM adapter so that we can
228     * serialize the properties with attr_print() instead of using ad-hoc,
229     * non-reusable, code and hard-coded format strings.
230     *
231     * TODO: save SASL username and password information so that we can
232     * correctly save a reused authenticated connection.
233     *
234     */
235    vstring_sprintf(dest_prop, "%s\n%s\n%s\n%u",
236		    STR(iter->dest), STR(iter->host), STR(iter->addr),
237		    session->features & SMTP_FEATURE_DESTINATION_MASK);
238
239    /*
240     * Encode the physical endpoint properties: all the session properties
241     * except for "session from cache", "best MX", or "RSET failure".
242     *
243     * XXX It would be nice to have a VSTRING to VSTREAM adapter so that we can
244     * serialize the properties with attr_print() instead of using obscure
245     * hard-coded format strings.
246     *
247     * XXX Should also record an absolute time when a session must be closed,
248     * how many non-delivering mail transactions there were during this
249     * session, and perhaps other statistics, so that we don't reuse a
250     * session too much.
251     *
252     * XXX Be sure to use unsigned types in the format string. Sign characters
253     * would be rejected by the alldig() test on the reading end.
254     */
255    vstring_sprintf(endp_prop, "%u\n%u\n%lu",
256		    session->reuse_count,
257		    session->features & SMTP_FEATURE_ENDPOINT_MASK,
258		    (long) session->expire_time);
259
260    /*
261     * Append the passivated SASL attributes.
262     */
263#ifdef notdef
264    if (smtp_sasl_enable)
265	smtp_sasl_passivate(endp_prop, session);
266#endif
267
268    /*
269     * Salvage the underlying file descriptor, and destroy the session
270     * object.
271     */
272    fd = vstream_fileno(session->stream);
273    vstream_fdclose(session->stream);
274    session->stream = 0;
275    smtp_session_free(session);
276
277    return (fd);
278}
279
280/* smtp_session_activate - re-activate a passivated SMTP_SESSION object */
281
282SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter,
283				            VSTRING *dest_prop,
284				            VSTRING *endp_prop)
285{
286    const char *myname = "smtp_session_activate";
287    SMTP_SESSION *session;
288    char   *dest_props;
289    char   *endp_props;
290    const char *prop;
291    const char *dest;
292    const char *host;
293    const char *addr;
294    unsigned features;			/* server features */
295    time_t  expire_time;		/* session re-use expiration time */
296    unsigned reuse_count;		/* # times reused */
297
298    /*
299     * XXX it would be nice to have a VSTRING to VSTREAM adapter so that we
300     * can de-serialize the properties with attr_scan(), instead of using
301     * ad-hoc, non-reusable code.
302     *
303     * XXX As a preliminary solution we use mystrtok(), but that function is not
304     * suitable for zero-length fields.
305     */
306    endp_props = STR(endp_prop);
307    if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) {
308	msg_warn("%s: bad cached session reuse count property", myname);
309	return (0);
310    }
311    reuse_count = atoi(prop);
312    if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) {
313	msg_warn("%s: bad cached session features property", myname);
314	return (0);
315    }
316    features = atoi(prop);
317    if ((prop = mystrtok(&endp_props, "\n")) == 0 || !alldig(prop)) {
318	msg_warn("%s: bad cached session expiration time property", myname);
319	return (0);
320    }
321#ifdef MISSING_STRTOUL
322    expire_time = strtol(prop, 0, 10);
323#else
324    expire_time = strtoul(prop, 0, 10);
325#endif
326
327    /*
328     * Clobber the iterator's current nexthop, host and address fields with
329     * cached-connection information. This is done when a session is looked
330     * up by request nexthop instead of address and port. It is the caller's
331     * responsibility to save and restore the request nexthop with
332     * SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST().
333     *
334     * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION.
335     *
336     * TODO: restore SASL username and password information so that we can
337     * correctly save a reused authenticated connection.
338     */
339    if (dest_prop && VSTRING_LEN(dest_prop)) {
340	dest_props = STR(dest_prop);
341	if ((dest = mystrtok(&dest_props, "\n")) == 0) {
342	    msg_warn("%s: missing cached session destination property", myname);
343	    return (0);
344	}
345	if ((host = mystrtok(&dest_props, "\n")) == 0) {
346	    msg_warn("%s: missing cached session hostname property", myname);
347	    return (0);
348	}
349	if ((addr = mystrtok(&dest_props, "\n")) == 0) {
350	    msg_warn("%s: missing cached session address property", myname);
351	    return (0);
352	}
353	if ((prop = mystrtok(&dest_props, "\n")) == 0 || !alldig(prop)) {
354	    msg_warn("%s: bad cached destination features property", myname);
355	    return (0);
356	}
357	features |= atoi(prop);
358	SMTP_ITER_CLOBBER(iter, dest, host, addr);
359    }
360
361    /*
362     * Allright, bundle up what we have sofar.
363     */
364#define NO_FLAGS	0
365
366    session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter,
367				 (time_t) 0, NO_FLAGS);
368    session->features = (features | SMTP_FEATURE_FROM_CACHE);
369    CACHE_THIS_SESSION_UNTIL(expire_time);
370    session->reuse_count = ++reuse_count;
371
372    if (msg_verbose)
373	msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, "
374		 "ttl=%ld, reuse=%d",
375		 myname, STR(iter->dest), STR(iter->host),
376		 STR(iter->addr), ntohs(iter->port), features,
377		 (long) (expire_time - time((time_t *) 0)),
378		 reuse_count);
379
380    /*
381     * Re-activate the SASL attributes.
382     */
383#ifdef notdef
384    if (smtp_sasl_enable && smtp_sasl_activate(session, endp_props) < 0) {
385	vstream_fdclose(session->stream);
386	session->stream = 0;
387	smtp_session_free(session);
388	return (0);
389    }
390#endif
391
392    return (session);
393}
394