1/*++
2/* NAME
3/*	clnt_stream 3
4/* SUMMARY
5/*	client endpoint maintenance
6/* SYNOPSIS
7/*	#include <clnt_stream.h>
8/*
9/*	CLNT_STREAM *clnt_stream_create(class, service, timeout, ttl)
10/*	const char *class;
11/*	const char *service;
12/*	int	timeout;
13/*	int	ttl;
14/*
15/*	VSTREAM	*clnt_stream_access(clnt_stream)
16/*	CLNT_STREAM *clnt_stream;
17/*
18/*	void	clnt_stream_recover(clnt_stream)
19/*	CLNT_STREAM *clnt_stream;
20/*
21/*	void	clnt_stream_free(clnt_stream)
22/*	CLNT_STREAM *clnt_stream;
23/* DESCRIPTION
24/*	This module maintains local IPC client endpoints that automatically
25/*	disconnect after a being idle for a configurable amount of time,
26/*	that disconnect after a configurable time to live,
27/*	and that transparently handle most server-initiated disconnects.
28/*	Server disconnect is detected by read-selecting the client endpoint.
29/*	The code assumes that the server has disconnected when the endpoint
30/*	becomes readable.
31/*
32/*	clnt_stream_create() instantiates a client endpoint.
33/*
34/*	clnt_stream_access() returns an open stream to the service specified
35/*	to clnt_stream_create(). The stream instance may change between calls.
36/*
37/*	clnt_stream_recover() recovers from a server-initiated disconnect
38/*	that happened in the middle of an I/O operation.
39/*
40/*	clnt_stream_free() destroys of the specified client endpoint.
41/*
42/*	Arguments:
43/* .IP class
44/*	The service class, private or public.
45/* .IP service
46/*	The service endpoint name. The name is limited to local IPC
47/*	over sockets or equivalent.
48/* .IP timeout
49/*	Idle time after which the client disconnects.
50/* .IP ttl
51/*	Upper bound on the time that a connection is allowed to persist.
52/* DIAGNOSTICS
53/*	Warnings: communication failure. Fatal error: mail system is down,
54/*	out of memory.
55/* SEE ALSO
56/*	mail_proto(3h) low-level mail component glue.
57/* LICENSE
58/* .ad
59/* .fi
60/*	The Secure Mailer license must be distributed with this software.
61/* AUTHOR(S)
62/*	Wietse Venema
63/*	IBM T.J. Watson Research
64/*	P.O. Box 704
65/*	Yorktown Heights, NY 10598, USA
66/*--*/
67
68/* System library. */
69
70#include <sys_defs.h>
71
72/* Utility library. */
73
74#include <msg.h>
75#include <mymalloc.h>
76#include <vstream.h>
77#include <events.h>
78#include <iostuff.h>
79
80/* Global library. */
81
82#include "mail_proto.h"
83#include "mail_params.h"
84#include "clnt_stream.h"
85
86/* Application-specific. */
87
88 /*
89  * CLNT_STREAM is an opaque structure. None of the access methods can easily
90  * be implemented as a macro, and access is not performance critical anyway.
91  */
92struct CLNT_STREAM {
93    VSTREAM *vstream;			/* buffered I/O */
94    int     timeout;			/* time before client disconnect */
95    int     ttl;			/* time before client disconnect */
96    char   *class;			/* server class */
97    char   *service;			/* server name */
98};
99
100static void clnt_stream_close(CLNT_STREAM *);
101
102/* clnt_stream_event - server-initiated disconnect or client-side timeout */
103
104static void clnt_stream_event(int unused_event, char *context)
105{
106    CLNT_STREAM *clnt_stream = (CLNT_STREAM *) context;
107
108    /*
109     * Sanity check. This routine causes the stream to be closed, so it
110     * cannot be called when the stream is already closed.
111     */
112    if (clnt_stream->vstream == 0)
113	msg_panic("clnt_stream_event: stream is closed");
114
115    clnt_stream_close(clnt_stream);
116}
117
118/* clnt_stream_ttl_event - client-side expiration */
119
120static void clnt_stream_ttl_event(int event, char *context)
121{
122
123    /*
124     * XXX This function is needed only because event_request_timer() cannot
125     * distinguish between requests that specify the same call-back routine
126     * and call-back context. The fix is obvious: specify a request ID along
127     * with the call-back routine, but there is too much code that would have
128     * to be changed.
129     *
130     * XXX Should we be concerned that an overly agressive optimizer will
131     * eliminate this function and replace calls to clnt_stream_ttl_event()
132     * by direct calls to clnt_stream_event()? It should not, because there
133     * exists code that takes the address of both functions.
134     */
135    clnt_stream_event(event, context);
136}
137
138/* clnt_stream_open - connect to service */
139
140static void clnt_stream_open(CLNT_STREAM *clnt_stream)
141{
142
143    /*
144     * Sanity check.
145     */
146    if (clnt_stream->vstream)
147	msg_panic("clnt_stream_open: stream is open");
148
149    /*
150     * Schedule a read event so that we can clean up when the remote side
151     * disconnects, and schedule a timer event so that we can cleanup an idle
152     * connection. Note that both events are handled by the same routine.
153     *
154     * Finally, schedule an event to force disconnection even when the
155     * connection is not idle. This is to prevent one client from clinging on
156     * to a server forever.
157     */
158    clnt_stream->vstream = mail_connect_wait(clnt_stream->class,
159					     clnt_stream->service);
160    close_on_exec(vstream_fileno(clnt_stream->vstream), CLOSE_ON_EXEC);
161    event_enable_read(vstream_fileno(clnt_stream->vstream), clnt_stream_event,
162		      (char *) clnt_stream);
163    event_request_timer(clnt_stream_event, (char *) clnt_stream,
164			clnt_stream->timeout);
165    event_request_timer(clnt_stream_ttl_event, (char *) clnt_stream,
166			clnt_stream->ttl);
167}
168
169/* clnt_stream_close - disconnect from service */
170
171static void clnt_stream_close(CLNT_STREAM *clnt_stream)
172{
173
174    /*
175     * Sanity check.
176     */
177    if (clnt_stream->vstream == 0)
178	msg_panic("clnt_stream_close: stream is closed");
179
180    /*
181     * Be sure to disable read and timer events.
182     */
183    if (msg_verbose)
184	msg_info("%s stream disconnect", clnt_stream->service);
185    event_disable_readwrite(vstream_fileno(clnt_stream->vstream));
186    event_cancel_timer(clnt_stream_event, (char *) clnt_stream);
187    event_cancel_timer(clnt_stream_ttl_event, (char *) clnt_stream);
188    (void) vstream_fclose(clnt_stream->vstream);
189    clnt_stream->vstream = 0;
190}
191
192/* clnt_stream_recover - recover from server-initiated disconnect */
193
194void    clnt_stream_recover(CLNT_STREAM *clnt_stream)
195{
196
197    /*
198     * Clean up. Don't re-connect until the caller needs it.
199     */
200    if (clnt_stream->vstream)
201	clnt_stream_close(clnt_stream);
202}
203
204/* clnt_stream_access - access a client stream */
205
206VSTREAM *clnt_stream_access(CLNT_STREAM *clnt_stream)
207{
208
209    /*
210     * Open a stream or restart the idle timer.
211     *
212     * Important! Do not restart the TTL timer!
213     */
214    if (clnt_stream->vstream == 0) {
215	clnt_stream_open(clnt_stream);
216    } else if (readable(vstream_fileno(clnt_stream->vstream))) {
217	clnt_stream_close(clnt_stream);
218	clnt_stream_open(clnt_stream);
219    } else {
220	event_request_timer(clnt_stream_event, (char *) clnt_stream,
221			    clnt_stream->timeout);
222    }
223    return (clnt_stream->vstream);
224}
225
226/* clnt_stream_create - create client stream connection */
227
228CLNT_STREAM *clnt_stream_create(const char *class, const char *service,
229				        int timeout, int ttl)
230{
231    CLNT_STREAM *clnt_stream;
232
233    /*
234     * Don't open the stream until the caller needs it.
235     */
236    clnt_stream = (CLNT_STREAM *) mymalloc(sizeof(*clnt_stream));
237    clnt_stream->vstream = 0;
238    clnt_stream->timeout = timeout;
239    clnt_stream->ttl = ttl;
240    clnt_stream->class = mystrdup(class);
241    clnt_stream->service = mystrdup(service);
242    return (clnt_stream);
243}
244
245/* clnt_stream_free - destroy client stream instance */
246
247void    clnt_stream_free(CLNT_STREAM *clnt_stream)
248{
249    if (clnt_stream->vstream)
250	clnt_stream_close(clnt_stream);
251    myfree(clnt_stream->class);
252    myfree(clnt_stream->service);
253    myfree((char *) clnt_stream);
254}
255