aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2013-07-10 15:16:05 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2013-07-12 11:50:07 +0300
commit8afb551c895d6870b0d4f427fa8205ae45ad0bf9 (patch)
tree6e208d7a54d2845035b366a906b35b390ccce96a /src
downloadvmod-dbrw-8afb551c895d6870b0d4f427fa8205ae45ad0bf9.tar.gz
vmod-dbrw-8afb551c895d6870b0d4f427fa8205ae45ad0bf9.tar.bz2
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile.am50
-rw-r--r--src/dbrw.h70
-rw-r--r--src/mysql.c263
-rw-r--r--src/pgsql.c7
-rw-r--r--src/sql.c158
-rw-r--r--src/vmod_dbrw.c422
-rw-r--r--src/vmod_dbrw.vcc5
-rw-r--r--src/wordsplit.c1624
-rw-r--r--src/wordsplit.h159
10 files changed, 2760 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..7f6e438
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+vcc_if.c
+vcc_if.h
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..845153b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,50 @@
+# This file is part of libvmod_dbrw
+# Copyright (C) 2013 Sergey Poznyakoff
+#
+# Libvmod_dbrw is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Libvmod_dbrw is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with libvmod_dbrw. If not, see <http://www.gnu.org/licenses/>.
+
+AM_CPPFLAGS = -I$(VARNISHSRC)/include -I$(VARNISHSRC)
+
+#dist_man_MANS = vmod_dbrw.3
+
+vmoddir = $(VMODDIR)
+vmod_LTLIBRARIES = libvmod_dbrw.la
+
+libvmod_dbrw_la_LDFLAGS = -module -export-dynamic -avoid-version
+libvmod_dbrw_la_LIBADD=@MYSQLLIBS@ @PGSQLLIBS@
+
+libvmod_dbrw_la_SOURCES = \
+ dbrw.h\
+ sql.c\
+ vmod_dbrw.c\
+ vcc_if.c vcc_if.h\
+ wordsplit.h\
+ wordsplit.c
+
+if USE_MYSQL
+ libvmod_dbrw_la_SOURCES += mysql.c
+endif
+
+if USE_PGSQL
+ libvmod_dbrw_la_SOURCES += pgsql.c
+endif
+
+vcc_if.c vcc_if.h: $(VARNISHSRC)/lib/libvmod_std/vmod.py $(top_srcdir)/src/vmod_dbrw.vcc
+ @PYTHON@ $(VARNISHSRC)/lib/libvmod_std/vmod.py $(top_srcdir)/src/vmod_dbrw.vcc
+
+EXTRA_DIST = \
+ vmod_dbrw.vcc
+
+CLEANFILES = $(builddir)/vcc_if.c $(builddir)/vcc_if.h
+
diff --git a/src/dbrw.h b/src/dbrw.h
new file mode 100644
index 0000000..beebc5e
--- /dev/null
+++ b/src/dbrw.h
@@ -0,0 +1,70 @@
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <syslog.h>
+
+struct dbrw_connection;
+
+struct dbrw_backend {
+ char *name;
+
+ int (*sql_init) (struct dbrw_connection *);
+ void (*sql_destroy) (struct dbrw_connection *);
+ int (*sql_connect) (struct dbrw_connection *);
+ int (*sql_disconnect) (struct dbrw_connection *);
+ char *(*sql_escape) (struct dbrw_connection *, const char *);
+ int (*sql_query) (struct dbrw_connection *, const char *);
+ unsigned (*sql_num_tuples) (struct dbrw_connection *);
+ unsigned (*sql_num_fields) (struct dbrw_connection *);
+ int (*sql_free_result) (struct dbrw_connection *);
+ const char *(*sql_get_column)(struct dbrw_connection *, unsigned,
+ unsigned);
+};
+
+enum {
+ state_init,
+ state_connected,
+ state_result,
+ state_error,
+ state_disabled
+};
+
+struct dbrw_config {
+ int debug_level;
+ struct dbrw_backend *backend;
+ char **param;
+ char *query;
+};
+
+struct dbrw_connection {
+ int state;
+ struct dbrw_config *conf;
+ void *data;
+};
+
+void dbrw_debug(const char *fmt, ...);
+void dbrw_error(const char *fmt, ...);
+
+#define debug(c,n,a) do { if ((c)->debug_level>=(n)) dbrw_debug a; } while (0)
+
+#ifdef USE_SQL_MYSQL
+struct dbrw_backend mysql_backend;
+#endif
+#ifdef USE_SQL_PGSQL
+struct dbrw_backend pgsql_backend;
+#endif
+
+char *findparam(char **params, char *name);
+
+int sql_init(struct dbrw_connection *);
+int sql_connect(struct dbrw_connection *pd);
+void sql_disconnect(struct dbrw_connection *pd);
+char *sql_escape(struct dbrw_connection *pd, const char *input);
+int sql_query(struct dbrw_connection *pd, const char *input);
+unsigned sql_num_tuples(struct dbrw_connection *pd);
+unsigned sql_num_fields(struct dbrw_connection *pd);
+void sql_free_result(struct dbrw_connection *pd);
+void sql_destroy(struct dbrw_connection *pd);
+const char *sql_get_column(struct dbrw_connection *pd, unsigned row, unsigned col);
+
diff --git a/src/mysql.c b/src/mysql.c
new file mode 100644
index 0000000..9f304a6
--- /dev/null
+++ b/src/mysql.c
@@ -0,0 +1,263 @@
+#include "dbrw.h"
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+#include <mysql/mysqld_error.h>
+
+struct vmod_mysql_data
+{
+ MYSQL *mysql;
+ MYSQL_RES *result;
+ char *buffer;
+ size_t bufsize;
+};
+
+
+static void
+check_errno(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+
+ switch (mysql_errno(mp->mysql)) {
+ case CR_SERVER_GONE_ERROR:
+ case CR_SERVER_LOST:
+ case ER_SERVER_SHUTDOWN:
+ case ER_ABORTING_CONNECTION:
+ sql_disconnect(conn);
+ if (conn->state == state_error) {
+ conn->state = state_disabled;
+ syslog(LOG_DAEMON|LOG_NOTICE,
+ "disabling MySQL connection");
+ }
+ break;
+ case ER_UNKNOWN_COM_ERROR:
+ case ER_ACCESS_DENIED_ERROR:
+ case ER_BAD_DB_ERROR:
+ case ER_WRONG_DB_NAME:
+ case ER_BAD_FIELD_ERROR:
+ case ER_BAD_HOST_ERROR:
+ case ER_BAD_TABLE_ERROR:
+ case ER_WRONG_FIELD_SPEC:
+ case ER_PARSE_ERROR:
+ case ER_EMPTY_QUERY:
+ case ER_FIELD_SPECIFIED_TWICE:
+ case ER_NO_SUCH_TABLE:
+ case ER_NOT_ALLOWED_COMMAND:
+ sql_disconnect(conn);
+ conn->state = state_disabled;
+ syslog(LOG_DAEMON|LOG_NOTICE, "disabling MySQL connection");
+ }
+}
+
+/* ************************************************************************* */
+/* Interface routines */
+
+static int
+s_mysql_init(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = calloc(1, sizeof (*mp));
+ if (!mp) {
+ dbrw_error("not enough memory");
+ return -1;
+ }
+ conn->data = mp;
+ return 0;
+}
+
+static void
+s_mysql_destroy(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ free(mp->buffer);
+ free(mp->mysql);
+ free(mp);
+ conn->data = NULL;
+}
+
+
+static int
+s_mysql_connect(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ char *host, *socket_name = NULL;
+ char *s;
+ int port;
+
+ mp->mysql = malloc(sizeof(MYSQL));
+ if (!mp->mysql) {
+ dbrw_error("not enough memory");
+ conn->state = state_disabled;
+ return -1;
+ }
+
+ mysql_init(mp->mysql);
+
+ host = findparam(conn->conf->param, "server");
+ if (host && host[0] == '/') {
+ host = "localhost";
+ socket_name = host;
+ }
+
+ s = findparam(conn->conf->param, "port");
+ if (s)
+ port = atoi(s);
+
+ s = findparam(conn->conf->param, "config");
+ if (s)
+ mysql_options(mp->mysql, MYSQL_READ_DEFAULT_FILE, s);
+
+ s = findparam(conn->conf->param, "group");
+ if (s)
+ mysql_options(mp->mysql, MYSQL_READ_DEFAULT_GROUP, s);
+
+ s = findparam(conn->conf->param, "cacert");
+ if (s)
+ mysql_ssl_set(mp->mysql, NULL, NULL, s, NULL, NULL);
+ debug(conn->conf, 1, ("connecting to database"));
+ if (!mysql_real_connect(mp->mysql,
+ host,
+ findparam(conn->conf->param, "user"),
+ findparam(conn->conf->param, "password"),
+ findparam(conn->conf->param, "database"),
+ port,
+ socket_name,
+ CLIENT_MULTI_RESULTS))
+ return -1;
+ debug(conn->conf, 1, ("connected to database"));
+
+ return 0;
+}
+
+static int
+s_mysql_disconnect(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ mysql_close(mp->mysql);
+ return 0;
+}
+
+static int
+s_mysql_query(struct dbrw_connection *conn, const char *query)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ int rc;
+ int i;
+ MYSQL *mysql;
+
+ for (i = 0; i < 10; i++) {
+ mysql = mp->mysql;
+ rc = mysql_query(mysql, query);
+ if (rc) {
+ check_errno(conn);
+ if (conn->state != state_init)
+ return -1;
+ sql_connect(conn);
+ if (conn->state != state_connected)
+ return -1;
+ continue;
+ }
+ if (!(mp->result = mysql_store_result(mp->mysql))) {
+ dbrw_error("cannot store result: %s",
+ mysql_error (mp->mysql));
+ conn->state = state_error;
+ rc = 1;
+ }
+ break;
+ }
+ return rc;
+}
+
+static int
+s_mysql_free_result(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ mysql_free_result(mp->result);
+ mp->result = NULL;
+ return 0;
+}
+
+static unsigned
+s_mysql_num_fields(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ return mysql_num_fields(mp->result);
+}
+
+static unsigned
+s_mysql_num_tuples(struct dbrw_connection *conn)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ return mysql_num_rows(mp->result);
+}
+
+static const char *
+s_mysql_get_column(struct dbrw_connection *conn, size_t nrow, size_t ncol)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ MYSQL_ROW row;
+
+ if (nrow >= mysql_num_rows(mp->result) ||
+ ncol >= mysql_num_fields (mp->result))
+ return NULL;
+
+ mysql_data_seek(mp->result, nrow);
+ row = mysql_fetch_row(mp->result);
+ if (!row)
+ return NULL;
+ return row[ncol];
+}
+
+static char *
+s_mysql_escape (struct dbrw_connection *conn, const char *arg)
+{
+ struct vmod_mysql_data *mp = conn->data;
+ size_t len, size;
+ char *p;
+
+ switch (conn->state) {
+ case state_connected:
+ case state_result:
+ break;
+
+ case state_error:
+ case state_init:
+ if (sql_connect(conn))
+ return NULL;
+ break;
+
+ default:
+ return NULL;
+ }
+
+ len = strlen(arg);
+ size = 2 * len + 1;
+ if (mp->bufsize < size) {
+ p = realloc(mp->buffer, size);
+ if (!p) {
+ dbrw_error("not enough memory");
+ return NULL;
+ }
+ mp->buffer = p;
+ mp->bufsize = size;
+ }
+
+ mysql_real_escape_string(mp->mysql, mp->buffer, arg, len);
+
+ p = strdup(mp->buffer);
+ if (!p)
+ dbrw_error("not enough memory");
+ return p;
+}
+
+struct dbrw_backend mysql_backend = {
+ "mysql",
+ s_mysql_init,
+ s_mysql_destroy,
+ s_mysql_connect,
+ s_mysql_disconnect,
+ s_mysql_escape,
+ s_mysql_query,
+ s_mysql_num_tuples,
+ s_mysql_num_fields,
+ s_mysql_free_result,
+ s_mysql_get_column
+};
diff --git a/src/pgsql.c b/src/pgsql.c
new file mode 100644
index 0000000..e67e26c
--- /dev/null
+++ b/src/pgsql.c
@@ -0,0 +1,7 @@
+/* This file is a placeholder */
+#include "dbrw.h"
+
+static struct dbrw_backend pgsql_backend = {
+ "pgsql",
+ /* FIXME */
+};
diff --git a/src/sql.c b/src/sql.c
new file mode 100644
index 0000000..9a8c6ec
--- /dev/null
+++ b/src/sql.c
@@ -0,0 +1,158 @@
+#include "dbrw.h"
+#include <assert.h>
+
+#define CONN_ASSERT(conn) do { \
+ if (!(conn)->conf) \
+ return; \
+ if ((conn)->state == state_disabled) \
+ return; \
+ } while(0)
+
+#define CONN_ASSERT_VAL(conn,v) do { \
+ if (!(conn)->conf) \
+ return v; \
+ if ((conn)->state == state_disabled) \
+ return v; \
+ } while(0)
+
+
+int
+sql_init(struct dbrw_connection *conn)
+{
+ CONN_ASSERT_VAL(conn, 1);
+ debug(conn->conf, 3, ("sql_init called"));
+ if (conn->conf->backend->sql_init)
+ return conn->conf->backend->sql_init(conn);
+ return 0;
+}
+
+void
+sql_destroy(struct dbrw_connection *conn)
+{
+ CONN_ASSERT(conn);
+ debug(conn->conf, 5, ("sql_destroy: state=%d", conn->state));
+ do {
+ switch (conn->state) {
+ case state_init:
+ break;
+ case state_result:
+ sql_free_result(conn);
+ break;
+ case state_connected:
+ sql_disconnect(conn);
+ break;
+ default:
+ /* Force destroy */
+ conn->state = state_init;
+ }
+ } while (conn->state != state_init);
+ if (conn->conf->backend->sql_destroy)
+ return conn->conf->backend->sql_destroy(conn);
+}
+
+int
+sql_connect(struct dbrw_connection *conn)
+{
+ CONN_ASSERT_VAL(conn, 1);
+ debug(conn->conf, 5, ("sql_connect: state=%d", conn->state));
+ assert(conn->conf->backend->sql_connect);
+ if (conn->conf->backend->sql_connect(conn))
+ return 1;
+ debug(conn->conf, 5, ("sql_connect: success"));
+ conn->state = state_connected;
+ return 0;
+}
+
+void
+sql_disconnect(struct dbrw_connection *conn)
+{
+ CONN_ASSERT(conn);
+ debug(conn->conf, 5, ("sql_disconnect: state=%d", conn->state));
+ assert(conn->conf->backend->sql_disconnect);
+ if (conn->conf->backend->sql_disconnect(conn) == 0)
+ conn->state = state_init;
+}
+
+char *
+sql_escape(struct dbrw_connection *conn, const char *input)
+{
+ CONN_ASSERT_VAL(conn, NULL);
+ debug(conn->conf, 5, ("sql_escape: state=%d; input=%s",
+ conn->state, input));
+ if (conn->conf->backend->sql_escape)
+ return conn->conf->backend->sql_escape(conn, input);
+ return strdup(input);
+}
+
+int
+sql_query(struct dbrw_connection *conn, const char *input)
+{
+ CONN_ASSERT_VAL(conn, 1);
+ debug(conn->conf, 1, ("state=%d, query=%s", conn->state, input));
+ do {
+ switch (conn->state) {
+ case state_init:
+ if (sql_connect(conn) || conn->state != state_connected)
+ return 1;
+ break;
+
+ case state_connected:
+ break;
+
+ case state_result:
+ sql_free_result(conn);
+ break;
+
+ case state_error:
+ sql_disconnect(conn);
+ if (conn->state != state_init)
+ return 1;
+ break;
+
+ default:
+ return 1;
+ }
+ } while (conn->state != state_connected);
+ if (conn->conf->backend->sql_query(conn, input) == 0) {
+ conn->state = state_result;
+ return 0;
+ }
+ return 1;
+}
+
+unsigned
+sql_num_tuples(struct dbrw_connection *conn)
+{
+ CONN_ASSERT_VAL(conn, 0);
+ assert(conn->conf->backend->sql_num_tuples);
+ return conn->conf->backend->sql_num_tuples(conn);
+}
+
+unsigned
+sql_num_fields(struct dbrw_connection *conn)
+{
+ CONN_ASSERT_VAL(conn, 0);
+ assert(conn->conf->backend->sql_num_fields);
+ return conn->conf->backend->sql_num_fields(conn);
+}
+
+void
+sql_free_result(struct dbrw_connection *conn)
+{
+ CONN_ASSERT(conn);
+ if (conn->conf->backend->sql_free_result) {
+ if (conn->conf->backend->sql_free_result(conn))
+ return;
+ }
+ conn->state = state_connected;
+}
+
+const char *
+sql_get_column(struct dbrw_connection *conn, unsigned row, unsigned col)
+{
+ CONN_ASSERT_VAL(conn, NULL);
+ assert(conn->conf->backend->sql_get_column);
+ if (conn->state != state_result)
+ return NULL;
+ return conn->conf->backend->sql_get_column(conn, row, col);
+}
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;
+}
diff --git a/src/vmod_dbrw.vcc b/src/vmod_dbrw.vcc
new file mode 100644
index 0000000..515883f
--- /dev/null
+++ b/src/vmod_dbrw.vcc
@@ -0,0 +1,5 @@
+Module dbrw
+Init dbrw_init
+Function STRING rewrite(PRIV_VCL, STRING)
+Function VOID config(PRIV_VCL, STRING, STRING, STRING)
+
diff --git a/src/wordsplit.c b/src/wordsplit.c
new file mode 100644
index 0000000..9047369
--- /dev/null
+++ b/src/wordsplit.c
@@ -0,0 +1,1624 @@
+/* wordsplit - a word splitter
+ Copyright (C) 2009-2012 Sergey Poznyakoff
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#if ENABLE_NLS
+# include <gettext.h>
+#else
+# define gettext(msgid) msgid
+#endif
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+#include <wordsplit.h>
+
+#define ISWS(c) ((c)==' '||(c)=='\t'||(c)=='\n')
+#define ISDELIM(ws,c) \
+ (strchr ((ws)->ws_delim, (c)) != NULL)
+#define ISPUNCT(c) (strchr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",(c))!=NULL)
+#define ISUPPER(c) ('A' <= ((unsigned) (c)) && ((unsigned) (c)) <= 'Z')
+#define ISLOWER(c) ('a' <= ((unsigned) (c)) && ((unsigned) (c)) <= 'z')
+#define ISALPHA(c) (ISUPPER(c) || ISLOWER(c))
+#define ISDIGIT(c) ('0' <= ((unsigned) (c)) && ((unsigned) (c)) <= '9')
+#define ISXDIGIT(c) (strchr("abcdefABCDEF", c)!=NULL)
+#define ISALNUM(c) (ISALPHA(c) || ISDIGIT(c))
+#define ISPRINT(c) (' ' <= ((unsigned) (c)) && ((unsigned) (c)) <= 127)
+
+#define ALLOC_INIT 128
+#define ALLOC_INCR 128
+
+static void
+_wsplt_alloc_die (struct wordsplit *wsp)
+{
+ wsp->ws_error (_("memory exhausted"));
+ abort ();
+}
+
+static void
+_wsplt_error (const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start (ap, fmt);
+ vfprintf (stderr, fmt, ap);
+ va_end (ap);
+ fputc ('\n', stderr);
+}
+
+static void wordsplit_free_nodes (struct wordsplit *);
+
+static int
+_wsplt_nomem (struct wordsplit *wsp)
+{
+ errno = ENOMEM;
+ wsp->ws_errno = WRDSE_NOSPACE;
+ if (wsp->ws_flags & WRDSF_ENOMEMABRT)
+ wsp->ws_alloc_die (wsp);
+ if (wsp->ws_flags & WRDSF_SHOWERR)
+ wordsplit_perror (wsp);
+ if (!(wsp->ws_flags & WRDSF_REUSE))
+ wordsplit_free (wsp);
+ wordsplit_free_nodes (wsp);
+ return wsp->ws_errno;
+}
+
+static void
+wordsplit_init0 (struct wordsplit *wsp)
+{
+ if (wsp->ws_flags & WRDSF_REUSE)
+ {
+ if (!(wsp->ws_flags & WRDSF_APPEND))
+ wordsplit_free_words (wsp);
+ }
+ else
+ {
+ wsp->ws_wordv = NULL;
+ wsp->ws_wordc = 0;
+ wsp->ws_wordn = 0;
+ }
+
+ wsp->ws_errno = 0;
+ wsp->ws_head = wsp->ws_tail = NULL;
+}
+
+static int
+wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
+ int flags)
+{
+ wsp->ws_flags = flags;
+
+ if (!(wsp->ws_flags & WRDSF_ALLOC_DIE))
+ wsp->ws_alloc_die = _wsplt_alloc_die;
+ if (!(wsp->ws_flags & WRDSF_ERROR))
+ wsp->ws_error = _wsplt_error;
+
+ if (!(wsp->ws_flags & WRDSF_NOVAR)
+ && !(wsp->ws_flags & (WRDSF_ENV | WRDSF_GETVAR)))
+ {
+ errno = EINVAL;
+ wsp->ws_errno = WRDSE_USAGE;
+ if (wsp->ws_flags & WRDSF_SHOWERR)
+ wordsplit_perror (wsp);
+ return wsp->ws_errno;
+ }
+
+ if (!(wsp->ws_flags & WRDSF_NOCMD))
+ {
+ errno = EINVAL;
+ wsp->ws_errno = WRDSE_NOSUPP;
+ if (wsp->ws_flags & WRDSF_SHOWERR)
+ wordsplit_perror (wsp);
+ return wsp->ws_errno;
+ }
+
+ if (wsp->ws_flags & WRDSF_SHOWDBG)
+ {
+ if (!(wsp->ws_flags & WRDSF_DEBUG))
+ {
+ if (wsp->ws_flags & WRDSF_ERROR)
+ wsp->ws_debug = wsp->ws_error;
+ else if (wsp->ws_flags & WRDSF_SHOWERR)
+ wsp->ws_debug = _wsplt_error;
+ else
+ wsp->ws_flags &= ~WRDSF_SHOWDBG;
+ }
+ }
+
+ wsp->ws_input = input;
+ wsp->ws_len = len;
+
+ if (!(wsp->ws_flags & WRDSF_DOOFFS))
+ wsp->ws_offs = 0;
+
+ if (!(wsp->ws_flags & WRDSF_DELIM))
+ wsp->ws_delim = " \t\n";
+
+ if (!(wsp->ws_flags & WRDSF_COMMENT))
+ wsp->ws_comment = NULL;
+
+ if (!(wsp->ws_flags & WRDSF_CLOSURE))
+ wsp->ws_closure = NULL;
+
+ wsp->ws_endp = 0;
+
+ wordsplit_init0 (wsp);
+
+ return 0;
+}
+
+static int
+alloc_space (struct wordsplit *wsp, size_t count)
+{
+ size_t offs = (wsp->ws_flags & WRDSF_DOOFFS) ? wsp->ws_offs : 0;
+ char **ptr;
+ size_t newalloc;
+
+ if (wsp->ws_wordv == NULL)
+ {
+ newalloc = offs + count > ALLOC_INIT ? count : ALLOC_INIT;
+ ptr = calloc (newalloc, sizeof (ptr[0]));
+ }
+ else if (wsp->ws_wordn < offs + wsp->ws_wordc +