summaryrefslogtreecommitdiffstats
path: root/src/util/dict_sockmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/dict_sockmap.c')
-rw-r--r--src/util/dict_sockmap.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/util/dict_sockmap.c b/src/util/dict_sockmap.c
new file mode 100644
index 0000000..becad1a
--- /dev/null
+++ b/src/util/dict_sockmap.c
@@ -0,0 +1,384 @@
+/*++
+/* NAME
+/* dict_sockmap 3
+/* SUMMARY
+/* dictionary manager interface to Sendmail-style socketmap server
+/* SYNOPSIS
+/* #include <dict_sockmap.h>
+/*
+/* DICT *dict_sockmap_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_sockmap_open() makes a Sendmail-style socketmap 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.
+/*
+/* Postfix socketmap names have the form inet:host:port:socketmap-name
+/* or unix:pathname:socketmap-name, where socketmap-name
+/* specifies the socketmap name that the socketmap server uses.
+/*
+/* To test this module, build the netstring and dict_open test
+/* programs. Run "./netstring nc -l portnumber" as the server,
+/* and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname"
+/* as the client.
+/* PROTOCOL
+/* .ad
+/* .fi
+/* The socketmap class implements a simple protocol: the client
+/* sends one request, and the server sends one reply.
+/* ENCODING
+/* .ad
+/* .fi
+/* Each request and reply are sent as one netstring object.
+/* REQUEST FORMAT
+/* .ad
+/* .fi
+/* .IP "<mapname> <space> <key>"
+/* Search the specified socketmap under the specified key.
+/* REPLY FORMAT
+/* .ad
+/* .fi
+/* Replies must be no longer than 100000 characters (not including
+/* the netstring encapsulation), and must have the following
+/* form:
+/* .IP "OK <space> <data>"
+/* The requested data was found.
+/* .IP "NOTFOUND <space>"
+/* The requested data was not found.
+/* .IP "TEMP <space> <reason>"
+/* .IP "TIMEOUT <space> <reason>"
+/* .IP "PERM <space> <reason>"
+/* The request failed. The reason, if non-empty, is descriptive
+/* text.
+/* SECURITY
+/* This map cannot be used for security-sensitive information,
+/* because neither the connection nor the server are authenticated.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* netstring(3) netstring stream I/O support
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown host or service name,
+/* attempt to update or iterate over map.
+/* BUGS
+/* The protocol limits are not yet configurable.
+/* 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 <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <auto_clnt.h>
+#include <netstring.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <htable.h>
+#include <dict_sockmap.h>
+
+ /*
+ * Socket map data structure.
+ */
+typedef struct {
+ DICT dict; /* parent class */
+ char *sockmap_name; /* on-the-wire socketmap name */
+ VSTRING *rdwr_buf; /* read/write buffer */
+ HTABLE_INFO *client_info; /* shared endpoint name and handle */
+} DICT_SOCKMAP;
+
+ /*
+ * Default limits.
+ */
+#define DICT_SOCKMAP_DEF_TIMEOUT 100 /* connect/read/write timeout */
+#define DICT_SOCKMAP_DEF_MAX_REPLY 100000 /* reply size limit */
+#define DICT_SOCKMAP_DEF_MAX_IDLE 10 /* close idle socket */
+#define DICT_SOCKMAP_DEF_MAX_TTL 100 /* close old socket */
+
+ /*
+ * Class variables.
+ */
+static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT;
+static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY;
+static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE;
+static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL;
+
+ /*
+ * The client handle is shared between socketmap instances that have the
+ * same inet:host:port or unix:pathame information. This could be factored
+ * out as a general module for reference-counted handles of any kind.
+ */
+static HTABLE *dict_sockmap_handles; /* shared handles */
+
+typedef struct {
+ AUTO_CLNT *client_handle; /* the client handle */
+ int refcount; /* the reference count */
+} DICT_SOCKMAP_REFC_HANDLE;
+
+#define DICT_SOCKMAP_RH_NAME(ht) (ht)->key
+#define DICT_SOCKMAP_RH_HANDLE(ht) \
+ ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle
+#define DICT_SOCKMAP_RH_REFCOUNT(ht) \
+ ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount
+
+ /*
+ * Socketmap protocol elements.
+ */
+#define DICT_SOCKMAP_PROT_OK "OK"
+#define DICT_SOCKMAP_PROT_NOTFOUND "NOTFOUND"
+#define DICT_SOCKMAP_PROT_TEMP "TEMP"
+#define DICT_SOCKMAP_PROT_TIMEOUT "TIMEOUT"
+#define DICT_SOCKMAP_PROT_PERM "PERM"
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_sockmap_lookup - socket map lookup */
+
+static const char *dict_sockmap_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_sockmap_lookup";
+ DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
+ AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info);
+ VSTREAM *fp;
+ int netstring_err;
+ char *reply_payload;
+ int except_count;
+ const char *error_class;
+
+ 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(100);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(STR(dict->fold_buf));
+ }
+
+ /*
+ * We retry connection-level errors once, to make server restarts
+ * transparent.
+ */
+ for (except_count = 0; /* see below */ ; except_count++) {
+
+ /*
+ * Look up the stream.
+ */
+ if ((fp = auto_clnt_access(sockmap_clnt)) == 0) {
+ msg_warn("table %s:%s lookup error: %m", dict->type, dict->name);
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+
+ /*
+ * Set up an exception handler.
+ */
+ netstring_setup(fp, dict_sockmap_timeout);
+ if ((netstring_err = vstream_setjmp(fp)) == 0) {
+
+ /*
+ * Send the query. This may raise an exception.
+ */
+ vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key);
+ NETSTRING_PUT_BUF(fp, dp->rdwr_buf);
+
+ /*
+ * Receive the response. This may raise an exception.
+ */
+ netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply);
+
+ /*
+ * If we got here, then no exception was raised.
+ */
+ break;
+ }
+
+ /*
+ * Handle exceptions.
+ */
+ else {
+
+ /*
+ * We retry a broken connection only once.
+ */
+ if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF
+ && errno != ETIMEDOUT) {
+ auto_clnt_recover(sockmap_clnt);
+ continue;
+ }
+
+ /*
+ * We do not retry other errors.
+ */
+ else {
+ msg_warn("table %s:%s lookup error: %s",
+ dict->type, dict->name,
+ netstring_strerror(netstring_err));
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+ }
+ }
+
+ /*
+ * Parse the reply.
+ */
+ VSTRING_TERMINATE(dp->rdwr_buf);
+ reply_payload = split_at(STR(dp->rdwr_buf), ' ');
+ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) {
+ dict->error = 0;
+ return (reply_payload);
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) {
+ dict->error = 0;
+ return (0);
+ }
+ /* We got no definitive reply. */
+ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) {
+ error_class = "temporary";
+ dict->error = DICT_ERR_RETRY;
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) {
+ error_class = "timeout";
+ dict->error = DICT_ERR_RETRY;
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) {
+ error_class = "permanent";
+ dict->error = DICT_ERR_CONFIG;
+ } else {
+ error_class = "unknown";
+ dict->error = DICT_ERR_RETRY;
+ }
+ while (reply_payload && ISSPACE(*reply_payload))
+ reply_payload++;
+ msg_warn("%s:%s socketmap server %s error%s%.200s",
+ dict->type, dict->name, error_class,
+ reply_payload && *reply_payload ? ": " : "",
+ reply_payload && *reply_payload ?
+ printable(reply_payload, '?') : "");
+ return (0);
+}
+
+/* dict_sockmap_close - close socket map */
+
+static void dict_sockmap_close(DICT *dict)
+{
+ const char *myname = "dict_sockmap_close";
+ DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
+
+ if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0)
+ msg_panic("%s: attempt to close a non-existent map", myname);
+ vstring_free(dp->rdwr_buf);
+ myfree(dp->sockmap_name);
+ if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) {
+ auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info));
+ htable_delete(dict_sockmap_handles,
+ DICT_SOCKMAP_RH_NAME(dp->client_info), myfree);
+ }
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_sockmap_open - open socket map */
+
+DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags)
+{
+ DICT_SOCKMAP *dp;
+ char *saved_name = 0;
+ char *sockmap;
+ DICT_SOCKMAP_REFC_HANDLE *ref_handle;
+ HTABLE_INFO *client_info;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_SOCKMAP_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_SOCKMAP, mapname));
+ if (dict_flags & DICT_FLAG_NO_UNAUTH)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s:%s map is not allowed for security-sensitive data",
+ DICT_TYPE_SOCKMAP, mapname));
+
+ /*
+ * Separate the socketmap name from the socketmap server name.
+ */
+ saved_name = mystrdup(mapname);
+ if ((sockmap = split_at_right(saved_name, ':')) == 0)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s requires server:socketmap argument",
+ DICT_TYPE_SOCKMAP));
+
+ /*
+ * Use one reference-counted client handle for all socketmaps with the
+ * same inet:host:port or unix:pathname information.
+ *
+ * XXX Todo: graceful degradation after endpoint syntax error.
+ */
+ if (dict_sockmap_handles == 0)
+ dict_sockmap_handles = htable_create(1);
+ if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) {
+ ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle));
+ client_info = htable_enter(dict_sockmap_handles,
+ saved_name, (void *) ref_handle);
+ /* XXX Late initialization, so we can reuse macros for consistency. */
+ DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1;
+ DICT_SOCKMAP_RH_HANDLE(client_info) =
+ auto_clnt_create(saved_name, dict_sockmap_timeout,
+ dict_sockmap_max_idle, dict_sockmap_max_ttl);
+ } else
+ DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1;
+
+ /*
+ * Instantiate a socket map handle.
+ */
+ dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp));
+ dp->rdwr_buf = vstring_alloc(100);
+ dp->sockmap_name = mystrdup(sockmap);
+ dp->client_info = client_info;
+ dp->dict.lookup = dict_sockmap_lookup;
+ dp->dict.close = dict_sockmap_close;
+ /* Don't look up parent domains or network superblocks. */
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+
+ DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict));
+}