diff options
Diffstat (limited to '')
-rw-r--r-- | src/util/dict_sockmap.c | 384 |
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)); +} |