summaryrefslogtreecommitdiffstats
path: root/src/util/dict_tcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/dict_tcp.c')
-rw-r--r--src/util/dict_tcp.c315
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));
+}