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   */
109 struct 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 
118 static void clnt_stream_close(CLNT_STREAM *);
119 
120 /* clnt_stream_event - server-initiated disconnect or client-side timeout */
121 
clnt_stream_event(int unused_event,void * context)122 static 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 
clnt_stream_ttl_event(int event,void * context)138 static 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 
clnt_stream_open(CLNT_STREAM * clnt_stream)158 static 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 
clnt_stream_close(CLNT_STREAM * clnt_stream)189 static 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 
clnt_stream_recover(CLNT_STREAM * clnt_stream)212 void    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 
clnt_stream_access(CLNT_STREAM * clnt_stream)224 VSTREAM *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 
clnt_stream_create(const char * class,const char * service,int timeout,int ttl,CLNT_STREAM_HANDSHAKE_FN handshake)252 CLNT_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 
clnt_stream_free(CLNT_STREAM * clnt_stream)273 void    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