aboutsummaryrefslogtreecommitdiff
path: root/src/vmod_dbrw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vmod_dbrw.c')
-rw-r--r--src/vmod_dbrw.c422
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;
+}

Return to:

Send suggestions and report system problems to the System administrator.