summaryrefslogtreecommitdiffstats
path: root/src/global/db_common.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
commitb7c15c31519dc44c1f691e0466badd556ffe9423 (patch)
treef944572f288bab482a615e09af627d9a2b6727d8 /src/global/db_common.c
parentInitial commit. (diff)
downloadpostfix-b7c15c31519dc44c1f691e0466badd556ffe9423.tar.xz
postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.zip
Adding upstream version 3.7.10.upstream/3.7.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/global/db_common.c575
1 files changed, 575 insertions, 0 deletions
diff --git a/src/global/db_common.c b/src/global/db_common.c
new file mode 100644
index 0000000..15e7a1c
--- /dev/null
+++ b/src/global/db_common.c
@@ -0,0 +1,575 @@
+/*++
+/* NAME
+/* db_common 3
+/* SUMMARY
+/* utilities common to network based dictionaries
+/* SYNOPSIS
+/* #include "db_common.h"
+/*
+/* int db_common_parse(dict, ctx, format, query)
+/* DICT *dict;
+/* void **ctx;
+/* const char *format;
+/* int query;
+/*
+/* void db_common_free_context(ctx)
+/* void *ctx;
+/*
+/* int db_common_expand(ctx, format, value, key, buf, quote_func);
+/* void *ctx;
+/* const char *format;
+/* const char *value;
+/* const char *key;
+/* VSTRING *buf;
+/* void (*quote_func)(DICT *, const char *, VSTRING *);
+/*
+/* int db_common_check_domain(domain_list, addr);
+/* STRING_LIST *domain_list;
+/* const char *addr;
+/*
+/* void db_common_sql_build_query(query,parser);
+/* VSTRING *query;
+/* CFG_PARSER *parser;
+/*
+/* DESCRIPTION
+/* This module implements utilities common to network based dictionaries.
+/*
+/* \fIdb_common_parse\fR parses query and result substitution templates.
+/* It must be called for each template before any calls to
+/* \fIdb_common_expand\fR. The \fIctx\fR argument must be initialized to
+/* a reference to a (void *)0 before the first template is parsed, this
+/* causes memory for the context to be allocated and the new pointer is
+/* stored in *ctx. When the dictionary is closed, this memory must be
+/* freed with a final call to \fBdb_common_free_context\fR.
+/*
+/* Calls for additional templates associated with the same map must use the
+/* same ctx argument. The context accumulates run-time lookup key and result
+/* validation information (inapplicable keys or results are skipped) and is
+/* needed later in each call of \fIdb_common_expand\fR. A non-zero return
+/* value indicates that data-dependent '%' expansions were found in the input
+/* template.
+/*
+/* db_common_alloc() provides a way to use db_common_parse_domain()
+/* etc. without prior db_common_parse() call.
+/*
+/* \fIdb_common_expand\fR expands the specifiers in \fIformat\fR.
+/* When the input data lacks all fields needed for the expansion, zero
+/* is returned and the query or result should be skipped. Otherwise
+/* the expansion is appended to the result buffer (after a comma if the
+/* result buffer is not empty).
+/*
+/* If not NULL, the \fBquote_func\fR callback performs database-specific
+/* quoting of each variable before expansion.
+/* \fBvalue\fR is the lookup key for query expansion and result for result
+/* expansion. \fBkey\fR is NULL for query expansion and the lookup key for
+/* result expansion.
+/* .PP
+/* The following '%' expansions are performed on \fBvalue\fR:
+/* .IP %%
+/* A literal percent character.
+/* .IP %s
+/* The entire lookup key \fIaddr\fR.
+/* .IP %u
+/* If \fBaddr\fR is a fully qualified address, the local part of the
+/* address. Otherwise \fIaddr\fR.
+/* .IP %d
+/* If \fIaddr\fR is a fully qualified address, the domain part of the
+/* address. Otherwise the query against the database is suppressed and
+/* the lookup returns no results.
+/*
+/* The following '%' expansions are performed on the lookup \fBkey\fR:
+/* .IP %S
+/* The entire lookup key \fIkey\fR.
+/* .IP %U
+/* If \fBkey\fR is a fully qualified address, the local part of the
+/* address. Otherwise \fIkey\fR.
+/* .IP %D
+/* If \fIkey\fR is a fully qualified address, the domain part of the
+/* address. Otherwise the query against the database is suppressed and
+/* the lookup returns no results.
+/* .PP
+/* \fIdb_common_check_domain\fR() checks the domain list so
+/* that query optimization can be performed. The result is >0
+/* (match found), 0 (no match), or <0 (dictionary error code).
+/*
+/* .PP
+/* \fIdb_common_sql_build_query\fR builds the "default"(backwards compatible)
+/* query from the 'table', 'select_field', 'where_field' and
+/* 'additional_conditions' parameters, checking for errors.
+/*
+/* DIAGNOSTICS
+/* Fatal errors: invalid substitution format, invalid string_list pattern,
+/* insufficient parameters.
+/* SEE ALSO
+/* dict(3) dictionary manager
+/* string_list(3) string list pattern matching
+/* match_ops(3) simple string or host pattern matching
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*
+/* Liviu Daia
+/* Institute of Mathematics of the Romanian Academy
+/* P.O. BOX 1-764
+/* RO-014700 Bucharest, ROMANIA
+/*
+/* Jose Luis Tallon
+/* G4 J.E. - F.I. - U.P.M.
+/* Campus de Montegancedo, S/N
+/* E-28660 Madrid, SPAIN
+/*
+/* Victor Duchovni
+/* Morgan Stanley
+/*--*/
+
+ /*
+ * System library.
+ */
+#include "sys_defs.h"
+#include <stddef.h>
+#include <string.h>
+
+ /*
+ * Global library.
+ */
+#include "cfg_parser.h"
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <vstring.h>
+#include <msg.h>
+#include <dict.h>
+
+ /*
+ * Application specific
+ */
+#include "db_common.h"
+
+#define DB_COMMON_KEY_DOMAIN (1 << 0)/* Need lookup key domain */
+#define DB_COMMON_KEY_USER (1 << 1)/* Need lookup key localpart */
+#define DB_COMMON_VALUE_DOMAIN (1 << 2)/* Need result domain */
+#define DB_COMMON_VALUE_USER (1 << 3)/* Need result localpart */
+#define DB_COMMON_KEY_PARTIAL (1 << 4)/* Key uses input substrings */
+
+typedef struct {
+ DICT *dict;
+ STRING_LIST *domain;
+ int flags;
+ int nparts;
+} DB_COMMON_CTX;
+
+/* db_common_alloc - allocate db_common context */
+
+void *db_common_alloc(DICT *dict)
+{
+ DB_COMMON_CTX *ctx;
+
+ ctx = (DB_COMMON_CTX *) mymalloc(sizeof *ctx);
+ ctx->dict = dict;
+ ctx->domain = 0;
+ ctx->flags = 0;
+ ctx->nparts = 0;
+ return ((void *) ctx);
+}
+
+/* db_common_parse - validate query or result template */
+
+int db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) *ctxPtr;
+ const char *cp;
+ int dynamic = 0;
+
+ if (ctx == 0)
+ ctx = (DB_COMMON_CTX *) (*ctxPtr = db_common_alloc(dict));
+
+ for (cp = format; *cp; ++cp)
+ if (*cp == '%')
+ switch (*++cp) {
+ case '%':
+ break;
+ case 'u':
+ ctx->flags |=
+ query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
+ : DB_COMMON_VALUE_USER;
+ dynamic = 1;
+ break;
+ case 'd':
+ ctx->flags |=
+ query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
+ : DB_COMMON_VALUE_DOMAIN;
+ dynamic = 1;
+ break;
+ case 's':
+ case 'S':
+ dynamic = 1;
+ break;
+ case 'U':
+ ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
+ dynamic = 1;
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+
+ /*
+ * Find highest %[1-9] index in query template. Input keys
+ * will be constrained to those with at least this many
+ * domain components. This makes the db_common_expand() code
+ * safe from invalid inputs.
+ */
+ if (ctx->nparts < *cp - '0')
+ ctx->nparts = *cp - '0';
+ /* FALLTHROUGH */
+ case 'D':
+ ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
+ dynamic = 1;
+ break;
+ default:
+ msg_fatal("db_common_parse: %s: Invalid %s template: %s",
+ ctx->dict->name, query ? "query" : "result", format);
+ }
+ return dynamic;
+}
+
+/* db_common_parse_domain - parse domain matchlist*/
+
+void db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+ char *domainlist;
+ const char *myname = "db_common_parse_domain";
+
+ domainlist = cfg_get_str(parser, "domain", "", 0, 0);
+ if (*domainlist) {
+ ctx->domain = string_list_init(parser->name, MATCH_FLAG_RETURN,
+ domainlist);
+ if (ctx->domain == 0)
+
+ /*
+ * The "domain" optimization skips input keys that may in fact
+ * have unwanted matches in the database, so failure to create
+ * the match list is fatal.
+ */
+ msg_fatal("%s: %s: domain match list creation using '%s' failed",
+ myname, parser->name, domainlist);
+ }
+ myfree(domainlist);
+}
+
+/* db_common_dict_partial - Does query use partial lookup keys? */
+
+int db_common_dict_partial(void *ctxPtr)
+{
+#if 0 /* Breaks recipient_delimiter */
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+
+ return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
+#endif
+ return (0);
+}
+
+/* db_common_free_ctx - free parse context */
+
+void db_common_free_ctx(void *ctxPtr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+
+ if (ctx->domain)
+ string_list_free(ctx->domain);
+ myfree((void *) ctxPtr);
+}
+
+/* db_common_expand - expand query and result templates */
+
+int db_common_expand(void *ctxArg, const char *format, const char *value,
+ const char *key, VSTRING *result,
+ db_quote_callback_t quote_func)
+{
+ const char *myname = "db_common_expand";
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
+ const char *vdomain = 0;
+ const char *kdomain = 0;
+ const char *domain = 0;
+ int dflag = key ? DB_COMMON_VALUE_DOMAIN : DB_COMMON_KEY_DOMAIN;
+ char *vuser = 0;
+ char *kuser = 0;
+ ARGV *parts = 0;
+ int i;
+ const char *cp;
+
+ /* Skip NULL values, silently. */
+ if (value == 0)
+ return (0);
+
+ /* Don't silenty skip empty query string or empty lookup results. */
+ if (*value == 0) {
+ if (key)
+ msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
+ " -- ignored", ctx->dict->type, ctx->dict->name, key);
+ else
+ msg_warn("table \"%s:%s\": empty query string"
+ " -- ignored", ctx->dict->type, ctx->dict->name);
+ return (0);
+ }
+ if (key) {
+ /* This is a result template and the input value is the result */
+ if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
+ if ((vdomain = strrchr(value, '@')) != 0)
+ ++vdomain;
+
+ if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0)
+ || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0))
+ return (0);
+
+ /* The result format may use the local or domain part of the key */
+ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
+ if ((kdomain = strrchr(key, '@')) != 0)
+ ++kdomain;
+
+ /*
+ * The key should already be checked before the query. No harm if the
+ * query did not get optimized out, so we just issue a warning.
+ */
+ if (((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
+ || (kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) {
+ msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
+ ctx->dict->name, value);
+ return (0);
+ }
+ } else {
+ /* This is a query template and the input value is the lookup key */
+ if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
+ if ((vdomain = strrchr(value, '@')) != 0)
+ ++vdomain;
+
+ if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
+ || (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0))
+ return (0);
+ }
+
+ if (ctx->nparts > 0) {
+ parts = argv_split(key ? kdomain : vdomain, ".");
+
+ /*
+ * Filter out input keys whose domains lack enough labels to fill-in
+ * the query template. See below and also db_common_parse() which
+ * initializes ctx->nparts.
+ */
+ if (parts->argc < ctx->nparts) {
+ argv_free(parts);
+ return (0);
+ }
+
+ /*
+ * Skip domains with leading, consecutive or trailing '.' separators
+ * among the required labels.
+ */
+ for (i = 0; i < ctx->nparts; i++)
+ if (*parts->argv[parts->argc - i - 1] == 0) {
+ argv_free(parts);
+ return (0);
+ }
+ }
+ if (VSTRING_LEN(result) > 0)
+ VSTRING_ADDCH(result, ',');
+
+#define QUOTE_VAL(d, q, v, buf) do { \
+ if (q) \
+ q(d, v, buf); \
+ else \
+ vstring_strcat(buf, v); \
+ } while (0)
+
+ /*
+ * Replace all instances of %s with the address to look up. Replace %u
+ * with the user portion, and %d with the domain portion. "%%" expands to
+ * "%". lowercase -> addr, uppercase -> key
+ */
+ for (cp = format; *cp; cp++) {
+ if (*cp == '%') {
+ switch (*++cp) {
+
+ case '%':
+ VSTRING_ADDCH(result, '%');
+ break;
+
+ case 's':
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'u':
+ if (vdomain) {
+ if (vuser == 0)
+ vuser = mystrndup(value, vdomain - value - 1);
+ QUOTE_VAL(ctx->dict, quote_func, vuser, result);
+ } else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'd':
+ if (!(ctx->flags & dflag))
+ msg_panic("%s: %s: %s: bad query/result template context",
+ myname, ctx->dict->name, format);
+ if (!vdomain)
+ msg_panic("%s: %s: %s: expanding domain-less key or value",
+ myname, ctx->dict->name, format);
+ QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
+ break;
+
+ case 'S':
+ if (key)
+ QUOTE_VAL(ctx->dict, quote_func, key, result);
+ else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ break;
+
+ case 'U':
+ if (key) {
+ if (kdomain) {
+ if (kuser == 0)
+ kuser = mystrndup(key, kdomain - key - 1);
+ QUOTE_VAL(ctx->dict, quote_func, kuser, result);
+ } else
+ QUOTE_VAL(ctx->dict, quote_func, key, result);
+ } else {
+ if (vdomain) {
+ if (vuser == 0)
+ vuser = mystrndup(value, vdomain - value - 1);
+ QUOTE_VAL(ctx->dict, quote_func, vuser, result);
+ } else
+ QUOTE_VAL(ctx->dict, quote_func, value, result);
+ }
+ break;
+
+ case 'D':
+ if (!(ctx->flags & DB_COMMON_KEY_DOMAIN))
+ msg_panic("%s: %s: %s: bad query/result template context",
+ myname, ctx->dict->name, format);
+ if ((domain = key ? kdomain : vdomain) == 0)
+ msg_panic("%s: %s: %s: expanding domain-less key or value",
+ myname, ctx->dict->name, format);
+ QUOTE_VAL(ctx->dict, quote_func, domain, result);
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+
+ /*
+ * Interpolate %[1-9] components into the query string. By
+ * this point db_common_parse() has identified the highest
+ * component index, and (see above) keys with fewer
+ * components have been filtered out. The "parts" ARGV is
+ * guaranteed to be initialized and hold enough elements to
+ * satisfy the query template.
+ */
+ if (!(ctx->flags & DB_COMMON_KEY_DOMAIN)
+ || ctx->nparts < *cp - '0')
+ msg_panic("%s: %s: %s: bad query/result template context",
+ myname, ctx->dict->name, format);
+ if (!parts || parts->argc < ctx->nparts)
+ msg_panic("%s: %s: %s: key has too few domain labels",
+ myname, ctx->dict->name, format);
+ QUOTE_VAL(ctx->dict, quote_func,
+ parts->argv[parts->argc - (*cp - '0')], result);
+ break;
+
+ default:
+ msg_fatal("%s: %s: invalid %s template '%s'", myname,
+ ctx->dict->name, key ? "result" : "query",
+ format);
+ }
+ } else
+ VSTRING_ADDCH(result, *cp);
+ }
+ VSTRING_TERMINATE(result);
+
+ if (vuser)
+ myfree(vuser);
+ if (kuser)
+ myfree(kuser);
+ if (parts)
+ argv_free(parts);
+
+ return (1);
+}
+
+
+/* db_common_check_domain - check domain list */
+
+int db_common_check_domain(void *ctxPtr, const char *addr)
+{
+ DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
+ char *domain;
+
+ if (ctx->domain) {
+ if ((domain = strrchr(addr, '@')) != NULL)
+ ++domain;
+ if (domain == NULL || domain == addr + 1)
+ return (0);
+ if (match_list_match(ctx->domain, domain) == 0)
+ return (ctx->domain->error);
+ }
+ return (1);
+}
+
+/* db_common_sql_build_query -- build query for SQL maptypes */
+
+void db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser)
+{
+ const char *myname = "db_common_sql_build_query";
+ char *table;
+ char *select_field;
+ char *where_field;
+ char *additional_conditions;
+
+ /*
+ * Build "old style" query: "select %s from %s where %s"
+ */
+ if ((table = cfg_get_str(parser, "table", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'table' parameter not defined", myname);
+
+ if ((select_field = cfg_get_str(parser, "select_field", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'select_field' parameter not defined", myname);
+
+ if ((where_field = cfg_get_str(parser, "where_field", NULL, 1, 0)) == 0)
+ msg_fatal("%s: 'where_field' parameter not defined", myname);
+
+ additional_conditions = cfg_get_str(parser, "additional_conditions",
+ "", 0, 0);
+
+ vstring_sprintf(query, "SELECT %s FROM %s WHERE %s='%%s' %s",
+ select_field, table, where_field,
+ additional_conditions);
+
+ myfree(table);
+ myfree(select_field);
+ myfree(where_field);
+ myfree(additional_conditions);
+}