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