/* $NetBSD: auto_clnt.c,v 1.4 2022/10/08 16:12:50 christos Exp $ */
/*++
/* NAME
/* auto_clnt 3
/* SUMMARY
/* client endpoint maintenance
/* SYNOPSIS
/* #include <auto_clnt.h>
/*
/* 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 <sys_defs.h>
#include <string.h>
/* Utility library. */
#include <msg.h>
#include <mymalloc.h>
#include <vstream.h>
#include <events.h>
#include <iostuff.h>
#include <connect.h>
#include <split_at.h>
#include <auto_clnt.h>
/* 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);
}