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