Home | History | Annotate | Line # | Download | only in util
      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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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