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