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