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 const buffer *sqlquery; 21 } vhostdb_config; 22 23 typedef struct { 24 void *vdata; 25 } plugin_config; 26 27 typedef struct { 28 PLUGIN_DATA; 29 plugin_config defaults; 30 plugin_config conf; 31 } plugin_data; 32 33 static void mod_vhostdb_dbconf_free (void *vdata) 34 { 35 vhostdb_config *dbconf = (vhostdb_config *)vdata; 36 if (!dbconf) return; 37 mysql_close(dbconf->dbconn); 38 free(dbconf); 39 } 40 41 static int mod_vhostdb_dbconf_setup (server *srv, const array *opts, void **vdata) 42 { 43 const buffer *sqlquery = NULL; 44 const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *sock=NULL; 45 unsigned int port = 0; 46 47 for (size_t i = 0; i < opts->used; ++i) { 48 const data_string *ds = (data_string *)opts->data[i]; 49 if (ds->type == TYPE_STRING && !buffer_string_is_empty(&ds->value)) { 50 if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) { 51 sqlquery = &ds->value; 52 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) { 53 dbname = ds->value.ptr; 54 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("user"))) { 55 user = ds->value.ptr; 56 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("password"))) { 57 pass = ds->value.ptr; 58 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("host"))) { 59 host = ds->value.ptr; 60 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("port"))) { 61 port = strtoul(ds->value.ptr, NULL, 10); 62 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sock"))) { 63 sock = ds->value.ptr; 64 } 65 } 66 } 67 68 /* required: 69 * - sql (sql query) 70 * - dbname 71 * - user 72 * 73 * optional: 74 * - password, default: empty 75 * - socket, default: mysql default 76 * - hostname, if set overrides socket 77 * - port, default: 3306 78 */ 79 80 if (!buffer_string_is_empty(sqlquery) 81 && dbname && *dbname && user && *user) { 82 vhostdb_config *dbconf; 83 MYSQL *dbconn = mysql_init(NULL); 84 if (NULL == dbconn) { 85 log_error(srv->errh, __FILE__, __LINE__, 86 "mysql_init() failed, exiting..."); 87 return -1; 88 } 89 90 #if MYSQL_VERSION_ID >= 50013 91 /* in mysql versions above 5.0.3 the reconnect flag is off by default */ 92 { 93 char reconnect = 1; 94 mysql_options(dbconn, MYSQL_OPT_RECONNECT, &reconnect); 95 } 96 #endif 97 98 /* CLIENT_MULTI_STATEMENTS first appeared in 4.1 */ 99 #if MYSQL_VERSION_ID < 40100 100 #ifndef CLIENT_MULTI_STATEMENTS 101 #define CLIENT_MULTI_STATEMENTS 0 102 #endif 103 #endif 104 if (!mysql_real_connect(dbconn, host, user, pass, dbname, port, sock, 105 CLIENT_MULTI_STATEMENTS)) { 106 log_error(srv->errh, __FILE__, __LINE__, "%s", mysql_error(dbconn)); 107 mysql_close(dbconn); 108 return -1; 109 } 110 111 fdevent_setfd_cloexec(dbconn->net.fd); 112 113 dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); 114 dbconf->dbconn = dbconn; 115 dbconf->sqlquery = sqlquery; 116 *vdata = dbconf; 117 } 118 119 return 0; 120 } 121 122 static void mod_vhostdb_patch_config (request_st * const r, plugin_data * const p); 123 124 static int mod_vhostdb_mysql_query(request_st * const r, void *p_d, buffer *docroot) 125 { 126 plugin_data *p = (plugin_data *)p_d; 127 vhostdb_config *dbconf; 128 unsigned cols; 129 MYSQL_ROW row; 130 MYSQL_RES *result; 131 132 /*(reuse buffer for sql query before generating docroot result)*/ 133 buffer *sqlquery = docroot; 134 buffer_clear(sqlquery); /*(also resets docroot (alias))*/ 135 136 mod_vhostdb_patch_config(r, p); 137 if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/ 138 dbconf = (vhostdb_config *)p->conf.vdata; 139 140 for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) { 141 if (NULL != (d = strchr(b, '?'))) { 142 /* escape the uri.authority */ 143 unsigned long len; 144 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 145 buffer_string_prepare_append(sqlquery, buffer_string_length(&r->uri.authority) * 2); 146 len = mysql_real_escape_string(dbconf->dbconn, 147 sqlquery->ptr + buffer_string_length(sqlquery), 148 CONST_BUF_LEN(&r->uri.authority)); 149 if ((unsigned long)~0 == len) return -1; 150 buffer_commit(sqlquery, len); 151 } else { 152 d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); 153 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 154 break; 155 } 156 } 157 158 if (mysql_real_query(dbconf->dbconn, CONST_BUF_LEN(sqlquery))) { 159 log_error(r->conf.errh, __FILE__, __LINE__, "%s", 160 mysql_error(dbconf->dbconn)); 161 buffer_clear(docroot); /*(reset buffer; no result)*/ 162 return -1; 163 } 164 165 buffer_clear(docroot); /*(reset buffer to store result)*/ 166 167 result = mysql_store_result(dbconf->dbconn); 168 cols = mysql_num_fields(result); 169 row = mysql_fetch_row(result); 170 if (row && cols >= 1) { 171 buffer_copy_string(docroot, row[0]); 172 } /* else no such virtual host */ 173 174 mysql_free_result(result); 175 #if MYSQL_VERSION_ID >= 40100 176 while (0 == mysql_next_result(dbconf->dbconn)) ; 177 #endif 178 return 0; 179 } 180 181 182 183 184 INIT_FUNC(mod_vhostdb_init) { 185 static http_vhostdb_backend_t http_vhostdb_backend_mysql = 186 { "mysql", mod_vhostdb_mysql_query, NULL }; 187 plugin_data *p = calloc(1, sizeof(*p)); 188 189 /* register http_vhostdb_backend_mysql */ 190 http_vhostdb_backend_mysql.p_d = p; 191 http_vhostdb_backend_set(&http_vhostdb_backend_mysql); 192 193 return p; 194 } 195 196 FREE_FUNC(mod_vhostdb_cleanup) { 197 plugin_data * const p = p_d; 198 if (NULL == p->cvlist) return; 199 /* (init i to 0 if global context; to 1 to skip empty global context) */ 200 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 201 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 202 for (; -1 != cpv->k_id; ++cpv) { 203 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 204 switch (cpv->k_id) { 205 case 0: /* vhostdb.<db> */ 206 mod_vhostdb_dbconf_free(cpv->v.v); 207 break; 208 default: 209 break; 210 } 211 } 212 } 213 } 214 215 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 216 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 217 case 0: /* vhostdb.<db> */ 218 if (cpv->vtype == T_CONFIG_LOCAL) 219 pconf->vdata = cpv->v.v; 220 break; 221 default:/* should not happen */ 222 return; 223 } 224 } 225 226 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 227 do { 228 mod_vhostdb_merge_config_cpv(pconf, cpv); 229 } while ((++cpv)->k_id != -1); 230 } 231 232 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) { 233 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 234 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 235 for (int i = 1, used = p->nconfig; i < used; ++i) { 236 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 237 mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]); 238 } 239 } 240 241 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { 242 static const config_plugin_keys_t cpk[] = { 243 { CONST_STR_LEN("vhostdb.mysql"), 244 T_CONFIG_ARRAY_KVSTRING, 245 T_CONFIG_SCOPE_CONNECTION } 246 ,{ NULL, 0, 247 T_CONFIG_UNSET, 248 T_CONFIG_SCOPE_UNSET } 249 }; 250 251 plugin_data * const p = p_d; 252 if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_mysql")) 253 return HANDLER_ERROR; 254 255 /* process and validate config directives 256 * (init i to 0 if global context; to 1 to skip empty global context) */ 257 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 258 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 259 for (; -1 != cpv->k_id; ++cpv) { 260 switch (cpv->k_id) { 261 case 0: /* vhostdb.<db> */ 262 if (cpv->v.a->used) { 263 if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v)) 264 return HANDLER_ERROR; 265 if (NULL != cpv->v.v) 266 cpv->vtype = T_CONFIG_LOCAL; 267 } 268 break; 269 default:/* should not happen */ 270 break; 271 } 272 } 273 } 274 275 /* initialize p->defaults from global config context */ 276 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 277 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 278 if (-1 != cpv->k_id) 279 mod_vhostdb_merge_config(&p->defaults, cpv); 280 } 281 282 return HANDLER_GO_ON; 283 } 284 285 286 int mod_vhostdb_mysql_plugin_init (plugin *p); 287 int mod_vhostdb_mysql_plugin_init (plugin *p) 288 { 289 p->version = LIGHTTPD_VERSION_ID; 290 p->name = "vhostdb_mysql"; 291 292 p->init = mod_vhostdb_init; 293 p->cleanup = mod_vhostdb_cleanup; 294 p->set_defaults = mod_vhostdb_set_defaults; 295 296 return 0; 297 } 298