Project

General

Profile

Feature #2163 » mod_mysql_vhost.c

tronox@hotmail.com, 2013-09-09 05:10

 
1
#include <unistd.h>
2
#include <stdio.h>
3
// end added by Shinrai
4
#include <string.h>
5
#include <stdlib.h>
6
#include "response.h"
7
// end added by Shinrai
8
#include <errno.h>
9
#include <fcntl.h>
10
#include <strings.h>
11

    
12
#ifdef HAVE_CONFIG_H
13
#include "config.h"
14
#endif
15

    
16
#ifdef HAVE_MYSQL
17
#include <mysql.h>
18
#endif
19

    
20
#include "plugin.h"
21
#include "log.h"
22

    
23
#include "stat_cache.h"
24
#ifdef DEBUG_MOD_MYSQL_VHOST
25
#define DEBUG
26
#endif
27

    
28
// added by Shinrai
29
char *
30
str_replace ( const char *string, const char *substr, const char *replacement ){
31
  char *tok = NULL;
32
  char *newstr = NULL;
33
  char *oldstr = NULL;
34
  char *head = NULL;
35
 
36
  /* if either substr or replacement is NULL, duplicate string a let caller handle it */
37
  if ( substr == NULL || replacement == NULL ) return strdup (string);
38
  newstr = strdup (string);
39
  head = newstr;
40
  while ( (tok = strstr ( head, substr ))){
41
    oldstr = newstr;
42
    newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 );
43
    /*failed to alloc mem, free old string and return NULL */
44
    if ( newstr == NULL ){
45
      free (oldstr);
46
      return NULL;
47
    }
48
    memcpy ( newstr, oldstr, tok - oldstr );
49
    memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) );
50
    memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) );
51
    memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 );
52
    /* move back head right after the last replacement */
53
    head = newstr + (tok - oldstr) + strlen( replacement );
54
    free (oldstr);
55
  }
56
  return newstr;
57
}
58
// end added by Shinrai
59
	
60
/*
61
 * Plugin for lighttpd to use MySQL
62
 *   for domain to directory lookups,
63
 *   i.e virtual hosts (vhosts).
64
 *
65
 * Optionally sets fcgi_offset and fcgi_arg
66
 *   in preparation for fcgi.c to handle
67
 *   per-user fcgi chroot jails.
68
 *
69
 * /ada@riksnet.se 2004-12-06
70
 */
