1 #include "first.h" 2 3 #include <libpq-fe.h> 4 5 #include <string.h> 6 #include <stdlib.h> 7 8 #include "base.h" 9 #include "http_vhostdb.h" 10 #include "log.h" 11 #include "plugin.h" 12 13 /* 14 * virtual host plugin using Postgres for domain to directory lookups 15 */ 16 17 typedef struct { 18 PGconn *dbconn; 19 const buffer *sqlquery; 20 } vhostdb_config; 21 22 typedef struct { 23 void *vdata; 24 array *options; 25 } plugin_config; 26 27 typedef struct { 28 PLUGIN_DATA; 29 plugin_config **config_storage; 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 PQfinish(dbconf->dbconn); 38 free(dbconf); 39 } 40 41 static int mod_vhostdb_dbconf_setup (server *srv, array *opts, void **vdata) 42 { 43 const buffer *sqlquery = NULL; 44 const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *port=NULL; 45 46 for (size_t i = 0; i < opts->used; ++i) { 47 const data_string *ds = (data_string *)opts->data[i]; 48 if (ds->type == TYPE_STRING) { 49 if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) { 50 sqlquery = &ds->value; 51 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) { 52 dbname = ds->value.ptr; 53 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("user"))) { 54 user = ds->value.ptr; 55 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("password"))) { 56 pass = ds->value.ptr; 57 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("host"))) { 58 host = ds->value.ptr; 59 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("port"))) { 60 port = ds->value.ptr; 61 } 62 } 63 } 64 65 /* required: 66 * - sql (sql query) 67 * - dbname 68 * - user (unless dbname is a pgsql conninfo URI) 69 * 70 * optional: 71 * - password, default: empty 72 * - hostname 73 * - port, default: 5432 74 */ 75 76 if (!buffer_string_is_empty(sqlquery) && NULL != dbname) { 77 vhostdb_config *dbconf; 78 PGconn *dbconn = PQsetdbLogin(host,port,NULL,NULL,dbname,user,pass); 79 if (NULL == dbconn) { 80 log_error_write(srv, __FILE__, __LINE__, "s", 81 "PGsetdbLogin() failed, exiting..."); 82 return -1; 83 } 84 85 if (CONNECTION_OK != PQstatus(dbconn)) { 86 log_error_write(srv, __FILE__, __LINE__, "s", 87 "Failed to login to database, exiting..."); 88 PQfinish(dbconn); 89 return -1; 90 } 91 92 /* Postgres sets FD_CLOEXEC on database socket descriptors */ 93 94 dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); 95 dbconf->dbconn = dbconn; 96 dbconf->sqlquery = sqlquery; 97 *vdata = dbconf; 98 } 99 100 return 0; 101 } 102 103 static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p); 104 105 static int mod_vhostdb_pgsql_query(server *srv, connection *con, void *p_d, buffer *docroot) 106 { 107 plugin_data *p = (plugin_data *)p_d; 108 vhostdb_config *dbconf; 109 PGresult *res; 110 int cols, rows; 111 112 /*(reuse buffer for sql query before generating docroot result)*/ 113 buffer *sqlquery = docroot; 114 buffer_clear(sqlquery); /*(also resets docroot (alias))*/ 115 116 mod_vhostdb_patch_connection(srv, con, p); 117 if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/ 118 dbconf = (vhostdb_config *)p->conf.vdata; 119 120 for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) { 121 if (NULL != (d = strchr(b, '?'))) { 122 /* escape the uri.authority */ 123 size_t len; 124 int err; 125 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 126 buffer_string_prepare_append(sqlquery, buffer_string_length(con->uri.authority) * 2); 127 len = PQescapeStringConn(dbconf->dbconn, 128 sqlquery->ptr + buffer_string_length(sqlquery), 129 CONST_BUF_LEN(con->uri.authority), &err); 130 buffer_commit(sqlquery, len); 131 if (0 != err) return -1; 132 } else { 133 d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); 134 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 135 break; 136 } 137 } 138 139 res = PQexec(dbconf->dbconn, sqlquery->ptr); 140 141 buffer_clear(docroot); /*(reset buffer to store result)*/ 142 143 if (PGRES_TUPLES_OK != PQresultStatus(res)) { 144 log_error_write(srv, __FILE__, __LINE__, "s", 145 PQerrorMessage(dbconf->dbconn)); 146 PQclear(res); 147 return -1; 148 } 149 150 cols = PQnfields(res); 151 rows = PQntuples(res); 152 if (rows == 1 && cols >= 1) { 153 buffer_copy_string(docroot, PQgetvalue(res, 0, 0)); 154 } /* else no such virtual host */ 155 156 PQclear(res); 157 return 0; 158 } 159 160 161 162 163 INIT_FUNC(mod_vhostdb_init) { 164 static http_vhostdb_backend_t http_vhostdb_backend_pgsql = 165 { "pgsql", mod_vhostdb_pgsql_query, NULL }; 166 plugin_data *p = calloc(1, sizeof(*p)); 167 168 /* register http_vhostdb_backend_pgsql */ 169 http_vhostdb_backend_pgsql.p_d = p; 170 http_vhostdb_backend_set(&http_vhostdb_backend_pgsql); 171 172 return p; 173 } 174 175 FREE_FUNC(mod_vhostdb_cleanup) { 176 plugin_data *p = p_d; 177 if (!p) return HANDLER_GO_ON; 178 179 if (p->config_storage) { 180 for (size_t i = 0; i < srv->config_context->used; i++) { 181 plugin_config *s = p->config_storage[i]; 182 if (!s) continue; 183 mod_vhostdb_dbconf_free(s->vdata); 184 array_free(s->options); 185 free(s); 186 } 187 free(p->config_storage); 188 } 189 free(p); 190 191 UNUSED(srv); 192 return HANDLER_GO_ON; 193 } 194 195 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { 196 plugin_data *p = p_d; 197 198 config_values_t cv[] = { 199 { "vhostdb.pgsql", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, 200 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 201 }; 202 203 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *)); 204 205 for (size_t i = 0; i < srv->config_context->used; ++i) { 206 data_config const *config = (data_config const*)srv->config_context->data[i]; 207 plugin_config *s = calloc(1, sizeof(plugin_config)); 208 209 s->options = array_init(); 210 cv[0].destination = s->options; 211 212 p->config_storage[i] = s; 213 214 if (config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 215 return HANDLER_ERROR; 216 } 217 218 if (!array_is_kvstring(s->options)) { 219 log_error_write(srv, __FILE__, __LINE__, "s", 220 "unexpected value for vhostdb.pgsql; expected list of \"option\" => \"value\""); 221 return HANDLER_ERROR; 222 } 223 224 if (s->options->used 225 && 0 != mod_vhostdb_dbconf_setup(srv, s->options, &s->vdata)) { 226 return HANDLER_ERROR; 227 } 228 } 229 230 return HANDLER_GO_ON; 231 } 232 233 #define PATCH(x) \ 234 p->conf.x = s->x; 235 static void mod_vhostdb_patch_connection (server *srv, connection *con, plugin_data *p) 236 { 237 plugin_config *s = p->config_storage[0]; 238 PATCH(vdata); 239 240 /* skip the first, the global context */ 241 for (size_t i = 1; i < srv->config_context->used; ++i) { 242 if (!config_check_cond(con, i)) continue; /* condition not matched */ 243 244 data_config *dc = (data_config *)srv->config_context->data[i]; 245 s = p->config_storage[i]; 246 247 /* merge config */ 248 for (size_t j = 0; j < dc->value->used; ++j) { 249 data_unset *du = dc->value->data[j]; 250 251 if (buffer_is_equal_string(&du->key,CONST_STR_LEN("vhostdb.pgsql"))){ 252 PATCH(vdata); 253 } 254 } 255 } 256 } 257 #undef PATCH 258 259 /* this function is called at dlopen() time and inits the callbacks */ 260 int mod_vhostdb_pgsql_plugin_init (plugin *p); 261 int mod_vhostdb_pgsql_plugin_init (plugin *p) 262 { 263 p->version = LIGHTTPD_VERSION_ID; 264 p->name = "vhostdb_pgsql"; 265 266 p->init = mod_vhostdb_init; 267 p->cleanup = mod_vhostdb_cleanup; 268 p->set_defaults = mod_vhostdb_set_defaults; 269 270 return 0; 271 } 272