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