71

    
72
#ifdef HAVE_MYSQL
73
typedef struct {
74
	MYSQL 	*mysql;
75

    
76
	buffer  *mydb;
77
	buffer  *myuser;
78
	buffer  *mypass;
79
	buffer  *mysock;
80

    
81
	buffer  *hostname;
82
	unsigned short port;
83

    
84
	buffer  *mysql_pre;
85
	buffer  *mysql_post;
86
} plugin_config;
87

    
88
/* global plugin data */
89
typedef struct {
90
	PLUGIN_DATA;
91

    
92
	buffer 	*tmp_buf;
93

    
94
	plugin_config **config_storage;
95

    
96
	plugin_config conf;
97
} plugin_data;
98

    
99
/* per connection plugin data */
100
typedef struct {
101
	buffer	*server_name;
102
	buffer	*document_root;
103
	buffer	*fcgi_arg;
104
	unsigned fcgi_offset;
105
} plugin_connection_data;
106

    
107
/* init the plugin data */
108
INIT_FUNC(mod_mysql_vhost_init) {
109
	plugin_data *p;
110

    
111
	p = calloc(1, sizeof(*p));
112

    
113
	p->tmp_buf = buffer_init();
114

    
115
	return p;
116
}
117

    
118
/* cleanup the plugin data */
119
SERVER_FUNC(mod_mysql_vhost_cleanup) {
120
	plugin_data *p = p_d;
121

    
122
	UNUSED(srv);
123

    
124
#ifdef DEBUG
125
	log_error_write(srv, __FILE__, __LINE__, "ss",
126
		"mod_mysql_vhost_cleanup", p ? "yes" : "NO");
127
#endif
128
	if (!p) return HANDLER_GO_ON;
129

    
130
	if (p->config_storage) {
131
		size_t i;
132
		for (i = 0; i < srv->config_context->used; i++) {
133
			plugin_config *s = p->config_storage[i];
134

    
135
			if (!s) continue;
136

    
137
			mysql_close(s->mysql);
138

    
139
			buffer_free(s->mydb);
140
			buffer_free(s->myuser);
141
			buffer_free(s->mypass);
142
			buffer_free(s->mysock);
143
			buffer_free(s->mysql_pre);
144
			buffer_free(s->mysql_post);
145
			buffer_free(s->hostname);
146

    
147
			free(s);
148
		}
149
		free(p->config_storage);
150
	}
151
	buffer_free(p->tmp_buf);
152

    
153
	free(p);
154

    
155
	return HANDLER_GO_ON;
156
}
157

    
158
/* handle the plugin per connection data */
159
static void* mod_mysql_vhost_connection_data(server *srv, connection *con, void *p_d)
160
{
161
	plugin_data *p = p_d;
162
	plugin_connection_data *c = con->plugin_ctx[p->id];
163

    
164
	UNUSED(srv);
165

    
166
#ifdef DEBUG
167
        log_error_write(srv, __FILE__, __LINE__, "ss",
168
		"mod_mysql_connection_data", c ? "old" : "NEW");
169
#endif
170

    
171
	if (c) return c;
172
	c = calloc(1, sizeof(*c));
173

    
174
	c->server_name = buffer_init();
175
	c->document_root = buffer_init();
176
	c->fcgi_arg = buffer_init();
177
	c->fcgi_offset = 0;
178

    
179
	return con->plugin_ctx[p->id] = c;
180
}
181

    
182
/* destroy the plugin per connection data */
183
CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close) {
184
	plugin_data *p = p_d;
185
	plugin_connection_data *c = con->plugin_ctx[p->id];
186

    
187
	UNUSED(srv);
188

    
189
#ifdef DEBUG
190
	log_error_write(srv, __FILE__, __LINE__, "ss",
191
		"mod_mysql_vhost_handle_connection_close", c ? "yes" : "NO");
192
#endif
193

    
194
	if (!c) return HANDLER_GO_ON;
195

    
196
	buffer_free(c->server_name);
197
	buffer_free(c->document_root);
198
	buffer_free(c->fcgi_arg);
199
	c->fcgi_offset = 0;
200

    
201
	free(c);
202

    
203
	con->plugin_ctx[p->id] = NULL;
204
	return HANDLER_GO_ON;
205
}
206

    
207
/* set configuration values */
208
SERVER_FUNC(mod_mysql_vhost_set_defaults) {
209
	plugin_data *p = p_d;
210

    
211
	// char *qmark;
212
	size_t i = 0;
213

    
214
	config_values_t cv[] = {
215
		{ "mysql-vhost.db",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
216
		{ "mysql-vhost.user",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
217
		{ "mysql-vhost.pass",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
218
		{ "mysql-vhost.sock",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
219
		{ "mysql-vhost.sql",	NULL, T_CONFIG_STRING, 	T_CONFIG_SCOPE_SERVER },
220
		{ "mysql-vhost.hostname", NULL, T_CONFIG_STRING,T_CONFIG_SCOPE_SERVER },
221
		{ "mysql-vhost.port",   NULL, T_CONFIG_SHORT,   T_CONFIG_SCOPE_SERVER },
222
                { NULL,			NULL, T_CONFIG_UNSET,	T_CONFIG_SCOPE_UNSET }
223
        };
224

    
225
	p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
226

    
227
	for (i = 0; i < srv->config_context->used; i++) {
228
		plugin_config *s;
229
		buffer *sel;
230

    
231

    
232
		s = calloc(1, sizeof(plugin_config));
233
		s->mydb = buffer_init();
234
		s->myuser = buffer_init();
235
		s->mypass = buffer_init();
236
		s->mysock = buffer_init();
237
		s->hostname = buffer_init();
238
		s->port   = 0;               /* default port for mysql */
239
		sel = buffer_init();
240
		s->mysql = NULL;
241

    
242
		s->mysql_pre = buffer_init();
243
		s->mysql_post = buffer_init();
244

    
245
		cv[0].destination = s->mydb;
246
		cv[1].destination = s->myuser;
247
		cv[2].destination = s->mypass;
248
		cv[3].destination = s->mysock;
249
		cv[4].destination = sel;
250
		cv[5].destination = s->hostname;
251
		cv[6].destination = &(s->port);
252

    
253
		p->config_storage[i] = s;
254

    
255
        	if (config_insert_values_global(srv,
256
			((data_config *)srv->config_context->data[i])->value,
257
			cv)) return HANDLER_ERROR;
258

    
259
		s->mysql_pre = buffer_init();
260
		s->mysql_post = buffer_init();
261

    
262
		// Old code from Lighttpd developers
263
		// if (sel->used && (qmark = strchr(sel->ptr, '?'))) {
264
			// *qmark = '\0';
265
			// buffer_copy_string(s->mysql_pre, sel->ptr);
266
			// buffer_copy_string(s->mysql_post, qmark+1);
267
		// } else {
268
			// buffer_copy_string_buffer(s->mysql_pre, sel);
269
		// }
270
		// added by Shinrai
271
		buffer_copy_string_buffer(s->mysql_pre, sel);
272
		// end added by Shinrai
273
		
274
		/* required:
275
		 * - username
276
		 * - database
277
		 *
278
		 * optional:
279
		 * - password, default: empty
280
		 * - socket, default: mysql default
281
		 * - hostname, if set overrides socket
282
		 * - port, default: 3306
283
		 */
284

    
285
		/* all have to be set */
286
		if (!(buffer_is_empty(s->myuser) ||
287
		      buffer_is_empty(s->mydb))) {
288
			my_bool reconnect = 1;
289

    
290
			if (NULL == (s->mysql = mysql_init(NULL))) {
291
				log_error_write(srv, __FILE__, __LINE__, "s", "mysql_init() failed, exiting...");
292

    
293
				return HANDLER_ERROR;
294
			}
295

    
296
#if MYSQL_VERSION_ID >= 50013
297
			/* in mysql versions above 5.0.3 the reconnect flag is off by default */
298
			mysql_options(s->mysql, MYSQL_OPT_RECONNECT, &reconnect);
299
#endif
300

    
301
#define FOO(x) (s->x->used ? s->x->ptr : NULL)
302

    
303
#if MYSQL_VERSION_ID >= 40100
304
                        /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */ 
305
			if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass),
306
						FOO(mydb), s->port, FOO(mysock), CLIENT_MULTI_STATEMENTS)) {
307
#else
308
			if (!mysql_real_connect(s->mysql, FOO(hostname), FOO(myuser), FOO(mypass),
309
						FOO(mydb), s->port, FOO(mysock), 0)) {
310
#endif
311
				log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(s->mysql));
312

    
313
				return HANDLER_ERROR;
314
			}
315
#undef FOO
316

    
317
#if 0
318
			/* set close_on_exec for mysql the hard way */
319
			/* Note: this only works as it is done during startup, */
320
			/* otherwise we cannot be sure that mysql is fd i-1 */
321
			{ int fd;
322
			if (-1 != (fd = open("/dev/null", 0))) {
323
				close(fd);
324
#ifdef FD_CLOEXEC
325
				fcntl(fd-1, F_SETFD, FD_CLOEXEC);
326
#endif
327
			} }
328
#else
329
#ifdef FD_CLOEXEC
330
			fcntl(s->mysql->net.fd, F_SETFD, FD_CLOEXEC);
331
#endif
332
#endif
333
		}
334
	}
335

    
336
	return HANDLER_GO_ON;
337
}
338

    
339
#define PATCH(x) \
340
	p->conf.x = s->x;
341
static int mod_mysql_vhost_patch_connection(server *srv, connection *con, plugin_data *p) {
342
	size_t i, j;
343
	plugin_config *s = p->config_storage[0];
344

    
345
	PATCH(mysql_pre);
346
	PATCH(mysql_post);
347
#ifdef HAVE_MYSQL
348
	PATCH(mysql);
349
#endif
350

    
351
	/* skip the first, the global context */
352
	for (i = 1; i < srv->config_context->used; i++) {
353
		data_config *dc = (data_config *)srv->config_context->data[i];
354
		s = p->config_storage[i];
355

    
356
		/* condition didn't match */
357
		if (!config_check_cond(srv, con, dc)) continue;
358

    
359
		/* merge config */
360
		for (j = 0; j < dc->value->used; j++) {
361
			data_unset *du = dc->value->data[j];
362

    
363
			if (buffer_is_equal_string(du->key, CONST_STR_LEN("mysql-vhost.sql"))) {
364
				PATCH(mysql_pre);
365
				PATCH(mysql_post);
366
			}
367
		}
368

    
369
		if (s->mysql) {
370
			PATCH(mysql);
371
		}
372
	}
373

    
374
	return 0;
375
}
376
#undef PATCH
377

    
378

    
379
/* handle document root request */
380
CONNECTION_FUNC(mod_mysql_vhost_handle_docroot) {
381
	plugin_data *p = p_d;
382
	plugin_connection_data *c;
383
	stat_cache_entry *sce;
384

    
385
	char *rel_url;
386
	buffer *uri;
387
	buffer *username;
388
	buffer *temp_path;
389
	unsigned  cols;
390
	MYSQL_ROW row;
391
	MYSQL_RES *result = NULL;
392

    
393
	/* no host specified? */
394
	if (!con->uri.authority->used) return HANDLER_GO_ON;
395

    
396
	mod_mysql_vhost_patch_connection(srv, con, p);
397

    
398
	if (!p->conf.mysql) return HANDLER_GO_ON;
399

    
400
	/* sets up connection data if not done yet */
401
	c = mod_mysql_vhost_connection_data(srv, con, p_d);
402

    
403
	/* check if cached this connection */
404
	if (c->server_name->used && /* con->uri.authority->used && */
405
            buffer_is_equal(c->server_name, con->uri.authority)) goto GO_ON;
406

    
407
	/* build and run SQL query */
408
	/* Old Lttpd Developed Code
409
	buffer_copy_string_buffer(p->tmp_buf, p->conf.mysql_pre);
410
	if (p->conf.mysql_post->used) {
411
		buffer_append_string_buffer(p->tmp_buf, con->uri.authority);
412
		buffer_append_string_buffer(p->tmp_buf, p->conf.mysql_post);
413
	}
414
	*/
415
	// added by Shinrai
416
	// char *basestring = "";
417
// #ifdef HAVE_MYSQL_REAL_ESCAPE_STRING
418
	// char *sqlquery = mysql_real_escape_string(p->conf.mysql, basestring, con->uri.authority->ptr, strlen(con->uri.authority->ptr));
419
// #else
420
	// char *sqlquery = mysql_escape_string(basestring, con->uri.authority->ptr, strlen(con->uri.authority->ptr));
421
// #endif
422
	buffer_copy_string(p->tmp_buf, str_replace(p->conf.mysql_pre->ptr,"?",con->uri.authority->ptr));
423
	
424
	if (con->uri.path->ptr[0] == '/' && con->uri.path->ptr[1] == '~' && con->uri.path->ptr[2] != '/'){
425
		username = buffer_init();
426
		uri = buffer_init();
427
		if (NULL == (rel_url = strchr(con->uri.path->ptr + 2, '/'))) {
428
			/* / is missing -> redirect to .../ as we are a user - DIRECTORY ! :) */
429
			http_response_redirect_to_directory(srv, con);
430

    
431
			return HANDLER_FINISHED;
432
		}
433

    
434
		buffer_copy_string(uri, "/~@@");
435
		buffer_copy_string_len(username, con->uri.path->ptr + 2, rel_url - (con->uri.path->ptr + 2));
436
		buffer_copy_string(uri, str_replace(uri->ptr,"@@",username->ptr));
437
		
438
		// Clean up the connection parameters so stuff is found correctly.
439
		buffer_copy_string(con->uri.path, str_replace(con->uri.path->ptr,uri->ptr,""));
440
		buffer_copy_string(con->physical.rel_path, str_replace(con->physical.rel_path->ptr,uri->ptr,""));
441
		
442
		// Modify the SQL to replace @@ with the username found.
443
		buffer_copy_string(p->tmp_buf, str_replace(p->tmp_buf->ptr,"@@",username->ptr));
444
	}
445
	// end added by Shinrai
446
	
447
   	if (mysql_query(p->conf.mysql, p->tmp_buf->ptr)) {
448
		log_error_write(srv, __FILE__, __LINE__, "s", mysql_error(p->conf.mysql));
449
		goto ERR500;
450
	}
451
	result = mysql_store_result(p->conf.mysql);
452
	cols = mysql_num_fields(result);
453
	row = mysql_fetch_row(result);
454
	// Check for null value, seems to crash.
455
	// if ((!row || row[0] == NULL) || cols < 1) {
456
	if (!row || cols < 1) {
457
		/* no such virtual host */
458
		mysql_free_result(result);
459
#if MYSQL_VERSION_ID >= 40100
460
		while (mysql_next_result(p->conf.mysql) == 0);
461
#endif
462
		return HANDLER_GO_ON;
463
	}
464

    
465
	/* sanity check that really is a directory */
466
	buffer_copy_string(p->tmp_buf, row[0]);
467
	BUFFER_APPEND_SLASH(p->tmp_buf);
468

    
469
	if (HANDLER_ERROR == stat_cache_get_entry(srv, con, p->tmp_buf, &sce)) {
470
		log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), p->tmp_buf);
471
		goto ERR500;
472
	}
