1 /*
2 * PgBouncer - Lightweight connection pooler for PostgreSQL.
3 *
4 * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * Config and auth file reading.
21 */
22
23 #include "bouncer.h"
24
25 #include <usual/fileutil.h>
26
27 /*
28 * ConnString parsing
29 */
30
31 /* just skip whitespace */
cstr_skip_ws(char * p)32 static char *cstr_skip_ws(char *p)
33 {
34 while (*p && *p == ' ')
35 p++;
36 return p;
37 }
38
39 /* parse parameter name before '=' */
cstr_get_key(char * p,char ** dst_p)40 static char *cstr_get_key(char *p, char **dst_p)
41 {
42 char *end;
43 p = cstr_skip_ws(p);
44 *dst_p = p;
45 while (*p && *p != '=' && *p != ' ')
46 p++;
47 end = p;
48 p = cstr_skip_ws(p);
49 /* fail if no '=' or empty name */
50 if (*p != '=' || *dst_p == end)
51 return NULL;
52 *end = 0;
53 return p + 1;
54 }
55
56 /* unquote the quoted value after first quote */
cstr_unquote_value(char * p)57 static char *cstr_unquote_value(char *p)
58 {
59 char *s = p;
60 while (1) {
61 if (!*p)
62 return NULL;
63 if (p[0] == '\'') {
64 if (p[1] == '\'')
65 p++;
66 else
67 break;
68 }
69 *s++ = *p++;
70 }
71 /* terminate actual value */
72 *s = 0;
73 /* return position after quote */
74 return p + 1;
75 }
76
77 /* parse value, possibly quoted */
cstr_get_value(char * p,char ** dst_p)78 static char *cstr_get_value(char *p, char **dst_p)
79 {
80 p = cstr_skip_ws(p);
81 if (*p == '\'') {
82 *dst_p = ++p;
83 p = cstr_unquote_value(p);
84 if (!p)
85 return NULL;
86 } else {
87 *dst_p = p;
88 while (*p && *p != ' ')
89 p++;
90 }
91 if (*p) {
92 /* if not EOL, cut value */
93 *p = 0;
94 p++;
95 }
96 /* disallow empty values */
97 if (*dst_p[0] == 0)
98 return NULL;
99 return p;
100 }
101
102 /*
103 * Get key=val pair from connstring. Returns position it stopped
104 * or NULL on error. EOF is signaled by *key = 0.
105 */
cstr_get_pair(char * p,char ** key_p,char ** val_p)106 static char * cstr_get_pair(char *p,
107 char **key_p,
108 char **val_p)
109 {
110 p = cstr_skip_ws(p);
111 *key_p = *val_p = p;
112 if (*p == 0)
113 return p;
114
115 /* read key */
116 p = cstr_get_key(p, key_p);
117 if (!p)
118 return NULL;
119
120 /* read value */
121 p = cstr_get_value(p, val_p);
122 if (!p)
123 return NULL;
124
125 log_noise("cstr_get_pair: \"%s\"=\"%s\"", *key_p, *val_p);
126
127 return cstr_skip_ws(p);
128 }
129
set_connect_query(PgDatabase * db,const char * new)130 static void set_connect_query(PgDatabase *db, const char *new)
131 {
132 const char *old = db->connect_query;
133 char *val = NULL;
134
135 if (old && new) {
136 if (strcmp(old, new) == 0)
137 return;
138 val = strdup(new);
139 if (val) {
140 free((void *)old);
141 db->connect_query = val;
142 }
143 } else if (new) {
144 val = strdup(new);
145 db->connect_query = val;
146 } else {
147 free((void *)db->connect_query);
148 db->connect_query = NULL;
149 }
150
151 if (new && !val)
152 log_error("no memory, cannot assign connect_query for %s", db->name);
153 }
154
set_autodb(const char * connstr)155 static bool set_autodb(const char *connstr)
156 {
157 char *tmp = strdup(connstr);
158 char *old = cf_autodb_connstr;
159
160 if (!tmp) {
161 log_error("no mem to change autodb_connstr");
162 return false;
163 }
164
165 cf_autodb_connstr = tmp;
166 if (old) {
167 if (strcmp(connstr, old) != 0)
168 tag_autodb_dirty();
169 free(old);
170 }
171
172 return true;
173 }
174
175 /* fill PgDatabase from connstr */
parse_database(void * base,const char * name,const char * connstr)176 bool parse_database(void *base, const char *name, const char *connstr)
177 {
178 char *p, *key, *val;
179 PktBuf *msg;
180 PgDatabase *db;
181 struct CfValue cv;
182 int pool_size = -1;
183 int min_pool_size = -1;
184 int res_pool_size = -1;
185 int max_db_connections = -1;
186 int dbname_ofs;
187 int pool_mode = POOL_INHERIT;
188
189 char *tmp_connstr;
190 const char *dbname = name;
191 char *host = NULL;
192 char *port = "5432";
193 char *username = NULL;
194 char *password = "";
195 char *auth_username = NULL;
196 char *client_encoding = NULL;
197 char *datestyle = NULL;
198 char *timezone = NULL;
199 char *connect_query = NULL;
200 char *appname = NULL;
201
202 int v_port;
203
204 cv.value_p = &pool_mode;
205 cv.extra = (const void *)pool_mode_map;
206
207 if (strcmp(name, "pgbouncer") == 0) {
208 log_error("database name \"%s\" is reserved", name);
209 return false;
210 }
211
212 if (strcmp(name, "*") == 0) {
213 return set_autodb(connstr);
214 }
215
216 tmp_connstr = strdup(connstr);
217 if (!tmp_connstr) {
218 log_error("out of memory");
219 return false;
220 }
221
222 p = tmp_connstr;
223 while (*p) {
224 p = cstr_get_pair(p, &key, &val);
225 if (p == NULL) {
226 log_error("syntax error in connection string");
227 goto fail;
228 } else if (!key[0]) {
229 break;
230 }
231
232 if (strcmp("dbname", key) == 0) {
233 dbname = val;
234 } else if (strcmp("host", key) == 0) {
235 host = val;
236 } else if (strcmp("port", key) == 0) {
237 port = val;
238 } else if (strcmp("user", key) == 0) {
239 username = val;
240 } else if (strcmp("password", key) == 0) {
241 password = val;
242 } else if (strcmp("auth_user", key) == 0) {
243 auth_username = val;
244 } else if (strcmp("client_encoding", key) == 0) {
245 client_encoding = val;
246 } else if (strcmp("datestyle", key) == 0) {
247 datestyle = val;
248 } else if (strcmp("timezone", key) == 0) {
249 timezone = val;
250 } else if (strcmp("pool_size", key) == 0) {
251 pool_size = atoi(val);
252 } else if (strcmp("min_pool_size", key) == 0) {
253 min_pool_size = atoi(val);
254 } else if (strcmp("reserve_pool", key) == 0) {
255 res_pool_size = atoi(val);
256 } else if (strcmp("max_db_connections", key) == 0) {
257 max_db_connections = atoi(val);
258 } else if (strcmp("pool_mode", key) == 0) {
259 if (!cf_set_lookup(&cv, val)) {
260 log_error("invalid pool mode: %s", val);
261 goto fail;
262 }
263 } else if (strcmp("connect_query", key) == 0) {
264 connect_query = val;
265 } else if (strcmp("application_name", key) == 0) {
266 appname = val;
267 } else {
268 log_error("unrecognized connection parameter: %s", key);
269 goto fail;
270 }
271 }
272
273 /* port= */
274 v_port = atoi(port);
275 if (v_port == 0) {
276 log_error("invalid port: %s", port);
277 goto fail;
278 }
279
280 db = add_database(name);
281 if (!db) {
282 log_error("cannot create database, no memory?");
283 goto fail;
284 }
285
286 /* host= */
287 if (host) {
288 host = strdup(host);
289 if (!host) {
290 log_error("failed to allocate host=");
291 goto fail;
292 }
293 }
294
295 /* tag the db as alive */
296 db->db_dead = false;
297 /* assuming not an autodb */
298 db->db_auto = false;
299 db->inactive_time = 0;
300
301 /* if updating old db, check if anything changed */
302 if (db->dbname) {
303 bool changed = false;
304 if (strcmp(db->dbname, dbname) != 0) {
305 changed = true;
306 } else if (!!host != !!db->host) {
307 changed = true;
308 } else if (host && strcmp(host, db->host) != 0) {
309 changed = true;
310 } else if (v_port != db->port) {
311 changed = true;
312 } else if (username && !db->forced_user) {
313 changed = true;
314 } else if (username && strcmp(username, db->forced_user->name) != 0) {
315 changed = true;
316 } else if (!username && db->forced_user) {
317 changed = true;
318 } else if ((db->connect_query && !connect_query)
319 || (!db->connect_query && connect_query)
320 || (connect_query && strcmp(connect_query, db->connect_query) != 0))
321 {
322 changed = true;
323 }
324 if (changed)
325 tag_database_dirty(db);
326 }
327
328 /* if pool_size < 0 it will be set later */
329 db->pool_size = pool_size;
330 db->min_pool_size = min_pool_size;
331 db->res_pool_size = res_pool_size;
332 db->pool_mode = pool_mode;
333 db->max_db_connections = max_db_connections;
334
335 if (db->host)
336 free(db->host);
337 db->host = host;
338 db->port = v_port;
339
340 /* assign connect_query */
341 set_connect_query(db, connect_query);
342
343 if (db->startup_params) {
344 msg = db->startup_params;
345 pktbuf_reset(msg);
346 } else {
347 msg = pktbuf_dynamic(128);
348 if (!msg)
349 die("out of memory");
350 db->startup_params = msg;
351 }
352
353 pktbuf_put_string(msg, "database");
354 dbname_ofs = msg->write_pos;
355 pktbuf_put_string(msg, dbname);
356
357 if (client_encoding) {
358 pktbuf_put_string(msg, "client_encoding");
359 pktbuf_put_string(msg, client_encoding);
360 }
361
362 if (datestyle) {
363 pktbuf_put_string(msg, "datestyle");
364 pktbuf_put_string(msg, datestyle);
365 }
366
367 if (timezone) {
368 pktbuf_put_string(msg, "timezone");
369 pktbuf_put_string(msg, timezone);
370 }
371
372 if (appname) {
373 pktbuf_put_string(msg, "application_name");
374 pktbuf_put_string(msg, appname);
375 }
376
377 if (auth_username != NULL) {
378 db->auth_user = find_user(auth_username);
379 if (!db->auth_user) {
380 db->auth_user = add_user(auth_username, "");
381 }
382 } else if (db->auth_user) {
383 db->auth_user = NULL;
384 }
385
386 /* if user is forced, create fake object for it */
387 if (username != NULL) {
388 if (!force_user(db, username, password))
389 log_warning("db setup failed, trying to continue");
390 } else if (db->forced_user) {
391 log_warning("losing forced user not supported,"
392 " keeping old setting");
393 }
394
395 /* remember dbname */
396 db->dbname = (char *)msg->buf + dbname_ofs;
397 free(tmp_connstr);
398 return true;
399 fail:
400 free(tmp_connstr);
401 return false;
402 }
403
parse_user(void * base,const char * name,const char * connstr)404 bool parse_user(void *base, const char *name, const char *connstr)
405 {
406 char *p, *key, *val, *tmp_connstr;
407 PgUser *user;
408 struct CfValue cv;
409 int pool_mode = POOL_INHERIT;
410 int max_user_connections = -1;
411
412
413 cv.value_p = &pool_mode;
414 cv.extra = (const void *)pool_mode_map;
415
416 tmp_connstr = strdup(connstr);
417 if (!tmp_connstr) {
418 log_error("out of memory");
419 return false;
420 }
421
422 p = tmp_connstr;
423 while (*p) {
424 p = cstr_get_pair(p, &key, &val);
425 if (p == NULL) {
426 log_error("syntax error in user settings");
427 goto fail;
428 } else if (!key[0]) {
429 break;
430 }
431
432 if (strcmp("pool_mode", key) == 0) {
433 if (!cf_set_lookup(&cv, val)) {
434 log_error("invalid pool mode: %s", val);
435 goto fail;
436 }
437 } else if (strcmp("max_user_connections", key) == 0) {
438 max_user_connections = atoi(val);
439 } else {
440 log_error("unrecognized user parameter: %s", key);
441 goto fail;
442 }
443 }
444
445 user = find_user(name);
446 if (!user) {
447 user = add_user(name, "");
448 if (!user) {
449 log_error("cannot create user, no memory?");
450 goto fail;
451 }
452 }
453
454 user->pool_mode = pool_mode;
455 user->max_user_connections = max_user_connections;
456
457 free(tmp_connstr);
458 return true;
459
460 fail:
461 free(tmp_connstr);
462 return false;
463 }
464
465 /*
466 * User file parsing
467 */
468
469 /* find next " in string, skipping escaped ones */
find_quote(char * p,bool start)470 static char *find_quote(char *p, bool start)
471 {
472 loop:
473 while (*p && *p != '"')
474 p++;
475 if (p[0] == '"' && p[1] == '"' && !start) {
476 p += 2;
477 goto loop;
478 }
479
480 return p;
481 }
482
483 /* string is unquoted while copying */
copy_quoted(char * dst,const char * src,int len)484 static void copy_quoted(char *dst, const char *src, int len)
485 {
486 char *end = dst + len - 1;
487 while (*src && dst < end) {
488 if (*src == '"')
489 src++;
490 *dst++ = *src++;
491 }
492 *dst = 0;
493 }
494
unquote_add_user(const char * username,const char * password)495 static void unquote_add_user(const char *username, const char *password)
496 {
497 char real_user[MAX_USERNAME];
498 char real_passwd[MAX_PASSWORD];
499 PgUser *user;
500
501 copy_quoted(real_user, username, sizeof(real_user));
502 copy_quoted(real_passwd, password, sizeof(real_passwd));
503
504 user = add_user(real_user, real_passwd);
505 if (!user)
506 log_warning("cannot create user, no memory");
507 }
508
auth_loaded(const char * fn)509 static bool auth_loaded(const char *fn)
510 {
511 static bool cache_set = false;
512 static struct stat cache;
513 struct stat cur;
514
515 /* no file specified */
516 if (fn == NULL) {
517 memset(&cache, 0, sizeof(cache));
518 cache_set = true;
519 return false;
520 }
521
522 if (stat(fn, &cur) < 0)
523 memset(&cur, 0, sizeof(cur));
524
525 if (cache_set && cache.st_dev == cur.st_dev
526 && cache.st_ino == cur.st_ino
527 && cache.st_mode == cur.st_mode
528 && cache.st_uid == cur.st_uid
529 && cache.st_gid == cur.st_gid
530 && cache.st_mtime == cur.st_mtime
531 && cache.st_size == cur.st_size)
532 return true;
533 cache = cur;
534 cache_set = true;
535 return false;
536 }
537
loader_users_check(void)538 bool loader_users_check(void)
539 {
540 if (auth_loaded(cf_auth_file))
541 return true;
542
543 return load_auth_file(cf_auth_file);
544 }
545
disable_users(void)546 static void disable_users(void)
547 {
548 PgUser *user;
549 struct List *item;
550
551 statlist_for_each(item, &user_list) {
552 user = container_of(item, PgUser, head);
553 user->passwd[0] = 0;
554 }
555 }
556
557 /* load list of users from auth_file */
load_auth_file(const char * fn)558 bool load_auth_file(const char *fn)
559 {
560 char *user, *password, *buf, *p;
561
562 /* No file to load? */
563 if (fn == NULL)
564 return false;
565
566 buf = load_file(fn, NULL);
567 if (buf == NULL) {
568 log_error("could not open auth_file %s: %s", fn, strerror(errno));
569 return false;
570 }
571
572 log_debug("loading auth_file: \"%s\"", fn);
573 disable_users();
574
575 p = buf;
576 while (*p) {
577 /* skip whitespace and empty lines */
578 while (*p && isspace(*p)) p++;
579 if (!*p)
580 break;
581
582 /* skip commented-out lines */
583 if (*p == ';') {
584 while (*p && *p != '\n') p++;
585 continue;
586 }
587
588 /* start of line */
589 if (*p != '"') {
590 log_error("broken auth file");
591 break;
592 }
593 user = ++p;
594 p = find_quote(p, false);
595 if (*p != '"') {
596 log_error("broken auth file");
597 break;
598 }
599 if (p - user >= MAX_USERNAME) {
600 log_error("username too long in auth file");
601 break;
602 }
603 *p++ = 0; /* tag username end */
604
605 /* get password */
606 p = find_quote(p, true);
607 if (*p != '"') {
608 log_error("broken auth file");
609 break;
610 }
611 password = ++p;
612 p = find_quote(p, false);
613 if (*p != '"') {
614 log_error("broken auth file");
615 break;
616 }
617 if (p - password >= MAX_PASSWORD) {
618 log_error("password too long in auth file");
619 break;
620 }
621 *p++ = 0; /* tag password end */
622
623 /* send them away */
624 unquote_add_user(user, password);
625
626 /* skip rest of the line */
627 while (*p && *p != '\n') p++;
628 }
629 free(buf);
630
631 return true;
632 }
633