xref: /lighttpd1.4/src/mod_vhostdb_pgsql.c (revision 5e14db43)
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