1c18f442aSGlenn Strauss /*
2c18f442aSGlenn Strauss * mod_vhostdb_pgsql - virtual hosts mapping from backend PostgreSQL database
3c18f442aSGlenn Strauss *
4c18f442aSGlenn Strauss * Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com All rights reserved
5c18f442aSGlenn Strauss * License: BSD 3-clause (same as lighttpd)
6c18f442aSGlenn Strauss */
72f83aac9SGlenn Strauss #include "first.h"
82f83aac9SGlenn Strauss
92f83aac9SGlenn Strauss #include <string.h>
102f83aac9SGlenn Strauss #include <stdlib.h>
112f83aac9SGlenn Strauss
120fd89187SGlenn Strauss #include <libpq-fe.h>
130fd89187SGlenn Strauss
140fd89187SGlenn Strauss #include "mod_vhostdb_api.h"
152f83aac9SGlenn Strauss #include "base.h"
162f83aac9SGlenn Strauss #include "log.h"
172f83aac9SGlenn Strauss #include "plugin.h"
182f83aac9SGlenn Strauss
192f83aac9SGlenn Strauss /*
20c18f442aSGlenn Strauss * virtual host plugin using PostgreSQL for domain to directory lookups
212f83aac9SGlenn Strauss */
222f83aac9SGlenn Strauss
232f83aac9SGlenn Strauss typedef struct {
242f83aac9SGlenn Strauss PGconn *dbconn;
25601c572cSGlenn Strauss const buffer *sqlquery;
262f83aac9SGlenn Strauss } vhostdb_config;
272f83aac9SGlenn Strauss
282f83aac9SGlenn Strauss typedef struct {
292f83aac9SGlenn Strauss void *vdata;
302f83aac9SGlenn Strauss } plugin_config;
312f83aac9SGlenn Strauss
322f83aac9SGlenn Strauss typedef struct {
332f83aac9SGlenn Strauss PLUGIN_DATA;
342a281ec6SGlenn Strauss plugin_config defaults;
352f83aac9SGlenn Strauss plugin_config conf;
362f83aac9SGlenn Strauss } plugin_data;
372f83aac9SGlenn Strauss
mod_vhostdb_dbconf_free(void * vdata)382f83aac9SGlenn Strauss static void mod_vhostdb_dbconf_free (void *vdata)
392f83aac9SGlenn Strauss {
402f83aac9SGlenn Strauss vhostdb_config *dbconf = (vhostdb_config *)vdata;
412f83aac9SGlenn Strauss if (!dbconf) return;
422f83aac9SGlenn Strauss PQfinish(dbconf->dbconn);
432f83aac9SGlenn Strauss free(dbconf);
442f83aac9SGlenn Strauss }
452f83aac9SGlenn Strauss
mod_vhostdb_dbconf_setup(server * srv,const array * opts,void ** vdata)462a281ec6SGlenn Strauss static int mod_vhostdb_dbconf_setup (server *srv, const array *opts, void **vdata)
472f83aac9SGlenn Strauss {
48601c572cSGlenn Strauss const buffer *sqlquery = NULL;
492f83aac9SGlenn Strauss const char *dbname=NULL, *user=NULL, *pass=NULL, *host=NULL, *port=NULL;
502f83aac9SGlenn Strauss
512f83aac9SGlenn Strauss for (size_t i = 0; i < opts->used; ++i) {
522f83aac9SGlenn Strauss const data_string *ds = (data_string *)opts->data[i];
532f83aac9SGlenn Strauss if (ds->type == TYPE_STRING) {
54ad9b7e00SGlenn Strauss if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("sql"))) {
55601c572cSGlenn Strauss sqlquery = &ds->value;
56ad9b7e00SGlenn Strauss } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("dbname"))) {
57601c572cSGlenn Strauss dbname = ds->value.ptr;
58ad9b7e00SGlenn Strauss } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("user"))) {
59601c572cSGlenn Strauss user = ds->value.ptr;
60ad9b7e00SGlenn Strauss } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("password"))) {
61601c572cSGlenn Strauss pass = ds->value.ptr;
62ad9b7e00SGlenn Strauss } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("host"))) {
63601c572cSGlenn Strauss host = ds->value.ptr;
64ad9b7e00SGlenn Strauss } else if (buffer_is_equal_caseless_string(&ds->key, CONST_STR_LEN("port"))) {
65601c572cSGlenn Strauss port = ds->value.ptr;
662f83aac9SGlenn Strauss }
672f83aac9SGlenn Strauss }
682f83aac9SGlenn Strauss }
692f83aac9SGlenn Strauss
702f83aac9SGlenn Strauss /* required:
712f83aac9SGlenn Strauss * - sql (sql query)
722f83aac9SGlenn Strauss * - dbname
732f83aac9SGlenn Strauss * - user (unless dbname is a pgsql conninfo URI)
742f83aac9SGlenn Strauss *
752f83aac9SGlenn Strauss * optional:
762f83aac9SGlenn Strauss * - password, default: empty
772f83aac9SGlenn Strauss * - hostname
782f83aac9SGlenn Strauss * - port, default: 5432
792f83aac9SGlenn Strauss */
802f83aac9SGlenn Strauss
81af3df29aSGlenn Strauss if (NULL != sqlquery && !buffer_is_blank(sqlquery) && NULL != dbname) {
822f83aac9SGlenn Strauss vhostdb_config *dbconf;
832f83aac9SGlenn Strauss PGconn *dbconn = PQsetdbLogin(host,port,NULL,NULL,dbname,user,pass);
842f83aac9SGlenn Strauss if (NULL == dbconn) {
85010c2894SGlenn Strauss log_error(srv->errh, __FILE__, __LINE__,
862f83aac9SGlenn Strauss "PGsetdbLogin() failed, exiting...");
872f83aac9SGlenn Strauss return -1;
882f83aac9SGlenn Strauss }
892f83aac9SGlenn Strauss
902f83aac9SGlenn Strauss if (CONNECTION_OK != PQstatus(dbconn)) {
91010c2894SGlenn Strauss log_error(srv->errh, __FILE__, __LINE__,
922f83aac9SGlenn Strauss "Failed to login to database, exiting...");
932f83aac9SGlenn Strauss PQfinish(dbconn);
942f83aac9SGlenn Strauss return -1;
952f83aac9SGlenn Strauss }
962f83aac9SGlenn Strauss
972f83aac9SGlenn Strauss /* Postgres sets FD_CLOEXEC on database socket descriptors */
982f83aac9SGlenn Strauss
99*5e14db43SGlenn Strauss dbconf = (vhostdb_config *)ck_calloc(1, sizeof(*dbconf));
1002f83aac9SGlenn Strauss dbconf->dbconn = dbconn;
1012f83aac9SGlenn Strauss dbconf->sqlquery = sqlquery;
1022f83aac9SGlenn Strauss *vdata = dbconf;
1032f83aac9SGlenn Strauss }
1042f83aac9SGlenn Strauss
1052f83aac9SGlenn Strauss return 0;
1062f83aac9SGlenn Strauss }
1072f83aac9SGlenn Strauss
1087c7f8c46SGlenn Strauss static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p);
1092f83aac9SGlenn Strauss
mod_vhostdb_pgsql_query(request_st * const r,void * p_d,buffer * docroot)1107c7f8c46SGlenn Strauss static int mod_vhostdb_pgsql_query(request_st * const r, void *p_d, buffer *docroot)
1112f83aac9SGlenn Strauss {
1122f83aac9SGlenn Strauss plugin_data *p = (plugin_data *)p_d;
1132f83aac9SGlenn Strauss vhostdb_config *dbconf;
1142f83aac9SGlenn Strauss PGresult *res;
1152f83aac9SGlenn Strauss int cols, rows;
1162f83aac9SGlenn Strauss
1172f83aac9SGlenn Strauss /*(reuse buffer for sql query before generating docroot result)*/
1182f83aac9SGlenn Strauss buffer *sqlquery = docroot;
119f69bd9cdSGlenn Strauss buffer_clear(sqlquery); /*(also resets docroot (alias))*/
1202f83aac9SGlenn Strauss
1217c7f8c46SGlenn Strauss mod_vhostdb_patch_config(r, p);
1222f83aac9SGlenn Strauss if (NULL == p->conf.vdata) return 0; /*(after resetting docroot)*/
1232f83aac9SGlenn Strauss dbconf = (vhostdb_config *)p->conf.vdata;
1242f83aac9SGlenn Strauss
1252f83aac9SGlenn Strauss for (char *b = dbconf->sqlquery->ptr, *d; *b; b = d+1) {
1262f83aac9SGlenn Strauss if (NULL != (d = strchr(b, '?'))) {
1272f83aac9SGlenn Strauss /* escape the uri.authority */
1282f83aac9SGlenn Strauss size_t len;
1292f83aac9SGlenn Strauss int err;
1302f83aac9SGlenn Strauss buffer_append_string_len(sqlquery, b, (size_t)(d - b));
131af3df29aSGlenn Strauss buffer_string_prepare_append(sqlquery,
132af3df29aSGlenn Strauss buffer_clen(&r->uri.authority) * 2);
1332f83aac9SGlenn Strauss len = PQescapeStringConn(dbconf->dbconn,
134af3df29aSGlenn Strauss sqlquery->ptr + buffer_clen(sqlquery),
135af3df29aSGlenn Strauss BUF_PTR_LEN(&r->uri.authority), &err);
1362f83aac9SGlenn Strauss buffer_commit(sqlquery, len);
1372f83aac9SGlenn Strauss if (0 != err) return -1;
1382f83aac9SGlenn Strauss } else {
139af3df29aSGlenn Strauss d = dbconf->sqlquery->ptr + buffer_clen(dbconf->sqlquery);
1402f83aac9SGlenn Strauss buffer_append_string_len(sqlquery, b, (size_t)(d - b));
1412f83aac9SGlenn Strauss break;
1422f83aac9SGlenn Strauss }
1432f83aac9SGlenn Strauss }
1442f83aac9SGlenn Strauss
1452f83aac9SGlenn Strauss res = PQexec(dbconf->dbconn, sqlquery->ptr);
1462f83aac9SGlenn Strauss
147f69bd9cdSGlenn Strauss buffer_clear(docroot); /*(reset buffer to store result)*/
1482f83aac9SGlenn Strauss
1492f83aac9SGlenn Strauss if (PGRES_TUPLES_OK != PQresultStatus(res)) {
1507c7f8c46SGlenn Strauss log_error(r->conf.errh, __FILE__, __LINE__, "%s",
1512f83aac9SGlenn Strauss PQerrorMessage(dbconf->dbconn));
1522f83aac9SGlenn Strauss PQclear(res);
1532f83aac9SGlenn Strauss return -1;
1542f83aac9SGlenn Strauss }
1552f83aac9SGlenn Strauss
1562f83aac9SGlenn Strauss cols = PQnfields(res);
1572f83aac9SGlenn Strauss rows = PQntuples(res);
1582f83aac9SGlenn Strauss if (rows == 1 && cols >= 1) {
1592f83aac9SGlenn Strauss buffer_copy_string(docroot, PQgetvalue(res, 0, 0));
1602f83aac9SGlenn Strauss } /* else no such virtual host */
1612f83aac9SGlenn Strauss
1622f83aac9SGlenn Strauss PQclear(res);
1632f83aac9SGlenn Strauss return 0;
1642f83aac9SGlenn Strauss }
1652f83aac9SGlenn Strauss
1662f83aac9SGlenn Strauss
1672f83aac9SGlenn Strauss
1682f83aac9SGlenn Strauss
INIT_FUNC(mod_vhostdb_init)1692f83aac9SGlenn Strauss INIT_FUNC(mod_vhostdb_init) {
1702f83aac9SGlenn Strauss static http_vhostdb_backend_t http_vhostdb_backend_pgsql =
1712f83aac9SGlenn Strauss { "pgsql", mod_vhostdb_pgsql_query, NULL };
172*5e14db43SGlenn Strauss plugin_data *p = ck_calloc(1, sizeof(*p));
1732f83aac9SGlenn Strauss
1742f83aac9SGlenn Strauss /* register http_vhostdb_backend_pgsql */
1752f83aac9SGlenn Strauss http_vhostdb_backend_pgsql.p_d = p;
1762f83aac9SGlenn Strauss http_vhostdb_backend_set(&http_vhostdb_backend_pgsql);
1772f83aac9SGlenn Strauss
1782f83aac9SGlenn Strauss return p;
1792f83aac9SGlenn Strauss }
1802f83aac9SGlenn Strauss
FREE_FUNC(mod_vhostdb_cleanup)181b73949e0SGlenn Strauss FREE_FUNC(mod_vhostdb_cleanup) {
182b73949e0SGlenn Strauss plugin_data * const p = p_d;
1832a281ec6SGlenn Strauss if (NULL == p->cvlist) return;
1842a281ec6SGlenn Strauss /* (init i to 0 if global context; to 1 to skip empty global context) */
1852a281ec6SGlenn Strauss for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
1862a281ec6SGlenn Strauss config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
1872a281ec6SGlenn Strauss for (; -1 != cpv->k_id; ++cpv) {
1882a281ec6SGlenn Strauss if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
1892a281ec6SGlenn Strauss switch (cpv->k_id) {
1902a281ec6SGlenn Strauss case 0: /* vhostdb.<db> */
1912a281ec6SGlenn Strauss mod_vhostdb_dbconf_free(cpv->v.v);
1922a281ec6SGlenn Strauss break;
1932a281ec6SGlenn Strauss default:
1942a281ec6SGlenn Strauss break;
1952a281ec6SGlenn Strauss }
1962a281ec6SGlenn Strauss }
1972a281ec6SGlenn Strauss }
1982a281ec6SGlenn Strauss }
1992a281ec6SGlenn Strauss
mod_vhostdb_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)2002a281ec6SGlenn Strauss static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
2012a281ec6SGlenn Strauss switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
2022a281ec6SGlenn Strauss case 0: /* vhostdb.<db> */
2032a281ec6SGlenn Strauss if (cpv->vtype == T_CONFIG_LOCAL)
2042a281ec6SGlenn Strauss pconf->vdata = cpv->v.v;
2052a281ec6SGlenn Strauss break;
2062a281ec6SGlenn Strauss default:/* should not happen */
2072a281ec6SGlenn Strauss return;
2082a281ec6SGlenn Strauss }
2092a281ec6SGlenn Strauss }
2102f83aac9SGlenn Strauss
mod_vhostdb_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)2112a281ec6SGlenn Strauss static void mod_vhostdb_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
2122a281ec6SGlenn Strauss do {
2132a281ec6SGlenn Strauss mod_vhostdb_merge_config_cpv(pconf, cpv);
2142a281ec6SGlenn Strauss } while ((++cpv)->k_id != -1);
2152a281ec6SGlenn Strauss }
2162a281ec6SGlenn Strauss
mod_vhostdb_patch_config(request_st * const r,plugin_data * const p)2177c7f8c46SGlenn Strauss static void mod_vhostdb_patch_config(request_st * const r, plugin_data * const p) {
218cc2134c8SGlenn Strauss p->conf = p->defaults; /* copy small struct instead of memcpy() */
219cc2134c8SGlenn Strauss /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
2202a281ec6SGlenn Strauss for (int i = 1, used = p->nconfig; i < used; ++i) {
2217c7f8c46SGlenn Strauss if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
2222a281ec6SGlenn Strauss mod_vhostdb_merge_config(&p->conf,p->cvlist + p->cvlist[i].v.u2[0]);
2232a281ec6SGlenn Strauss }
2242a281ec6SGlenn Strauss }
2252a281ec6SGlenn Strauss
SETDEFAULTS_FUNC(mod_vhostdb_set_defaults)2262a281ec6SGlenn Strauss SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
2272a281ec6SGlenn Strauss static const config_plugin_keys_t cpk[] = {
2282a281ec6SGlenn Strauss { CONST_STR_LEN("vhostdb.pgsql"),
22903b4c993SGlenn Strauss T_CONFIG_ARRAY_KVSTRING,
2302a281ec6SGlenn Strauss T_CONFIG_SCOPE_CONNECTION }
2312a281ec6SGlenn Strauss ,{ NULL, 0,
2322a281ec6SGlenn Strauss T_CONFIG_UNSET,
2332a281ec6SGlenn Strauss T_CONFIG_SCOPE_UNSET }
2342f83aac9SGlenn Strauss };
2352f83aac9SGlenn Strauss
2362a281ec6SGlenn Strauss plugin_data * const p = p_d;
2372a281ec6SGlenn Strauss if (!config_plugin_values_init(srv, p, cpk, "mod_vhostdb_pgsql"))
2382a281ec6SGlenn Strauss return HANDLER_ERROR;
2392f83aac9SGlenn Strauss
2402a281ec6SGlenn Strauss /* process and validate config directives
2412a281ec6SGlenn Strauss * (init i to 0 if global context; to 1 to skip empty global context) */
2422a281ec6SGlenn Strauss for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
2432a281ec6SGlenn Strauss config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
2442a281ec6SGlenn Strauss for (; -1 != cpv->k_id; ++cpv) {
2452a281ec6SGlenn Strauss switch (cpv->k_id) {
2462a281ec6SGlenn Strauss case 0: /* vhostdb.<db> */
2472a281ec6SGlenn Strauss if (cpv->v.a->used) {
2482a281ec6SGlenn Strauss if (0 != mod_vhostdb_dbconf_setup(srv, cpv->v.a, &cpv->v.v))
249bd77abe0SGlenn Strauss return HANDLER_ERROR;
2502a281ec6SGlenn Strauss if (NULL != cpv->v.v)
2512a281ec6SGlenn Strauss cpv->vtype = T_CONFIG_LOCAL;
2522a281ec6SGlenn Strauss }
2532a281ec6SGlenn Strauss break;
2542a281ec6SGlenn Strauss default:/* should not happen */
2552a281ec6SGlenn Strauss break;
2562a281ec6SGlenn Strauss }
2572a281ec6SGlenn Strauss }
258bd77abe0SGlenn Strauss }
259bd77abe0SGlenn Strauss
2602a281ec6SGlenn Strauss /* initialize p->defaults from global config context */
2612a281ec6SGlenn Strauss if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
2622a281ec6SGlenn Strauss const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
2632a281ec6SGlenn Strauss if (-1 != cpv->k_id)
2642a281ec6SGlenn Strauss mod_vhostdb_merge_config(&p->defaults, cpv);
2652f83aac9SGlenn Strauss }
2662f83aac9SGlenn Strauss
2672f83aac9SGlenn Strauss return HANDLER_GO_ON;
2682f83aac9SGlenn Strauss }
2692f83aac9SGlenn Strauss
2702f83aac9SGlenn Strauss
271b82d7b8aSGlenn Strauss __attribute_cold__
2722f83aac9SGlenn Strauss int mod_vhostdb_pgsql_plugin_init (plugin *p);
mod_vhostdb_pgsql_plugin_init(plugin * p)2732f83aac9SGlenn Strauss int mod_vhostdb_pgsql_plugin_init (plugin *p)
2742f83aac9SGlenn Strauss {
2752f83aac9SGlenn Strauss p->version = LIGHTTPD_VERSION_ID;
276e2de4e58SGlenn Strauss p->name = "vhostdb_pgsql";
2772f83aac9SGlenn Strauss
2782f83aac9SGlenn Strauss p->init = mod_vhostdb_init;
2792f83aac9SGlenn Strauss p->cleanup = mod_vhostdb_cleanup;
2802f83aac9SGlenn Strauss p->set_defaults = mod_vhostdb_set_defaults;
2812f83aac9SGlenn Strauss
2822f83aac9SGlenn Strauss return 0;
2832f83aac9SGlenn Strauss }
284