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