xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_sockmap.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /*	$NetBSD: dict_sockmap.c,v 1.5 2017/02/14 01:16:49 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_sockmap 3
6 /* SUMMARY
7 /*	dictionary manager interface to Sendmail-style socketmap server
8 /* SYNOPSIS
9 /*	#include <dict_sockmap.h>
10 /*
11 /*	DICT	*dict_sockmap_open(map, open_flags, dict_flags)
12 /*	const char *map;
13 /*	int	open_flags;
14 /*	int	dict_flags;
15 /* DESCRIPTION
16 /*	dict_sockmap_open() makes a Sendmail-style socketmap server
17 /*	accessible via the generic dictionary operations described
18 /*	in dict_open(3).  The only implemented operation is dictionary
19 /*	lookup. This map type can be useful for simulating a dynamic
20 /*	lookup table.
21 /*
22 /*	Postfix socketmap names have the form inet:host:port:socketmap-name
23 /*	or unix:pathname:socketmap-name, where socketmap-name
24 /*	specifies the socketmap name that the socketmap server uses.
25 /*
26 /*	To test this module, build the netstring and dict_open test
27 /*	programs. Run "./netstring nc -l portnumber" as the server,
28 /*	and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname"
29 /*	as the client.
30 /* PROTOCOL
31 /* .ad
32 /* .fi
33 /*	The socketmap class implements a simple protocol: the client
34 /*	sends one request, and the server sends one reply.
35 /* ENCODING
36 /* .ad
37 /* .fi
38 /*	Each request and reply are sent as one netstring object.
39 /* REQUEST FORMAT
40 /* .ad
41 /* .fi
42 /* .IP "<mapname> <space> <key>"
43 /*	Search the specified socketmap under the specified key.
44 /* REPLY FORMAT
45 /* .ad
46 /* .fi
47 /*	Replies must be no longer than 100000 characters (not including
48 /*	the netstring encapsulation), and must have the following
49 /*	form:
50 /* .IP "OK <space> <data>"
51 /*	The requested data was found.
52 /* .IP "NOTFOUND <space>"
53 /*	The requested data was not found.
54 /* .IP "TEMP <space> <reason>"
55 /* .IP "TIMEOUT <space> <reason>"
56 /* .IP "PERM <space> <reason>"
57 /*	The request failed. The reason, if non-empty, is descriptive
58 /*	text.
59 /* SECURITY
60 /*	This map cannot be used for security-sensitive information,
61 /*	because neither the connection nor the server are authenticated.
62 /* SEE ALSO
63 /*	dict(3) generic dictionary manager
64 /*	netstring(3) netstring stream I/O support
65 /* DIAGNOSTICS
66 /*	Fatal errors: out of memory, unknown host or service name,
67 /*	attempt to update or iterate over map.
68 /* BUGS
69 /*	The protocol limits are not yet configurable.
70 /* LICENSE
71 /* .ad
72 /* .fi
73 /*	The Secure Mailer license must be distributed with this software.
74 /* AUTHOR(S)
75 /*	Wietse Venema
76 /*	IBM T.J. Watson Research
77 /*	P.O. Box 704
78 /*	Yorktown Heights, NY 10598, USA
79 /*--*/
80 
81  /*
82   * System library.
83   */
84 #include <sys_defs.h>
85 #include <errno.h>
86 #include <string.h>
87 #include <ctype.h>
88 
89  /*
90   * Utility library.
91   */
92 #include <mymalloc.h>
93 #include <msg.h>
94 #include <vstream.h>
95 #include <auto_clnt.h>
96 #include <netstring.h>
97 #include <split_at.h>
98 #include <stringops.h>
99 #include <htable.h>
100 #include <dict_sockmap.h>
101 
102  /*
103   * Socket map data structure.
104   */
105 typedef struct {
106     DICT    dict;			/* parent class */
107     char   *sockmap_name;		/* on-the-wire socketmap name */
108     VSTRING *rdwr_buf;			/* read/write buffer */
109     HTABLE_INFO *client_info;		/* shared endpoint name and handle */
110 } DICT_SOCKMAP;
111 
112  /*
113   * Default limits.
114   */
115 #define DICT_SOCKMAP_DEF_TIMEOUT	100	/* connect/read/write timeout */
116 #define DICT_SOCKMAP_DEF_MAX_REPLY	100000	/* reply size limit */
117 #define DICT_SOCKMAP_DEF_MAX_IDLE	10	/* close idle socket */
118 #define DICT_SOCKMAP_DEF_MAX_TTL	100	/* close old socket */
119 
120  /*
121   * Class variables.
122   */
123 static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT;
124 static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY;
125 static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE;
126 static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL;
127 
128  /*
129   * The client handle is shared between socketmap instances that have the
130   * same inet:host:port or unix:pathame information. This could be factored
131   * out as a general module for reference-counted handles of any kind.
132   */
133 static HTABLE *dict_sockmap_handles;	/* shared handles */
134 
135 typedef struct {
136     AUTO_CLNT *client_handle;		/* the client handle */
137     int     refcount;			/* the reference count */
138 } DICT_SOCKMAP_REFC_HANDLE;
139 
140 #define DICT_SOCKMAP_RH_NAME(ht)	(ht)->key
141 #define DICT_SOCKMAP_RH_HANDLE(ht) \
142 	((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle
143 #define DICT_SOCKMAP_RH_REFCOUNT(ht) \
144 	((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount
145 
146  /*
147   * Socketmap protocol elements.
148   */
149 #define DICT_SOCKMAP_PROT_OK		"OK"
150 #define DICT_SOCKMAP_PROT_NOTFOUND	"NOTFOUND"
151 #define DICT_SOCKMAP_PROT_TEMP		"TEMP"
152 #define DICT_SOCKMAP_PROT_TIMEOUT	"TIMEOUT"
153 #define DICT_SOCKMAP_PROT_PERM		"PERM"
154 
155  /*
156   * SLMs.
157   */
158 #define STR(x)	vstring_str(x)
159 #define LEN(x)	VSTRING_LEN(x)
160 
161 /* dict_sockmap_lookup - socket map lookup */
162 
dict_sockmap_lookup(DICT * dict,const char * key)163 static const char *dict_sockmap_lookup(DICT *dict, const char *key)
164 {
165     const char *myname = "dict_sockmap_lookup";
166     DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
167     AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info);
168     VSTREAM *fp;
169     int     netstring_err;
170     char   *reply_payload;
171     int     except_count;
172     const char *error_class;
173 
174     if (msg_verbose)
175 	msg_info("%s: key %s", myname, key);
176 
177     /*
178      * Optionally fold the key.
179      */
180     if (dict->flags & DICT_FLAG_FOLD_MUL) {
181 	if (dict->fold_buf == 0)
182 	    dict->fold_buf = vstring_alloc(100);
183 	vstring_strcpy(dict->fold_buf, key);
184 	key = lowercase(STR(dict->fold_buf));
185     }
186 
187     /*
188      * We retry connection-level errors once, to make server restarts
189      * transparent.
190      */
191     for (except_count = 0; /* see below */ ; except_count++) {
192 
193 	/*
194 	 * Look up the stream.
195 	 */
196 	if ((fp = auto_clnt_access(sockmap_clnt)) == 0) {
197 	    msg_warn("table %s:%s lookup error: %m", dict->type, dict->name);
198 	    dict->error = DICT_ERR_RETRY;
199 	    return (0);
200 	}
201 
202 	/*
203 	 * Set up an exception handler.
204 	 */
205 	netstring_setup(fp, dict_sockmap_timeout);
206 	if ((netstring_err = vstream_setjmp(fp)) == 0) {
207 
208 	    /*
209 	     * Send the query. This may raise an exception.
210 	     */
211 	    vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key);
212 	    NETSTRING_PUT_BUF(fp, dp->rdwr_buf);
213 
214 	    /*
215 	     * Receive the response. This may raise an exception.
216 	     */
217 	    netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply);
218 
219 	    /*
220 	     * If we got here, then no exception was raised.
221 	     */
222 	    break;
223 	}
224 
225 	/*
226 	 * Handle exceptions.
227 	 */
228 	else {
229 
230 	    /*
231 	     * We retry a broken connection only once.
232 	     */
233 	    if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF
234 		&& errno != ETIMEDOUT) {
235 		auto_clnt_recover(sockmap_clnt);
236 		continue;
237 	    }
238 
239 	    /*
240 	     * We do not retry other errors.
241 	     */
242 	    else {
243 		msg_warn("table %s:%s lookup error: %s",
244 			 dict->type, dict->name,
245 			 netstring_strerror(netstring_err));
246 		dict->error = DICT_ERR_RETRY;
247 		return (0);
248 	    }
249 	}
250     }
251 
252     /*
253      * Parse the reply.
254      */
255     VSTRING_TERMINATE(dp->rdwr_buf);
256     reply_payload = split_at(STR(dp->rdwr_buf), ' ');
257     if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) {
258 	dict->error = 0;
259 	return (reply_payload);
260     } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) {
261 	dict->error = 0;
262 	return (0);
263     }
264     /* We got no definitive reply. */
265     if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) {
266 	error_class = "temporary";
267 	dict->error = DICT_ERR_RETRY;
268     } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) {
269 	error_class = "timeout";
270 	dict->error = DICT_ERR_RETRY;
271     } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) {
272 	error_class = "permanent";
273 	dict->error = DICT_ERR_CONFIG;
274     } else {
275 	error_class = "unknown";
276 	dict->error = DICT_ERR_RETRY;
277     }
278     while (reply_payload && ISSPACE(*reply_payload))
279 	reply_payload++;
280     msg_warn("%s:%s socketmap server %s error%s%.200s",
281 	     dict->type, dict->name, error_class,
282 	     reply_payload && *reply_payload ? ": " : "",
283 	     reply_payload && *reply_payload ?
284 	     printable(reply_payload, '?') : "");
285     return (0);
286 }
287 
288 /* dict_sockmap_close - close socket map */
289 
dict_sockmap_close(DICT * dict)290 static void dict_sockmap_close(DICT *dict)
291 {
292     const char *myname = "dict_sockmap_close";
293     DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
294 
295     if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0)
296 	msg_panic("%s: attempt to close a non-existent map", myname);
297     vstring_free(dp->rdwr_buf);
298     myfree(dp->sockmap_name);
299     if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) {
300 	auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info));
301 	htable_delete(dict_sockmap_handles,
302 		      DICT_SOCKMAP_RH_NAME(dp->client_info), myfree);
303     }
304     if (dict->fold_buf)
305 	vstring_free(dict->fold_buf);
306     dict_free(dict);
307 }
308 
309 /* dict_sockmap_open - open socket map */
310 
dict_sockmap_open(const char * mapname,int open_flags,int dict_flags)311 DICT   *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags)
312 {
313     DICT_SOCKMAP *dp;
314     char   *saved_name = 0;
315     char   *sockmap;
316     DICT_SOCKMAP_REFC_HANDLE *ref_handle;
317     HTABLE_INFO *client_info;
318 
319     /*
320      * Let the optimizer worry about eliminating redundant code.
321      */
322 #define DICT_SOCKMAP_OPEN_RETURN(d) do { \
323 	DICT *__d = (d); \
324 	if (saved_name != 0) \
325 	    myfree(saved_name); \
326 	return (__d); \
327     } while (0)
328 
329     /*
330      * Sanity checks.
331      */
332     if (open_flags != O_RDONLY)
333 	DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
334 						open_flags, dict_flags,
335 				  "%s:%s map requires O_RDONLY access mode",
336 						DICT_TYPE_SOCKMAP, mapname));
337     if (dict_flags & DICT_FLAG_NO_UNAUTH)
338 	DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
339 						open_flags, dict_flags,
340 		     "%s:%s map is not allowed for security-sensitive data",
341 						DICT_TYPE_SOCKMAP, mapname));
342 
343     /*
344      * Separate the socketmap name from the socketmap server name.
345      */
346     saved_name = mystrdup(mapname);
347     if ((sockmap = split_at_right(saved_name, ':')) == 0)
348 	DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
349 						open_flags, dict_flags,
350 				    "%s requires server:socketmap argument",
351 						DICT_TYPE_SOCKMAP));
352 
353     /*
354      * Use one reference-counted client handle for all socketmaps with the
355      * same inet:host:port or unix:pathname information.
356      *
357      * XXX Todo: graceful degradation after endpoint syntax error.
358      */
359     if (dict_sockmap_handles == 0)
360 	dict_sockmap_handles = htable_create(1);
361     if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) {
362 	ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle));
363 	client_info = htable_enter(dict_sockmap_handles,
364 				   saved_name, (void *) ref_handle);
365 	/* XXX Late initialization, so we can reuse macros for consistency. */
366 	DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1;
367 	DICT_SOCKMAP_RH_HANDLE(client_info) =
368 	    auto_clnt_create(saved_name, dict_sockmap_timeout,
369 			     dict_sockmap_max_idle, dict_sockmap_max_ttl);
370     } else
371 	DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1;
372 
373     /*
374      * Instantiate a socket map handle.
375      */
376     dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp));
377     dp->rdwr_buf = vstring_alloc(100);
378     dp->sockmap_name = mystrdup(sockmap);
379     dp->client_info = client_info;
380     dp->dict.lookup = dict_sockmap_lookup;
381     dp->dict.close = dict_sockmap_close;
382     /* Don't look up parent domains or network superblocks. */
383     dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
384 
385     DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict));
386 }
387