/*++ /* NAME /* auto_clnt 3 /* SUMMARY /* client endpoint maintenance /* SYNOPSIS /* #include /* /* typedef void (*AUTO_CLNT_HANDSHAKE_FN)(VSTREAM *); /* /* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl) /* const char *service; /* int timeout; /* int max_idle; /* int max_ttl; /* /* VSTREAM *auto_clnt_access(auto_clnt) /* AUTO_CLNT *auto_clnt; /* /* void auto_clnt_recover(auto_clnt) /* AUTO_CLNT *auto_clnt; /* /* const char *auto_clnt_name(auto_clnt) /* AUTO_CLNT *auto_clnt; /* /* void auto_clnt_free(auto_clnt) /* AUTO_CLNT *auto_clnt; /* /* void auto_clnt_control(auto_clnt, name, value, ... AUTO_CLNT_CTL_END) /* AUTO_CLNT *auto_clnt; /* int name; /* DESCRIPTION /* This module maintains IPC client endpoints that automatically /* disconnect after a being idle for a configurable amount of time, /* that disconnect after a configurable time to live, /* and that transparently handle most server-initiated disconnects. /* /* This module tries each operation only a limited number of /* times and then reports an error. This is unlike the /* clnt_stream(3) module which will retry forever, so that /* the application never experiences an error. /* /* auto_clnt_create() instantiates a client endpoint. /* /* auto_clnt_access() returns an open stream to the service specified /* to auto_clnt_create(). The stream instance may change between calls. /* The result is a null pointer in case of failure. /* /* auto_clnt_recover() recovers from a server-initiated disconnect /* that happened in the middle of an I/O operation. /* /* auto_clnt_name() returns the name of the specified client endpoint. /* /* auto_clnt_free() destroys of the specified client endpoint. /* /* auto_clnt_control() allows the user to fine tune the behavior of /* the specified client. The arguments are a list of (name, value) /* terminated with AUTO_CLNT_CTL_END. /* The following lists the names and the types of the corresponding /* value arguments. /* .IP "AUTO_CLNT_CTL_HANDSHAKE(VSTREAM *)" /* A pointer to function that will be called at the start of a /* new connection, and that returns 0 in case of success. /* .PP /* Arguments: /* .IP service /* The service argument specifies "transport:servername" where /* transport is currently limited to one of the following: /* .RS /* .IP inet /* servername has the form "inet:host:port". /* .IP local /* servername has the form "local:private/servicename" or /* "local:public/servicename". This is the preferred way to /* specify Postfix daemons that are configured as "unix" in /* master.cf. /* .IP unix /* servername has the form "unix:private/servicename" or /* "unix:public/servicename". This does not work on Solaris, /* where Postfix uses STREAMS instead of UNIX-domain sockets. /* .RE /* .IP timeout /* The time limit for sending, receiving, or for connecting /* to a server. Specify a value <=0 to disable the time limit. /* .IP max_idle /* Idle time after which the client disconnects. Specify 0 to /* disable the limit. /* .IP max_ttl /* Upper bound on the time that a connection is allowed to persist. /* Specify 0 to disable the limit. /* .IP open_action /* Application call-back routine that opens a stream or returns a /* null pointer upon failure. In case of success, the call-back routine /* is expected to set the stream pathname to the server endpoint name. /* .IP context /* Application context that is passed to the open_action routine. /* .IP handshake /* A null pointer, or a pointer to function that will be called /* at the start of a new connection and that returns 0 in case /* of success. /* DIAGNOSTICS /* Warnings: communication failure. Fatal error: out of memory. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #include /* Utility library. */ #include #include #include #include #include #include #include #include /* Application-specific. */ /* * AUTO_CLNT is an opaque structure. None of the access methods can easily * be implemented as a macro, and access is not performance critical anyway. */ struct AUTO_CLNT { VSTREAM *vstream; /* buffered I/O */ char *endpoint; /* host:port or pathname */ int timeout; /* I/O time limit */ int max_idle; /* time before client disconnect */ int max_ttl; /* time before client disconnect */ AUTO_CLNT_HANDSHAKE_FN handshake; /* new connection only */ int (*connect) (const char *, int, int); /* unix, local, inet */ }; static void auto_clnt_close(AUTO_CLNT *); /* auto_clnt_event - server-initiated disconnect or client-side max_idle */ static void auto_clnt_event(int unused_event, void *context) { AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context; /* * Sanity check. This routine causes the stream to be closed, so it * cannot be called when the stream is already closed. */ if (auto_clnt->vstream == 0) msg_panic("auto_clnt_event: stream is closed"); auto_clnt_close(auto_clnt); } /* auto_clnt_ttl_event - client-side expiration */ static void auto_clnt_ttl_event(int event, void *context) { /* * XXX This function is needed only because event_request_timer() cannot * distinguish between requests that specify the same call-back routine * and call-back context. The fix is obvious: specify a request ID along * with the call-back routine, but there is too much code that would have * to be changed. * * XXX Should we be concerned that an overly aggressive optimizer will * eliminate this function and replace calls to auto_clnt_ttl_event() by * direct calls to auto_clnt_event()? It should not, because there exists * code that takes the address of both functions. */ auto_clnt_event(event, context); } /* auto_clnt_open - connect to service */ static void auto_clnt_open(AUTO_CLNT *auto_clnt) { const char *myname = "auto_clnt_open"; int fd; /* * Sanity check. */ if (auto_clnt->vstream) msg_panic("auto_clnt_open: stream is open"); /* * Schedule a read event so that we can clean up when the remote side * disconnects, and schedule a timer event so that we can cleanup an idle * connection. Note that both events are handled by the same routine. * * Finally, schedule an event to force disconnection even when the * connection is not idle. This is to prevent one client from clinging on * to a server forever. */ fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout); if (fd < 0) { msg_warn("connect to %s: %m", auto_clnt->endpoint); } else { if (msg_verbose) msg_info("%s: connected to %s", myname, auto_clnt->endpoint); auto_clnt->vstream = vstream_fdopen(fd, O_RDWR); vstream_control(auto_clnt->vstream, CA_VSTREAM_CTL_PATH(auto_clnt->endpoint), CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout), CA_VSTREAM_CTL_END); } if (auto_clnt->vstream != 0) { close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC); event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event, (void *) auto_clnt); if (auto_clnt->max_idle > 0) event_request_timer(auto_clnt_event, (void *) auto_clnt, auto_clnt->max_idle); if (auto_clnt->max_ttl > 0) event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt, auto_clnt->max_ttl); } } /* auto_clnt_close - disconnect from service */ static void auto_clnt_close(AUTO_CLNT *auto_clnt) { const char *myname = "auto_clnt_close"; /* * Sanity check. */ if (auto_clnt->vstream == 0) msg_panic("%s: stream is closed", myname); /* * Be sure to disable read and timer events. */ if (msg_verbose) msg_info("%s: disconnect %s stream", myname, VSTREAM_PATH(auto_clnt->vstream)); event_disable_readwrite(vstream_fileno(auto_clnt->vstream)); event_cancel_timer(auto_clnt_event, (void *) auto_clnt); event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt); (void) vstream_fclose(auto_clnt->vstream); auto_clnt->vstream = 0; } /* auto_clnt_recover - recover from server-initiated disconnect */ void auto_clnt_recover(AUTO_CLNT *auto_clnt) { /* * Clean up. Don't re-connect until the caller needs it. */ if (auto_clnt->vstream) auto_clnt_close(auto_clnt); } /* auto_clnt_access - access a client stream */ VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt) { AUTO_CLNT_HANDSHAKE_FN handshake; /* * Open a stream or restart the idle timer. * * Important! Do not restart the TTL timer! */ if (auto_clnt->vstream == 0) { auto_clnt_open(auto_clnt); handshake = (auto_clnt->vstream ? auto_clnt->handshake : 0); } else { if (auto_clnt->max_idle > 0) event_request_timer(auto_clnt_event, (void *) auto_clnt, auto_clnt->max_idle); handshake = 0; } if (handshake != 0 && handshake(auto_clnt->vstream) != 0) return (0); return (auto_clnt->vstream); } /* auto_clnt_create - create client stream object */ AUTO_CLNT *auto_clnt_create(const char *service, int timeout, int max_idle, int max_ttl) { const char *myname = "auto_clnt_create"; char *transport = mystrdup(service); char *endpoint; AUTO_CLNT *auto_clnt; /* * Don't open the stream until the caller needs it. */ if ((endpoint = split_at(transport, ':')) == 0 || *endpoint == 0 || *transport == 0) msg_fatal("need service transport:endpoint instead of \"%s\"", service); if (msg_verbose) msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint); auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt)); auto_clnt->vstream = 0; auto_clnt->endpoint = mystrdup(endpoint); auto_clnt->timeout = timeout; auto_clnt->max_idle = max_idle; auto_clnt->max_ttl = max_ttl; auto_clnt->handshake = 0; if (strcmp(transport, "inet") == 0) { auto_clnt->connect = inet_connect; } else if (strcmp(transport, "local") == 0) { auto_clnt->connect = LOCAL_CONNECT; } else if (strcmp(transport, "unix") == 0) { auto_clnt->connect = unix_connect; } else { msg_fatal("invalid transport name: %s in service: %s", transport, service); } myfree(transport); return (auto_clnt); } /* auto_clnt_name - return client stream name */ const char *auto_clnt_name(AUTO_CLNT *auto_clnt) { return (auto_clnt->endpoint); } /* auto_clnt_free - destroy client stream instance */ void auto_clnt_free(AUTO_CLNT *auto_clnt) { if (auto_clnt->vstream) auto_clnt_close(auto_clnt); myfree(auto_clnt->endpoint); myfree((void *) auto_clnt); } /* auto_clnt_control - fine control */ void auto_clnt_control(AUTO_CLNT *client, int name,...) { const char *myname = "auto_clnt_control"; va_list ap; for (va_start(ap, name); name != AUTO_CLNT_CTL_END; name = va_arg(ap, int)) { switch (name) { case AUTO_CLNT_CTL_HANDSHAKE: client->handshake = va_arg(ap, AUTO_CLNT_HANDSHAKE_FN); break; default: msg_panic("%s: bad name %d", myname, name); } } va_end(ap); }