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