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 <string.h> 10 #include <stdlib.h> 11 12 #include <libpq-fe.h> 13 14 #include "mod_vhostdb_api.h" 15 #include "base.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 (NULL != sqlquery && !buffer_is_blank(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, 132 buffer_clen(&r->uri.authority) * 2); 133 len = PQescapeStringConn(dbconf->dbconn, 134 sqlquery->ptr + buffer_clen(sqlquery), 135 BUF_PTR_LEN(&r->uri.authority), &err); 136 buffer_commit(sqlquery, len); 137 if (0 != err) return -1; 138 } else { 139 d = dbconf->sqlquery->ptr + buffer_clen(dbconf->sqlquery); 140 buffer_append_string_len(sqlquery, b, (size_t)(d - b)); 141 break; 142 } 143 } 144 145 res = PQexec(dbconf->dbconn, sqlquery->ptr); 146 147 buffer_clear(docroot); /*(reset buffer to store result)*/ 148 149 if (PGRES_TUPLES_OK != PQresultStatus(res)) { 150 log_error(r->conf.errh, __FILE__, __LINE__, "%s", 151 PQerrorMessage(dbconf->dbconn)); 152 PQclear(res); 153 return -1; 154 } 155 156 cols = PQnfields(res); 157 rows = PQntuples(res); 158 if (rows == 1 && cols >= 1) { 159 buffer_copy_string(docroot, PQgetvalue(res, 0, 0)); 160 } /* else no such virtual host */ 161 162 PQclear(res); 163 return 0; 164 } 165 166 167 168 169 INIT_FUNC(mod_vhostdb_init) { 170 static http_vhostdb_backend_t http_vhostdb_backend_pgsql = 171 { "pgsql", mod_vhostdb_pgsql_query, NULL }; 172 plugin_data *p = calloc(1, sizeof(*p)); 173 174 /* register http_vhostdb_backend_pgsql */ 175 http_vhostdb_backend_pgsql.p_d = p; 176 http_vhostdb_backend_set(&http_vhostdb_backend_pgsql); 177 178 return p; 179 } 180 181 FREE_FUNC(mod_vhostdb_cleanup) { 182 plugin_data * const p = p_d; 183 if (NULL == p->cvlist) return; 184 /* (init i to 0 if global context; to 1 to skip empty global context) */ 185 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { 186 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 187 for (; -1 != cpv->k_id; ++cpv) { 188 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; 189 switch (cpv->k_id) { 190 case 0: /* vhostdb.<db> */ 191 mod_vhostdb_dbconf_free(cpv->v.v); 192 break; 193 default: 194 break; 195 } 196 } 197 } 198 } 199 200 static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 201 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 202 case 0: /* vhostdb.<db> */ 203 if (cpv->vtype == T_CONFIG_LOCAL) 204 pconf->vdata = cpv->v.v; 205 break; 206 default:/* should not happen */ 207 return; 208 } 209 } 210 211 static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 212 do { 213 mod_vhostdb_merge_config_cpv(pconf, cpv); 214 } while ((++cpv)->k_id != -1); 215 } 216 217 static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) { 218 p->conf = p->defaults; /* copy small struct instead of memcpy() */ 219 /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/ 220 for (int i = 1, used = p->nconfig; i < used; ++i) { 221 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 222 mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]); 223 } 224 } 225 226 SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { 227 static const config_plugin_keys_t cpk[] = { 228 { CONST_STR_LEN("vhostdb.pgsql"), 229 T_CONFIG_ARRAY_KVSTRING, 230 T_CONFIG_SCOPE_CONNECTION } 231 ,{ NULL, 0, 232 T_CONFIG_UNSET, 233 T_CONFIG_SCOPE_UNSET } 234 }; 235 236 plugin_data * const p = p_d; 237 if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_pgsql")) 238 return HANDLER_ERROR; 239 240 /* process and validate config directives 241 * (init i to 0 if global context; to 1 to skip empty global context) */ 242 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 243 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 244 for (; -1 != cpv->k_id; ++cpv) { 245 switch (cpv->k_id) { 246 case 0: /* vhostdb.<db> */ 247 if (cpv->v.a->used) { 248 if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v)) 249 return HANDLER_ERROR; 250 if (NULL != cpv->v.v) 251 cpv->vtype = T_CONFIG_LOCAL; 252 } 253 break; 254 default:/* should not happen */ 255 break; 256 } 257 } 258 } 259 260 /* initialize p->defaults from global config context */ 261 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 262 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 263 if (-1 != cpv->k_id) 264 mod_vhostdb_merge_config(&p->defaults, cpv); 265 } 266 267 return HANDLER_GO_ON; 268 } 269 270 271 int mod_vhostdb_pgsql_plugin_init (plugin *p); 272 int mod_vhostdb_pgsql_plugin_init (plugin *p) 273 { 274 p->version = LIGHTTPD_VERSION_ID; 275 p->name = "vhostdb_pgsql"; 276 277 p->init = mod_vhostdb_init; 278 p->cleanup = mod_vhostdb_cleanup; 279 p->set_defaults = mod_vhostdb_set_defaults; 280 281 return 0; 282 } 283