473
	if (!S_ISDIR(sce->st.st_mode)) {
474
		log_error_write(srv, __FILE__, __LINE__, "sb", "Not a directory", p->tmp_buf);
475
		goto ERR500;
476
	}
477

    
478
	/* cache the data */
479
	buffer_copy_string_buffer(c->server_name, con->uri.authority);
480
	buffer_copy_string_buffer(c->document_root, p->tmp_buf);
481

    
482
	/* fcgi_offset and fcgi_arg are optional */
483
	if (cols > 1 && row[1]) {
484
		c->fcgi_offset = atoi(row[1]);
485

    
486
		if (cols > 2 && row[2]) {
487
			buffer_copy_string(c->fcgi_arg, row[2]);
488
		} else {
489
			c->fcgi_arg->used = 0;
490
		}
491
	} else {
492
		c->fcgi_offset = c->fcgi_arg->used = 0;
493
	}
494
	mysql_free_result(result);
495
#if MYSQL_VERSION_ID >= 40100
496
	while (mysql_next_result(p->conf.mysql) == 0);
497
#endif
498

    
499
	/* fix virtual server and docroot */
500
GO_ON:	buffer_copy_string_buffer(con->server_name, c->server_name);
501
	buffer_copy_string_buffer(con->physical.doc_root, c->document_root);
