/*++ /* 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 #include /* * Global library. */ #include "cfg_parser.h" /* * Utility library. */ #include #include #include #include /* * 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); }