2 * PgBouncer - Lightweight connection pooler for PostgreSQL.
4 * Copyright (c) 2007-2009 Marko Kreen, Skype Technologies OÜ
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.
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.
20 * Client connection handling
25 static bool check_client_passwd(PgSocket
*client
, const char *passwd
)
27 char md5
[MD5_PASSWD_LEN
+ 1];
29 PgUser
*user
= client
->auth_user
;
31 /* disallow empty passwords */
32 if (!*passwd
|| !*user
->passwd
)
35 switch (cf_auth_type
) {
37 return strcmp(user
->passwd
, passwd
) == 0;
39 correct
= crypt(user
->passwd
, (char *)client
->tmp_login_salt
);
40 return correct
&& strcmp(correct
, passwd
) == 0;
42 if (strlen(passwd
) != MD5_PASSWD_LEN
)
44 if (!isMD5(user
->passwd
))
45 pg_md5_encrypt(user
->passwd
, user
->name
, strlen(user
->name
), user
->passwd
);
46 pg_md5_encrypt(user
->passwd
+ 3, (char *)client
->tmp_login_salt
, 4, md5
);
47 return strcmp(md5
, passwd
) == 0;
52 bool set_pool(PgSocket
*client
, const char *dbname
, const char *username
)
58 db
= find_database(dbname
);
60 db
= register_auto_database(dbname
);
62 disconnect_client(client
, true, "No such database: %s", dbname
);
66 slog_info(client
, "registered new auto-database: db = %s", dbname
);
71 if (cf_auth_type
== AUTH_ANY
) {
72 /* ignore requested user */
75 if (db
->forced_user
== NULL
) {
76 slog_error(client
, "auth_type=any requires forced user");
77 disconnect_client(client
, true, "bouncer config error");
80 client
->auth_user
= db
->forced_user
;
82 /* the user clients wants to log in as */
83 user
= find_user(username
);
85 disconnect_client(client
, true, "No such user: %s", username
);
88 client
->auth_user
= user
;
91 /* pool user may be forced */
93 user
= db
->forced_user
;
94 client
->pool
= get_pool(db
, user
);
96 disconnect_client(client
, true, "no memory for pool");
100 return check_fast_fail(client
);
103 static bool decide_startup_pool(PgSocket
*client
, PktHdr
*pkt
)
105 const char *username
= NULL
, *dbname
= NULL
;
106 const char *key
, *val
;
109 key
= mbuf_get_string(&pkt
->data
);
110 if (!key
|| *key
== 0)
112 val
= mbuf_get_string(&pkt
->data
);
116 if (strcmp(key
, "database") == 0)
118 else if (strcmp(key
, "user") == 0)
120 else if (strcmp(key
, "application_name") == 0)
122 else if (varcache_set(&client
->vars
, key
, val
))
123 slog_debug(client
, "got var: %s=%s", key
, val
);
124 else if (strlist_contains(cf_ignore_startup_params
, key
)) {
125 slog_debug(client
, "ignoring startup parameter: %s=%s", key
, val
);
127 slog_warning(client
, "unsupported startup parameter: %s=%s", key
, val
);
128 disconnect_client(client
, true, "Unsupported startup parameter: %s", key
);
133 disconnect_client(client
, true, "No username supplied");
137 disconnect_client(client
, true, "No database supplied");
141 /* check if limit allows, dont limit admin db
142 nb: new incoming conn will be attached to PgSocket, thus
143 get_active_client_count() counts it */
144 if (get_active_client_count() > cf_max_client_conn
) {
145 if (strcmp(dbname
, "pgbouncer") != 0) {
146 disconnect_client(client
, true, "no more connections allowed");
151 /* find pool and log about it */
152 if (set_pool(client
, dbname
, username
)) {
153 if (cf_log_connections
)
154 slog_info(client
, "login attempt: db=%s user=%s", dbname
, username
);
157 if (cf_log_connections
)
158 slog_info(client
, "login failed: db=%s user=%s", dbname
, username
);
163 /* mask to get offset into valid_crypt_salt[] */
164 #define SALT_MASK 0x3F
166 static const char valid_crypt_salt
[] =
167 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
169 static bool send_client_authreq(PgSocket
*client
)
173 int auth
= cf_auth_type
;
176 if (auth
== AUTH_CRYPT
) {
178 get_random_bytes(randbuf
, saltlen
);
179 client
->tmp_login_salt
[0] = valid_crypt_salt
[randbuf
[0] & SALT_MASK
];
180 client
->tmp_login_salt
[1] = valid_crypt_salt
[randbuf
[1] & SALT_MASK
];
181 client
->tmp_login_salt
[2] = 0;
182 } else if (cf_auth_type
== AUTH_MD5
) {
184 get_random_bytes((void*)client
->tmp_login_salt
, saltlen
);
185 } else if (auth
== AUTH_ANY
)
188 SEND_generic(res
, client
, 'R', "ib", auth
, client
->tmp_login_salt
, saltlen
);
192 /* decide on packets of client in login phase */
193 static bool handle_client_startup(PgSocket
*client
, PktHdr
*pkt
)
197 SBuf
*sbuf
= &client
->sbuf
;
199 /* don't tolerate partial packets */
200 if (incomplete_pkt(pkt
)) {
201 disconnect_client(client
, true, "client sent partial pkt in startup phase");
205 if (client
->wait_for_welcome
) {
206 if (finish_client_login(client
)) {
207 /* the packet was already parsed */
208 sbuf_prepare_skip(sbuf
, pkt
->len
);
216 slog_noise(client
, "C: req SSL");
217 slog_noise(client
, "P: nak");
219 /* reject SSL attempt */
220 if (!sbuf_answer(&client
->sbuf
, "N", 1)) {
221 disconnect_client(client
, false, "failed to nak SSL");
226 disconnect_client(client
, true, "Old V2 protocol not supported");
230 disconnect_client(client
, true, "client re-sent startup pkt");
234 if (!decide_startup_pool(client
, pkt
))
237 if (client
->pool
->db
->admin
) {
238 if (!admin_pre_login(client
))
242 if (cf_auth_type
<= AUTH_TRUST
|| client
->own_user
) {
243 if (!finish_client_login(client
))
246 if (!send_client_authreq(client
)) {
247 disconnect_client(client
, false, "failed to send auth req");
252 case 'p': /* PasswordMessage */
253 /* haven't requested it */
254 if (cf_auth_type
<= AUTH_TRUST
) {
255 disconnect_client(client
, true, "unrequested passwd pkt");
259 passwd
= mbuf_get_string(&pkt
->data
);
260 if (passwd
&& check_client_passwd(client
, passwd
)) {
261 if (!finish_client_login(client
))
264 disconnect_client(client
, true, "Auth failed");
269 if (mbuf_avail(&pkt
->data
) == BACKENDKEY_LEN
) {
270 const uint8_t *key
= mbuf_get_bytes(&pkt
->data
, BACKENDKEY_LEN
);
271 memcpy(client
->cancel_key
, key
, BACKENDKEY_LEN
);
272 accept_cancel_request(client
);
274 disconnect_client(client
, false, "bad cancel request");
277 disconnect_client(client
, false, "bad packet");
280 sbuf_prepare_skip(sbuf
, pkt
->len
);
281 client
->request_time
= get_cached_time();
285 /* decide on packets of logged-in client */
286 static bool handle_client_work(PgSocket
*client
, PktHdr
*pkt
)
288 SBuf
*sbuf
= &client
->sbuf
;
292 /* request immidiate response from server */
293 case 'H': /* Flush */
296 /* one-packet queries */
297 case 'Q': /* Query */
298 case 'F': /* FunctionCall */
300 /* copy end markers */
301 case 'c': /* CopyDone(F/B) */
302 case 'f': /* CopyFail(F/B) */
305 * extended protocol allows server (and thus pooler)
306 * to buffer packets until sync or flush is sent by client
308 case 'P': /* Parse */
309 case 'E': /* Execute */
310 case 'C': /* Close */
312 case 'D': /* Describe */
313 case 'd': /* CopyData(F/B) */
316 if (!client
->query_start
) {
317 client
->pool
->stats
.request_count
++;
318 client
->query_start
= get_cached_time();
321 if (client
->pool
->db
->admin
)
322 return admin_handle_client(client
, pkt
);
325 if (!find_server(client
))
328 client
->pool
->stats
.client_bytes
+= pkt
->len
;
330 /* tag the server as dirty */
331 client
->link
->ready
= 0;
333 /* forward the packet */
334 sbuf_prepare_send(sbuf
, &client
->link
->sbuf
, pkt
->len
);
337 /* client wants to go away */
339 slog_error(client
, "unknown pkt from client: %d/0x%x", pkt
->type
, pkt
->type
);
340 disconnect_client(client
, true, "unknown pkt");
342 case 'X': /* Terminate */
343 disconnect_client(client
, false, "client close request");
349 /* callback from SBuf */
350 bool client_proto(SBuf
*sbuf
, SBufEvent evtype
, MBuf
*data
)
353 PgSocket
*client
= container_of(sbuf
, PgSocket
, sbuf
);
357 Assert(!is_server_socket(client
));
358 Assert(client
->sbuf
.sock
);
359 Assert(client
->state
!= CL_FREE
);
361 /* may happen if close failed */
362 if (client
->state
== CL_JUSTFREE
)
366 case SBUF_EV_CONNECT_OK
:
367 case SBUF_EV_CONNECT_FAILED
:
368 /* ^ those should not happen */
369 case SBUF_EV_RECV_FAILED
:
370 disconnect_client(client
, false, "client unexpected eof");
372 case SBUF_EV_SEND_FAILED
:
373 disconnect_server(client
->link
, false, "Server connection closed");
376 if (mbuf_avail(data
) < NEW_HEADER_LEN
&& client
->state
!= CL_LOGIN
) {
377 slog_noise(client
, "C: got partial header, trying to wait a bit");
381 if (!get_header(data
, &pkt
)) {
382 disconnect_client(client
, true, "bad packet header");
385 slog_noise(client
, "pkt='%c' len=%d", pkt_desc(&pkt
), pkt
.len
);
387 client
->request_time
= get_cached_time();
388 switch (client
->state
) {
390 res
= handle_client_startup(client
, &pkt
);
393 if (client
->wait_for_welcome
)
394 res
= handle_client_startup(client
, &pkt
);
396 res
= handle_client_work(client
, &pkt
);
399 fatal("why waiting client in client_proto()");
401 fatal("bad client state: %d", client
->state
);
405 /* client is not interested in it */
407 case SBUF_EV_PKT_CALLBACK
: