xref: /lighttpd1.4/src/mod_vhostdb_mysql.c (revision e11f72b1)
1 #include "first.h"
2 
3 #include <mysql.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 MySQL for domain to directory lookups
16  */
17 
18 typedef struct {
19     MYSQL *dbconn;
20     const buffer *sqlquery;
21 } vhostdb_config;
22 
23 typedef struct {
24     void *vdata;
25 } plugin_config;
26 
27 typedef struct {
28     PLUGIN_DATA;
29     plugin_config defaults;
30     plugin_config conf;
31 } plugin_data;
32 
33 static void mod_vhostdb_dbconf_free (void *vdata)
34 {
35     vhostdb_config *dbconf = (vhostdb_config *)vdata;
36     if (!dbconf) return;
37     mysql_close(dbconf->dbconn);
38     free(dbconf);
39 }
40 
41 static int mod_vhostdb_dbconf_setup (server *srv, const array *opts, void **vdata)
42 {
43     const buffer *sqlquery = NULL;
44     const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *sock=NULL;
45     unsigned int port = 0;
46 
47     for (size_t i = 0; i < opts->used; ++i) {
48         const data_string *ds = (data_string *)opts->data[i];
49         if (ds->type == TYPE_STRING && !buffer_string_is_empty(&ds->value)) {
50             if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) {
51                 sqlquery = &ds->value;
52             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) {
53                 dbname = ds->value.ptr;
54             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("user"))) {
55                 user = ds->value.ptr;
56             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("password"))) {
57                 pass = ds->value.ptr;
58             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("host"))) {
59                 host = ds->value.ptr;
60             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("port"))) {
61                 port = strtoul(ds->value.ptr, NULL, 10);
62             } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sock"))) {
63                 sock = ds->value.ptr;
64             }
65         }
66     }
67 
68     /* required:
69      * - sql    (sql query)
70      * - dbname
71      * - user
72      *
73      * optional:
74      * - password, default: empty
75      * - socket, default: mysql default
76      * - hostname, if set overrides socket
77      * - port, default: 3306
78      */
79 
80     if (!buffer_string_is_empty(sqlquery)
81         && dbname && *dbname && user && *user) {
82         vhostdb_config *dbconf;
83         MYSQL *dbconn = mysql_init(NULL);
84         if (NULL == dbconn) {
85             log_error(srv->errh, __FILE__, __LINE__,
86               "mysql_init() failed, exiting...");
87             return -1;
88         }
89 
90       #if MYSQL_VERSION_ID >= 50013
91         /* in mysql versions above 5.0.3 the reconnect flag is off by default */
92         {
93             char reconnect = 1;
94             mysql_options(dbconn, MYSQL_OPT_RECONNECT, &reconnect);
95         }
96       #endif
97 
98         /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */
99       #if MYSQL_VERSION_ID < 40100
100         #ifndef CLIENT_MULTI_STATEMENTS
101         #define CLIENT_MULTI_STATEMENTS 0
102         #endif
103       #endif
104         if (!mysql_real_connect(dbconn, host, user, pass, dbname, port, sock,
105                                 CLIENT_MULTI_STATEMENTS)) {
106             log_error(srv->errh, __FILE__, __LINE__, "%s", mysql_error(dbconn));
107             mysql_close(dbconn);
108             return -1;
109         }
110 
111         fdevent_setfd_cloexec(dbconn->net.fd);
112 
113         dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf));
114         dbconf->dbconn = dbconn;
115         dbconf->sqlquery = sqlquery;
116         *vdata = dbconf;
117     }
118 
119     return 0;
120 }
121 
122 static void mod_vhostdb_patch_config (request_st * const r, plugin_data * const p);
123 
124 static int mod_vhostdb_mysql_query(request_st * const r, void *p_d, buffer *docroot)
125 {
126     plugin_data *p = (plugin_data *)p_d;
127     vhostdb_config *dbconf;
128     unsigned  cols;
129     MYSQL_ROW row;
130     MYSQL_RES *result;
131 
132     /*(reuse buffer for sql query before generating docroot result)*/
133     buffer *sqlquery = docroot;
134     buffer_clear(sqlquery); /*(also resets docroot (alias))*/
135 
136     mod_vhostdb_patch_config(r, p);
137     if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/
138     dbconf = (vhostdb_config *)p->conf.vdata;
139 
140     for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) {
141         if (NULL != (d = strchr(b, '?'))) {
142             /* escape the uri.authority */
143             unsigned long len;
144             buffer_append_string_len(sqlquery, b, (size_t)(d - b));
145             buffer_string_prepare_append(sqlquery, buffer_string_length(&r->uri.authority) * 2);
146             len = mysql_real_escape_string(dbconf->dbconn,
147                     sqlquery->ptr + buffer_string_length(sqlquery),
148                     CONST_BUF_LEN(&r->uri.authority));
149             if ((unsigned long)~0 == len) return -1;
150             buffer_commit(sqlquery, len);
151         } else {
152             d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery);
153             buffer_append_string_len(sqlquery, b, (size_t)(d - b));
154             break;
155         }
156     }
157 
158     if (mysql_real_query(dbconf->dbconn, CONST_BUF_LEN(sqlquery))) {
159         log_error(r->conf.errh, __FILE__, __LINE__, "%s",
160           mysql_error(dbconf->dbconn));
161         buffer_clear(docroot); /*(reset buffer; no result)*/
162         return -1;
163     }
164 
165     buffer_clear(docroot); /*(reset buffer to store result)*/
166 
167     result = mysql_store_result(dbconf->dbconn);
168     cols = mysql_num_fields(result);
169     row = mysql_fetch_row(result);
170     if (row && cols >= 1) {
171         buffer_copy_string(docroot, row[0]);
172     } /* else no such virtual host */
173 
174     mysql_free_result(result);
175   #if MYSQL_VERSION_ID >= 40100
176     while (0 == mysql_next_result(dbconf->dbconn)) ;
177   #endif
178     return 0;
179 }
180 
181 
182 
183 
184 INIT_FUNC(mod_vhostdb_init) {
185     static http_vhostdb_backend_t http_vhostdb_backend_mysql =
186       { "mysql", mod_vhostdb_mysql_query, NULL };
187     plugin_data *p = calloc(1, sizeof(*p));
188 
189     /* register http_vhostdb_backend_mysql */
190     http_vhostdb_backend_mysql.p_d = p;
191     http_vhostdb_backend_set(&http_vhostdb_backend_mysql);
192 
193     return p;
194 }
195 
196 FREE_FUNC(mod_vhostdb_cleanup) {
197     plugin_data * const p = p_d;
198     if (NULL == p->cvlist) return;
199     /* (init i to 0 if global context; to 1 to skip empty global context) */
200     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
201         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
202         for (; -1 != cpv->k_id; ++cpv) {
203             if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
204             switch (cpv->k_id) {
205               case 0: /* vhostdb.<db> */
206                 mod_vhostdb_dbconf_free(cpv->v.v);
207                 break;
208               default:
209                 break;
210             }
211         }
212     }
213 }
214 
215 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
216     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
217       case 0: /* vhostdb.<db> */
218         if (cpv->vtype == T_CONFIG_LOCAL)
219             pconf->vdata = cpv->v.v;
220         break;
221       default:/* should not happen */
222         return;
223     }
224 }
225 
226 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
227     do {
228         mod_vhostdb_merge_config_cpv(pconf, cpv);
229     } while ((++cpv)->k_id != -1);
230 }
231 
232 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) {
233     p->conf = p->defaults; /* copy small struct instead of memcpy() */
234     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
235     for (int i = 1, used = p->nconfig; i < used; ++i) {
236         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
237             mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]);
238     }
239 }
240 
241 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
242     static const config_plugin_keys_t cpk[] = {
243       { CONST_STR_LEN("vhostdb.mysql"),
244         T_CONFIG_ARRAY_KVSTRING,
245         T_CONFIG_SCOPE_CONNECTION }
246      ,{ NULL, 0,
247         T_CONFIG_UNSET,
248         T_CONFIG_SCOPE_UNSET }
249     };
250 
251     plugin_data * const p = p_d;
252     if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_mysql"))
253         return HANDLER_ERROR;
254 
255     /* process and validate config directives
256      * (init i to 0 if global context; to 1 to skip empty global context) */
257     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
258         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
259         for (; -1 != cpv->k_id; ++cpv) {
260             switch (cpv->k_id) {
261               case 0: /* vhostdb.<db> */
262                 if (cpv->v.a->used) {
263                     if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v))
264                         return HANDLER_ERROR;
265                     if (NULL != cpv->v.v)
266                         cpv->vtype = T_CONFIG_LOCAL;
267                 }
268                 break;
269               default:/* should not happen */
270                 break;
271             }
272         }
273     }
274 
275     /* initialize p->defaults from global config context */
276     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
277         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
278         if (-1 != cpv->k_id)
279             mod_vhostdb_merge_config(&p->defaults, cpv);
280     }
281 
282     return HANDLER_GO_ON;
283 }
284 
285 
286 int mod_vhostdb_mysql_plugin_init (plugin *p);
287 int mod_vhostdb_mysql_plugin_init (plugin *p)
288 {
289     p->version          = LIGHTTPD_VERSION_ID;
290     p->name             = "vhostdb_mysql";
291 
292     p->init             = mod_vhostdb_init;
293     p->cleanup          = mod_vhostdb_cleanup;
294     p->set_defaults     = mod_vhostdb_set_defaults;
295 
296     return 0;
297 }
298