xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/scache_clnt.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
1 /*	$NetBSD: scache_clnt.c,v 1.3 2022/10/08 16:12:45 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	scache_clnt 3
6 /* SUMMARY
7 /*	session cache manager client
8 /* SYNOPSIS
9 /*	#include <scache.h>
10 /* DESCRIPTION
11 /*	SCACHE *scache_clnt_create(server, timeout, idle_limit, ttl_limit)
12 /*	const char *server;
13 /*	int	timeout;
14 /*	int	idle_limit;
15 /*	int	ttl_limit;
16 /* DESCRIPTION
17 /*	This module implements the client-side protocol of the
18 /*	session cache service.
19 /*
20 /*	scache_clnt_create() creates a session cache service client.
21 /*
22 /*	Arguments:
23 /* .IP server
24 /*	The session cache service name.
25 /* .IP timeout
26 /*	Time limit for connect, send or receive operations.
27 /* .IP idle_limit
28 /*	Idle time after which the client disconnects.
29 /* .IP ttl_limit
30 /*	Upper bound on the time that a connection is allowed to persist.
31 /* DIAGNOSTICS
32 /*	Fatal error: memory allocation problem;
33 /*	warning: communication error;
34 /*	panic: internal consistency failure.
35 /* SEE ALSO
36 /*	scache(3), generic session cache API
37 /* LICENSE
38 /* .ad
39 /* .fi
40 /*	The Secure Mailer license must be distributed with this software.
41 /* AUTHOR(S)
42 /*	Wietse Venema
43 /*	IBM T.J. Watson Research
44 /*	P.O. Box 704
45 /*	Yorktown Heights, NY 10598, USA
46 /*
47 /*	Wietse Venema
48 /*	Google, Inc.
49 /*	111 8th Avenue
50 /*	New York, NY 10011, USA
51 /*--*/
52 
53 /* System library. */
54 
55 #include <sys_defs.h>
56 #include <errno.h>
57 
58 /* Utility library. */
59 
60 #include <msg.h>
61 #include <mymalloc.h>
62 #include <auto_clnt.h>
63 #include <stringops.h>
64 
65 /*#define msg_verbose 1*/
66 
67 /* Global library. */
68 
69 #include <mail_proto.h>
70 #include <mail_params.h>
71 #include <scache.h>
72 
73 /* Application-specific. */
74 
75  /*
76   * SCACHE_CLNT is a derived type from the SCACHE super-class.
77   */
78 typedef struct {
79     SCACHE  scache[1];			/* super-class */
80     AUTO_CLNT *auto_clnt;		/* client endpoint */
81 #ifdef CANT_WRITE_BEFORE_SENDING_FD
82     VSTRING *dummy;			/* dummy buffer */
83 #endif
84 } SCACHE_CLNT;
85 
86 #define STR(x) vstring_str(x)
87 
88 #define SCACHE_MAX_TRIES	2
89 
90 /* scache_clnt_handshake - receive server protocol announcement */
91 
scache_clnt_handshake(VSTREAM * stream)92 static int scache_clnt_handshake(VSTREAM *stream)
93 {
94     return (attr_scan(stream, ATTR_FLAG_STRICT,
95 		   RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SCACHE),
96 		      ATTR_TYPE_END));
97 }
98 
99 /* scache_clnt_save_endp - save endpoint */
100 
scache_clnt_save_endp(SCACHE * scache,int endp_ttl,const char * endp_label,const char * endp_prop,int fd)101 static void scache_clnt_save_endp(SCACHE *scache, int endp_ttl,
102 				          const char *endp_label,
103 				          const char *endp_prop, int fd)
104 {
105     SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
106     const char *myname = "scache_clnt_save_endp";
107     VSTREAM *stream;
108     int     status;
109     int     tries;
110     int     count = 0;
111 
112     if (msg_verbose)
113 	msg_info("%s: endp=%s prop=%s fd=%d",
114 		 myname, endp_label, endp_prop, fd);
115 
116     /*
117      * Sanity check.
118      */
119     if (endp_ttl <= 0)
120 	msg_panic("%s: bad endp_ttl: %d", myname, endp_ttl);
121 
122     /*
123      * Try a few times before disabling the cache. We use synchronous calls;
124      * the session cache service is CPU bound and making the client
125      * asynchronous would just complicate the code.
126      */
127     for (tries = 0; sp->auto_clnt != 0; tries++) {
128 	if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
129 	    errno = 0;
130 	    count += 1;
131 	    if (attr_print(stream, ATTR_FLAG_NONE,
132 			 SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_SAVE_ENDP),
133 			   SEND_ATTR_INT(MAIL_ATTR_TTL, endp_ttl),
134 			   SEND_ATTR_STR(MAIL_ATTR_LABEL, endp_label),
135 			   SEND_ATTR_STR(MAIL_ATTR_PROP, endp_prop),
136 			   ATTR_TYPE_END) != 0
137 		|| vstream_fflush(stream)
138 #ifdef CANT_WRITE_BEFORE_SENDING_FD
139 		|| attr_scan(stream, ATTR_FLAG_STRICT,
140 			     RECV_ATTR_STR(MAIL_ATTR_DUMMY, sp->dummy),
141 			     ATTR_TYPE_END) != 1
142 #endif
143 		|| LOCAL_SEND_FD(vstream_fileno(stream), fd) < 0
144 		|| attr_scan(stream, ATTR_FLAG_STRICT,
145 			     RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
146 			     ATTR_TYPE_END) != 1) {
147 		if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
148 		    msg_warn("problem talking to service %s: %m",
149 			     VSTREAM_PATH(stream));
150 		/* Give up or recover. */
151 	    } else {
152 		if (msg_verbose && status != 0)
153 		    msg_warn("%s: descriptor save failed with status %d",
154 			     myname, status);
155 		break;
156 	    }
157 	}
158 	/* Give up or recover. */
159 	if (tries >= SCACHE_MAX_TRIES - 1) {
160 	    msg_warn("disabling connection caching");
161 	    auto_clnt_free(sp->auto_clnt);
162 	    sp->auto_clnt = 0;
163 	    break;
164 	}
165 	sleep(1);				/* XXX make configurable */
166 	auto_clnt_recover(sp->auto_clnt);
167     }
168     /* Always close the descriptor before returning. */
169     if (close(fd) < 0)
170 	msg_warn("%s: close(%d): %m", myname, fd);
171 }
172 
173 /* scache_clnt_find_endp - look up cached session */
174 
scache_clnt_find_endp(SCACHE * scache,const char * endp_label,VSTRING * endp_prop)175 static int scache_clnt_find_endp(SCACHE *scache, const char *endp_label,
176 				         VSTRING *endp_prop)
177 {
178     SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
179     const char *myname = "scache_clnt_find_endp";
180     VSTREAM *stream;
181     int     status;
182     int     tries;
183     int     fd;
184 
185     /*
186      * Try a few times before disabling the cache. We use synchronous calls;
187      * the session cache service is CPU bound and making the client
188      * asynchronous would just complicate the code.
189      */
190     for (tries = 0; sp->auto_clnt != 0; tries++) {
191 	if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
192 	    errno = 0;
193 	    if (attr_print(stream, ATTR_FLAG_NONE,
194 			 SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_FIND_ENDP),
195 			   SEND_ATTR_STR(MAIL_ATTR_LABEL, endp_label),
196 			   ATTR_TYPE_END) != 0
197 		|| vstream_fflush(stream)
198 		|| attr_scan(stream, ATTR_FLAG_STRICT,
199 			     RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
200 			     RECV_ATTR_STR(MAIL_ATTR_PROP, endp_prop),
201 			     ATTR_TYPE_END) != 2) {
202 		if (msg_verbose || (errno != EPIPE && errno != ENOENT))
203 		    msg_warn("problem talking to service %s: %m",
204 			     VSTREAM_PATH(stream));
205 		/* Give up or recover. */
206 	    } else if (status != 0) {
207 		if (msg_verbose)
208 		    msg_info("%s: not found: %s", myname, endp_label);
209 		return (-1);
210 	    } else if (
211 #ifdef CANT_WRITE_BEFORE_SENDING_FD
212 		       attr_print(stream, ATTR_FLAG_NONE,
213 				  SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
214 				  ATTR_TYPE_END) != 0
215 		       || vstream_fflush(stream) != 0
216 		       || read_wait(vstream_fileno(stream),
217 				    stream->timeout) < 0 ||	/* XXX */
218 #endif
219 		       (fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
220 		if (msg_verbose || (errno != EPIPE && errno != ENOENT))
221 		    msg_warn("problem talking to service %s: %m",
222 			     VSTREAM_PATH(stream));
223 		/* Give up or recover. */
224 	    } else {
225 #ifdef MUST_READ_AFTER_SENDING_FD
226 		(void) attr_print(stream, ATTR_FLAG_NONE,
227 				  SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
228 				  ATTR_TYPE_END);
229 		(void) vstream_fflush(stream);
230 #endif
231 		if (msg_verbose)
232 		    msg_info("%s: endp=%s prop=%s fd=%d",
233 			     myname, endp_label, STR(endp_prop), fd);
234 		return (fd);
235 	    }
236 	}
237 	/* Give up or recover. */
238 	if (tries >= SCACHE_MAX_TRIES - 1) {
239 	    msg_warn("disabling connection caching");
240 	    auto_clnt_free(sp->auto_clnt);
241 	    sp->auto_clnt = 0;
242 	    return (-1);
243 	}
244 	sleep(1);				/* XXX make configurable */
245 	auto_clnt_recover(sp->auto_clnt);
246     }
247     return (-1);
248 }
249 
250 /* scache_clnt_save_dest - create destination/endpoint association */
251 
scache_clnt_save_dest(SCACHE * scache,int dest_ttl,const char * dest_label,const char * dest_prop,const char * endp_label)252 static void scache_clnt_save_dest(SCACHE *scache, int dest_ttl,
253 				          const char *dest_label,
254 				          const char *dest_prop,
255 				          const char *endp_label)
256 {
257     SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
258     const char *myname = "scache_clnt_save_dest";
259     VSTREAM *stream;
260     int     status;
261     int     tries;
262 
263     if (msg_verbose)
264 	msg_info("%s: dest_label=%s dest_prop=%s endp_label=%s",
265 		 myname, dest_label, dest_prop, endp_label);
266 
267     /*
268      * Sanity check.
269      */
270     if (dest_ttl <= 0)
271 	msg_panic("%s: bad dest_ttl: %d", myname, dest_ttl);
272 
273     /*
274      * Try a few times before disabling the cache. We use synchronous calls;
275      * the session cache service is CPU bound and making the client
276      * asynchronous would just complicate the code.
277      */
278     for (tries = 0; sp->auto_clnt != 0; tries++) {
279 	if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
280 	    errno = 0;
281 	    if (attr_print(stream, ATTR_FLAG_NONE,
282 			 SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_SAVE_DEST),
283 			   SEND_ATTR_INT(MAIL_ATTR_TTL, dest_ttl),
284 			   SEND_ATTR_STR(MAIL_ATTR_LABEL, dest_label),
285 			   SEND_ATTR_STR(MAIL_ATTR_PROP, dest_prop),
286 			   SEND_ATTR_STR(MAIL_ATTR_LABEL, endp_label),
287 			   ATTR_TYPE_END) != 0
288 		|| vstream_fflush(stream)
289 		|| attr_scan(stream, ATTR_FLAG_STRICT,
290 			     RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
291 			     ATTR_TYPE_END) != 1) {
292 		if (msg_verbose || (errno != EPIPE && errno != ENOENT))
293 		    msg_warn("problem talking to service %s: %m",
294 			     VSTREAM_PATH(stream));
295 		/* Give up or recover. */
296 	    } else {
297 		if (msg_verbose && status != 0)
298 		    msg_warn("%s: destination save failed with status %d",
299 			     myname, status);
300 		break;
301 	    }
302 	}
303 	/* Give up or recover. */
304 	if (tries >= SCACHE_MAX_TRIES - 1) {
305 	    msg_warn("disabling connection caching");
306 	    auto_clnt_free(sp->auto_clnt);
307 	    sp->auto_clnt = 0;
308 	    break;
309 	}
310 	sleep(1);				/* XXX make configurable */
311 	auto_clnt_recover(sp->auto_clnt);
312     }
313 }
314 
315 /* scache_clnt_find_dest - look up cached session */
316 
scache_clnt_find_dest(SCACHE * scache,const char * dest_label,VSTRING * dest_prop,VSTRING * endp_prop)317 static int scache_clnt_find_dest(SCACHE *scache, const char *dest_label,
318 				         VSTRING *dest_prop,
319 				         VSTRING *endp_prop)
320 {
321     SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
322     const char *myname = "scache_clnt_find_dest";
323     VSTREAM *stream;
324     int     status;
325     int     tries;
326     int     fd;
327 
328     /*
329      * Try a few times before disabling the cache. We use synchronous calls;
330      * the session cache service is CPU bound and making the client
331      * asynchronous would just complicate the code.
332      */
333     for (tries = 0; sp->auto_clnt != 0; tries++) {
334 	if ((stream = auto_clnt_access(sp->auto_clnt)) != 0) {
335 	    errno = 0;
336 	    if (attr_print(stream, ATTR_FLAG_NONE,
337 			 SEND_ATTR_STR(MAIL_ATTR_REQ, SCACHE_REQ_FIND_DEST),
338 			   SEND_ATTR_STR(MAIL_ATTR_LABEL, dest_label),
339 			   ATTR_TYPE_END) != 0
340 		|| vstream_fflush(stream)
341 		|| attr_scan(stream, ATTR_FLAG_STRICT,
342 			     RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
343 			     RECV_ATTR_STR(MAIL_ATTR_PROP, dest_prop),
344 			     RECV_ATTR_STR(MAIL_ATTR_PROP, endp_prop),
345 			     ATTR_TYPE_END) != 3) {
346 		if (msg_verbose || (errno != EPIPE && errno != ENOENT))
347 		    msg_warn("problem talking to service %s: %m",
348 			     VSTREAM_PATH(stream));
349 		/* Give up or recover. */
350 	    } else if (status != 0) {
351 		if (msg_verbose)
352 		    msg_info("%s: not found: %s", myname, dest_label);
353 		return (-1);
354 	    } else if (
355 #ifdef CANT_WRITE_BEFORE_SENDING_FD
356 		       attr_print(stream, ATTR_FLAG_NONE,
357 				  SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
358 				  ATTR_TYPE_END) != 0
359 		       || vstream_fflush(stream) != 0
360 		       || read_wait(vstream_fileno(stream),
361 				    stream->timeout) < 0 ||	/* XXX */
362 #endif
363 		       (fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) {
364 		if (msg_verbose || (errno != EPIPE && errno != ENOENT))
365 		    msg_warn("problem talking to service %s: %m",
366 			     VSTREAM_PATH(stream));
367 		/* Give up or recover. */
368 	    } else {
369 #ifdef MUST_READ_AFTER_SENDING_FD
370 		(void) attr_print(stream, ATTR_FLAG_NONE,
371 				  SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
372 				  ATTR_TYPE_END);
373 		(void) vstream_fflush(stream);
374 #endif
375 		if (msg_verbose)
376 		    msg_info("%s: dest=%s dest_prop=%s endp_prop=%s fd=%d",
377 		    myname, dest_label, STR(dest_prop), STR(endp_prop), fd);
378 		return (fd);
379 	    }
380 	}
381 	/* Give up or recover. */
382 	if (tries >= SCACHE_MAX_TRIES - 1) {
383 	    msg_warn("disabling connection caching");
384 	    auto_clnt_free(sp->auto_clnt);
385 	    sp->auto_clnt = 0;
386 	    return (-1);
387 	}
388 	sleep(1);				/* XXX make configurable */
389 	auto_clnt_recover(sp->auto_clnt);
390     }
391     return (-1);
392 }
393 
394 /* scache_clnt_size - dummy */
395 
scache_clnt_size(SCACHE * unused_scache,SCACHE_SIZE * size)396 static void scache_clnt_size(SCACHE *unused_scache, SCACHE_SIZE *size)
397 {
398     /* XXX Crap in a hurry. */
399     size->dest_count = 0;
400     size->endp_count = 0;
401     size->sess_count = 0;
402 }
403 
404 /* scache_clnt_free - destroy cache */
405 
scache_clnt_free(SCACHE * scache)406 static void scache_clnt_free(SCACHE *scache)
407 {
408     SCACHE_CLNT *sp = (SCACHE_CLNT *) scache;
409 
410     if (sp->auto_clnt)
411 	auto_clnt_free(sp->auto_clnt);
412 #ifdef CANT_WRITE_BEFORE_SENDING_FD
413     vstring_free(sp->dummy);
414 #endif
415     myfree((void *) sp);
416 }
417 
418 /* scache_clnt_create - initialize */
419 
scache_clnt_create(const char * server,int timeout,int idle_limit,int ttl_limit)420 SCACHE *scache_clnt_create(const char *server, int timeout,
421 			           int idle_limit, int ttl_limit)
422 {
423     SCACHE_CLNT *sp = (SCACHE_CLNT *) mymalloc(sizeof(*sp));
424     char   *service;
425 
426     sp->scache->save_endp = scache_clnt_save_endp;
427     sp->scache->find_endp = scache_clnt_find_endp;
428     sp->scache->save_dest = scache_clnt_save_dest;
429     sp->scache->find_dest = scache_clnt_find_dest;
430     sp->scache->size = scache_clnt_size;
431     sp->scache->free = scache_clnt_free;
432 
433     service = concatenate("local:" MAIL_CLASS_PRIVATE "/", server, (char *) 0);
434     sp->auto_clnt = auto_clnt_create(service, timeout, idle_limit, ttl_limit);
435     auto_clnt_control(sp->auto_clnt,
436 		      AUTO_CLNT_CTL_HANDSHAKE, scache_clnt_handshake,
437 		      AUTO_CLNT_CTL_END);
438     myfree(service);
439 
440 #ifdef CANT_WRITE_BEFORE_SENDING_FD
441     sp->dummy = vstring_alloc(1);
442 #endif
443 
444     return (sp->scache);
445 }
446