xref: /lighttpd1.4/src/mod_vhostdb_dbi.c (revision 5e14db43)
1 /*
2  * mod_vhostdb_dbi - virtual hosts mapping from backend DBI interface
3  *
4  * Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
5  * License: BSD 3-clause (same as lighttpd)
6  */
7 #include "first.h"
8 
9 #include <string.h>
10 #include <stdlib.h>
11 
12 #include <dbi/dbi.h>
13 
14 #include "mod_vhostdb_api.h"
15 #include "base.h"
16 #include "fdevent.h"
17 #include "log.h"
18 #include "plugin.h"
19 
20 /*
21  * virtual host plugin using DBI for domain to directory lookups
22  *
23  * e.g.
24  *   vhostdb.dbi = ( "sql"    => "SELECT docroot FROM vhosts WHERE host='?'"
25  *                   "dbtype" => "sqlite3",
26  *                   "dbname" => "mydb.sqlite",
27  *                   "sqlite3_dbdir" => "/path/to/sqlite/dbs/" )
28  */
29 
30 typedef struct {
31     dbi_conn dbconn;
32     dbi_inst dbinst;
33     const buffer *sqlquery;
34     log_error_st *errh;
35     short reconnect_count;
36 } vhostdb_config;
37 
38 typedef struct {
39     void *vdata;
40 } plugin_config;
41 
42 typedef struct {
43     PLUGIN_DATA;
44     plugin_config defaults;
45     plugin_config conf;
46 } plugin_data;
47 
48 /* used to reconnect to the database when we get disconnected */
mod_vhostdb_dbi_error_callback(dbi_conn dbconn,void * vdata)49 static void mod_vhostdb_dbi_error_callback (dbi_conn dbconn, void *vdata)
50 {
51     vhostdb_config *dbconf = (vhostdb_config *)vdata;
52     const char *errormsg = NULL;
53     /*assert(dbconf->dbconn == dbconn);*/
54 
55     while (++dbconf->reconnect_count <= 3) { /* retry */
56         if (0 == dbi_conn_connect(dbconn)) {
57             fdevent_setfd_cloexec(dbi_conn_get_socket(dbconn));
58             return;
59         }
60     }
61 
62     dbi_conn_error(dbconn, &errormsg);
63     log_error(dbconf->errh,__FILE__,__LINE__,"dbi_conn_connect(): %s",errormsg);
64 }
65 
mod_vhostdb_dbconf_free(void * vdata)66 static void mod_vhostdb_dbconf_free (void *vdata)
67 {
68     vhostdb_config *dbconf = (vhostdb_config *)vdata;
69     if (!dbconf) return;
70     dbi_conn_close(dbconf->dbconn);
71     dbi_shutdown_r(dbconf->dbinst);
72     free(dbconf);
73 }
74 
mod_vhostdb_dbconf_setup(server * srv,const array * opts,void ** vdata)75 static int mod_vhostdb_dbconf_setup (server *srv, const array *opts, void **vdata)
76 {
77     const buffer *sqlquery = NULL;
78     const buffer *dbtype=NULL, *dbname=NULL;
79 
80     for (size_t i = 0; i < opts->used; ++i) {
81         const data_string *ds = (data_string *)opts->data[i];
82         if (ds->type == TYPE_STRING) {
83             if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) {
84                 sqlquery = &ds->value;
85             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) {
86                 dbname = &ds->value;
87             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbtype"))) {
88                 dbtype = &ds->value;
89             }
90         }
91     }
92 
93     /* required:
94      * - sql    (sql query)
95      * - dbtype
96      * - dbname
97      *
98      * optional:
99      * - username, some databases don't require this (sqlite)
100      * - password, default: empty
101      * - socket, default: database type default
102      * - hostname, if set overrides socket
103      * - port, default: database default
104      * - encoding, default: database default
105      */
106 
107     if (sqlquery && !buffer_is_blank(sqlquery) && dbname && dbtype) {
108         /* create/initialise database */
109         vhostdb_config *dbconf;
110         dbi_inst dbinst = NULL;
111         dbi_conn dbconn;
112         if (dbi_initialize_r(NULL, &dbinst) < 1) {
113             log_error(srv->errh, __FILE__, __LINE__,
114               "dbi_initialize_r() failed.  "
115               "Do you have the DBD for this db type installed?");
116             return -1;
117         }
118         dbconn = dbi_conn_new_r(dbtype->ptr, dbinst);
119         if (NULL == dbconn) {
120             log_error(srv->errh, __FILE__, __LINE__,
121               "dbi_conn_new_r() failed.  "
122               "Do you have the DBD for this db type installed?");
123             dbi_shutdown_r(dbinst);
124             return -1;
125         }
126 
127         /* set options */
128         for (size_t j = 0; j < opts->used; ++j) {
129             data_unset *du = opts->data[j];
130             const buffer *opt = &du->key;
131             if (!buffer_is_blank(opt)) {
132                 if (du->type == TYPE_INTEGER) {
133                     data_integer *di = (data_integer *)du;
134                     dbi_conn_set_option_numeric(dbconn, opt->ptr, di->value);
135                 } else if (du->type == TYPE_STRING) {
136                     data_string *ds = (data_string *)du;
137                     if (&ds->value != sqlquery && &ds->value != dbtype) {
138                         dbi_conn_set_option(dbconn, opt->ptr, ds->value.ptr);
139                     }
140                 }
141             }
142         }
143 
144         dbconf = (vhostdb_config *)ck_calloc(1, sizeof(*dbconf));
145         dbconf->dbinst = dbinst;
146         dbconf->dbconn = dbconn;
147         dbconf->sqlquery = sqlquery;
148         dbconf->errh = srv->errh;
149         dbconf->reconnect_count = 0;
150         *vdata = dbconf;
151 
152         /* used to automatically reconnect to the database */
153         dbi_conn_error_handler(dbconn, mod_vhostdb_dbi_error_callback, dbconf);
154 
155         /* connect to database */
156         mod_vhostdb_dbi_error_callback(dbconn, dbconf);
157         if (dbconf->reconnect_count >= 3) {
158             mod_vhostdb_dbconf_free(dbconf);
159             return -1;
160         }
161     }
162 
163     return 0;
164 }
165 
166 static void mod_vhostdb_patch_config (request_st * const r, plugin_data * const p);
167 
mod_vhostdb_dbi_query(request_st * const r,void * p_d,buffer * docroot)168 static int mod_vhostdb_dbi_query(request_st * const r, void *p_d, buffer *docroot)
169 {
170     plugin_data *p = (plugin_data *)p_d;
171     vhostdb_config *dbconf;
172     dbi_result result;
173     unsigned long long nrows;
174     int retry_count = 0;
175 
176     /*(reuse buffer for sql query before generating docroot result)*/
177     buffer *sqlquery = docroot;
178     buffer_clear(sqlquery); /*(also resets docroot (alias))*/
179 
180     mod_vhostdb_patch_config(r, p);
181     if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/
182     dbconf = (vhostdb_config *)p->conf.vdata;
183 
184     for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) {
185         if (NULL != (d = strchr(b, '?'))) {
186             /* escape the uri.authority */
187             char *esc = NULL;
188             size_t len = dbi_conn_escape_string_copy(dbconf->dbconn,
189                                                      r->uri.authority.ptr,&esc);
190             buffer_append_str2(sqlquery, b, (size_t)(d - b), esc, len);
191             free(esc);
192             if (0 == len) return -1;
193         } else {
194             d = dbconf->sqlquery->ptr + buffer_clen(dbconf->sqlquery);
195             buffer_append_string_len(sqlquery, b, (size_t)(d - b));
196             break;
197         }
198     }
199 
200     /* reset our reconnect-attempt counter, this is a new query. */
201     dbconf->reconnect_count = 0;
202 
203     do {
204         result = dbi_conn_query(dbconf->dbconn, sqlquery->ptr);
205     } while (!result && ++retry_count < 2);
206 
207     buffer_clear(docroot); /*(reset buffer to store result)*/
208 
209     if (!result) {
210         const char *errmsg;
211         dbi_conn_error(dbconf->dbconn, &errmsg);
212         log_error(r->conf.errh, __FILE__, __LINE__, "%s", errmsg);
213         return -1;
214     }
215 
216     nrows = dbi_result_get_numrows(result);
217     if (nrows && nrows != DBI_ROW_ERROR && dbi_result_next_row(result)) {
218         buffer_copy_string(docroot, dbi_result_get_string_idx(result, 1));
219     } /* else no such virtual host */
220 
221     dbi_result_free(result);
222     return 0;
223 }
224 
225 
226 
227 
INIT_FUNC(mod_vhostdb_init)228 INIT_FUNC(mod_vhostdb_init) {
229     static http_vhostdb_backend_t http_vhostdb_backend_dbi =
230       { "dbi", mod_vhostdb_dbi_query, NULL };
231     plugin_data *p = ck_calloc(1, sizeof(*p));
232 
233     /* register http_vhostdb_backend_dbi */
234     http_vhostdb_backend_dbi.p_d = p;
235     http_vhostdb_backend_set(&http_vhostdb_backend_dbi);
236 
237     return p;
238 }
239 
FREE_FUNC(mod_vhostdb_cleanup)240 FREE_FUNC(mod_vhostdb_cleanup) {
241     plugin_data * const p = p_d;
242     if (NULL == p->cvlist) return;
243     /* (init i to 0 if global context; to 1 to skip empty global context) */
244     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
245         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
246         for (; -1 != cpv->k_id; ++cpv) {
247             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
248             switch (cpv->k_id) {
249               case 0: /* vhostdb.<db> */
250                 mod_vhostdb_dbconf_free(cpv->v.v);
251                 break;
252               default:
253                 break;
254             }
255         }
256     }
257 }
258 
mod_vhostdb_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)259 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
260     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
261       case 0: /* vhostdb.<db> */
262         if (cpv->vtype == T_CONFIG_LOCAL)
263             pconf->vdata = cpv->v.v;
264         break;
265       default:/* should not happen */
266         return;
267     }
268 }
269 
mod_vhostdb_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)270 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
271     do {
272         mod_vhostdb_merge_config_cpv(pconf, cpv);
273     } while ((++cpv)->k_id != -1);
274 }
275 
mod_vhostdb_patch_config(request_st * const r,plugin_data * const p)276 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) {
277     p->conf = p->defaults; /* copy small struct instead of memcpy() */
278     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
279     for (int i = 1, used = p->nconfig; i < used; ++i) {
280         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
281             mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]);
282     }
283 }
284 
SETDEFAULTS_FUNC(mod_vhostdb_set_defaults)285 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
286     static const config_plugin_keys_t cpk[] = {
287       { CONST_STR_LEN("vhostdb.dbi"),
288         T_CONFIG_ARRAY_KVANY,
289         T_CONFIG_SCOPE_CONNECTION }
290      ,{ NULL, 0,
291         T_CONFIG_UNSET,
292         T_CONFIG_SCOPE_UNSET }
293     };
294 
295     plugin_data * const p = p_d;
296     if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_dbi"))
297         return HANDLER_ERROR;
298 
299     /* process and validate config directives
300      * (init i to 0 if global context; to 1 to skip empty global context) */
301     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
302         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
303         for (; -1 != cpv->k_id; ++cpv) {
304             switch (cpv->k_id) {
305               case 0: /* vhostdb.<db> */
306                 if (cpv->v.a->used) {
307                     if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v))
308                         return HANDLER_ERROR;
309                     if (NULL != cpv->v.v)
310                         cpv->vtype = T_CONFIG_LOCAL;
311                 }
312                 break;
313               default:/* should not happen */
314                 break;
315             }
316         }
317     }
318 
319     /* initialize p->defaults from global config context */
320     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
321         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
322         if (-1 != cpv->k_id)
323             mod_vhostdb_merge_config(&p->defaults, cpv);
324     }
325 
326     return HANDLER_GO_ON;
327 }
328 
329 
330 __attribute_cold__
331 int mod_vhostdb_dbi_plugin_init (plugin *p);
mod_vhostdb_dbi_plugin_init(plugin * p)332 int mod_vhostdb_dbi_plugin_init (plugin *p)
333 {
334     p->version          = LIGHTTPD_VERSION_ID;
335     p->name             = "vhostdb_dbi";
336 
337     p->init             = mod_vhostdb_init;
338     p->cleanup          = mod_vhostdb_cleanup;
339     p->set_defaults     = mod_vhostdb_set_defaults;
340 
341     return 0;
342 }
343