16 #include "stat_cache.h"
17 #ifdef DEBUG_MOD_MYSQL_VHOST
22 * Plugin for lighttpd to use MySQL
23 * for domain to directory lookups,
24 * i.e virtual hosts (vhosts).
26 * /ada@riksnet.se 2004-12-06
45 /* global plugin data */
51 plugin_config
**config_storage
;
56 /* per connection plugin data */
59 buffer
*document_root
;
60 } plugin_connection_data
;
62 /* init the plugin data */
63 INIT_FUNC(mod_mysql_vhost_init
) {
66 p
= calloc(1, sizeof(*p
));
68 p
->tmp_buf
= buffer_init();
73 /* cleanup the plugin data */
74 SERVER_FUNC(mod_mysql_vhost_cleanup
) {
80 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
81 "mod_mysql_vhost_cleanup", p
? "yes" : "NO");
83 if (!p
) return HANDLER_GO_ON
;
85 if (p
->config_storage
) {
87 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
88 plugin_config
*s
= p
->config_storage
[i
];
92 mysql_close(s
->mysql
);
95 buffer_free(s
->myuser
);
96 buffer_free(s
->mypass
);
97 buffer_free(s
->mysock
);
98 buffer_free(s
->mysql_pre
);
99 buffer_free(s
->mysql_post
);
100 buffer_free(s
->hostname
);
104 free(p
->config_storage
);
106 buffer_free(p
->tmp_buf
);
110 return HANDLER_GO_ON
;
113 /* handle the plugin per connection data */
114 static void* mod_mysql_vhost_connection_data(server
*srv
, connection
*con
, void *p_d
)
116 plugin_data
*p
= p_d
;
117 plugin_connection_data
*c
= con
->plugin_ctx
[p
->id
];
122 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
123 "mod_mysql_connection_data", c
? "old" : "NEW");
127 c
= calloc(1, sizeof(*c
));
129 c
->server_name
= buffer_init();
130 c
->document_root
= buffer_init();
132 return con
->plugin_ctx
[p
->id
] = c
;
135 /* destroy the plugin per connection data */
136 CONNECTION_FUNC(mod_mysql_vhost_handle_connection_close
) {
137 plugin_data
*p
= p_d
;
138 plugin_connection_data
*c
= con
->plugin_ctx
[p
->id
];
143 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
144 "mod_mysql_vhost_handle_connection_close", c
? "yes" : "NO");
147 if (!c
) return HANDLER_GO_ON
;
149 buffer_free(c
->server_name
);
150 buffer_free(c
->document_root
);
154 con
->plugin_ctx
[p
->id
] = NULL
;
155 return HANDLER_GO_ON
;
158 /* set configuration values */
159 SERVER_FUNC(mod_mysql_vhost_set_defaults
) {
160 plugin_data
*p
= p_d
;
166 config_values_t cv
[] = {
167 { "mysql-vhost.db", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
168 { "mysql-vhost.user", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
169 { "mysql-vhost.pass", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
170 { "mysql-vhost.sock", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
171 { "mysql-vhost.sql", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
172 { "mysql-vhost.hostname", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
173 { "mysql-vhost.port", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
}, /* 6 */
174 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
177 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
180 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
181 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
184 s
= calloc(1, sizeof(plugin_config
));
185 s
->mydb
= buffer_init();
186 s
->myuser
= buffer_init();
187 s
->mypass
= buffer_init();
188 s
->mysock
= buffer_init();
189 s
->hostname
= buffer_init();
190 s
->port
= 0; /* default port for mysql */
193 s
->mysql_pre
= buffer_init();
194 s
->mysql_post
= buffer_init();
196 cv
[0].destination
= s
->mydb
;
197 cv
[1].destination
= s
->myuser
;
198 cv
[2].destination
= s
->mypass
;
199 cv
[3].destination
= s
->mysock
;
201 cv
[4].destination
= sel
;
202 cv
[5].destination
= s
->hostname
;
203 cv
[6].destination
= &(s
->port
);
205 p
->config_storage
[i
] = s
;
207 if (config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
208 return HANDLER_ERROR
;
211 s
->mysql_pre
= buffer_init();
212 s
->mysql_post
= buffer_init();
214 if (!buffer_string_is_empty(sel
) && (qmark
= strchr(sel
->ptr
, '?'))) {
216 buffer_copy_string(s
->mysql_pre
, sel
->ptr
);
217 buffer_copy_string(s
->mysql_post
, qmark
+1);
219 buffer_copy_buffer(s
->mysql_pre
, sel
);
227 * - password, default: empty
228 * - socket, default: mysql default
229 * - hostname, if set overrides socket
230 * - port, default: 3306
233 /* all have to be set */
234 if (!(buffer_string_is_empty(s
->myuser
) ||
235 buffer_string_is_empty(s
->mydb
))) {
236 my_bool reconnect
= 1;
238 if (NULL
== (s
->mysql
= mysql_init(NULL
))) {
239 log_error_write(srv
, __FILE__
, __LINE__
, "s", "mysql_init() failed, exiting...");
242 return HANDLER_ERROR
;
245 #if MYSQL_VERSION_ID >= 50013
246 /* in mysql versions above 5.0.3 the reconnect flag is off by default */
247 mysql_options(s
->mysql
, MYSQL_OPT_RECONNECT
, &reconnect
);
250 #define FOO(x) (buffer_string_is_empty(s->x) ? NULL : s->x->ptr)
252 #if MYSQL_VERSION_ID >= 40100
253 /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */
254 if (!mysql_real_connect(s
->mysql
, FOO(hostname
), FOO(myuser
), FOO(mypass
),
255 FOO(mydb
), s
->port
, FOO(mysock
), CLIENT_MULTI_STATEMENTS
)) {
257 if (!mysql_real_connect(s
->mysql
, FOO(hostname
), FOO(myuser
), FOO(mypass
),
258 FOO(mydb
), s
->port
, FOO(mysock
), 0)) {
260 log_error_write(srv
, __FILE__
, __LINE__
, "s", mysql_error(s
->mysql
));
263 return HANDLER_ERROR
;
267 fd_close_on_exec(s
->mysql
->net
.fd
);
272 return HANDLER_GO_ON
;
277 static int mod_mysql_vhost_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
279 plugin_config
*s
= p
->config_storage
[0];
287 /* skip the first, the global context */
288 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
289 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
290 s
= p
->config_storage
[i
];
292 /* condition didn't match */
293 if (!config_check_cond(srv
, con
, dc
)) continue;
296 for (j
= 0; j
< dc
->value
->used
; j
++) {
297 data_unset
*du
= dc
->value
->data
[j
];
299 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("mysql-vhost.sql"))) {
315 /* handle document root request */
316 CONNECTION_FUNC(mod_mysql_vhost_handle_docroot
) {
317 plugin_data
*p
= p_d
;
318 plugin_connection_data
*c
;
319 stat_cache_entry
*sce
;
323 MYSQL_RES
*result
= NULL
;
325 /* no host specified? */
326 if (buffer_string_is_empty(con
->uri
.authority
)) return HANDLER_GO_ON
;
328 mod_mysql_vhost_patch_connection(srv
, con
, p
);
330 if (!p
->conf
.mysql
) return HANDLER_GO_ON
;
331 if (buffer_string_is_empty(p
->conf
.mysql_pre
)) return HANDLER_GO_ON
;
333 /* sets up connection data if not done yet */
334 c
= mod_mysql_vhost_connection_data(srv
, con
, p_d
);
336 /* check if cached this connection */
337 if (buffer_is_equal(c
->server_name
, con
->uri
.authority
)) goto GO_ON
;
339 /* build and run SQL query */
340 buffer_copy_buffer(p
->tmp_buf
, p
->conf
.mysql_pre
);
341 if (!buffer_is_empty(p
->conf
.mysql_post
)) {
342 /* escape the uri.authority */
343 unsigned long to_len
;
345 buffer_string_prepare_append(p
->tmp_buf
, buffer_string_length(con
->uri
.authority
) * 2);
347 to_len
= mysql_real_escape_string(p
->conf
.mysql
,
348 p
->tmp_buf
->ptr
+ buffer_string_length(p
->tmp_buf
),
349 CONST_BUF_LEN(con
->uri
.authority
));
350 buffer_commit(p
->tmp_buf
, to_len
);
352 buffer_append_string_buffer(p
->tmp_buf
, p
->conf
.mysql_post
);
354 if (mysql_real_query(p
->conf
.mysql
, CONST_BUF_LEN(p
->tmp_buf
))) {
355 log_error_write(srv
, __FILE__
, __LINE__
, "s", mysql_error(p
->conf
.mysql
));
358 result
= mysql_store_result(p
->conf
.mysql
);
359 cols
= mysql_num_fields(result
);
360 row
= mysql_fetch_row(result
);
361 if (!row
|| cols
< 1) {
362 /* no such virtual host */
363 mysql_free_result(result
);
364 #if MYSQL_VERSION_ID >= 40100
365 while (mysql_next_result(p
->conf
.mysql
) == 0);
367 return HANDLER_GO_ON
;
370 /* sanity check that really is a directory */
371 buffer_copy_string(p
->tmp_buf
, row
[0]);
372 buffer_append_slash(p
->tmp_buf
);
374 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, p
->tmp_buf
, &sce
)) {
375 log_error_write(srv
, __FILE__
, __LINE__
, "sb", strerror(errno
), p
->tmp_buf
);
378 if (!S_ISDIR(sce
->st
.st_mode
)) {
379 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "Not a directory", p
->tmp_buf
);
384 buffer_copy_buffer(c
->server_name
, con
->uri
.authority
);
385 buffer_copy_buffer(c
->document_root
, p
->tmp_buf
);
387 mysql_free_result(result
);
388 #if MYSQL_VERSION_ID >= 40100
389 while (mysql_next_result(p
->conf
.mysql
) == 0);
392 /* fix virtual server and docroot */
394 buffer_copy_buffer(con
->server_name
, c
->server_name
);
395 buffer_copy_buffer(con
->physical
.doc_root
, c
->document_root
);
398 log_error_write(srv
, __FILE__
, __LINE__
, "sbb",
399 result
? "NOT CACHED" : "cached",
400 con
->server_name
, con
->physical
.doc_root
);
402 return HANDLER_GO_ON
;
405 if (result
) mysql_free_result(result
);
406 #if MYSQL_VERSION_ID >= 40100
407 while (mysql_next_result(p
->conf
.mysql
) == 0);
409 con
->http_status
= 500; /* Internal Error */
411 return HANDLER_FINISHED
;
414 /* this function is called at dlopen() time and inits the callbacks */
415 int mod_mysql_vhost_plugin_init(plugin
*p
);
416 int mod_mysql_vhost_plugin_init(plugin
*p
) {
417 p
->version
= LIGHTTPD_VERSION_ID
;
418 p
->name
= buffer_init_string("mysql_vhost");
420 p
->init
= mod_mysql_vhost_init
;
421 p
->cleanup
= mod_mysql_vhost_cleanup
;
422 p
->connection_reset
= mod_mysql_vhost_handle_connection_close
;
424 p
->set_defaults
= mod_mysql_vhost_set_defaults
;
425 p
->handle_docroot
= mod_mysql_vhost_handle_docroot
;
430 /* we don't have mysql support, this plugin does nothing */
431 int mod_mysql_vhost_plugin_init(plugin
*p
);
432 int mod_mysql_vhost_plugin_init(plugin
*p
) {
433 p
->version
= LIGHTTPD_VERSION_ID
;
434 p
->name
= buffer_init_string("mysql_vhost");