Apply fast-fail at connect time.
[pgbouncer.git] / src / client.c
blobe43ee128df91990047824bce1b617e23a9408ab0
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.
20 * Client connection handling
23 #include "bouncer.h"
25 static bool check_client_passwd(PgSocket *client, const char *passwd)
27 char md5[MD5_PASSWD_LEN + 1];
28 const char *correct;
29 PgUser *user = client->auth_user;
31 /* disallow empty passwords */
32 if (!*passwd || !*user->passwd)
33 return false;
35 switch (cf_auth_type) {
36 case AUTH_PLAIN:
37 return strcmp(user->passwd, passwd) == 0;
38 case AUTH_CRYPT:
39 correct = crypt(user->passwd, (char *)client->tmp_login_salt);
40 return correct && strcmp(correct, passwd) == 0;
41 case AUTH_MD5:
42 if (strlen(passwd) != MD5_PASSWD_LEN)
43 return false;
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;
49 return false;
52 bool set_pool(PgSocket *client, const char *dbname, const char *username)
54 PgDatabase *db;
55 PgUser *user;
57 /* find database */
58 db = find_database(dbname);
59 if (!db) {
60 db = register_auto_database(dbname);
61 if (!db) {
62 disconnect_client(client, true, "No such database: %s", dbname);
63 return false;
65 else {
66 slog_info(client, "registered new auto-database: db = %s", dbname );
70 /* find user */
71 if (cf_auth_type == AUTH_ANY) {
72 /* ignore requested user */
73 user = NULL;
75 if (db->forced_user == NULL) {
76 slog_error(client, "auth_type=any requires forced user");
77 disconnect_client(client, true, "bouncer config error");
78 return false;
80 client->auth_user = db->forced_user;
81 } else {
82 /* the user clients wants to log in as */
83 user = find_user(username);
84 if (!user) {
85 disconnect_client(client, true, "No such user: %s", username);
86 return false;
88 client->auth_user = user;
91 /* pool user may be forced */
92 if (db->forced_user)
93 user = db->forced_user;
94 client->pool = get_pool(db, user);
95 if (!client->pool) {
96 disconnect_client(client, true, "no memory for pool");
97 return false;
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;
108 while (1) {
109 key = mbuf_get_string(&pkt->data);
110 if (!key || *key == 0)
111 break;
112 val = mbuf_get_string(&pkt->data);
113 if (!val)
114 break;
116 if (strcmp(key, "database") == 0)
117 dbname = val;
118 else if (strcmp(key, "user") == 0)
119 username = val;
120 else if (strcmp(key, "application_name") == 0)
121 /* ignore */ ;
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);
126 } else {
127 slog_warning(client, "unsupported startup parameter: %s=%s", key, val);
128 disconnect_client(client, true, "Unsupported startup parameter: %s", key);
129 return false;
132 if (!username) {
133 disconnect_client(client, true, "No username supplied");
134 return false;
136 if (!dbname) {
137 disconnect_client(client, true, "No database supplied");
138 return false;
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");
147 return false;
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);
155 return true;
156 } else {
157 if (cf_log_connections)
158 slog_info(client, "login failed: db=%s user=%s", dbname, username);
159 return false;
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)
171 uint8_t saltlen = 0;
172 int res;
173 int auth = cf_auth_type;
174 uint8_t randbuf[2];
176 if (auth == AUTH_CRYPT) {
177 saltlen = 2;
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) {
183 saltlen = 4;
184 get_random_bytes((void*)client->tmp_login_salt, saltlen);
185 } else if (auth == AUTH_ANY)
186 auth = AUTH_TRUST;
188 SEND_generic(res, client, 'R', "ib", auth, client->tmp_login_salt, saltlen);
189 return res;
192 /* decide on packets of client in login phase */
193 static bool handle_client_startup(PgSocket *client, PktHdr *pkt)
195 const char *passwd;
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");
202 return false;
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);
209 return true;
210 } else
211 return false;
214 switch (pkt->type) {
215 case PKT_SSLREQ:
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");
222 return false;
224 break;
225 case PKT_STARTUP_V2:
226 disconnect_client(client, true, "Old V2 protocol not supported");
227 return false;
228 case PKT_STARTUP:
229 if (client->pool) {
230 disconnect_client(client, true, "client re-sent startup pkt");
231 return false;
234 if (!decide_startup_pool(client, pkt))
235 return false;
237 if (client->pool->db->admin) {
238 if (!admin_pre_login(client))
239 return false;
242 if (cf_auth_type <= AUTH_TRUST || client->own_user) {
243 if (!finish_client_login(client))
244 return false;
245 } else {
246 if (!send_client_authreq(client)) {
247 disconnect_client(client, false, "failed to send auth req");
248 return false;
251 break;
252 case 'p': /* PasswordMessage */
253 /* haven't requested it */
254 if (cf_auth_type <= AUTH_TRUST) {
255 disconnect_client(client, true, "unrequested passwd pkt");
256 return false;
259 passwd = mbuf_get_string(&pkt->data);
260 if (passwd && check_client_passwd(client, passwd)) {
261 if (!finish_client_login(client))
262 return false;
263 } else {
264 disconnect_client(client, true, "Auth failed");
265 return false;
267 break;
268 case PKT_CANCEL:
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);
273 } else
274 disconnect_client(client, false, "bad cancel request");
275 return false;
276 default:
277 disconnect_client(client, false, "bad packet");
278 return false;
280 sbuf_prepare_skip(sbuf, pkt->len);
281 client->request_time = get_cached_time();
282 return true;
285 /* decide on packets of logged-in client */
286 static bool handle_client_work(PgSocket *client, PktHdr *pkt)
288 SBuf *sbuf = &client->sbuf;
290 switch (pkt->type) {
292 /* request immidiate response from server */
293 case 'H': /* Flush */
294 case 'S': /* Sync */
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 */
311 case 'B': /* Bind */
312 case 'D': /* Describe */
313 case 'd': /* CopyData(F/B) */
315 /* update stats */
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);
324 /* aquire server */
325 if (!find_server(client))
326 return false;
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);
335 break;
337 /* client wants to go away */
338 default:
339 slog_error(client, "unknown pkt from client: %d/0x%x", pkt->type, pkt->type);
340 disconnect_client(client, true, "unknown pkt");
341 return false;
342 case 'X': /* Terminate */
343 disconnect_client(client, false, "client close request");
344 return false;
346 return true;
349 /* callback from SBuf */
350 bool client_proto(SBuf *sbuf, SBufEvent evtype, MBuf *data)
352 bool res = false;
353 PgSocket *client = container_of(sbuf, PgSocket, sbuf);
354 PktHdr pkt;
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)
363 return false;
365 switch (evtype) {
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");
371 break;
372 case SBUF_EV_SEND_FAILED:
373 disconnect_server(client->link, false, "Server connection closed");
374 break;
375 case SBUF_EV_READ:
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");
378 return false;
381 if (!get_header(data, &pkt)) {
382 disconnect_client(client, true, "bad packet header");
383 return false;
385 slog_noise(client, "pkt='%c' len=%d", pkt_desc(&pkt), pkt.len);
387 client->request_time = get_cached_time();
388 switch (client->state) {
389 case CL_LOGIN:
390 res = handle_client_startup(client, &pkt);
391 break;
392 case CL_ACTIVE:
393 if (client->wait_for_welcome)
394 res = handle_client_startup(client, &pkt);
395 else
396 res = handle_client_work(client, &pkt);
397 break;
398 case CL_WAITING:
399 fatal("why waiting client in client_proto()");
400 default:
401 fatal("bad client state: %d", client->state);
403 break;
404 case SBUF_EV_FLUSH:
405 /* client is not interested in it */
406 break;
407 case SBUF_EV_PKT_CALLBACK:
408 /* unused ATM */
409 break;
411 return res;