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