1 #include "first.h" 2 3 #include <mysql.h> 4 5 #include <string.h> 6 #include <stdlib.h> 7 8 #include "base.h" 9 #include "http_vhostdb.h" 10 #include "fdevent.h" 11 #include "log.h" 12 #include "plugin.h" 13 14 /* 15 * virtual host plugin using MySQL for domain to directory lookups 16 */ 17 18 typedef struct { 19 MYSQL *dbconn; 20 buffer *sqlquery; 21 } vhostdb_config; 22 23 typedef struct { 24 void *vdata; 25 array *options; 26 } plugin_config; 27 28 typedef struct { 29 PLUGIN_DATA; 30 plugin_config **config_storage; 31 plugin_config conf; 32 } plugin_data; 33 34 static void mod_vhostdb_dbconf_free (void *vdata) 35 { 36 vhostdb_config *dbconf = (vhostdb_config *)vdata; 37 if (!dbconf) return; 38 mysql_close(dbconf->dbconn); 39 free(dbconf); 40 } 41 42 static int mod_vhostdb_dbconf_setup (server *srv, array *opts, void **vdata) 43 { 44 buffer *sqlquery = NULL; 45 const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *sock=NULL; 46 unsigned int port = 0; 47 48 for (size_t i = 0; i < opts->used; ++i) { 49 const data_string *ds = (data_string *)opts->data[i]; 50 if (ds->type == TYPE_STRING && !buffer_string_is_empty(ds->value)) { 51 if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("sql"))) { 52 sqlquery = ds->value; 53 } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("dbname"))) { 54 dbname = ds->value->ptr; 55 } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("user"))) { 56 user = ds->value->ptr; 57 } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("password"))) { 58 pass = ds->value->ptr; 59 } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("host"))) { 60 host = ds->value->ptr; 61 } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("port"))) { 62 port = strtoul(ds->value->ptr, NULL, 10); 63 } else if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("sock"))) { 64 sock = ds->value->ptr; 65 } 66 } 67 } 68 69 /* required: 70 * - sql (sql query) 71 * - dbname 72 * - user 73 * 74 * optional: 75 * - password, default: empty 76 * - socket, default: mysql default 77 * - hostname, if set overrides socket 78 * - port, default: 3306 79 */ 80 81 if (!buffer_string_is_empty(sqlquery) 82 && dbname && *dbname && user && *user) { 83 vhostdb_config *dbconf; 84 MYSQL *dbconn = mysql_init(NULL); 85 if (NULL == dbconn) { 86 log_error_write(srv, __FILE__, __LINE__, "s", 87 "mysql_init() failed, exiting..."); 88 return -1; 89 } 90 91 #if MYSQL_VERSION_ID >= 50013 92 /* in mysql versions above 5.0.3 the reconnect flag is off by default */ 93 { 94 char reconnect = 1; 95 mysql_options(dbconn, MYSQL_OPT_RECONNECT, &reconnect); 96 } 97 #endif 98 99 /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */ 100 #if MYSQL_VERSION_ID < 40100 101 #ifndef CLIENT_MULTI_STATEMENTS 102 #define CLIENT_MULTI_STATEMENTS 0 103 #endif 104 #endif 105 if (!mysql_real_connect(dbconn, host, user, pass, dbname, port, sock, 106 CLIENT_MULTI_STATEMENTS)) { 107 log_error_write(srv, __FILE__, __LINE__, "s", 108 mysql_error(dbconn)); 109 mysql_close(dbconn); 110 return -1; 111 } 112 113 fdevent_setfd_cloexec(dbconn->net.fd); 114 115 dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); 116 dbconf->dbconn = dbconn; 117 dbconf->sqlquery = sqlquery; 118 *vdata = dbconf; 119 } 120 121 return 0; 122 } 123 124 static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p); 125 126 static int mod_vhostdb_mysql_query(server *srv, connection *con, void *p_d, buffer *docroot) 127 { 128 plugin_data *p = (plugin_data *)p_d; 129 vhostdb_config *dbconf; 130 unsigned cols; 131 MYSQL_ROW row; 132 MYSQL_RES *result; 133 134 /*(reuse buffer for sql query before generating docroot result)*/ 135 buffer *sqlquery = docroot; 136 buffer_clear(sqlquery); /*(also resets docroot (alias))*/ 137 138 mod_vhostdb_patch_connection(srv, con, p); 139 if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/ 140 dbconf = (vhostdb_config *)p->conf.vdata; 141 142 for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) { 143 if (NULL != (d = strchr(b, '?'))) { 144 /* escape the uri.authority */ 145 unsigned long len; 146 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 147 buffer_string_prepare_append(sqlquery, buffer_string_length(con->uri.authority) * 2); 148 len = mysql_real_escape_string(dbconf->dbconn, 149 sqlquery->ptr + buffer_string_length(sqlquery), 150 CONST_BUF_LEN(con->uri.authority)); 151 if ((unsigned long)~0 == len) return -1; 152 buffer_commit(sqlquery, len); 153 } else { 154 d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); 155 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 156 break; 157 } 158 } 159 160 if (mysql_real_query(dbconf->dbconn, CONST_BUF_LEN(sqlquery))) { 161 log_error_write(srv, __FILE__, __LINE__, "s", 162 mysql_error(dbconf->dbconn)); 163 buffer_clear(docroot); /*(reset buffer; no result)*/ 164 return -1; 165 } 166 167 buffer_clear(docroot); /*(reset buffer to store result)*/ 168 169 result = mysql_store_result(dbconf->dbconn); 170 cols = mysql_num_fields(result); 171 row = mysql_fetch_row(result); 172 if (row && cols >= 1) { 173 buffer_copy_string(docroot, row[0]); 174 } /* else no such virtual host */ 175 176 mysql_free_result(result); 177 #if MYSQL_VERSION_ID >= 40100 178 while (0 == mysql_next_result(dbconf->dbconn)) ; 179 #endif 180 return 0; 181 } 182 183 184 185 186 INIT_FUNC(mod_vhostdb_init) { 187 static http_vhostdb_backend_t http_vhostdb_backend_mysql = 188 { "mysql", mod_vhostdb_mysql_query, NULL }; 189 plugin_data *p = calloc(1, sizeof(*p)); 190 191 /* register http_vhostdb_backend_mysql */ 192 http_vhostdb_backend_mysql.p_d = p; 193 http_vhostdb_backend_set(&http_vhostdb_backend_mysql); 194 195 return p; 196 } 197 198 FREE_FUNC(mod_vhostdb_cleanup) { 199 plugin_data *p = p_d; 200 if (!p) return HANDLER_GO_ON; 201 202 if (p->config_storage) { 203 for (size_t i = 0; i < srv->config_context->used; i++) { 204 plugin_config *s = p->config_storage[i]; 205 if (!s) continue; 206 mod_vhostdb_dbconf_free(s->vdata); 207 array_free(s->options); 208 free(s); 209 } 210 free(p->config_storage); 211 } 212 free(p); 213 214 UNUSED(srv); 215 return HANDLER_GO_ON; 216 } 217 218 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { 219 plugin_data *p = p_d; 220 221 config_values_t cv[] = { 222 { "vhostdb.mysql", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, 223 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 224 }; 225 226 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 227 228 for (size_t i = 0; i < srv->config_context->used; ++i) { 229 data_config const *config = (data_config const*)srv->config_context->data[i]; 230 plugin_config *s = calloc(1, sizeof(plugin_config)); 231 232 s->options = array_init(); 233 cv[0].destination = s->options; 234 235 p->config_storage[i] = s; 236 237 if (config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 238 return HANDLER_ERROR; 239 } 240 241 if (!array_is_kvstring(s->options)) { 242 log_error_write(srv, __FILE__, __LINE__, "s", 243 "unexpected value for vhostdb.mysql; expected list of \"option\" => \"value\""); 244 return HANDLER_ERROR; 245 } 246 247 if (s->options->used 248 && 0 != mod_vhostdb_dbconf_setup(srv, s->options, &s->vdata)) { 249 return HANDLER_ERROR; 250 } 251 } 252 253 return HANDLER_GO_ON; 254 } 255 256 #define PATCH(x) \ 257 p->conf.x = s->x; 258 static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p) 259 { 260 plugin_config *s = p->config_storage[0]; 261 PATCH(vdata); 262 263 /* skip the first, the global context */ 264 for (size_t i = 1; i < srv->config_context->used; ++i) { 265 data_config *dc = (data_config *)srv->config_context->data[i]; 266 s = p->config_storage[i]; 267 268 /* condition didn't match */ 269 if (!config_check_cond(srv, con, dc)) continue; 270 271 /* merge config */ 272 for (size_t j = 0; j < dc->value->used; ++j) { 273 data_unset *du = dc->value->data[j]; 274 275 if (buffer_is_equal_string(du->key,CONST_STR_LEN("vhostdb.mysql"))){ 276 PATCH(vdata); 277 } 278 } 279 } 280 } 281 #undef PATCH 282 283 /* this function is called at dlopen() time and inits the callbacks */ 284 int mod_vhostdb_mysql_plugin_init (plugin *p); 285 int mod_vhostdb_mysql_plugin_init (plugin *p) 286 { 287 p->version = LIGHTTPD_VERSION_ID; 288 p->name = buffer_init_string("vhostdb_mysql"); 289 290 p->init = mod_vhostdb_init; 291 p->cleanup = mod_vhostdb_cleanup; 292 p->set_defaults = mod_vhostdb_set_defaults; 293 294 return 0; 295 } 296