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
mod_vhostdb_dbconf_free(void * vdata)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
mod_vhostdb_dbconf_setup(server * srv,const array * opts,void ** vdata)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 *)ck_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
mod_vhostdb_pgsql_query(request_st * const r,void * p_d,buffer * docroot)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
INIT_FUNC(mod_vhostdb_init)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 = ck_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
FREE_FUNC(mod_vhostdb_cleanup)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
mod_vhostdb_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)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
mod_vhostdb_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)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
mod_vhostdb_patch_config(request_st * const r,plugin_data * const p)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
SETDEFAULTS_FUNC(mod_vhostdb_set_defaults)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 __attribute_cold__
272 int mod_vhostdb_pgsql_plugin_init (plugin *p);
mod_vhostdb_pgsql_plugin_init(plugin * p)273 int mod_vhostdb_pgsql_plugin_init (plugin *p)
274 {
275 p->version = LIGHTTPD_VERSION_ID;
276 p->name = "vhostdb_pgsql";
277
278 p->init = mod_vhostdb_init;
279 p->cleanup = mod_vhostdb_cleanup;
280 p->set_defaults = mod_vhostdb_set_defaults;
281
282 return 0;
283 }
284