diff options
Diffstat (limited to 'src/util/dict_tcp.c')
-rw-r--r-- | src/util/dict_tcp.c | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/util/dict_tcp.c b/src/util/dict_tcp.c new file mode 100644 index 0000000..922f449 --- /dev/null +++ b/src/util/dict_tcp.c @@ -0,0 +1,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)); +} |