lighttpd1.4/src/mod_mysql_vhost.c

378 lines
11 KiB
C
Raw Normal View History

#include "first.h"
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <mysql.h>
#include "base.h"
#include "plugin.h"
#include "fdevent.h"
#include "log.h"
#include "stat_cache.h"
/*
* Plugin for lighttpd to use MySQL
* for domain to directory lookups,
* i.e virtual hosts (vhosts).
*
* /ada@riksnet.se 2004-12-06
*/
typedef struct {
MYSQL *mysql;
const buffer *mysql_query;
} plugin_config;
typedef struct {
PLUGIN_DATA;
plugin_config defaults;
plugin_config conf;
buffer tmp_buf;
} plugin_data;
typedef struct {
buffer *server_name;
buffer *document_root;
} plugin_connection_data;
INIT_FUNC(mod_mysql_vhost_init) {
return calloc(1, sizeof(plugin_data));
}
/* cleanup the mysql connections */
FREE_FUNC(mod_mysql_vhost_cleanup) {
plugin_data * const p = p_d;
free(p->tmp_buf.ptr);
if (NULL == p->cvlist) return;
/* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
switch (cpv->k_id) {
case 1: /* mysql-vhost.db */
mysql_close(cpv->v.v);
break;
default:
break;
}
}
}
}
static void* mod_mysql_vhost_connection_data(request_st * const r, void *p_d)
{
plugin_data *p = p_d;
plugin_connection_data *c = r->plugin_ctx[p->id];
if (c) return c;
c = calloc(1, sizeof(*c));
c->server_name = buffer_init();
c->document_root = buffer_init();
return r->plugin_ctx[p->id] = c;
}
REQUEST_FUNC(mod_mysql_vhost_handle_request_reset) {
plugin_data *p = p_d;
plugin_connection_data *c = r->plugin_ctx[p->id];
if (!c) return HANDLER_GO_ON;
buffer_free(c->server_name);
buffer_free(c->document_root);
free(c);
r->plugin_ctx[p->id] = NULL;
return HANDLER_GO_ON;
}
static void mod_mysql_vhost_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
case 0: /* mysql-vhost.sql */
pconf->mysql_query = cpv->v.b;
break;
case 1: /* mysql-vhost.db */
if (cpv->vtype == T_CONFIG_LOCAL)
pconf->mysql = cpv->v.v;
break;
case 2: /* mysql-vhost.user */
case 3: /* mysql-vhost.pass */
case 4: /* mysql-vhost.sock */
case 5: /* mysql-vhost.hostname */
case 6: /* mysql-vhost.port */
break;
default:/* should not happen */
return;
}
}
static void mod_mysql_vhost_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
do {
mod_mysql_vhost_merge_config_cpv(pconf, cpv);
} while ((++cpv)->k_id != -1);
}
static void mod_mysql_vhost_patch_config(request_st * const r, plugin_data * const p) {
p->conf = p->defaults; /* copy small struct instead of memcpy() */
/*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
for (int i = 1, used = p->nconfig; i < used; ++i) {
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
mod_mysql_vhost_merge_config(&p->conf,
p->cvlist + p->cvlist[i].v.u2[0]);
}
}
static MYSQL * mod_mysql_vhost_db_setup (server *srv, const char *dbname, const char *user, const char *pass, const char *sock, const char *host, unsigned short port) {
/* required:
* - database
* - username
*
* optional:
* - password, default: empty
* - socket, default: mysql default
* - hostname, if set overrides socket
* - port, default: 3306
*/
MYSQL * const my = mysql_init(NULL);
if (NULL == my) {
log_error(srv->errh, __FILE__, __LINE__,
"mysql_init() failed, exiting...");
return NULL;
}
#if MYSQL_VERSION_ID >= 50013
/* in mysql versions above 5.0.3 the reconnect flag is off by default */
char reconnect = 1;
mysql_options(my, MYSQL_OPT_RECONNECT, &reconnect);
#endif
unsigned long flags = 0;
#if MYSQL_VERSION_ID >= 40100
/* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */
flags |= CLIENT_MULTI_STATEMENTS;
#endif
if (!mysql_real_connect(my, host, user, pass, dbname, port, sock, flags)) {
log_error(srv->errh, __FILE__, __LINE__, "%s", mysql_error(my));
mysql_close(my);
return NULL;
}
fdevent_setfd_cloexec(my->net.fd);
return my;
}
SETDEFAULTS_FUNC(mod_mysql_vhost_set_defaults) {
static const config_plugin_keys_t cpk[] = {
{ CONST_STR_LEN("mysql-vhost.sql"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("mysql-vhost.db"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("mysql-vhost.user"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("mysql-vhost.pass"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("mysql-vhost.sock"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("mysql-vhost.hostname"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("mysql-vhost.port"),
T_CONFIG_SHORT,
T_CONFIG_SCOPE_CONNECTION }
,{ NULL, 0,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }
};
log_error(srv->errh, __FILE__, __LINE__,
"mod_mysql_vhost is deprecated and will be removed in a future version; "
"please migrate to use mod_vhostdb_mysql");
plugin_data * const p = p_d;
if (!config_plugin_values_init(srv, p, cpk, "mod_mysql_vhost"))
return HANDLER_ERROR;
/* process and validate config directives
* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *sock=NULL;
unsigned short port = 0;
config_plugin_value_t *db = NULL;
for (; -1 != cpv->k_id; ++cpv) {
switch (cpv->k_id) {
case 0: /* mysql_vhost.sql */
break;
case 1: /* mysql_vhost.db */
if (!buffer_string_is_empty(cpv->v.b)) {
db = cpv;
dbname = cpv->v.b->ptr;
}
break;
case 2: /* mysql_vhost.user */
if (!buffer_string_is_empty(cpv->v.b))
user = cpv->v.b->ptr;
break;
case 3: /* mysql_vhost.pass */
if (!buffer_string_is_empty(cpv->v.b))
pass = cpv->v.b->ptr;
break;
case 4: /* mysql_vhost.sock */
if (!buffer_string_is_empty(cpv->v.b))
sock = cpv->v.b->ptr;
break;
case 5: /* mysql_vhost.hostname */
if (!buffer_string_is_empty(cpv->v.b))
host = cpv->v.b->ptr;
break;
case 6: /* mysql_vhost.port */
port = cpv->v.shrt;
break;
default:/* should not happen */
break;
}
}
if (dbname && user) {
cpv = db;
cpv->v.v =
mod_mysql_vhost_db_setup(srv,dbname,user,pass,sock,host,port);
if (NULL == db->v.v) return HANDLER_ERROR;
cpv->vtype = T_CONFIG_LOCAL;
}
}
/* initialize p->defaults from global config context */
if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
if (-1 != cpv->k_id)
mod_mysql_vhost_merge_config(&p->defaults, cpv);
}
return HANDLER_GO_ON;
}
REQUEST_FUNC(mod_mysql_vhost_handle_docroot) {
plugin_data *p = p_d;
plugin_connection_data *c;
unsigned cols;
MYSQL_ROW row;
MYSQL_RES *result = NULL;
/* no host specified? */
if (buffer_string_is_empty(&r->uri.authority)) return HANDLER_GO_ON;
mod_mysql_vhost_patch_config(r, p);
if (!p->conf.mysql) return HANDLER_GO_ON;
if (buffer_string_is_empty(p->conf.mysql_query)) return HANDLER_GO_ON;
/* sets up connection data if not done yet */
c = mod_mysql_vhost_connection_data(r, p_d);
/* check if cached this connection */
if (buffer_is_equal(c->server_name, &r->uri.authority)) goto GO_ON;
/* build and run SQL query */
buffer * const b = &p->tmp_buf;
buffer_clear(b);
for (const char *ptr = p->conf.mysql_query->ptr, *d; *ptr; ptr = d+1) {
if (NULL != (d = strchr(ptr, '?'))) {
/* escape the uri.authority */
unsigned long to_len;
buffer_append_string_len(b, ptr, (size_t)(d - ptr));
buffer_string_prepare_append(b, buffer_string_length(&r->uri.authority) * 2);
to_len = mysql_real_escape_string(p->conf.mysql,
b->ptr + buffer_string_length(b),
CONST_BUF_LEN(&r->uri.authority));
if ((unsigned long)~0 == to_len) goto ERR500;
buffer_commit(b, to_len);
} else {
d = p->conf.mysql_query->ptr + buffer_string_length(p->conf.mysql_query);
buffer_append_string_len(b, ptr, (size_t)(d - ptr));
break;
}
}
if (mysql_real_query(p->conf.mysql, CONST_BUF_LEN(b))) {
log_error(r->conf.errh, __FILE__, __LINE__, "%s", mysql_error(p->conf.mysql));
goto ERR500;
}
result = mysql_store_result(p->conf.mysql);
cols = mysql_num_fields(result);
row = mysql_fetch_row(result);
if (!row || cols < 1) {
/* no such virtual host */
mysql_free_result(result);
#if MYSQL_VERSION_ID >= 40100
while (mysql_next_result(p->conf.mysql) == 0);
#endif
return HANDLER_GO_ON;
}
/* sanity check that really is a directory */
buffer_copy_string(b, row[0]);
buffer_append_slash(b);
2020-10-09 17:33:54 +00:00
if (!stat_cache_path_isdir(b)) {
log_perror(r->conf.errh, __FILE__, __LINE__, "%s", b->ptr);
goto ERR500;
}
/* cache the data */
buffer_copy_buffer(c->server_name, &r->uri.authority);
buffer_copy_buffer(c->document_root, b);
mysql_free_result(result);
#if MYSQL_VERSION_ID >= 40100
while (mysql_next_result(p->conf.mysql) == 0);
#endif
/* fix virtual server and docroot */
GO_ON:
r->server_name = &r->server_name_buf;
buffer_copy_buffer(&r->server_name_buf, c->server_name);
buffer_copy_buffer(&r->physical.doc_root, c->document_root);
return HANDLER_GO_ON;
ERR500:
if (result) mysql_free_result(result);
#if MYSQL_VERSION_ID >= 40100
while (mysql_next_result(p->conf.mysql) == 0);
#endif
r->http_status = 500; /* Internal Error */
r->handler_module = NULL;
return HANDLER_FINISHED;
}
int mod_mysql_vhost_plugin_init(plugin *p);
int mod_mysql_vhost_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = "mysql_vhost";
p->init = mod_mysql_vhost_init;
p->cleanup = mod_mysql_vhost_cleanup;
p->handle_request_reset = mod_mysql_vhost_handle_request_reset;
p->set_defaults = mod_mysql_vhost_set_defaults;
p->handle_docroot = mod_mysql_vhost_handle_docroot;
return 0;
}