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