xref: /lighttpd1.4/src/mod_vhostdb_dbi.c (revision 70b1af06)
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 <dbi/dbi.h>
10 
11 #include <string.h>
12 #include <stdlib.h>
13 
14 #include "base.h"
15 #include "http_vhostdb.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  *                   "sqlite_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 */
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 
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 
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 (!buffer_string_is_empty(sqlquery)
108         && !buffer_is_empty(dbname) && !buffer_is_empty(dbtype)) {
109         /* create/initialise database */
110         vhostdb_config *dbconf;
111         dbi_inst dbinst = NULL;
112         dbi_conn dbconn;
113         if (dbi_initialize_r(NULL, &dbinst) < 1) {
114             log_error(srv->errh, __FILE__, __LINE__,
115               "dbi_initialize_r() failed.  "
116               "Do you have the DBD for this db type installed?");
117             return -1;
118         }
119         dbconn = dbi_conn_new_r(dbtype->ptr, dbinst);
120         if (NULL == dbconn) {
121             log_error(srv->errh, __FILE__, __LINE__,
122               "dbi_conn_new_r() failed.  "
123               "Do you have the DBD for this db type installed?");
124             dbi_shutdown_r(dbinst);
125             return -1;
126         }
127 
128         /* set options */
129         for (size_t j = 0; j < opts->used; ++j) {
130             data_unset *du = opts->data[j];
131             const buffer *opt = &du->key;
132             if (!buffer_string_is_empty(opt)) {
133                 if (du->type == TYPE_INTEGER) {
134                     data_integer *di = (data_integer *)du;
135                     dbi_conn_set_option_numeric(dbconn, opt->ptr, di->value);
136                 } else if (du->type == TYPE_STRING) {
137                     data_string *ds = (data_string *)du;
138                     if (&ds->value != sqlquery && &ds->value != dbtype) {
139                         dbi_conn_set_option(dbconn, opt->ptr, ds->value.ptr);
140                     }
141                 }
142             }
143         }
144 
145         dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf));
146         dbconf->dbinst = dbinst;
147         dbconf->dbconn = dbconn;
148         dbconf->sqlquery = sqlquery;
149         dbconf->errh = srv->errh;
150         dbconf->reconnect_count = 0;
151         *vdata = dbconf;
152 
153         /* used to automatically reconnect to the database */
154         dbi_conn_error_handler(dbconn, mod_vhostdb_dbi_error_callback, dbconf);
155 
156         /* connect to database */
157         mod_vhostdb_dbi_error_callback(dbconn, dbconf);
158         if (dbconf->reconnect_count >= 3) {
159             mod_vhostdb_dbconf_free(dbconf);
160             return -1;
161         }
162     }
163 
164     return 0;
165 }
166 
167 static void mod_vhostdb_patch_config (request_st * const r, plugin_data * const p);
168 
169 static int mod_vhostdb_dbi_query(request_st * const r, void *p_d, buffer *docroot)
170 {
171     plugin_data *p = (plugin_data *)p_d;
172     vhostdb_config *dbconf;
173     dbi_result result;
174     unsigned long long nrows;
175     int retry_count = 0;
176 
177     /*(reuse buffer for sql query before generating docroot result)*/
178     buffer *sqlquery = docroot;
179     buffer_clear(sqlquery); /*(also resets docroot (alias))*/
180 
181     mod_vhostdb_patch_config(r, p);
182     if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/
183     dbconf = (vhostdb_config *)p->conf.vdata;
184 
185     for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) {
186         if (NULL != (d = strchr(b, '?'))) {
187             /* escape the uri.authority */
188             char *esc = NULL;
189             size_t len = dbi_conn_escape_string_copy(dbconf->dbconn, r->uri.authority.ptr, &esc);
190             buffer_append_string_len(sqlquery, b, (size_t)(d - b));
191             buffer_append_string_len(sqlquery, esc, len);
192             free(esc);
193             if (0 == len) return -1;
194         } else {
195             d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery);
196             buffer_append_string_len(sqlquery, b, (size_t)(d - b));
197             break;
198         }
199     }
200 
201     /* reset our reconnect-attempt counter, this is a new query. */
202     dbconf->reconnect_count = 0;
203 
204     do {
205         result = dbi_conn_query(dbconf->dbconn, sqlquery->ptr);
206     } while (!result && ++retry_count < 2);
207 
208     buffer_clear(docroot); /*(reset buffer to store result)*/
209 
210     if (!result) {
211         const char *errmsg;
212         dbi_conn_error(dbconf->dbconn, &errmsg);
213         log_error(r->conf.errh, __FILE__, __LINE__, "%s", errmsg);
214         return -1;
215     }
216 
217     nrows = dbi_result_get_numrows(result);
218     if (nrows && nrows != DBI_ROW_ERROR && dbi_result_next_row(result)) {
219         buffer_copy_string(docroot, dbi_result_get_string_idx(result, 1));
220     } /* else no such virtual host */
221 
222     dbi_result_free(result);
223     return 0;
224 }
225 
226 
227 
228 
229 INIT_FUNC(mod_vhostdb_init) {
230     static http_vhostdb_backend_t http_vhostdb_backend_dbi =
231       { "dbi", mod_vhostdb_dbi_query, NULL };
232     plugin_data *p = calloc(1, sizeof(*p));
233 
234     /* register http_vhostdb_backend_dbi */
235     http_vhostdb_backend_dbi.p_d = p;
236     http_vhostdb_backend_set(&http_vhostdb_backend_dbi);
237 
238     return p;
239 }
240 
241 FREE_FUNC(mod_vhostdb_cleanup) {
242     plugin_data * const p = p_d;
243     if (NULL == p->cvlist) return;
244     /* (init i to 0 if global context; to 1 to skip empty global context) */
245     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
246         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
247         for (; -1 != cpv->k_id; ++cpv) {
248             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
249             switch (cpv->k_id) {
250               case 0: /* vhostdb.<db> */
251                 mod_vhostdb_dbconf_free(cpv->v.v);
252                 break;
253               default:
254                 break;
255             }
256         }
257     }
258 }
259 
260 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
261     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
262       case 0: /* vhostdb.<db> */
263         if (cpv->vtype == T_CONFIG_LOCAL)
264             pconf->vdata = cpv->v.v;
265         break;
266       default:/* should not happen */
267         return;
268     }
269 }
270 
271 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
272     do {
273         mod_vhostdb_merge_config_cpv(pconf, cpv);
274     } while ((++cpv)->k_id != -1);
275 }
276 
277 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) {
278     p->conf = p->defaults; /* copy small struct instead of memcpy() */
279     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
280     for (int i = 1, used = p->nconfig; i < used; ++i) {
281         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
282             mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]);
283     }
284 }
285 
286 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
287     static const config_plugin_keys_t cpk[] = {
288       { CONST_STR_LEN("vhostdb.dbi"),
289         T_CONFIG_ARRAY_KVANY,
290         T_CONFIG_SCOPE_CONNECTION }
291      ,{ NULL, 0,
292         T_CONFIG_UNSET,
293         T_CONFIG_SCOPE_UNSET }
294     };
295 
296     plugin_data * const p = p_d;
297     if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_dbi"))
298         return HANDLER_ERROR;
299 
300     /* process and validate config directives
301      * (init i to 0 if global context; to 1 to skip empty global context) */
302     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
303         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
304         for (; -1 != cpv->k_id; ++cpv) {
305             switch (cpv->k_id) {
306               case 0: /* vhostdb.<db> */
307                 if (cpv->v.a->used) {
308                     if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v))
309                         return HANDLER_ERROR;
310                     if (NULL != cpv->v.v)
311                         cpv->vtype = T_CONFIG_LOCAL;
312                 }
313                 break;
314               default:/* should not happen */
315                 break;
316             }
317         }
318     }
319 
320     /* initialize p->defaults from global config context */
321     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
322         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
323         if (-1 != cpv->k_id)
324             mod_vhostdb_merge_config(&p->defaults, cpv);
325     }
326 
327     return HANDLER_GO_ON;
328 }
329 
330 
331 int 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