summaryrefslogtreecommitdiffstats
path: root/src/util/dict_tcp.c
blob: 922f449a4d9110dd03c5b73a316291cd1ae1d54c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/*++
/* NAME
/*	dict_tcp 3
/* SUMMARY
/*	dictionary manager interface to tcp-based lookup tables
/* SYNOPSIS
/*	#include <dict_tcp.h>
/*
/*	DICT	*dict_tcp_open(map, open_flags, dict_flags)
/*	const char *map;
/*	int	open_flags;
/*	int	dict_flags;
/* DESCRIPTION
/*	dict_tcp_open() makes a TCP server accessible via the generic
/*	dictionary operations described in dict_open(3).
/*	The only implemented operation is dictionary lookup. This map
/*	type can be useful for simulating a dynamic lookup table.
/*
/*	Map names have the form host:port.
/*
/*	The TCP map class implements a very simple protocol: the client
/*	sends a request, and the server sends one reply. Requests and
/*	replies are sent as one line of ASCII text, terminated by the
/*	ASCII newline character. Request and reply parameters (see below)
/*	are separated by whitespace.
/* ENCODING
/* .ad
/* .fi
/*	In request and reply parameters, the character % and any non-printing
/*	and whitespace characters must be replaced by %XX, XX being the
/*	corresponding ASCII hexadecimal character value. The hexadecimal codes
/*	can be specified in any case (upper, lower, mixed).
/* REQUEST FORMAT
/* .ad
/* .fi
/*	Requests are strings that serve as lookup key in the simulated
/*	table.
/* .IP "get SPACE key NEWLINE"
/*	Look up data under the specified key.
/* .IP "put SPACE key SPACE value NEWLINE"
/*	This request is currently not implemented.
/* REPLY FORMAT
/* .ad
/* .fi
/*	Replies must be no longer than 4096 characters including the
/*	newline terminator, and must have the following form:
/* .IP "500 SPACE text NEWLINE"
/*	In case of a lookup request, the requested data does not exist.
/*	In case of an update request, the request was rejected.
/*	The text gives the nature of the problem.
/* .IP "400 SPACE text NEWLINE"
/*	This indicates an error condition. The text gives the nature of
/*	the problem. The client should retry the request later.
/* .IP "200 SPACE text NEWLINE"
/*	The request was successful. In the case of a lookup request,
/*	the text contains an encoded version of the requested data.
/* SECURITY
/*	This map must not be used for security sensitive information,
/*	because neither the connection nor the server are authenticated.
/* SEE ALSO
/*	dict(3) generic dictionary manager
/*	hex_quote(3) http-style quoting
/* DIAGNOSTICS
/*	Fatal errors: out of memory, unknown host or service name,
/*	attempt to update or iterate over map.
/* BUGS
/*	Only the lookup method is currently implemented.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*--*/

/* System library. */

#include "sys_defs.h"
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <connect.h>
#include <hex_quote.h>
#include <dict.h>
#include <stringops.h>
#include <dict_tcp.h>

/* Application-specific. */

typedef struct {
    DICT    dict;			/* generic members */
    VSTRING *raw_buf;			/* raw I/O buffer */
    VSTRING *hex_buf;			/* quoted I/O buffer */
    VSTREAM *fp;			/* I/O stream */
} DICT_TCP;

#define DICT_TCP_MAXTRY	10		/* attempts before giving up */
#define DICT_TCP_TMOUT	100		/* connect/read/write timeout */
#define DICT_TCP_MAXLEN	4096		/* server reply size limit */

#define STR(x)		vstring_str(x)

/* dict_tcp_connect - connect to TCP server */

static int dict_tcp_connect(DICT_TCP *dict_tcp)
{
    int     fd;

    /*
     * Connect to the server. Enforce a time limit on all operations so that
     * we do not get stuck.
     */
    if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) {
	msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name);
	return (-1);
    }
    dict_tcp->fp = vstream_fdopen(fd, O_RDWR);
    vstream_control(dict_tcp->fp,
		    CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT),
		    CA_VSTREAM_CTL_END);

    /*
     * Allocate per-map I/O buffers on the fly.
     */
    if (dict_tcp->raw_buf == 0) {
	dict_tcp->raw_buf = vstring_alloc(10);
	dict_tcp->hex_buf = vstring_alloc(10);
    }
    return (0);
}

/* dict_tcp_disconnect - disconnect from TCP server */

static void dict_tcp_disconnect(DICT_TCP *dict_tcp)
{
    (void) vstream_fclose(dict_tcp->fp);
    dict_tcp->fp = 0;
}

/* dict_tcp_lookup - request TCP server */

