diff options
Diffstat (limited to 'src/vmod_dbrw.c')
-rw-r--r-- | src/vmod_dbrw.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/src/vmod_dbrw.c b/src/vmod_dbrw.c new file mode 100644 index 0000000..16bbb57 --- /dev/null +++ b/src/vmod_dbrw.c @@ -0,0 +1,422 @@ +#include "dbrw.h" +#include <stdarg.h> +#include <regex.h> +#include "wordsplit.h" +#include "vrt.h" +#include "vcc_if.h" +#include "bin/varnishd/cache.h" + +static pthread_once_t thread_once = PTHREAD_ONCE_INIT; +static pthread_key_t thread_key; + +void +dbrw_debug(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsyslog(LOG_DAEMON|LOG_DEBUG, fmt, ap); + va_end(ap); +} + +void +dbrw_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsyslog(LOG_DAEMON|LOG_ERR, fmt, ap); + va_end(ap); +} + +static void +conn_free(void *f) +{ + struct dbrw_connection *p = f; + sql_disconnect(p); + sql_destroy(p); +} + +static void +make_key() +{ + pthread_key_create(&thread_key, conn_free); +} + +int +dbrw_init(struct vmod_priv *priv, const struct VCL_conf *vclconf) +{ + struct dbrw_config *conf = malloc(sizeof(*conf)); + if (!conf) { + dbrw_error("not enough memory"); + abort(); + } + priv->priv = conf; + pthread_once(&thread_once, make_key); + return 0; +} + +char * +findparam(char **params, char *name) +{ + char *p, *q; + + while (*params) { + p = *params++; + for (q = name; *p && *q && *p == *q; p++, q++); + if (*q == 0 && *p == '=') + return p+1; + } + return NULL; +} + +static void +argv_free(char **argv) +{ + if (!argv) + return; + while (*argv) { + free(*argv); + ++*argv; + } +} + +static struct dbrw_backend *bcktab[] = { +#ifdef USE_SQL_MYSQL + &mysql_backend, +#endif +#ifdef USE_SQL_PGSQL + &pgsql_backend, +#endif + NULL +}; + +static struct dbrw_backend * +find_backend(const char *name) +{ + int i; + + for (i = 0; bcktab[i]; i++) { + if (strcmp(bcktab[i]->name, name) == 0) + return bcktab[i]; + } + return NULL; +} + + + +/* Configure the module. + BACKEND - "mysql" or "pgsql" + PARAM - VAR=VALUE*: + db=S + port=N + socket=S + user=S + password=S + options=S + debug=N + QUERY - Query to obtain the redirection target + */ +void +vmod_config(struct sess *sp, struct vmod_priv *priv, const char *bkname, + const char *param, const char *query) +{ + int rc; + char *s; + struct wordsplit ws; + struct dbrw_config *conf = priv->priv; + + ws.ws_delim = ";"; + if (wordsplit(param, &ws, + WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_QUOTE | + WRDSF_CESCAPES | + WRDSF_DELIM)) { + dbrw_error("cannot split string `%s': %s", + param, wordsplit_strerror (&ws)); + return; + } + conf->param = ws.ws_wordv; + ws.ws_wordv = NULL; + ws.ws_wordc = 0; + wordsplit_free(&ws); + + conf->query = strdup(query); + if (!conf->query) { + dbrw_error("not enough memory"); + argv_free(conf->param); + conf->param = NULL; + return; + } + + /* Select backend */ + conf->backend = find_backend(bkname); + if (!conf->backend) { + dbrw_error("unsupported backend: %s", bkname); + argv_free(conf->param); + free(conf->query); + param = NULL; + return; + } + + s = findparam(conf->param, "debug"); + conf->debug_level = atoi(s); +} + +static char * +expand_backref(struct sess *sp, const char *str, const char *val, + size_t matchcount, regmatch_t *matches) +{ + unsigned u; + char *b, *e, *p; + + u = WS_Reserve(sp->wrk->ws, 0); + e = b = sp->wrk->ws->f; + e += u; + p = b; + + while (*val) { + if (u == 0) { + WS_Release(sp->wrk->ws, 0); + return NULL; + } + if (*val == '\\' || *val == '$') { + if (val[1] >= 0 && val[1] <= '9') { + int n = val[1] - '0'; + if (n < matchcount) { + size_t len; + + len = matches[n].rm_eo - + matches[n].rm_so; + + if (len > u) { + WS_Release(sp->wrk->ws, 0); + return NULL; + } + + memcpy(p, str + matches[n].rm_so, len); + + p += len; + u -= len; + val += 2; + continue; + } + } else if (*val == '\\' && val[1]) { + *p++ = val[1]; + u--; + val += 2; + continue; + } + } + + *p++ = *val++; + u--; + } + + if (u == 0) { + WS_Release(sp->wrk->ws, 0); + return NULL; + } + *p++ = 0; + u--; + + if (u) + WS_Release(sp->wrk->ws, u); + + return b; +} + +static regmatch_t *matches; /* Match map */ +static size_t matchsize; /* Total number of entries in matches */ + +#define ISEMPTY(s) ((s) == NULL || (s)[0] == 0) + +static char * +findmatch(struct sess *sp, struct dbrw_connection *conn, char **param) +{ + unsigned i; + unsigned nt, nf; + char *res; + struct wordsplit ws; + int wsflags = WRDSF_NOCMD | WRDSF_QUOTE | + WRDSF_NOSPLIT | + WRDSF_ENV | WRDSF_UNDEF; + + if ((nt = sql_num_tuples(conn)) == 0 || + (nf = sql_num_fields(conn)) == 0) + return NULL; + + debug(conn->conf, 2, ("query returned %u tuples, %u columns", nt, nf)); + + if (nf < 3) + return WS_Dup(sp->ws, sql_get_column(conn, 0, 0)); + + /* Three fields: + result + pattern + value + */ + ws.ws_env = (const char **)param; + res = NULL; + for (i = 0; i < nt; i++) { + const char *pat = sql_get_column(conn, i, 1); + const char *val = sql_get_column(conn, i, 2); + regex_t re; + int rc; + size_t matchcount; /* Number of used entries in matches */ + + if (ISEMPTY(pat)) { + res = WS_Dup(sp->ws, sql_get_column(conn, i, 0)); + break; + } + + debug(conn->conf, 1, ("considering \"%s\" ~ \"%s\"", pat, val)); + if (wordsplit(val, &ws, wsflags)) { + dbrw_error("cannot expand string `%s': %s", + val, wordsplit_strerror(&ws)); + continue; + } + wsflags |= WRDSF_REUSE; + + val = ws.ws_wordv[0]; + debug(conn->conf, 1, ("expanded to \"%s\" ~ \"%s\"", + pat, val)); + + rc = regcomp(&re, pat, REG_EXTENDED); + if (rc) { + char errbuf[512]; + + regerror(rc, &re, errbuf, sizeof(errbuf)); + dbrw_error("cannot compile regexp \"%s\": %s", + pat, errbuf); + continue; + } + + matchcount = re.re_nsub + 1; + if (matchsize < matchcount) { + void *p = realloc(matches, + sizeof(matches[0]) * matchcount); + if (!p) { + dbrw_error("not enough memory"); + regfree(&re); + continue; + } + matches = p; + matchsize = matchcount; + } + + rc = regexec(&re, val, matchcount, matches, 0); + + if (rc == 0) + res = expand_backref(sp, val, + sql_get_column(conn, i, 0), + matchcount, matches); + regfree(&re); + if (rc == 0) { + debug(conn->conf, 1, ("match")); + break; + } + } + + if (wsflags & WRDSF_REUSE) + wordsplit_free(&ws); + + return res; +} + +static struct dbrw_connection * +get_connection(struct dbrw_config *conf) +{ + struct dbrw_connection *cp = pthread_getspecific(thread_key); + + if (!cp) { + cp = malloc(sizeof(*cp)); + if (!cp) + return NULL; + cp->state = state_init; + cp->conf = conf; + cp->data = NULL; + + if (sql_init(cp)) { + free(cp); + return NULL; + } + } + return cp; +} + +const char * +vmod_rewrite(struct sess *sp, struct vmod_priv *priv, const char *arg) +{ + struct dbrw_config *conf = priv->priv; + struct dbrw_connection *cp; + struct wordsplit ws, wsenv; + char *query_str; + int i, rc; + char *res; + + debug(conf, 2, ("vmod_dbrw(%s) begin", arg)); + if (!conf) { + debug(conf, 2, ("vmod_rewrite: no conf; exiting")); + return NULL; + } + cp = get_connection(conf); + if (!cp) { + debug(conf, 2, ("vmod_rewrite: no private data; exiting")); + return NULL; + } + + if (sql_connect(cp) || cp->state != state_connected) + return NULL; + + debug(conf, 2, ("vmod_rewrite: splitting arg")); + wsenv.ws_delim = ";"; + if (wordsplit(arg, &wsenv, WRDSF_NOVAR|WRDSF_NOCMD|WRDSF_DELIM)) { + dbrw_error("cannot split string `%s': %s", + arg, wordsplit_strerror(&wsenv)); + return NULL; + } + + if (conf->backend->sql_escape) { + debug(conf, 2, ("escaping variables")); + for (i = 0; i < wsenv.ws_wordc; i++) { + char *p = sql_escape(cp, wsenv.ws_wordv[i]); + if (!p) { + dbrw_error("cannot expand argument"); + wordsplit_free(&wsenv); + return NULL; + } + free(wsenv.ws_wordv[i]); + wsenv.ws_wordv[i] = p; + debug(conf, 3, ("%d: %s",i,p)); + } + } + + debug(conf, 2, ("expanding query")); + ws.ws_env = (const char **)wsenv.ws_wordv; + rc = wordsplit(conf->query, &ws, + WRDSF_NOCMD | WRDSF_QUOTE | + WRDSF_NOSPLIT | + WRDSF_ENV | WRDSF_UNDEF); + if (rc) { + dbrw_error("cannot expand query `%s': %s", + conf->query, wordsplit_strerror(&ws)); + wordsplit_free(&wsenv); + return NULL; + } + + rc = sql_query(cp, ws.ws_wordv[0]); + wordsplit_free(&ws); + + if (rc) { + debug(conf, 1, ("vmod_rewrite: query failed")); + wordsplit_free(&wsenv); + return NULL; + } + + res = findmatch(sp, cp, wsenv.ws_wordv); + wordsplit_free(&wsenv); + + sql_free_result(cp); + + debug(conf, 1, ("vmod_rewrite: res=%s", res ? res : NULL)); + + return res; +} |