Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: db_common.c,v 1.3 2022/10/08 16:12:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	db_common 3
      6 /* SUMMARY
      7 /*	utilities common to network based dictionaries
      8 /* SYNOPSIS
      9 /*	#include "db_common.h"
     10 /*
     11 /*	int	db_common_parse(dict, ctx, format, query)
     12 /*	DICT	*dict;
     13 /*	void	**ctx;
     14 /*	const char *format;
     15 /*	int	query;
     16 /*
     17 /*	void	db_common_free_context(ctx)
     18 /*	void	*ctx;
     19 /*
     20 /*	int	db_common_expand(ctx, format, value, key, buf, quote_func);
     21 /*	void	*ctx;
     22 /*	const char *format;
     23 /*	const char *value;
     24 /*	const char *key;
     25 /*	VSTRING	*buf;
     26 /*	void	(*quote_func)(DICT *, const char *, VSTRING *);
     27 /*
     28 /*	int	db_common_check_domain(domain_list, addr);
     29 /*	STRING_LIST *domain_list;
     30 /*	const char *addr;
     31 /*
     32 /*	void	db_common_sql_build_query(query,parser);
     33 /*	VSTRING	*query;
     34 /*	CFG_PARSER *parser;
     35 /*
     36 /* DESCRIPTION
     37 /*	This module implements utilities common to network based dictionaries.
     38 /*
     39 /*	\fIdb_common_parse\fR parses query and result substitution templates.
     40 /*	It must be called for each template before any calls to
     41 /*	\fIdb_common_expand\fR. The \fIctx\fR argument must be initialized to
     42 /*	a reference to a (void *)0 before the first template is parsed, this
     43 /*	causes memory for the context to be allocated and the new pointer is
     44 /*	stored in *ctx. When the dictionary is closed, this memory must be
     45 /*	freed with a final call to \fBdb_common_free_context\fR.
     46 /*
     47 /*	Calls for additional templates associated with the same map must use the
     48 /*	same ctx argument. The context accumulates run-time lookup key and result
     49 /*	validation information (inapplicable keys or results are skipped) and is
     50 /*	needed later in each call of \fIdb_common_expand\fR. A non-zero return
     51 /*	value indicates that data-dependent '%' expansions were found in the input
     52 /*	template.
     53 /*
     54 /*	db_common_alloc() provides a way to use db_common_parse_domain()
     55 /*	etc. without prior db_common_parse() call.
     56 /*
     57 /*	\fIdb_common_expand\fR expands the specifiers in \fIformat\fR.
     58 /*	When the input data lacks all fields needed for the expansion, zero
     59 /*	is returned and the query or result should be skipped. Otherwise
     60 /*	the expansion is appended to the result buffer (after a comma if the
     61 /*	result buffer is not empty).
     62 /*
     63 /*	If not NULL, the \fBquote_func\fR callback performs database-specific
     64 /*	quoting of each variable before expansion.
     65 /*	\fBvalue\fR is the lookup key for query expansion and result for result
     66 /*	expansion. \fBkey\fR is NULL for query expansion and the lookup key for
     67 /*	result expansion.
     68 /* .PP
     69 /*	The following '%' expansions are performed on \fBvalue\fR:
     70 /* .IP %%
     71 /*	A literal percent character.
     72 /* .IP %s
     73 /*	The entire lookup key \fIaddr\fR.
     74 /* .IP %u
     75 /*	If \fBaddr\fR is a fully qualified address, the local part of the
     76 /*	address.  Otherwise \fIaddr\fR.
     77 /* .IP %d
     78 /*	If \fIaddr\fR is a fully qualified address, the domain part of the
     79 /*	address.  Otherwise the query against the database is suppressed and
     80 /*	the lookup returns no results.
     81 /*
     82 /*	The following '%' expansions are performed on the lookup \fBkey\fR:
     83 /* .IP %S
     84 /*	The entire lookup key \fIkey\fR.
     85 /* .IP %U
     86 /*	If \fBkey\fR is a fully qualified address, the local part of the
     87 /*	address.  Otherwise \fIkey\fR.
     88 /* .IP %D
     89 /*	If \fIkey\fR is a fully qualified address, the domain part of the
     90 /*	address.  Otherwise the query against the database is suppressed and
     91 /*	the lookup returns no results.
     92 /* .PP
     93 /*	\fIdb_common_check_domain\fR() checks the domain list so
     94 /*	that query optimization can be performed. The result is >0
     95 /*	(match found), 0 (no match), or <0 (dictionary error code).
     96 /*
     97 /* .PP
     98 /*	\fIdb_common_sql_build_query\fR builds the "default"(backwards compatible)
     99 /*	query from the 'table', 'select_field', 'where_field' and
    100 /*	'additional_conditions' parameters, checking for errors.
    101 /*
    102 /* DIAGNOSTICS
    103 /*	Fatal errors: invalid substitution format, invalid string_list pattern,
    104 /*	insufficient parameters.
    105 /* SEE ALSO
    106 /*	dict(3) dictionary manager
    107 /*	string_list(3) string list pattern matching
    108 /*	match_ops(3) simple string or host pattern matching
    109 /* LICENSE
    110 /* .ad
    111 /* .fi
    112 /*	The Secure Mailer license must be distributed with this software.
    113 /* AUTHOR(S)
    114 /*	Wietse Venema
    115 /*	IBM T.J. Watson Research
    116 /*	P.O. Box 704
    117 /*	Yorktown Heights, NY 10598, USA
    118 /*
    119 /*	Wietse Venema
    120 /*	Google, Inc.
    121 /*	111 8th Avenue
    122 /*	New York, NY 10011, USA
    123 /*
    124 /*	Liviu Daia
    125 /*	Institute of Mathematics of the Romanian Academy
    126 /*	P.O. BOX 1-764
    127 /*	RO-014700 Bucharest, ROMANIA
    128 /*
    129 /*	Jose Luis Tallon
    130 /*	G4 J.E. - F.I. - U.P.M.
    131 /*	Campus de Montegancedo, S/N
    132 /*	E-28660 Madrid, SPAIN
    133 /*
    134 /*	Victor Duchovni
    135 /*	Morgan Stanley
    136 /*--*/
    137 
    138  /*
    139   * System library.
    140   */
    141 #include "sys_defs.h"
    142 #include <stddef.h>
    143 #include <string.h>
    144 
    145  /*
    146   * Global library.
    147   */
    148 #include "cfg_parser.h"
    149 
    150  /*
    151   * Utility library.
    152   */
    153 #include <mymalloc.h>
    154 #include <vstring.h>
    155 #include <msg.h>
    156 #include <dict.h>
    157 
    158  /*
    159   * Application specific
    160   */
    161 #include "db_common.h"
    162 
    163 #define	DB_COMMON_KEY_DOMAIN	(1 << 0)/* Need lookup key domain */
    164 #define	DB_COMMON_KEY_USER	(1 << 1)/* Need lookup key localpart */
    165 #define	DB_COMMON_VALUE_DOMAIN	(1 << 2)/* Need result domain */
    166 #define	DB_COMMON_VALUE_USER	(1 << 3)/* Need result localpart */
    167 #define	DB_COMMON_KEY_PARTIAL	(1 << 4)/* Key uses input substrings */
    168 
    169 typedef struct {
    170     DICT   *dict;
    171     STRING_LIST *domain;
    172     int     flags;
    173     int     nparts;
    174 } DB_COMMON_CTX;
    175 
    176 /* db_common_alloc - allocate db_common context */
    177 
    178 void   *db_common_alloc(DICT *dict)
    179 {
    180     DB_COMMON_CTX *ctx;
    181 
    182     ctx = (DB_COMMON_CTX *) mymalloc(sizeof *ctx);
    183     ctx->dict = dict;
    184     ctx->domain = 0;
    185     ctx->flags = 0;
    186     ctx->nparts = 0;
    187     return ((void *) ctx);
    188 }
    189 
    190 /* db_common_parse - validate query or result template */
    191 
    192 int     db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query)
    193 {
    194     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) *ctxPtr;
    195     const char *cp;
    196     int     dynamic = 0;
    197 
    198     if (ctx == 0)
    199 	ctx = (DB_COMMON_CTX *) (*ctxPtr = db_common_alloc(dict));
    200 
    201     for (cp = format; *cp; ++cp)
    202 	if (*cp == '%')
    203 	    switch (*++cp) {
    204 	    case '%':
    205 		break;
    206 	    case 'u':
    207 		ctx->flags |=
    208 		    query ? DB_COMMON_KEY_USER | DB_COMMON_KEY_PARTIAL
    209 		    : DB_COMMON_VALUE_USER;
    210 		dynamic = 1;
    211 		break;
    212 	    case 'd':
    213 		ctx->flags |=
    214 		    query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
    215 		    : DB_COMMON_VALUE_DOMAIN;
    216 		dynamic = 1;
    217 		break;
    218 	    case 's':
    219 	    case 'S':
    220 		dynamic = 1;
    221 		break;
    222 	    case 'U':
    223 		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_USER;
    224 		dynamic = 1;
    225 		break;
    226 	    case '1':
    227 	    case '2':
    228 	    case '3':
    229 	    case '4':
    230 	    case '5':
    231 	    case '6':
    232 	    case '7':
    233 	    case '8':
    234 	    case '9':
    235 
    236 		/*
    237 		 * Find highest %[1-9] index in query template. Input keys
    238 		 * will be constrained to those with at least this many
    239 		 * domain components. This makes the db_common_expand() code
    240 		 * safe from invalid inputs.
    241 		 */
    242 		if (ctx->nparts < *cp - '0')
    243 		    ctx->nparts = *cp - '0';
    244 		/* FALLTHROUGH */
    245 	    case 'D':
    246 		ctx->flags |= DB_COMMON_KEY_PARTIAL | DB_COMMON_KEY_DOMAIN;
    247 		dynamic = 1;
    248 		break;
    249 	    default:
    250 		msg_fatal("db_common_parse: %s: Invalid %s template: %s",
    251 		       ctx->dict->name, query ? "query" : "result", format);
    252 	    }
    253     return dynamic;
    254 }
    255 
    256 /* db_common_parse_domain - parse domain matchlist*/
    257 
    258 void    db_common_parse_domain(CFG_PARSER *parser, void *ctxPtr)
    259 {
    260     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
    261     char   *domainlist;
    262     const char *myname = "db_common_parse_domain";
    263 
    264     domainlist = cfg_get_str(parser, "domain", "", 0, 0);
    265     if (*domainlist) {
    266 	ctx->domain = string_list_init(parser->name, MATCH_FLAG_RETURN,
    267 				       domainlist);
    268 	if (ctx->domain == 0)
    269 
    270 	    /*
    271 	     * The "domain" optimization skips input keys that may in fact
    272 	     * have unwanted matches in the database, so failure to create
    273 	     * the match list is fatal.
    274 	     */
    275 	    msg_fatal("%s: %s: domain match list creation using '%s' failed",
    276 		      myname, parser->name, domainlist);
    277     }
    278     myfree(domainlist);
    279 }
    280 
    281 /* db_common_dict_partial - Does query use partial lookup keys? */
    282 
    283 int     db_common_dict_partial(void *ctxPtr)
    284 {
    285 #if 0					/* Breaks recipient_delimiter */
    286     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
    287 
    288     return (ctx->domain || ctx->flags & DB_COMMON_KEY_PARTIAL);
    289 #endif
    290     return (0);
    291 }
    292 
    293 /* db_common_free_ctx - free parse context */
    294 
    295 void    db_common_free_ctx(void *ctxPtr)
    296 {
    297     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
    298 
    299     if (ctx->domain)
    300 	string_list_free(ctx->domain);
    301     myfree((void *) ctxPtr);
    302 }
    303 
    304 /* db_common_expand - expand query and result templates */
    305 
    306 int     db_common_expand(void *ctxArg, const char *format, const char *value,
    307 			         const char *key, VSTRING *result,
    308 			         db_quote_callback_t quote_func)
    309 {
    310     const char *myname = "db_common_expand";
    311     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxArg;
    312     const char *vdomain = 0;
    313     const char *kdomain = 0;
    314     const char *domain = 0;
    315     int     dflag = key ? DB_COMMON_VALUE_DOMAIN : DB_COMMON_KEY_DOMAIN;
    316     char   *vuser = 0;
    317     char   *kuser = 0;
    318     ARGV   *parts = 0;
    319     int     i;
    320     const char *cp;
    321 
    322     /* Skip NULL values, silently. */
    323     if (value == 0)
    324 	return (0);
    325 
    326     /* Don't silenty skip empty query string or empty lookup results. */
    327     if (*value == 0) {
    328 	if (key)
    329 	    msg_warn("table \"%s:%s\": empty lookup result for: \"%s\""
    330 		     " -- ignored", ctx->dict->type, ctx->dict->name, key);
    331 	else
    332 	    msg_warn("table \"%s:%s\": empty query string"
    333 		     " -- ignored", ctx->dict->type, ctx->dict->name);
    334 	return (0);
    335     }
    336     if (key) {
    337 	/* This is a result template and the input value is the result */
    338 	if (ctx->flags & (DB_COMMON_VALUE_DOMAIN | DB_COMMON_VALUE_USER))
    339 	    if ((vdomain = strrchr(value, '@')) != 0)
    340 		++vdomain;
    341 
    342 	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_VALUE_DOMAIN) != 0)
    343 	    || (vdomain == value + 1 && (ctx->flags & DB_COMMON_VALUE_USER) != 0))
    344 	    return (0);
    345 
    346 	/* The result format may use the local or domain part of the key */
    347 	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
    348 	    if ((kdomain = strrchr(key, '@')) != 0)
    349 		++kdomain;
    350 
    351 	/*
    352 	 * The key should already be checked before the query. No harm if the
    353 	 * query did not get optimized out, so we just issue a warning.
    354 	 */
    355 	if (((!kdomain || !*kdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
    356 	|| (kdomain == key + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0)) {
    357 	    msg_warn("%s: %s: lookup key '%s' skipped after query", myname,
    358 		     ctx->dict->name, value);
    359 	    return (0);
    360 	}
    361     } else {
    362 	/* This is a query template and the input value is the lookup key */
    363 	if (ctx->flags & (DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_USER))
    364 	    if ((vdomain = strrchr(value, '@')) != 0)
    365 		++vdomain;
    366 
    367 	if (((!vdomain || !*vdomain) && (ctx->flags & DB_COMMON_KEY_DOMAIN) != 0)
    368 	|| (vdomain == value + 1 && (ctx->flags & DB_COMMON_KEY_USER) != 0))
    369 	    return (0);
    370     }
    371 
    372     if (ctx->nparts > 0) {
    373 	parts = argv_split(key ? kdomain : vdomain, ".");
    374 
    375 	/*
    376 	 * Filter out input keys whose domains lack enough labels to fill-in
    377 	 * the query template. See below and also db_common_parse() which
    378 	 * initializes ctx->nparts.
    379 	 */
    380 	if (parts->argc < ctx->nparts) {
    381 	    argv_free(parts);
    382 	    return (0);
    383 	}
    384 
    385 	/*
    386 	 * Skip domains with leading, consecutive or trailing '.' separators
    387 	 * among the required labels.
    388 	 */
    389 	for (i = 0; i < ctx->nparts; i++)
    390 	    if (*parts->argv[parts->argc - i - 1] == 0) {
    391 		argv_free(parts);
    392 		return (0);
    393 	    }
    394     }
    395     if (VSTRING_LEN(result) > 0)
    396 	VSTRING_ADDCH(result, ',');
    397 
    398 #define QUOTE_VAL(d, q, v, buf) do { \
    399 	if (q) \
    400 	    q(d, v, buf); \
    401 	else \
    402 	    vstring_strcat(buf, v); \
    403     } while (0)
    404 
    405     /*
    406      * Replace all instances of %s with the address to look up. Replace %u
    407      * with the user portion, and %d with the domain portion. "%%" expands to
    408      * "%".  lowercase -> addr, uppercase -> key
    409      */
    410     for (cp = format; *cp; cp++) {
    411 	if (*cp == '%') {
    412 	    switch (*++cp) {
    413 
    414 	    case '%':
    415 		VSTRING_ADDCH(result, '%');
    416 		break;
    417 
    418 	    case 's':
    419 		QUOTE_VAL(ctx->dict, quote_func, value, result);
    420 		break;
    421 
    422 	    case 'u':
    423 		if (vdomain) {
    424 		    if (vuser == 0)
    425 			vuser = mystrndup(value, vdomain - value - 1);
    426 		    QUOTE_VAL(ctx->dict, quote_func, vuser, result);
    427 		} else
    428 		    QUOTE_VAL(ctx->dict, quote_func, value, result);
    429 		break;
    430 
    431 	    case 'd':
    432 		if (!(ctx->flags & dflag))
    433 		    msg_panic("%s: %s: %s: bad query/result template context",
    434 			      myname, ctx->dict->name, format);
    435 		if (!vdomain)
    436 		    msg_panic("%s: %s: %s: expanding domain-less key or value",
    437 			      myname, ctx->dict->name, format);
    438 		QUOTE_VAL(ctx->dict, quote_func, vdomain, result);
    439 		break;
    440 
    441 	    case 'S':
    442 		if (key)
    443 		    QUOTE_VAL(ctx->dict, quote_func, key, result);
    444 		else
    445 		    QUOTE_VAL(ctx->dict, quote_func, value, result);
    446 		break;
    447 
    448 	    case 'U':
    449 		if (key) {
    450 		    if (kdomain) {
    451 			if (kuser == 0)
    452 			    kuser = mystrndup(key, kdomain - key - 1);
    453 			QUOTE_VAL(ctx->dict, quote_func, kuser, result);
    454 		    } else
    455 			QUOTE_VAL(ctx->dict, quote_func, key, result);
    456 		} else {
    457 		    if (vdomain) {
    458 			if (vuser == 0)
    459 			    vuser = mystrndup(value, vdomain - value - 1);
    460 			QUOTE_VAL(ctx->dict, quote_func, vuser, result);
    461 		    } else
    462 			QUOTE_VAL(ctx->dict, quote_func, value, result);
    463 		}
    464 		break;
    465 
    466 	    case 'D':
    467 		if (!(ctx->flags & DB_COMMON_KEY_DOMAIN))
    468 		    msg_panic("%s: %s: %s: bad query/result template context",
    469 			      myname, ctx->dict->name, format);
    470 		if ((domain = key ? kdomain : vdomain) == 0)
    471 		    msg_panic("%s: %s: %s: expanding domain-less key or value",
    472 			      myname, ctx->dict->name, format);
    473 		QUOTE_VAL(ctx->dict, quote_func, domain, result);
    474 		break;
    475 
    476 	    case '1':
    477 	    case '2':
    478 	    case '3':
    479 	    case '4':
    480 	    case '5':
    481 	    case '6':
    482 	    case '7':
    483 	    case '8':
    484 	    case '9':
    485 
    486 		/*
    487 		 * Interpolate %[1-9] components into the query string. By
    488 		 * this point db_common_parse() has identified the highest
    489 		 * component index, and (see above) keys with fewer
    490 		 * components have been filtered out. The "parts" ARGV is
    491 		 * guaranteed to be initialized and hold enough elements to
    492 		 * satisfy the query template.
    493 		 */
    494 		if (!(ctx->flags & DB_COMMON_KEY_DOMAIN)
    495 		    || ctx->nparts < *cp - '0')
    496 		    msg_panic("%s: %s: %s: bad query/result template context",
    497 			      myname, ctx->dict->name, format);
    498 		if (!parts || parts->argc < ctx->nparts)
    499 		    msg_panic("%s: %s: %s: key has too few domain labels",
    500 			      myname, ctx->dict->name, format);
    501 		QUOTE_VAL(ctx->dict, quote_func,
    502 			  parts->argv[parts->argc - (*cp - '0')], result);
    503 		break;
    504 
    505 	    default:
    506 		msg_fatal("%s: %s: invalid %s template '%s'", myname,
    507 			  ctx->dict->name, key ? "result" : "query",
    508 			  format);
    509 	    }
    510 	} else
    511 	    VSTRING_ADDCH(result, *cp);
    512     }
    513     VSTRING_TERMINATE(result);
    514 
    515     if (vuser)
    516 	myfree(vuser);
    517     if (kuser)
    518 	myfree(kuser);
    519     if (parts)
    520 	argv_free(parts);
    521 
    522     return (1);
    523 }
    524 
    525 
    526 /* db_common_check_domain - check domain list */
    527 
    528 int     db_common_check_domain(void *ctxPtr, const char *addr)
    529 {
    530     DB_COMMON_CTX *ctx = (DB_COMMON_CTX *) ctxPtr;
    531     char   *domain;
    532 
    533     if (ctx->domain) {
    534 	if ((domain = strrchr(addr, '@')) != NULL)
    535 	    ++domain;
    536 	if (domain == NULL || domain == addr + 1)
    537 	    return (0);
    538 	if (match_list_match(ctx->domain, domain) == 0)
    539 	    return (ctx->domain->error);
    540     }
    541     return (1);
    542 }
    543 
    544 /* db_common_sql_build_query -- build query for SQL maptypes */
    545 
    546 void    db_common_sql_build_query(VSTRING *query, CFG_PARSER *parser)
    547 {
    548     const char *myname = "db_common_sql_build_query";
    549     char   *table;
    550     char   *select_field;
    551     char   *where_field;
    552     char   *additional_conditions;
    553 
    554     /*
    555      * Build "old style" query: "select %s from %s where %s"
    556      */
    557     if ((table = cfg_get_str(parser, "table", NULL, 1, 0)) == 0)
    558 	msg_fatal("%s: 'table' parameter not defined", myname);
    559 
    560     if ((select_field = cfg_get_str(parser, "select_field", NULL, 1, 0)) == 0)
    561 	msg_fatal("%s: 'select_field' parameter not defined", myname);
    562 
    563     if ((where_field = cfg_get_str(parser, "where_field", NULL, 1, 0)) == 0)
    564 	msg_fatal("%s: 'where_field' parameter not defined", myname);
    565 
    566     additional_conditions = cfg_get_str(parser, "additional_conditions",
    567 					"", 0, 0);
    568 
    569     vstring_sprintf(query, "SELECT %s FROM %s WHERE %s='%%s' %s",
    570 		    select_field, table, where_field,
    571 		    additional_conditions);
    572 
    573     myfree(table);
    574     myfree(select_field);
    575     myfree(where_field);
    576     myfree(additional_conditions);
    577 }
    578