1 #include <unistd.h>
2 #include <stdio.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <strings.h>
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #ifdef HAVE_MYSQL
12 #include <mysql.h>
13 #endif
14 
15 #include "plugin.h"
16 #include "log.h"
17 
18 #include "stat_cache.h"
19 #ifdef DEBUG_MOD_MYSQL_VHOST
20 #define DEBUG
21 #endif
22 
23 /*
24  * Plugin for lighttpd to use MySQL
25  *   for domain to directory lookups,
26  *   i.e virtual hosts (vhosts).
27  *
28  * Optionally sets fcgi_offset and fcgi_arg
29  *   in preparation for fcgi.c to handle
30  *   per-user fcgi chroot jails.
31  *
32  * /[email protected] 2004-12-06
33  */
34 
35 #ifdef HAVE_MYSQL
36 typedef struct {
37 	MYSQL 	*mysql;
38 
39 	buffer  *mydb;
40 	buffer  *myuser;
41 	buffer  *mypass;
42 	buffer  *mysock;
43 
44 	buffer  *hostname;
45 	unsigned short port;
46 
47 	buffer  *mysql_pre;
48 	buffer  *mysql_post;
49 } plugin_config;
50 
51 /* global plugin data */
52 typedef struct {
53 	PLUGIN_DATA;
54 
55 	buffer 	*tmp_buf;
56 
57 	plugin_config **config_storage;
58 
59 	plugin_config conf;
60 } plugin_data;
61 
62 /* per connection plugin data */
63 typedef struct {
64 	buffer	*server_name;
65 	buffer	*document_root;
66 	buffer	*fcgi_arg;
67 	unsigned fcgi_offset;
68 } plugin_connection_data;
69 
70 /* init the plugin data */
INIT_FUNC(mod_mysql_vhost_init)71 INIT_FUNC(mod_mysql_vhost_init) {
72 	plugin_data *p;
73 
74 	p = calloc(1, sizeof(*p));
75 
76 	p->tmp_buf = buffer_init();
77 
78 	return p;
79 }
80 
81 /* cleanup the plugin data */
SERVER_FUNC(mod_mysql_vhost_cleanup)82 SERVER_FUNC(mod_mysql_vhost_cleanup) {
83 	plugin_data *p = p_d;
84 
85 	UNUSED(srv);
86 
87 #ifdef DEBUG
88 	log_error_write(srv, __FILE__, __LINE__, "ss",
89 		"mod_mysql_vhost_cleanup", p ? "yes" : "NO");
90 #endif
91 	if (!p) return HANDLER_GO_ON;
92 
93 	if (p->config_storage) {
94 		size_t i;
95 		for (i = 0; i < srv->config_context->used; i++) {
96 			plugin_config *s = p->config_storage[i];
97 
98 			if (!s) continue;
99 
100 			mysql_close(s->mysql);
101 
102 			buffer_free(s->mydb);
103 			buffer_free(s->myuser);
104 			buffer_free(s->mypass);
105 			buffer_free(s->mysock);
106 			buffer_free(s->mysql_pre);
107 			buffer_free(s->mysql_post);
108 			buffer_free(s->hostname);
109 
110 			free(s);
111 		}
112 		free(p->config_storage);
113 	}
114 	buffer_free(p->tmp_buf);
115 
116 	free(p);
117 
118 	return HANDLER_GO_ON;
119 }
120 
121 /* handle the plugin per connection data */
mod_mysql_vhost_connection_data(server * srv,connection * con,void * p_d)122 static void* mod_mysql_vhost_connection_data(server *srv, connection *con, void *p_d)
123 {
124 	plugin_data *p = p_d;
125 	plugin_connection_data *c = con->plugin_ctx[p->id];
126 
127 	UNUSED(srv);
128 
129 #ifdef DEBUG
130         log_error_write(srv, __FILE__, __LINE__, "ss",
131 		"mod_mysql_connection_data", c ? "old" : "NEW");
132 #endif
133 
134 	if (c) return c;
135 	c = calloc(1, sizeof(*c));
136 
137 	c->server_name = buffer_init();
138 	c->document_root = buffer_init();
139 	c->fcgi_arg = buffer_init();
140 	c->fcgi_offset = 0;
141 
142 	return con->plugin_ctx[p->id] = c;
143 }
144 
145 /* destroy the plugin per connection data */
CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close)146 CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close) {
147 	plugin_data *p = p_d;
148 	plugin_connection_data *c = con->plugin_ctx[p->id];
149 
150 	UNUSED(srv);
151 
152 #ifdef DEBUG
153 	log_error_write(srv, __FILE__, __LINE__, "ss",
154 		"mod_mysql_vhost_handle_connection_close", c ? "yes" : "NO");
155 #endif
156 
157 	if (!c) return HANDLER_GO_ON;
158 
159 	buffer_free(c->server_name);
160 	buffer_free(c->document_root);
161 	buffer_free(c->fcgi_arg);
162 	c->fcgi_offset = 0;
163 
164 	free(c);
165 
166 	con->plugin_ctx[p->id] = NULL;
167 	return HANDLER_GO_ON;
168 }
169 
170 /* set configuration values */
SERVER_FUNC(mod_mysql_vhost_set_defaults)171 SERVER_FUNC(mod_mysql_vhost_set_defaults) {
172 	plugin_data *p = p_d;
173 
174 	char *qmark;
175 	size_t i = 0;
176 
177 	config_values_t cv[] = {
178 		{ "mysql-vhost.db",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
179 		{ "mysql-vhost.user",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
180 		{ "mysql-vhost.pass",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
181 		{ "mysql-vhost.sock",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
182 		{ "mysql-vhost.sql",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
183 		{ "mysql-vhost.hostname", NULL, T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER },
184 		{ "mysql-vhost.port",   NULL, T_CONFIG_SHORT,   T_CONFIG_SCOPE_SERVER },
185                 { NULL,			NULL, T_CONFIG_UNSET,	T_CONFIG_SCOPE_UNSET }
186         };
187 
188 	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
189 
190 	for (i = 0; i < srv->config_context->used; i++) {
191 		plugin_config *s;
192 		buffer *sel;
193 
194 
195 		s = calloc(1, sizeof(plugin_config));
196 		s->mydb = buffer_init();
197 		s->myuser = buffer_init();
198 		s->mypass = buffer_init();
199 		s->mysock = buffer_init();
200 		s->hostname = buffer_init();
201 		s->port   = 0;               /* default port for mysql */
202 		sel = buffer_init();
203 		s->mysql = NULL;
204 
205 		s->mysql_pre = buffer_init();
206 		s->mysql_post = buffer_init();
207 
208 		cv[0].destination = s->mydb;
209 		cv[1].destination = s->myuser;
210 		cv[2].destination = s->mypass;
211 		cv[3].destination = s->mysock;
212 		cv[4].destination = sel;
213 		cv[5].destination = s->hostname;
214 		cv[6].destination = &(s->port);
215 
216 		p->config_storage[i] = s;
217 
218         	if (config_insert_values_global(srv,
219 			((data_config *)srv->config_context->data[i])->value,
220 			cv)) return HANDLER_ERROR;
221 
222 		s->mysql_pre = buffer_init();
223 		s->mysql_post = buffer_init();
224 
225 		if (sel->used && (qmark = strchr(sel->ptr, '?'))) {
226 			*qmark = '\0';
227 			buffer_copy_string(s->mysql_pre, sel->ptr);
228 			buffer_copy_string(s->mysql_post, qmark+1);
229 		} else {
230 			buffer_copy_string_buffer(s->mysql_pre, sel);
231 		}
232 
233 		/* required:
234 		 * - username
235 		 * - database
236 		 *
237 		 * optional:
238 		 * - password, default: empty
239 		 * - socket, default: mysql default
240 		 * - hostname, if set overrides socket
241 		 * - port, default: 3306
242 		 */
243 
244 		/* all have to be set */
245 		if (!(buffer_is_empty(s->myuser) ||
246 		      buffer_is_empty(s->mydb))) {
247 			my_bool reconnect = 1;
248 
249 			if (NULL == (s->mysql = mysql_init(NULL))) {
250 				log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting...");
251 
252 				return HANDLER_ERROR;
253 			}
254 
255 #if MYSQL_VERSION_ID >= 50013
256 			/* in mysql versions above 5.0.3 the reconnect flag is off by default */
257 			mysql_options(s->mysql, MYSQL_OPT_RECONNECT, &reconnect);
258 #endif
259 
260 #define FOO(x) (s->x->used ? s->x->ptr : NULL)
261 
262 #if MYSQL_VERSION_ID >= 40100
263                         /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */
264 			if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass),
265 						FOO(mydb), s->port, FOO(mysock), CLIENT_MULTI_STATEMENTS)) {
266 #else
267 			if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass),
268 						FOO(mydb), s->port, FOO(mysock), 0)) {
269 #endif
270 				log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql));
271 
272 				return HANDLER_ERROR;
273 			}
274 #undef FOO
275 
276 #if 0
277 			/* set close_on_exec for mysql the hard way */
278 			/* Note: this only works as it is done during startup, */
279 			/* otherwise we cannot be sure that mysql is fd i-1 */
280 			{ int fd;
281 			if (-1 != (fd = open("/dev/null", 0))) {
282 				close(fd);
283 #ifdef FD_CLOEXEC
284 				fcntl(fd-1, F_SETFD, FD_CLOEXEC);
285 #endif
286 			} }
287 #else
288 #ifdef FD_CLOEXEC
289 			fcntl(s->mysql->net.fd, F_SETFD, FD_CLOEXEC);
290 #endif
291 #endif
292 		}
293 	}
294 
295 	return HANDLER_GO_ON;
296 }
297 
298 #define PATCH(x) \
299 	p->conf.x = s->x;
300 static int mod_mysql_vhost_patch_connection(server *srv, connection *con, plugin_data *p) {
301 	size_t i, j;
302 	plugin_config *s = p->config_storage[0];
303 
304 	PATCH(mysql_pre);
305 	PATCH(mysql_post);
306 #ifdef HAVE_MYSQL
307 	PATCH(mysql);
308 #endif
309 
310 	/* skip the first, the global context */
311 	for (i = 1; i < srv->config_context->used; i++) {
312 		data_config *dc = (data_config *)srv->config_context->data[i];
313 		s = p->config_storage[i];
314 
315 		/* condition didn't match */
316 		if (!config_check_cond(srv, con, dc)) continue;
317 
318 		/* merge config */
319 		for (j = 0; j < dc->value->used; j++) {
320 			data_unset *du = dc->value->data[j];
321 
322 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("mysql-vhost.sql"))) {
323 				PATCH(mysql_pre);
324 				PATCH(mysql_post);
325 			}
326 		}
327 
328 		if (s->mysql) {
329 			PATCH(mysql);
330 		}
331 	}
332 
333 	return 0;
334 }
335 #undef PATCH
336 
337 
338 /* handle document root request */
339 CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
340 	plugin_data *p = p_d;
341 	plugin_connection_data *c;
342 	stat_cache_entry *sce;
343 
344 	unsigned  cols;
345 	MYSQL_ROW row;
346 	MYSQL_RES *result = NULL;
347 
348 	/* no host specified? */
349 	if (!con->uri.authority->used) return HANDLER_GO_ON;
350 
351 	mod_mysql_vhost_patch_connection(srv, con, p);
352 
353 	if (!p->conf.mysql) return HANDLER_GO_ON;
354 
355 	/* sets up connection data if not done yet */
356 	c = mod_mysql_vhost_connection_data(srv, con, p_d);
357 
358 	/* check if cached this connection */
359 	if (c->server_name->used && /* con->uri.authority->used && */
360             buffer_is_equal(c->server_name, con->uri.authority)) goto GO_ON;
361 
362 	/* build and run SQL query */
363 	buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre);
364 	if (p->conf.mysql_post->used) {
365 		buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
366 		buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
367 	}
368    	if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) {
369 		log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql));
370 		goto ERR500;
371 	}
372 	result = mysql_store_result(p->conf.mysql);
373 	cols = mysql_num_fields(result);
374 	row = mysql_fetch_row(result);
375 	if (!row || cols < 1) {
376 		/* no such virtual host */
377 		mysql_free_result(result);
378 #if MYSQL_VERSION_ID >= 40100
379 		while (mysql_next_result(p->conf.mysql) == 0);
380 #endif
381 		return HANDLER_GO_ON;
382 	}
383 
384 	/* sanity check that really is a directory */
385 	buffer_copy_string(p->tmp_buf, row[0]);
386 	BUFFER_APPEND_SLASH(p->tmp_buf);
387 
388 	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) {
389 		log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
390 		goto ERR500;
391 	}
392         if (!S_ISDIR(sce->st.st_mode)) {
393 		log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", p->tmp_buf);
394 		goto ERR500;
395 	}
396 
397 	/* cache the data */
398 	buffer_copy_string_buffer(c->server_name, con->uri.authority);
399 	buffer_copy_string_buffer(c->document_root, p->tmp_buf);
400 
401 	/* fcgi_offset and fcgi_arg are optional */
402 	if (cols > 1 && row[1]) {
403 		c->fcgi_offset = atoi(row[1]);
404 
405 		if (cols > 2 && row[2]) {
406 			buffer_copy_string(c->fcgi_arg, row[2]);
407 		} else {
408 			c->fcgi_arg->used = 0;
409 		}
410 	} else {
411 		c->fcgi_offset = c->fcgi_arg->used = 0;
412 	}
413 	mysql_free_result(result);
414 #if MYSQL_VERSION_ID >= 40100
415 	while (mysql_next_result(p->conf.mysql) == 0);
416 #endif
417 
418 	/* fix virtual server and docroot */
419 GO_ON:	buffer_copy_string_buffer(con->server_name, c->server_name);
420 	buffer_copy_string_buffer(con->physical.doc_root, c->document_root);
421 
422 #ifdef DEBUG
423 	log_error_write(srv, __FILE__, __LINE__, "sbbdb",
424 		result ? "NOT CACHED" : "cached",
425 		con->server_name, con->physical.doc_root,
426 		c->fcgi_offset, c->fcgi_arg);
427 #endif
428 	return HANDLER_GO_ON;
429 
430 ERR500:	if (result) mysql_free_result(result);
431 #if MYSQL_VERSION_ID >= 40100
432 	while (mysql_next_result(p->conf.mysql) == 0);
433 #endif
434 	con->http_status = 500; /* Internal Error */
435 	con->mode = DIRECT;
436 	return HANDLER_FINISHED;
437 }
438 
439 /* this function is called at dlopen() time and inits the callbacks */
440 int mod_mysql_vhost_plugin_init(plugin *p);
441 int mod_mysql_vhost_plugin_init(plugin *p) {
442 	p->version        = LIGHTTPD_VERSION_ID;
443 	p->name           = buffer_init_string("mysql_vhost");
444 
445 	p->init           = mod_mysql_vhost_init;
446 	p->cleanup        = mod_mysql_vhost_cleanup;
447 	p->connection_reset = mod_mysql_vhost_handle_connection_close;
448 
449 	p->set_defaults   = mod_mysql_vhost_set_defaults;
450 	p->handle_docroot = mod_mysql_vhost_handle_docroot;
451 
452 	return 0;
453 }
454 #else
455 /* we don't have mysql support, this plugin does nothing */
456 int mod_mysql_vhost_plugin_init(plugin *p);
457 int mod_mysql_vhost_plugin_init(plugin *p) {
458 	p->version     = LIGHTTPD_VERSION_ID;
459 	p->name        = buffer_init_string("mysql_vhost");
460 
461 	return 0;
462 }
463 #endif
464