7 /* dictionary manager interface to tcp-based lookup tables
9 /* #include <dict_tcp.h>
11 /* DICT *dict_tcp_open(map, open_flags, dict_flags)
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.
21 /* Map names have the form host:port.
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.
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).
38 /* Requests are strings that serve as lookup key in the simulated
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.
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.
60 /* This map must not be used for security sensitive information,
61 /* because neither the connection nor the server are authenticated.
63 /* dict(3) generic dictionary manager
64 /* hex_quote(3) http-style quoting
66 /* Fatal errors: out of memory, unknown host or service name,
67 /* attempt to update or iterate over map.
69 /* Only the lookup method is currently implemented.
73 /* The Secure Mailer license must be distributed with this software.
76 /* IBM T.J. Watson Research
78 /* Yorktown Heights, NY 10598, USA
89 /* Utility library. */
95 #include <vstring_vstream.h>
97 #include <hex_quote.h>
99 #include <stringops.h>
100 #include <dict_tcp.h>
102 /* Application-specific. */
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 */
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 */
115 #define STR(x) vstring_str(x)
117 /* dict_tcp_connect - connect to TCP server */
119 static int dict_tcp_connect(DICT_TCP
*dict_tcp
)
124 * Connect to the server. Enforce a time limit on all operations so that
125 * we do not get stuck.
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
);
131 dict_tcp
->fp
= vstream_fdopen(fd
, O_RDWR
);
132 vstream_control(dict_tcp
->fp
,
133 VSTREAM_CTL_TIMEOUT
, DICT_TCP_TMOUT
,
137 * Allocate per-map I/O buffers on the fly.
139 if (dict_tcp
->raw_buf
== 0) {
140 dict_tcp
->raw_buf
= vstring_alloc(10);
141 dict_tcp
->hex_buf
= vstring_alloc(10);
146 /* dict_tcp_disconnect - disconnect from TCP server */
148 static void dict_tcp_disconnect(DICT_TCP
*dict_tcp
)
150 (void) vstream_fclose(dict_tcp
->fp
);
154 /* dict_tcp_lookup - request TCP server */
156 static const char *dict_tcp_lookup(DICT
*dict
, const char *key
)
158 DICT_TCP
*dict_tcp
= (DICT_TCP
*) dict
;
159 const char *myname
= "dict_tcp_lookup";
164 #define RETURN(errval, result) { dict_errno = errval; return (result); }
167 msg_info("%s: key %s", myname
, key
);
170 * Optionally fold the key.
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
));
178 for (tries
= 0; /* see below */ ; /* see below */ ) {
181 * Connect to the server, or use an existing connection.
183 if (dict_tcp
->fp
!= 0 || dict_tcp_connect(dict_tcp
) == 0) {
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.
190 hex_quote(dict_tcp
->hex_buf
, key
);
191 vstream_fprintf(dict_tcp
->fp
, "get %s\n", STR(dict_tcp
->hex_buf
));
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
,
200 * Disconnect from the server if it can't talk to us.
203 msg_warn("read TCP map reply from %s: unexpected EOF (%m)",
204 dict_tcp
->dict
.name
);
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
);
212 * Try to connect a limited number of times before giving up.
214 if (++tries
>= DICT_TCP_MAXTRY
)
215 RETURN(DICT_ERR_RETRY
, 0);
218 * Sleep between attempts, instead of hammering the server.
223 msg_info("%s: recv: %s", myname
, STR(dict_tcp
->hex_buf
));
226 * Check the general reply syntax. If the reply is malformed, disconnect
227 * and try again later.
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);
240 * Examine the reply status code. If the reply is malformed, disconnect
241 * and try again later.
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);
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);
257 msg_info("%s: not found: %s",
258 myname
, printable(STR(dict_tcp
->hex_buf
), '_'));
259 RETURN(DICT_ERR_NONE
, 0);
262 msg_info("%s: found: %s",
263 myname
, printable(STR(dict_tcp
->raw_buf
), '_'));
264 RETURN(DICT_ERR_NONE
, STR(dict_tcp
->raw_buf
));
268 /* dict_tcp_close - close TCP map */
270 static void dict_tcp_close(DICT
*dict
)
272 DICT_TCP
*dict_tcp
= (DICT_TCP
*) dict
;
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
);
281 vstring_free(dict
->fold_buf
);
285 /* dict_tcp_open - open TCP map */
287 DICT
*dict_tcp_open(const char *map
, int open_flags
, int dict_flags
)
296 if (dict_flags
& DICT_FLAG_NO_UNAUTH
)
297 msg_fatal("%s:%s map is not allowed for security sensitive data",
299 if (open_flags
!= O_RDONLY
)
300 msg_fatal("%s:%s map requires O_RDONLY access mode",
304 * Create the dictionary handle. Do not open the connection until the
305 * first request is made.
307 dict_tcp
= (DICT_TCP
*) dict_alloc(DICT_TYPE_TCP
, map
, sizeof(*dict_tcp
));
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);
316 return (DICT_DEBUG (&dict_tcp
->dict
));