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