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