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