xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_tcp.c (revision e89934bbf778a6d6d6894877c4da59d0c7835b0f)
1 /*	$NetBSD: dict_tcp.c,v 1.2 2017/02/14 01:16:49 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_tcp 3
6 /* SUMMARY
7 /*	dictionary manager interface to tcp-based lookup tables
8 /* SYNOPSIS
9 /*	#include <dict_tcp.h>
10 /*
11 /*	DICT	*dict_tcp_open(map, open_flags, dict_flags)
12 /*	const char *map;
13 /*	int	open_flags;
14 /*	int	dict_flags;
15 /* DESCRIPTION
16 /*	dict_tcp_open() makes a TCP server accessible via the generic
17 /*	dictionary operations described in dict_open(3).
18 /*	The only implemented operation is dictionary lookup. This map
19 /*	type can be useful for simulating a dynamic lookup table.
20 /*
21 /*	Map names have the form host:port.
22 /*
23 /*	The TCP map class implements a very simple protocol: the client
24 /*	sends a request, and the server sends one reply. Requests and
25 /*	replies are sent as one line of ASCII text, terminated by the
26 /*	ASCII newline character. Request and reply parameters (see below)
27 /*	are separated by whitespace.
28 /* ENCODING
29 /* .ad
30 /* .fi
31 /*	In request and reply parameters, the character % and any non-printing
32 /*	and whitespace characters must be replaced by %XX, XX being the
33 /*	corresponding ASCII hexadecimal character value. The hexadecimal codes
34 /*	can be specified in any case (upper, lower, mixed).
35 /* REQUEST FORMAT
36 /* .ad
37 /* .fi
38 /*	Requests are strings that serve as lookup key in the simulated
39 /*	table.
40 /* .IP "get SPACE key NEWLINE"
41 /*	Look up data under the specified key.
42 /* .IP "put SPACE key SPACE value NEWLINE"
43 /*	This request is currently not implemented.
44 /* REPLY FORMAT
45 /* .ad
46 /* .fi
47 /*	Replies must be no longer than 4096 characters including the
48 /*	newline terminator, and must have the following form:
49 /* .IP "500 SPACE text NEWLINE"
50 /*	In case of a lookup request, the requested data does not exist.
51 /*	In case of an update request, the request was rejected.
52 /*	The text gives the nature of the problem.
53 /* .IP "400 SPACE text NEWLINE"
54 /*	This indicates an error condition. The text gives the nature of
55 /*	the problem. The client should retry the request later.
56 /* .IP "200 SPACE text NEWLINE"
57 /*	The request was successful. In the case of a lookup request,
58 /*	the text contains an encoded version of the requested data.
59 /* SECURITY
60 /*	This map must not 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 /*	hex_quote(3) http-style quoting
65 /* DIAGNOSTICS
66 /*	Fatal errors: out of memory, unknown host or service name,
67 /*	attempt to update or iterate over map.
68 /* BUGS
69 /*	Only the lookup method is currently implemented.
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 /* System library. */
82 
83 #include "sys_defs.h"
84 #include <unistd.h>
85 #include <string.h>
86 #include <errno.h>
87 #include <ctype.h>
88 
89 /* Utility library. */
90 
91 #include <msg.h>
92 #include <mymalloc.h>
93 #include <vstring.h>
94 #include <vstream.h>
95 #include <vstring_vstream.h>
96 #include <connect.h>
97 #include <hex_quote.h>
98 #include <dict.h>
99 #include <stringops.h>
100 #include <dict_tcp.h>
101 
102 /* Application-specific. */
103 
104 typedef struct {
105     DICT    dict;			/* generic members */
106     VSTRING *raw_buf;			/* raw I/O buffer */
107     VSTRING *hex_buf;			/* quoted I/O buffer */
108     VSTREAM *fp;			/* I/O stream */
109 } DICT_TCP;
110 
111 #define DICT_TCP_MAXTRY	10		/* attempts before giving up */
112 #define DICT_TCP_TMOUT	100		/* connect/read/write timeout */
113 #define DICT_TCP_MAXLEN	4096		/* server reply size limit */
114 
115 #define STR(x)		vstring_str(x)
116 
117 /* dict_tcp_connect - connect to TCP server */
118 
dict_tcp_connect(DICT_TCP * dict_tcp)119 static int dict_tcp_connect(DICT_TCP *dict_tcp)
120 {
121     int     fd;
122 
123     /*
124      * Connect to the server. Enforce a time limit on all operations so that
125      * we do not get stuck.
126      */
127     if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) {
128 	msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name);
129 	return (-1);
130     }
131     dict_tcp->fp = vstream_fdopen(fd, O_RDWR);
132     vstream_control(dict_tcp->fp,
133 		    CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT),
134 		    CA_VSTREAM_CTL_END);
135 
136     /*
137      * Allocate per-map I/O buffers on the fly.
138      */
139     if (dict_tcp->raw_buf == 0) {
140 	dict_tcp->raw_buf = vstring_alloc(10);
141 	dict_tcp->hex_buf = vstring_alloc(10);
142     }
143     return (0);
144 }
145 
146 /* dict_tcp_disconnect - disconnect from TCP server */
147 
dict_tcp_disconnect(DICT_TCP * dict_tcp)148 static void dict_tcp_disconnect(DICT_TCP *dict_tcp)
149 {
150     (void) vstream_fclose(dict_tcp->fp);
151     dict_tcp->fp = 0;
152 }
153 
154 /* dict_tcp_lookup - request TCP server */
155 
dict_tcp_lookup(DICT * dict,const char * key)156 static const char *dict_tcp_lookup(DICT *dict, const char *key)
157 {
158     DICT_TCP *dict_tcp = (DICT_TCP *) dict;
159     const char *myname = "dict_tcp_lookup";
160     int     tries;
161     char   *start;
162     int     last_ch;
163 
164 #define RETURN(errval, result) { dict->error = errval; return (result); }
165 
166     if (msg_verbose)
167 	msg_info("%s: key %s", myname, key);
168 
169     /*
170      * Optionally fold the key.
171      */
172     if (dict->flags & DICT_FLAG_FOLD_MUL) {
173 	if (dict->fold_buf == 0)
174 	    dict->fold_buf = vstring_alloc(10);
175 	vstring_strcpy(dict->fold_buf, key);
176 	key = lowercase(vstring_str(dict->fold_buf));
177     }
178     for (tries = 0; /* see below */ ; /* see below */ ) {
179 
180 	/*
181 	 * Connect to the server, or use an existing connection.
182 	 */
183 	if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) {
184 
185 	    /*
186 	     * Send request and receive response. Both are %XX quoted and
187 	     * both are terminated by newline. This encoding is convenient
188 	     * for data that is mostly text.
189 	     */
190 	    hex_quote(dict_tcp->hex_buf, key);
191 	    vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf));
192 	    if (msg_verbose)
193 		msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf));
194 	    last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp,
195 					     DICT_TCP_MAXLEN);
196 	    if (last_ch == '\n')
197 		break;
198 
199 	    /*
200 	     * Disconnect from the server if it can't talk to us.
201 	     */
202 	    if (last_ch < 0)
203 		msg_warn("read TCP map reply from %s: unexpected EOF (%m)",
204 			 dict_tcp->dict.name);
205 	    else
206 		msg_warn("read TCP map reply from %s: text longer than %d",
207 			 dict_tcp->dict.name, DICT_TCP_MAXLEN);
208 	    dict_tcp_disconnect(dict_tcp);
209 	}
210 
211 	/*
212 	 * Try to connect a limited number of times before giving up.
213 	 */
214 	if (++tries >= DICT_TCP_MAXTRY)
215 	    RETURN(DICT_ERR_RETRY, 0);
216 
217 	/*
218 	 * Sleep between attempts, instead of hammering the server.
219 	 */
220 	sleep(1);
221     }
222     if (msg_verbose)
223 	msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf));
224 
225     /*
226      * Check the general reply syntax. If the reply is malformed, disconnect
227      * and try again later.
228      */
229     if (start = STR(dict_tcp->hex_buf),
230 	!ISDIGIT(start[0]) || !ISDIGIT(start[1])
231 	|| !ISDIGIT(start[2]) || !ISSPACE(start[3])
232 	|| !hex_unquote(dict_tcp->raw_buf, start + 4)) {
233 	msg_warn("read TCP map reply from %s: malformed reply: %.100s",
234 	       dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
235 	dict_tcp_disconnect(dict_tcp);
236 	RETURN(DICT_ERR_RETRY, 0);
237     }
238 
239     /*
240      * Examine the reply status code. If the reply is malformed, disconnect
241      * and try again later.
242      */
243     switch (start[0]) {
244     default:
245 	msg_warn("read TCP map reply from %s: bad status code: %.100s",
246 	       dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
247 	dict_tcp_disconnect(dict_tcp);
248 	RETURN(DICT_ERR_RETRY, 0);
249     case '4':
250 	if (msg_verbose)
251 	    msg_info("%s: soft error: %s",
252 		     myname, printable(STR(dict_tcp->hex_buf), '_'));
253 	dict_tcp_disconnect(dict_tcp);
254 	RETURN(DICT_ERR_RETRY, 0);
255     case '5':
256 	if (msg_verbose)
257 	    msg_info("%s: not found: %s",
258 		     myname, printable(STR(dict_tcp->hex_buf), '_'));
259 	RETURN(DICT_ERR_NONE, 0);
260     case '2':
261 	if (msg_verbose)
262 	    msg_info("%s: found: %s",
263 		     myname, printable(STR(dict_tcp->raw_buf), '_'));
264 	RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf));
265     }
266 }
267 
268 /* dict_tcp_close - close TCP map */
269 
dict_tcp_close(DICT * dict)270 static void dict_tcp_close(DICT *dict)
271 {
272     DICT_TCP *dict_tcp = (DICT_TCP *) dict;
273 
274     if (dict_tcp->fp)
275 	(void) vstream_fclose(dict_tcp->fp);
276     if (dict_tcp->raw_buf)
277 	vstring_free(dict_tcp->raw_buf);
278     if (dict_tcp->hex_buf)
279 	vstring_free(dict_tcp->hex_buf);
280     if (dict->fold_buf)
281 	vstring_free(dict->fold_buf);
282     dict_free(dict);
283 }
284 
285 /* dict_tcp_open - open TCP map */
286 
dict_tcp_open(const char * map,int open_flags,int dict_flags)287 DICT   *dict_tcp_open(const char *map, int open_flags, int dict_flags)
288 {
289     DICT_TCP *dict_tcp;
290 
291     /*
292      * Sanity checks.
293      */
294     if (dict_flags & DICT_FLAG_NO_UNAUTH)
295 	return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
296 		     "%s:%s map is not allowed for security sensitive data",
297 			       DICT_TYPE_TCP, map));
298     if (open_flags != O_RDONLY)
299 	return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
300 			       "%s:%s map requires O_RDONLY access mode",
301 			       DICT_TYPE_TCP, map));
302 
303     /*
304      * Create the dictionary handle. Do not open the connection until the
305      * first request is made.
306      */
307     dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp));
308     dict_tcp->fp = 0;
309     dict_tcp->raw_buf = dict_tcp->hex_buf = 0;
310     dict_tcp->dict.lookup = dict_tcp_lookup;
311     dict_tcp->dict.close = dict_tcp_close;
312     dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
313     if (dict_flags & DICT_FLAG_FOLD_MUL)
314 	dict_tcp->dict.fold_buf = vstring_alloc(10);
315 
316     return (DICT_DEBUG (&dict_tcp->dict));
317 }
318