xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/auto_clnt.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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