diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-07-10 15:16:05 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2013-07-12 11:50:07 +0300 |
commit | 8afb551c895d6870b0d4f427fa8205ae45ad0bf9 (patch) | |
tree | 6e208d7a54d2845035b366a906b35b390ccce96a /src | |
download | vmod-dbrw-8afb551c895d6870b0d4f427fa8205ae45ad0bf9.tar.gz vmod-dbrw-8afb551c895d6870b0d4f427fa8205ae45ad0bf9.tar.bz2 |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/.gitignore | 2 | ||||
-rw-r--r-- | src/Makefile.am | 50 | ||||
-rw-r--r-- | src/dbrw.h | 70 | ||||
-rw-r--r-- | src/mysql.c | 263 | ||||
-rw-r--r-- | src/pgsql.c | 7 | ||||
-rw-r--r-- | src/sql.c | 158 | ||||
-rw-r--r-- | src/vmod_dbrw.c | 422 | ||||
-rw-r--r-- | src/vmod_dbrw.vcc | 5 | ||||
-rw-r--r-- | src/wordsplit.c | 1624 | ||||
-rw-r--r-- | src/wordsplit.h | 159 |
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 + |