502
	// added by Shinrai
503
	if (uri->used) {
504
		temp_path = buffer_init();
505
		buffer_copy_string_buffer(temp_path, c->document_root);
506
		if (NULL != (rel_url = strchr(con->physical.rel_path->ptr + 2, '/'))) {
507
			buffer_append_string(temp_path, rel_url + 1); /* skip the / */
508
		}
509
		// Clean up the physical path. We needed the database lookup to find the document_root before we could do this.
510
		buffer_copy_string_buffer(con->physical.path, temp_path);
511
	}
512
	// end added by Shinrai
513

    
514
#ifdef DEBUG
515
	log_error_write(srv, __FILE__, __LINE__, "sbbdb",
516
		result ? "NOT CACHED" : "cached",
517
		con->server_name, con->physical.doc_root,
518
		c->fcgi_offset, c->fcgi_arg);
519
#endif
520
	return HANDLER_GO_ON;
521

    
522
ERR500:	if (result) mysql_free_result(result);
523
#if MYSQL_VERSION_ID >= 40100
524
	while (mysql_next_result(p->conf.mysql) == 0);
525
#endif
526
	con->http_status = 500; /* Internal Error */
527
	con->mode = DIRECT;
528
	return HANDLER_FINISHED;
529
}
530

    
531
/* this function is called at dlopen() time and inits the callbacks */
532
int mod_mysql_vhost_plugin_init(plugin *p);
533
int mod_mysql_vhost_plugin_init(plugin *p) {
534
	p->version        = LIGHTTPD_VERSION_ID;
535
	p->name           = buffer_init_string("mysql_vhost");
536

    
537
	p->init           = mod_mysql_vhost_init;
538
	p->cleanup        = mod_mysql_vhost_cleanup;
539
	p->connection_reset = mod_mysql_vhost_handle_connection_close;
540

    
541
	p->set_defaults   = mod_mysql_vhost_set_defaults;
542
	p->handle_docroot = mod_mysql_vhost_handle_docroot;
543

    
544
	return 0;
545
}
546
#else
547
/* we don't have mysql support, this plugin does nothing */
548
int mod_mysql_vhost_plugin_init(plugin *p);
549
int mod_mysql_vhost_plugin_init(plugin *p) {
550
	p->version     = LIGHTTPD_VERSION_ID;
551
	p->name        = buffer_init_string("mysql_vhost");
552

    
553
	return 0;
554
}
555
#endif
(4-4/4)