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  */
78typedef 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
92static 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
101static 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
175static 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
252static 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
317static 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
396static 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
406static 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
420SCACHE *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