1 /* $NetBSD: auto_clnt.c,v 1.4 2022/10/08 16:12:50 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* auto_clnt 3
6 /* SUMMARY
7 /* client endpoint maintenance
8 /* SYNOPSIS
9 /* #include <auto_clnt.h>
10 /*
11 /* typedef void (*AUTO_CLNT_HANDSHAKE_FN)(VSTREAM *);
12 /*
13 /* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl)
14 /* const char *service;
15 /* int timeout;
16 /* int max_idle;
17 /* int max_ttl;
18 /*
19 /* VSTREAM *auto_clnt_access(auto_clnt)
20 /* AUTO_CLNT *auto_clnt;
21 /*
22 /* void auto_clnt_recover(auto_clnt)
23 /* AUTO_CLNT *auto_clnt;
24 /*
25 /* const char *auto_clnt_name(auto_clnt)
26 /* AUTO_CLNT *auto_clnt;
27 /*
28 /* void auto_clnt_free(auto_clnt)
29 /* AUTO_CLNT *auto_clnt;
30 /*
31 /* void auto_clnt_control(auto_clnt, name, value, ... AUTO_CLNT_CTL_END)
32 /* AUTO_CLNT *auto_clnt;
33 /* int name;
34 /* DESCRIPTION
35 /* This module maintains IPC client endpoints that automatically
36 /* disconnect after a being idle for a configurable amount of time,
37 /* that disconnect after a configurable time to live,
38 /* and that transparently handle most server-initiated disconnects.
39 /*
40 /* This module tries each operation only a limited number of
41 /* times and then reports an error. This is unlike the
42 /* clnt_stream(3) module which will retry forever, so that
43 /* the application never experiences an error.
44 /*
45 /* auto_clnt_create() instantiates a client endpoint.
46 /*
47 /* auto_clnt_access() returns an open stream to the service specified
48 /* to auto_clnt_create(). The stream instance may change between calls.
49 /* The result is a null pointer in case of failure.
50 /*
51 /* auto_clnt_recover() recovers from a server-initiated disconnect
52 /* that happened in the middle of an I/O operation.
53 /*
54 /* auto_clnt_name() returns the name of the specified client endpoint.
55 /*
56 /* auto_clnt_free() destroys of the specified client endpoint.
57 /*
58 /* auto_clnt_control() allows the user to fine tune the behavior of
59 /* the specified client. The arguments are a list of (name, value)
60 /* terminated with AUTO_CLNT_CTL_END.
61 /* The following lists the names and the types of the corresponding
62 /* value arguments.
63 /* .IP "AUTO_CLNT_CTL_HANDSHAKE(VSTREAM *)"
64 /* A pointer to function that will be called at the start of a
65 /* new connection, and that returns 0 in case of success.
66 /* .PP
67 /* Arguments:
68 /* .IP service
69 /* The service argument specifies "transport:servername" where
70 /* transport is currently limited to one of the following:
71 /* .RS
72 /* .IP inet
73 /* servername has the form "inet:host:port".
74 /* .IP local
75 /* servername has the form "local:private/servicename" or
76 /* "local:public/servicename". This is the preferred way to
77 /* specify Postfix daemons that are configured as "unix" in
78 /* master.cf.
79 /* .IP unix
80 /* servername has the form "unix:private/servicename" or
81 /* "unix:public/servicename". This does not work on Solaris,
82 /* where Postfix uses STREAMS instead of UNIX-domain sockets.
83 /* .RE
84 /* .IP timeout
85 /* The time limit for sending, receiving, or for connecting
86 /* to a server. Specify a value <=0 to disable the time limit.
87 /* .IP max_idle
88 /* Idle time after which the client disconnects. Specify 0 to
89 /* disable the limit.
90 /* .IP max_ttl
91 /* Upper bound on the time that a connection is allowed to persist.
92 /* Specify 0 to disable the limit.
93 /* .IP open_action
94 /* Application call-back routine that opens a stream or returns a
95 /* null pointer upon failure. In case of success, the call-back routine
96 /* is expected to set the stream pathname to the server endpoint name.
97 /* .IP context
98 /* Application context that is passed to the open_action routine.
99 /* .IP handshake
100 /* A null pointer, or a pointer to function that will be called
101 /* at the start of a new connection and that returns 0 in case
102 /* of success.
103 /* DIAGNOSTICS
104 /* Warnings: communication failure. Fatal error: out of memory.
105 /* LICENSE
106 /* .ad
107 /* .fi
108 /* The Secure Mailer license must be distributed with this software.
109 /* AUTHOR(S)
110 /* Wietse Venema
111 /* IBM T.J. Watson Research
112 /* P.O. Box 704
113 /* Yorktown Heights, NY 10598, USA
114 /*
115 /* Wietse Venema
116 /* Google, Inc.
117 /* 111 8th Avenue
118 /* New York, NY 10011, USA
119 /*--*/
120
121 /* System library. */
122
123 #include <sys_defs.h>
124 #include <string.h>
125
126 /* Utility library. */
127
128 #include <msg.h>
129 #include <mymalloc.h>
130 #include <vstream.h>
131 #include <events.h>
132 #include <iostuff.h>
133 #include <connect.h>
134 #include <split_at.h>
135 #include <auto_clnt.h>
136
137 /* Application-specific. */
138
139 /*
140 * AUTO_CLNT is an opaque structure. None of the access methods can easily
141 * be implemented as a macro, and access is not performance critical anyway.
142 */
143 struct AUTO_CLNT {
144 VSTREAM *vstream; /* buffered I/O */
145 char *endpoint; /* host:port or pathname */
146 int timeout; /* I/O time limit */
147 int max_idle; /* time before client disconnect */
148 int max_ttl; /* time before client disconnect */
149 AUTO_CLNT_HANDSHAKE_FN handshake; /* new connection only */
150 int (*connect) (const char *, int, int); /* unix, local, inet */
151 };
152
153 static void auto_clnt_close(AUTO_CLNT *);
154
155 /* auto_clnt_event - server-initiated disconnect or client-side max_idle */
156
auto_clnt_event(int unused_event,void * context)157 static void auto_clnt_event(int unused_event, void *context)
158 {
159 AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context;
160
161 /*
162 * Sanity check. This routine causes the stream to be closed, so it
163 * cannot be called when the stream is already closed.
164 */
165 if (auto_clnt->vstream == 0)
166 msg_panic("auto_clnt_event: stream is closed");
167
168 auto_clnt_close(auto_clnt);
169 }
170
171 /* auto_clnt_ttl_event - client-side expiration */
172
auto_clnt_ttl_event(int event,void * context)173 static void auto_clnt_ttl_event(int event, void *context)
174 {
175
176 /*
177 * XXX This function is needed only because event_request_timer() cannot
178 * distinguish between requests that specify the same call-back routine
179 * and call-back context. The fix is obvious: specify a request ID along
180 * with the call-back routine, but there is too much code that would have
181 * to be changed.
182 *
183 * XXX Should we be concerned that an overly aggressive optimizer will
184 * eliminate this function and replace calls to auto_clnt_ttl_event() by
185 * direct calls to auto_clnt_event()? It should not, because there exists
186 * code that takes the address of both functions.
187 */
188 auto_clnt_event(event, context);
189 }
190
191 /* auto_clnt_open - connect to service */
192
auto_clnt_open(AUTO_CLNT * auto_clnt)193 static void auto_clnt_open(AUTO_CLNT *auto_clnt)
194 {
195 const char *myname = "auto_clnt_open";
196 int fd;
197
198 /*
199 * Sanity check.
200 */
201 if (auto_clnt->vstream)
202 msg_panic("auto_clnt_open: stream is open");
203
204 /*
205 * Schedule a read event so that we can clean up when the remote side
206 * disconnects, and schedule a timer event so that we can cleanup an idle
207 * connection. Note that both events are handled by the same routine.
208 *
209 * Finally, schedule an event to force disconnection even when the
210 * connection is not idle. This is to prevent one client from clinging on
211 * to a server forever.
212 */
213 fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout);
214 if (fd < 0) {
215 msg_warn("connect to %s: %m", auto_clnt->endpoint);
216 } else {
217 if (msg_verbose)
218 msg_info("%s: connected to %s", myname, auto_clnt->endpoint);
219 auto_clnt->vstream = vstream_fdopen(fd, O_RDWR);
220 vstream_control(auto_clnt->vstream,
221 CA_VSTREAM_CTL_PATH(auto_clnt->endpoint),
222 CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout),
223 CA_VSTREAM_CTL_END);
224 }
225
226 if (auto_clnt->vstream != 0) {
227 close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC);
228 event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event,
229 (void *) auto_clnt);
230 if (auto_clnt->max_idle > 0)
231 event_request_timer(auto_clnt_event, (void *) auto_clnt,
232 auto_clnt->max_idle);
233 if (auto_clnt->max_ttl > 0)
234 event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt,
235 auto_clnt->max_ttl);
236 }
237 }
238
239 /* auto_clnt_close - disconnect from service */
240
auto_clnt_close(AUTO_CLNT * auto_clnt)241 static void auto_clnt_close(AUTO_CLNT *auto_clnt)
242 {
243 const char *myname = "auto_clnt_close";
244
245 /*
246 * Sanity check.
247 */
248 if (auto_clnt->vstream == 0)
249 msg_panic("%s: stream is closed", myname);
250
251 /*
252 * Be sure to disable read and timer events.
253 */
254 if (msg_verbose)
255 msg_info("%s: disconnect %s stream",
256 myname, VSTREAM_PATH(auto_clnt->vstream));
257 event_disable_readwrite(vstream_fileno(auto_clnt->vstream));
258 event_cancel_timer(auto_clnt_event, (void *) auto_clnt);
259 event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt);
260 (void) vstream_fclose(auto_clnt->vstream);
261 auto_clnt->vstream = 0;
262 }
263
264 /* auto_clnt_recover - recover from server-initiated disconnect */
265
auto_clnt_recover(AUTO_CLNT * auto_clnt)266 void auto_clnt_recover(AUTO_CLNT *auto_clnt)
267 {
268
269 /*
270 * Clean up. Don't re-connect until the caller needs it.
271 */
272 if (auto_clnt->vstream)
273 auto_clnt_close(auto_clnt);
274 }
275
276 /* auto_clnt_access - access a client stream */
277
auto_clnt_access(AUTO_CLNT * auto_clnt)278 VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt)
279 {
280 AUTO_CLNT_HANDSHAKE_FN handshake;
281
282 /*
283 * Open a stream or restart the idle timer.
284 *
285 * Important! Do not restart the TTL timer!
286 */
287 if (auto_clnt->vstream == 0) {
288 auto_clnt_open(auto_clnt);
289 handshake = (auto_clnt->vstream ? auto_clnt->handshake : 0);
290 } else {
291 if (auto_clnt->max_idle > 0)
292 event_request_timer(auto_clnt_event, (void *) auto_clnt,
293 auto_clnt->max_idle);
294 handshake = 0;
295 }
296 if (handshake != 0 && handshake(auto_clnt->vstream) != 0)
297 return (0);
298 return (auto_clnt->vstream);
299 }
300
301 /* auto_clnt_create - create client stream object */
302
auto_clnt_create(const char * service,int timeout,int max_idle,int max_ttl)303 AUTO_CLNT *auto_clnt_create(const char *service, int timeout,
304 int max_idle, int max_ttl)
305 {
306 const char *myname = "auto_clnt_create";
307 char *transport = mystrdup(service);
308 char *endpoint;
309 AUTO_CLNT *auto_clnt;
310
311 /*
312 * Don't open the stream until the caller needs it.
313 */
314 if ((endpoint = split_at(transport, ':')) == 0
315 || *endpoint == 0 || *transport == 0)
316 msg_fatal("need service transport:endpoint instead of \"%s\"", service);
317 if (msg_verbose)
318 msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
319 auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt));
320 auto_clnt->vstream = 0;
321 auto_clnt->endpoint = mystrdup(endpoint);
322 auto_clnt->timeout = timeout;
323 auto_clnt->max_idle = max_idle;
324 auto_clnt->max_ttl = max_ttl;
325 auto_clnt->handshake = 0;
326 if (strcmp(transport, "inet") == 0) {
327 auto_clnt->connect = inet_connect;
328 } else if (strcmp(transport, "local") == 0) {
329 auto_clnt->connect = LOCAL_CONNECT;
330 } else if (strcmp(transport, "unix") == 0) {
331 auto_clnt->connect = unix_connect;
332 } else {
333 msg_fatal("invalid transport name: %s in service: %s",
334 transport, service);
335 }
336 myfree(transport);
337 return (auto_clnt);
338 }
339
340 /* auto_clnt_name - return client stream name */
341
auto_clnt_name(AUTO_CLNT * auto_clnt)342 const char *auto_clnt_name(AUTO_CLNT *auto_clnt)
343 {
344 return (auto_clnt->endpoint);
345 }
346
347 /* auto_clnt_free - destroy client stream instance */
348
auto_clnt_free(AUTO_CLNT * auto_clnt)349 void auto_clnt_free(AUTO_CLNT *auto_clnt)
350 {
351 if (auto_clnt->vstream)
352 auto_clnt_close(auto_clnt);
353 myfree(auto_clnt->endpoint);
354 myfree((void *) auto_clnt);
355 }
356
357 /* auto_clnt_control - fine control */
358
auto_clnt_control(AUTO_CLNT * client,int name,...)359 void auto_clnt_control(AUTO_CLNT *client, int name,...)
360 {
361 const char *myname = "auto_clnt_control";
362 va_list ap;
363
364 for (va_start(ap, name); name != AUTO_CLNT_CTL_END; name = va_arg(ap, int)) {
365 switch (name) {
366 case AUTO_CLNT_CTL_HANDSHAKE:
367 client->handshake = va_arg(ap, AUTO_CLNT_HANDSHAKE_FN);
368 break;
369 default:
370 msg_panic("%s: bad name %d", myname, name);
371 }
372 }
373 va_end(ap);
374 }
375