1 #include "first.h" 2 3 #include <dbi/dbi.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 DBI for domain to directory lookups 16 * 17 * e.g. 18 * vhostdb.dbi = ( "sql" => "SELECT docroot FROM vhosts WHERE host='?'" 19 * "dbtype" => "sqlite3", 20 * "dbname" => "mydb.sqlite", 21 * "sqlite_dbdir" => "/path/to/sqlite/dbs/" ) 22 */ 23 24 typedef struct { 25 dbi_conn dbconn; 26 dbi_inst dbinst; 27 const buffer *sqlquery; 28 log_error_st *errh; 29 short reconnect_count; 30 } vhostdb_config; 31 32 typedef struct { 33 void *vdata; 34 } plugin_config; 35 36 typedef struct { 37 PLUGIN_DATA; 38 plugin_config defaults; 39 plugin_config conf; 40 } plugin_data; 41 42 /* used to reconnect to the database when we get disconnected */ 43 static void mod_vhostdb_dbi_error_callback (dbi_conn dbconn, void *vdata) 44 { 45 vhostdb_config *dbconf = (vhostdb_config *)vdata; 46 const char *errormsg = NULL; 47 /*assert(dbconf->dbconn == dbconn);*/ 48 49 while (++dbconf->reconnect_count <= 3) { /* retry */ 50 if (0 == dbi_conn_connect(dbconn)) { 51 fdevent_setfd_cloexec(dbi_conn_get_socket(dbconn)); 52 return; 53 } 54 } 55 56 dbi_conn_error(dbconn, &errormsg); 57 log_error(dbconf->errh,__FILE__,__LINE__,"dbi_conn_connect(): %s",errormsg); 58 } 59 60 static void mod_vhostdb_dbconf_free (void *vdata) 61 { 62 vhostdb_config *dbconf = (vhostdb_config *)vdata; 63 if (!dbconf) return; 64 dbi_conn_close(dbconf->dbconn); 65 dbi_shutdown_r(dbconf->dbinst); 66 free(dbconf); 67 } 68 69 static int mod_vhostdb_dbconf_setup (server *srv, const array *opts, void **vdata) 70 { 71 const buffer *sqlquery = NULL; 72 const buffer *dbtype=NULL, *dbname=NULL; 73 74 for (size_t i = 0; i < opts->used; ++i) { 75 const data_string *ds = (data_string *)opts->data[i]; 76 if (ds->type == TYPE_STRING) { 77 if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) { 78 sqlquery = &ds->value; 79 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) { 80 dbname = &ds->value; 81 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbtype"))) { 82 dbtype = &ds->value; 83 } 84 } 85 } 86 87 /* required: 88 * - sql (sql query) 89 * - dbtype 90 * - dbname 91 * 92 * optional: 93 * - username, some databases don't require this (sqlite) 94 * - password, default: empty 95 * - socket, default: database type default 96 * - hostname, if set overrides socket 97 * - port, default: database default 98 * - encoding, default: database default 99 */ 100 101 if (!buffer_string_is_empty(sqlquery) 102 && !buffer_is_empty(dbname) && !buffer_is_empty(dbtype)) { 103 /* create/initialise database */ 104 vhostdb_config *dbconf; 105 dbi_inst dbinst = NULL; 106 dbi_conn dbconn; 107 if (dbi_initialize_r(NULL, &dbinst) < 1) { 108 log_error(srv->errh, __FILE__, __LINE__, 109 "dbi_initialize_r() failed. " 110 "Do you have the DBD for this db type installed?"); 111 return -1; 112 } 113 dbconn = dbi_conn_new_r(dbtype->ptr, dbinst); 114 if (NULL == dbconn) { 115 log_error(srv->errh, __FILE__, __LINE__, 116 "dbi_conn_new_r() failed. " 117 "Do you have the DBD for this db type installed?"); 118 dbi_shutdown_r(dbinst); 119 return -1; 120 } 121 122 /* set options */ 123 for (size_t j = 0; j < opts->used; ++j) { 124 data_unset *du = opts->data[j]; 125 const buffer *opt = &du->key; 126 if (!buffer_string_is_empty(opt)) { 127 if (du->type == TYPE_INTEGER) { 128 data_integer *di = (data_integer *)du; 129 dbi_conn_set_option_numeric(dbconn, opt->ptr, di->value); 130 } else if (du->type == TYPE_STRING) { 131 data_string *ds = (data_string *)du; 132 if (&ds->value != sqlquery && &ds->value != dbtype) { 133 dbi_conn_set_option(dbconn, opt->ptr, ds->value.ptr); 134 } 135 } 136 } 137 } 138 139 dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); 140 dbconf->dbinst = dbinst; 141 dbconf->dbconn = dbconn; 142 dbconf->sqlquery = sqlquery; 143 dbconf->errh = srv->errh; 144 dbconf->reconnect_count = 0; 145 *vdata = dbconf; 146 147 /* used to automatically reconnect to the database */ 148 dbi_conn_error_handler(dbconn, mod_vhostdb_dbi_error_callback, dbconf); 149 150 /* connect to database */ 151 mod_vhostdb_dbi_error_callback(dbconn, dbconf); 152 if (dbconf->reconnect_count >= 3) { 153 mod_vhostdb_dbconf_free(dbconf); 154 return -1; 155 } 156 } 157 158 return 0; 159 } 160 161 static void mod_vhostdb_patch_config (request_st * const r, plugin_data * const p); 162 163 static int mod_vhostdb_dbi_query(request_st * const r, void *p_d, buffer *docroot) 164 { 165 plugin_data *p = (plugin_data *)p_d; 166 vhostdb_config *dbconf; 167 dbi_result result; 168 unsigned long long nrows; 169 int retry_count = 0; 170 171 /*(reuse buffer for sql query before generating docroot result)*/ 172 buffer *sqlquery = docroot; 173 buffer_clear(sqlquery); /*(also resets docroot (alias))*/ 174 175 mod_vhostdb_patch_config(r, p); 176 if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/ 177 dbconf = (vhostdb_config *)p->conf.vdata; 178 179 for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) { 180 if (NULL != (d = strchr(b, '?'))) { 181 /* escape the uri.authority */ 182 char *esc = NULL; 183 size_t len = dbi_conn_escape_string_copy(dbconf->dbconn, r->uri.authority.ptr, &esc); 184 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 185 buffer_append_string_len(sqlquery, esc, len); 186 free(esc); 187 if (0 == len) return -1; 188 } else { 189 d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); 190 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 191 break; 192 } 193 } 194 195 /* reset our reconnect-attempt counter, this is a new query. */ 196 dbconf->reconnect_count = 0; 197 198 do { 199 result = dbi_conn_query(dbconf->dbconn, sqlquery->ptr); 200 } while (!result && ++retry_count < 2); 201 202 buffer_clear(docroot); /*(reset buffer to store result)*/ 203 204 if (!result) { 205 const char *errmsg; 206 dbi_conn_error(dbconf->dbconn, &errmsg); 207 log_error(r->conf.errh, __FILE__, __LINE__, "%s", errmsg); 208 return -1; 209 } 210 211 nrows = dbi_result_get_numrows(result); 212 if (nrows && nrows != DBI_ROW_ERROR && dbi_result_next_row(result)) { 213 buffer_copy_string(docroot, dbi_result_get_string_idx(result, 1)); 214 } /* else no such virtual host */ 215 216 dbi_result_free(result); 217 return 0; 218 } 219 220 221 222 223 INIT_FUNC(mod_vhostdb_init) { 224 static http_vhostdb_backend_t http_vhostdb_backend_dbi = 225 { "dbi", mod_vhostdb_dbi_query, NULL }; 226 plugin_data *p = calloc(1, sizeof(*p)); 227 228 /* register http_vhostdb_backend_dbi */ 229 http_vhostdb_backend_dbi.p_d = p; 230 http_vhostdb_backend_set(&http_vhostdb_backend_dbi); 231 232 return p; 233 } 234 235 FREE_FUNC(mod_vhostdb_cleanup) { 236 plugin_data * const p = p_d; 237 if (NULL == p->cvlist) return; 238 /* (init i to 0 if global context; to 1 to skip empty global context) */ 239 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 240 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 241 for (; -1 != cpv->k_id; ++cpv) { 242 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 243 switch (cpv->k_id) { 244 case 0: /* vhostdb.<db> */ 245 mod_vhostdb_dbconf_free(cpv->v.v); 246 break; 247 default: 248 break; 249 } 250 } 251 } 252 } 253 254 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 255 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 256 case 0: /* vhostdb.<db> */ 257 if (cpv->vtype == T_CONFIG_LOCAL) 258 pconf->vdata = cpv->v.v; 259 break; 260 default:/* should not happen */ 261 return; 262 } 263 } 264 265 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 266 do { 267 mod_vhostdb_merge_config_cpv(pconf, cpv); 268 } while ((++cpv)->k_id != -1); 269 } 270 271 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) { 272 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 273 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 274 for (int i = 1, used = p->nconfig; i < used; ++i) { 275 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 276 mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]); 277 } 278 } 279 280 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { 281 static const config_plugin_keys_t cpk[] = { 282 { CONST_STR_LEN("vhostdb.dbi"), 283 T_CONFIG_ARRAY_KVSTRING, 284 T_CONFIG_SCOPE_CONNECTION } 285 ,{ NULL, 0, 286 T_CONFIG_UNSET, 287 T_CONFIG_SCOPE_UNSET } 288 }; 289 290 plugin_data * const p = p_d; 291 if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_dbi")) 292 return HANDLER_ERROR; 293 294 /* process and validate config directives 295 * (init i to 0 if global context; to 1 to skip empty global context) */ 296 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 297 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 298 for (; -1 != cpv->k_id; ++cpv) { 299 switch (cpv->k_id) { 300 case 0: /* vhostdb.<db> */ 301 if (cpv->v.a->used) { 302 if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v)) 303 return HANDLER_ERROR; 304 if (NULL != cpv->v.v) 305 cpv->vtype = T_CONFIG_LOCAL; 306 } 307 break; 308 default:/* should not happen */ 309 break; 310 } 311 } 312 } 313 314 /* initialize p->defaults from global config context */ 315 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 316 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 317 if (-1 != cpv->k_id) 318 mod_vhostdb_merge_config(&p->defaults, cpv); 319 } 320 321 return HANDLER_GO_ON; 322 } 323 324 325 int mod_vhostdb_dbi_plugin_init (plugin *p); 326 int mod_vhostdb_dbi_plugin_init (plugin *p) 327 { 328 p->version = LIGHTTPD_VERSION_ID; 329 p->name = "vhostdb_dbi"; 330 331 p->init = mod_vhostdb_init; 332 p->cleanup = mod_vhostdb_cleanup; 333 p->set_defaults = mod_vhostdb_set_defaults; 334 335 return 0; 336 } 337