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