1 /* 2 * mod_vhostdb_pgsql - virtual hosts mapping from backend PostgreSQL database 3 * 4 * Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com All rights reserved 5 * License: BSD 3-clause (same as lighttpd) 6 */ 7 #include "first.h" 8 9 #include <libpq-fe.h> 10 11 #include <string.h> 12 #include <stdlib.h> 13 14 #include "base.h" 15 #include "http_vhostdb.h" 16 #include "log.h" 17 #include "plugin.h" 18 19 /* 20 * virtual host plugin using PostgreSQL for domain to directory lookups 21 */ 22 23 typedef struct { 24 PGconn *dbconn; 25 const buffer *sqlquery; 26 } vhostdb_config; 27 28 typedef struct { 29 void *vdata; 30 } plugin_config; 31 32 typedef struct { 33 PLUGIN_DATA; 34 plugin_config defaults; 35 plugin_config conf; 36 } plugin_data; 37 38 static void mod_vhostdb_dbconf_free (void *vdata) 39 { 40 vhostdb_config *dbconf = (vhostdb_config *)vdata; 41 if (!dbconf) return; 42 PQfinish(dbconf->dbconn); 43 free(dbconf); 44 } 45 46 static int mod_vhostdb_dbconf_setup (server *srv, const array *opts, void **vdata) 47 { 48 const buffer *sqlquery = NULL; 49 const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *port=NULL; 50 51 for (size_t i = 0; i < opts->used; ++i) { 52 const data_string *ds = (data_string *)opts->data[i]; 53 if (ds->type == TYPE_STRING) { 54 if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) { 55 sqlquery = &ds->value; 56 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) { 57 dbname = ds->value.ptr; 58 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("user"))) { 59 user = ds->value.ptr; 60 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("password"))) { 61 pass = ds->value.ptr; 62 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("host"))) { 63 host = ds->value.ptr; 64 } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("port"))) { 65 port = ds->value.ptr; 66 } 67 } 68 } 69 70 /* required: 71 * - sql (sql query) 72 * - dbname 73 * - user (unless dbname is a pgsql conninfo URI) 74 * 75 * optional: 76 * - password, default: empty 77 * - hostname 78 * - port, default: 5432 79 */ 80 81 if (!buffer_string_is_empty(sqlquery) && NULL != dbname) { 82 vhostdb_config *dbconf; 83 PGconn *dbconn = PQsetdbLogin(host,port,NULL,NULL,dbname,user,pass); 84 if (NULL == dbconn) { 85 log_error(srv->errh, __FILE__, __LINE__, 86 "PGsetdbLogin() failed, exiting..."); 87 return -1; 88 } 89 90 if (CONNECTION_OK != PQstatus(dbconn)) { 91 log_error(srv->errh, __FILE__, __LINE__, 92 "Failed to login to database, exiting..."); 93 PQfinish(dbconn); 94 return -1; 95 } 96 97 /* Postgres sets FD_CLOEXEC on database socket descriptors */ 98 99 dbconf = (vhostdb_config *)calloc(1, sizeof(*dbconf)); 100 dbconf->dbconn = dbconn; 101 dbconf->sqlquery = sqlquery; 102 *vdata = dbconf; 103 } 104 105 return 0; 106 } 107 108 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p); 109 110 static int mod_vhostdb_pgsql_query(request_st * const r, void *p_d, buffer *docroot) 111 { 112 plugin_data *p = (plugin_data *)p_d; 113 vhostdb_config *dbconf; 114 PGresult *res; 115 int cols, rows; 116 117 /*(reuse buffer for sql query before generating docroot result)*/ 118 buffer *sqlquery = docroot; 119 buffer_clear(sqlquery); /*(also resets docroot (alias))*/ 120 121 mod_vhostdb_patch_config(r, p); 122 if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/ 123 dbconf = (vhostdb_config *)p->conf.vdata; 124 125 for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) { 126 if (NULL != (d = strchr(b, '?'))) { 127 /* escape the uri.authority */ 128 size_t len; 129 int err; 130 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 131 buffer_string_prepare_append(sqlquery, buffer_string_length(&r->uri.authority) * 2); 132 len = PQescapeStringConn(dbconf->dbconn, 133 sqlquery->ptr + buffer_string_length(sqlquery), 134 CONST_BUF_LEN(&r->uri.authority), &err); 135 buffer_commit(sqlquery, len); 136 if (0 != err) return -1; 137 } else { 138 d = dbconf->sqlquery->ptr + buffer_string_length(dbconf->sqlquery); 139 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 140 break; 141 } 142 } 143 144 res = PQexec(dbconf->dbconn, sqlquery->ptr); 145 146 buffer_clear(docroot); /*(reset buffer to store result)*/ 147 148 if (PGRES_TUPLES_OK != PQresultStatus(res)) { 149 log_error(r->conf.errh, __FILE__, __LINE__, "%s", 150 PQerrorMessage(dbconf->dbconn)); 151 PQclear(res); 152 return -1; 153 } 154 155 cols = PQnfields(res); 156 rows = PQntuples(res); 157 if (rows == 1 && cols >= 1) { 158 buffer_copy_string(docroot, PQgetvalue(res, 0, 0)); 159 } /* else no such virtual host */ 160 161 PQclear(res); 162 return 0; 163 } 164 165 166 167 168 INIT_FUNC(mod_vhostdb_init) { 169 static http_vhostdb_backend_t http_vhostdb_backend_pgsql = 170 { "pgsql", mod_vhostdb_pgsql_query, NULL }; 171 plugin_data *p = calloc(1, sizeof(*p)); 172 173 /* register http_vhostdb_backend_pgsql */ 174 http_vhostdb_backend_pgsql.p_d = p; 175 http_vhostdb_backend_set(&http_vhostdb_backend_pgsql); 176 177 return p; 178 } 179 180 FREE_FUNC(mod_vhostdb_cleanup) { 181 plugin_data * const p = p_d; 182 if (NULL == p->cvlist) return; 183 /* (init i to 0 if global context; to 1 to skip empty global context) */ 184 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 185 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 186 for (; -1 != cpv->k_id; ++cpv) { 187 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 188 switch (cpv->k_id) { 189 case 0: /* vhostdb.<db> */ 190 mod_vhostdb_dbconf_free(cpv->v.v); 191 break; 192 default: 193 break; 194 } 195 } 196 } 197 } 198 199 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 200 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 201 case 0: /* vhostdb.<db> */ 202 if (cpv->vtype == T_CONFIG_LOCAL) 203 pconf->vdata = cpv->v.v; 204 break; 205 default:/* should not happen */ 206 return; 207 } 208 } 209 210 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 211 do { 212 mod_vhostdb_merge_config_cpv(pconf, cpv); 213 } while ((++cpv)->k_id != -1); 214 } 215 216 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) { 217 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 218 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 219 for (int i = 1, used = p->nconfig; i < used; ++i) { 220 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 221 mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]); 222 } 223 } 224 225 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { 226 static const config_plugin_keys_t cpk[] = { 227 { CONST_STR_LEN("vhostdb.pgsql"), 228 T_CONFIG_ARRAY_KVSTRING, 229 T_CONFIG_SCOPE_CONNECTION } 230 ,{ NULL, 0, 231 T_CONFIG_UNSET, 232 T_CONFIG_SCOPE_UNSET } 233 }; 234 235 plugin_data * const p = p_d; 236 if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_pgsql")) 237 return HANDLER_ERROR; 238 239 /* process and validate config directives 240 * (init i to 0 if global context; to 1 to skip empty global context) */ 241 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 242 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 243 for (; -1 != cpv->k_id; ++cpv) { 244 switch (cpv->k_id) { 245 case 0: /* vhostdb.<db> */ 246 if (cpv->v.a->used) { 247 if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v)) 248 return HANDLER_ERROR; 249 if (NULL != cpv->v.v) 250 cpv->vtype = T_CONFIG_LOCAL; 251 } 252 break; 253 default:/* should not happen */ 254 break; 255 } 256 } 257 } 258 259 /* initialize p->defaults from global config context */ 260 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 261 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 262 if (-1 != cpv->k_id) 263 mod_vhostdb_merge_config(&p->defaults, cpv); 264 } 265 266 return HANDLER_GO_ON; 267 } 268 269 270 int mod_vhostdb_pgsql_plugin_init (plugin *p); 271 int mod_vhostdb_pgsql_plugin_init (plugin *p) 272 { 273 p->version = LIGHTTPD_VERSION_ID; 274 p->name = "vhostdb_pgsql"; 275 276 p->init = mod_vhostdb_init; 277 p->cleanup = mod_vhostdb_cleanup; 278 p->set_defaults = mod_vhostdb_set_defaults; 279 280 return 0; 281 } 282