Project

General

Profile

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