/* $NetBSD: db_common.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */
/*++
/* 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-depedent '%' 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
/* 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
/*
/* 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);
}