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  * Operations with server config parameters.
21  */
22 
23 #include "bouncer.h"
24 
25 #include <usual/pgutil.h>
26 
27 struct var_lookup {
28 	const char *name;
29 	enum VarCacheIdx idx;
30 };
31 
32 static const struct var_lookup lookup [] = {
33  {"client_encoding",             VClientEncoding },
34  {"DateStyle",                   VDateStyle },
35  {"TimeZone",                    VTimeZone },
36  {"standard_conforming_strings", VStdStr },
37  {"application_name",            VAppName },
38  {NULL},
39 };
40 
41 static struct StrPool *vpool;
42 
get_value(VarCache * cache,const struct var_lookup * lk)43 static inline struct PStr *get_value(VarCache *cache, const struct var_lookup *lk)
44 {
45 	return cache->var_list[lk->idx];
46 }
47 
varcache_set(VarCache * cache,const char * key,const char * value)48 bool varcache_set(VarCache *cache, const char *key, const char *value)
49 {
50 	const struct var_lookup *lk;
51 	struct PStr *pstr = NULL;
52 
53 	if (!vpool) {
54 		vpool = strpool_create(USUAL_ALLOC);
55 		if (!vpool)
56 			return false;
57 	}
58 
59 	for (lk = lookup; lk->name; lk++) {
60 		if (strcasecmp(lk->name, key) == 0)
61 			goto set_value;
62 	}
63 	return false;
64 
65 set_value:
66 	/* drop old value */
67 	strpool_decref(cache->var_list[lk->idx]);
68 	cache->var_list[lk->idx] = NULL;
69 
70 	/* NULL value? */
71 	if (!value)
72 		return false;
73 
74 	/* set new value */
75 	pstr = strpool_get(vpool, value, strlen(value));
76 	if (!pstr)
77 		return false;
78 	cache->var_list[lk->idx] = pstr;
79 	return true;
80 }
81 
apply_var(PktBuf * pkt,const char * key,const struct PStr * cval,const struct PStr * sval)82 static int apply_var(PktBuf *pkt, const char *key,
83 		     const struct PStr *cval,
84 		     const struct PStr *sval)
85 {
86 	char buf[128];
87 	char qbuf[128];
88 	unsigned len;
89 
90 	/* if unset, skip */
91 	if (!cval || !sval || !*cval->str)
92 		return 0;
93 
94 	/* if equal, skip */
95 	if (cval == sval)
96 		return 0;
97 
98 	/* ignore case difference */
99 	if (strcasecmp(cval->str, sval->str) == 0)
100 		return 0;
101 
102 	/* the string may have been taken from startup pkt */
103 	if (!pg_quote_literal(qbuf, cval->str, sizeof(qbuf)))
104 		return 0;
105 
106 	/* add SET statement to packet */
107 	len = snprintf(buf, sizeof(buf), "SET %s=%s;", key, qbuf);
108 	if (len < sizeof(buf)) {
109 		pktbuf_put_bytes(pkt, buf, len);
110 		return 1;
111 	} else {
112 		log_warning("got too long value, skipping");
113 		return 0;
114 	}
115 }
116 
varcache_apply(PgSocket * server,PgSocket * client,bool * changes_p)117 bool varcache_apply(PgSocket *server, PgSocket *client, bool *changes_p)
118 {
119 	int changes = 0;
120 	struct PStr *cval, *sval;
121 	const struct var_lookup *lk;
122 	int sql_ofs;
123 	struct PktBuf *pkt = pktbuf_temp();
124 
125 	pktbuf_start_packet(pkt, 'Q');
126 
127 	/* grab query position inside pkt */
128 	sql_ofs = pktbuf_written(pkt);
129 
130 	for (lk = lookup; lk->name; lk++) {
131 		sval = get_value(&server->vars, lk);
132 		cval = get_value(&client->vars, lk);
133 		changes += apply_var(pkt, lk->name, cval, sval);
134 	}
135 	*changes_p = changes > 0;
136 	if (!changes)
137 		return true;
138 
139 	pktbuf_put_char(pkt, 0);
140 	pktbuf_finish_packet(pkt);
141 
142 	slog_debug(server, "varcache_apply: %s", pkt->buf + sql_ofs);
143 	return pktbuf_send_immediate(pkt, server);
144 }
145 
varcache_fill_unset(VarCache * src,PgSocket * dst)146 void varcache_fill_unset(VarCache *src, PgSocket *dst)
147 {
148 	struct PStr *srcval, *dstval;
149 	const struct var_lookup *lk;
150 	for (lk = lookup; lk->name; lk++) {
151 		srcval = src->var_list[lk->idx];
152 		dstval = dst->vars.var_list[lk->idx];
153 		if (!dstval) {
154 			strpool_incref(srcval);
155 			dst->vars.var_list[lk->idx] = srcval;
156 		}
157 	}
158 }
159 
varcache_clean(VarCache * cache)160 void varcache_clean(VarCache *cache)
161 {
162 	int i;
163 	for (i = 0; i < NumVars; i++) {
164 		strpool_decref(cache->var_list[i]);
165 		cache->var_list[i] = NULL;
166 	}
167 }
168 
varcache_add_params(PktBuf * pkt,VarCache * vars)169 void varcache_add_params(PktBuf *pkt, VarCache *vars)
170 {
171 	struct PStr *val;
172 	const struct var_lookup *lk;
173 	for (lk = lookup; lk->name; lk++) {
174 		val = vars->var_list[lk->idx];
175 		if (val)
176 			pktbuf_write_ParameterStatus(pkt, lk->name, val->str);
177 	}
178 }
179 
varcache_deinit(void)180 void varcache_deinit(void)
181 {
182 	strpool_free(vpool);
183 	vpool = NULL;
184 }
185