static const char *dict_tcp_lookup(DICT *dict, const char *key)
{
    DICT_TCP *dict_tcp = (DICT_TCP *) dict;
    const char *myname = "dict_tcp_lookup";
    int     tries;
    char   *start;
    int     last_ch;

#define RETURN(errval, result) { dict->error = errval; return (result); }

    if (msg_verbose)
	msg_info("%s: key %s", myname, key);

    /*
     * Optionally fold the key.
     */
    if (dict->flags & DICT_FLAG_FOLD_MUL) {
	if (dict->fold_buf == 0)
	    dict->fold_buf = vstring_alloc(10);
	vstring_strcpy(dict->fold_buf, key);
	key = lowercase(vstring_str(dict->fold_buf));
    }
    for (tries = 0; /* see below */ ; /* see below */ ) {

	/*
	 * Connect to the server, or use an existing connection.
	 */
	if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) {

	    /*
	     * Send request and receive response. Both are %XX quoted and
	     * both are terminated by newline. This encoding is convenient
	     * for data that is mostly text.
	     */
	    hex_quote(dict_tcp->hex_buf, key);
	    vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf));
	    if (msg_verbose)
		msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf));
	    last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp,
					     DICT_TCP_MAXLEN);
	    if (last_ch == '\n')
		break;

	    /*
	     * Disconnect from the server if it can't talk to us.
	     */
	    if (last_ch < 0)
		msg_warn("read TCP map reply from %s: unexpected EOF (%m)",
			 dict_tcp->dict.name);
	    else
		msg_warn("read TCP map reply from %s: text longer than %d",
			 dict_tcp->dict.name, DICT_TCP_MAXLEN);
	    dict_tcp_disconnect(dict_tcp);
	}

	/*
	 * Try to connect a limited number of times before giving up.
	 */
	if (++tries >= DICT_TCP_MAXTRY)
	    RETURN(DICT_ERR_RETRY, 0);

	/*
	 * Sleep between attempts, instead of hammering the server.
	 */
	sleep(1);
    }
    if (msg_verbose)
	msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf));

    /*
     * Check the general reply syntax. If the reply is malformed, disconnect
     * and try again later.
     */
    if (start = STR(dict_tcp->hex_buf),
	!ISDIGIT(start[0]) || !ISDIGIT(start[1])
	|| !ISDIGIT(start[2]) || !ISSPACE(start[3])
	|| !hex_unquote(dict_tcp->raw_buf, start + 4)) {
	msg_warn("read TCP map reply from %s: malformed reply: %.100s",
	       dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
	dict_tcp_disconnect(dict_tcp);
	RETURN(DICT_ERR_RETRY, 0);
    }

    /*
     * Examine the reply status code. If the reply is malformed, disconnect
     * and try again later.
     */
    switch (start[0]) {
    default:
	msg_warn("read TCP map reply from %s: bad status code: %.100s",
	       dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
	dict_tcp_disconnect(dict_tcp);
	RETURN(DICT_ERR_RETRY, 0);
    case '4':
	if (msg_verbose)
	    msg_info("%s: soft error: %s",
		     myname, printable(STR(dict_tcp->hex_buf), '_'));
	dict_tcp_disconnect(dict_tcp);
	RETURN(DICT_ERR_RETRY, 0);
    case '5':
	if (msg_verbose)
	    msg_info("%s: not found: %s",
		     myname, printable(STR(dict_tcp->hex_buf), '_'));
	RETURN(DICT_ERR_NONE, 0);
    case '2':
	if (msg_verbose)
	    msg_info("%s: found: %s",
		     myname, printable(STR(dict_tcp->raw_buf), '_'));
	RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf));
    }
}

/* dict_tcp_close - close TCP map */

static void dict_tcp_close(DICT *dict)
{
    DICT_TCP *dict_tcp = (DICT_TCP *) dict;

    if (dict_tcp->fp)
	(void) vstream_fclose(dict_tcp->fp);
    if (dict_tcp->raw_buf)
	vstring_free(dict_tcp->raw_buf);
    if (dict_tcp->hex_buf)
	vstring_free(dict_tcp->hex_buf);
    if (dict->fold_buf)
	vstring_free(dict->fold_buf);
    dict_free(dict);
}

/* dict_tcp_open - open TCP map */

DICT   *dict_tcp_open(const char *map, int open_flags, int dict_flags)
{
    DICT_TCP *dict_tcp;

    /*
     * Sanity checks.
     */
    if (dict_flags & DICT_FLAG_NO_UNAUTH)
	return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
		     "%s:%s map is not allowed for security sensitive data",
			       DICT_TYPE_TCP, map));
    if (open_flags != O_RDONLY)
	return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
			       "%s:%s map requires O_RDONLY access mode",
			       DICT_TYPE_TCP, map));

    /*
     * Create the dictionary handle. Do not open the connection until the
     * first request is made.
     */
    dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp));
    dict_tcp->fp = 0;
    dict_tcp->raw_buf = dict_tcp->hex_buf = 0;
    dict_tcp->dict.lookup = dict_tcp_lookup;
    dict_tcp->dict.close = dict_tcp_close;
    dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
    if (dict_flags & DICT_FLAG_FOLD_MUL)
	dict_tcp->dict.fold_buf = vstring_alloc(10);

    return (DICT_DEBUG (&dict_tcp->dict));
}