]> granicus.if.org Git - pgbouncer/blob - src/client.c
Clarify few login-related log messages
[pgbouncer] / src / client.c
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  * Client connection handling
21  */
22
23 #include "bouncer.h"
24
25 static bool check_client_passwd(PgSocket *client, const char *passwd)
26 {
27         char md5[MD5_PASSWD_LEN + 1];
28         const char *correct;
29         PgUser *user = client->auth_user;
30
31         /* disallow empty passwords */
32         if (!*passwd || !*user->passwd)
33                 return false;
34
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;
48         }
49         return false;
50 }
51
52 bool set_pool(PgSocket *client, const char *dbname, const char *username)
53 {
54         PgDatabase *db;
55         PgUser *user;
56
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");
63                         return false;
64                 }
65                 else {
66                         slog_info(client, "registered new auto-database: db = %s", dbname );
67                 }
68         }
69
70         /* find user */
71         if (cf_auth_type == AUTH_ANY) {
72                 /* ignore requested user */
73                 user = NULL;
74
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;
79                 }
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");
86                         return false;
87                 }
88                 client->auth_user = user;
89         }
90
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;
98         }
99
100         return true;
101 }
102
103 static bool decide_startup_pool(PgSocket *client, PktHdr *pkt)
104 {
105         const char *username = NULL, *dbname = NULL;
106         const char *key, *val;
107
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;
115
116                 if (strcmp(key, "database") == 0)
117                         dbname = val;
118                 else if (strcmp(key, "user") == 0)
119                         username = val;
120                 else if (varcache_set(&client->vars, key, val))
121                         slog_debug(client, "got var: %s=%s", key, val);
122                 else if (strlist_contains(cf_ignore_startup_params, key)) {
123                         slog_debug(client, "ignoring startup parameter: %s=%s", key, val);
124                 } else {
125                         slog_warning(client, "unsupported startup parameter: %s=%s", key, val);
126                         disconnect_client(client, true, "Unknown startup parameter");
127                         return false;
128                 }
129         }
130         if (!username) {
131                 disconnect_client(client, true, "No username supplied");
132                 return false;
133         }
134         if (!dbname) {
135                 disconnect_client(client, true, "No database supplied");
136                 return false;
137         }
138
139         /* check if limit allows, dont limit admin db
140            nb: new incoming conn will be attached to PgSocket, thus
141            get_active_client_count() counts it */
142         if (get_active_client_count() > cf_max_client_conn) {
143                 if (strcmp(dbname, "pgbouncer") != 0) {
144                         disconnect_client(client, true, "no more connections allowed");
145                         return false;
146                 }
147         }
148
149         /* find pool and log about it */
150         if (set_pool(client, dbname, username)) {
151                 if (cf_log_connections)
152                         slog_info(client, "login attempt: db=%s user=%s", dbname, username);
153                 return true;
154         } else {
155                 if (cf_log_connections)
156                         slog_info(client, "login failed: db=%s user=%s", dbname, username);
157                 return false;
158         }
159 }
160
161 /* mask to get offset into valid_crypt_salt[] */
162 #define SALT_MASK  0x3F
163
164 static const char valid_crypt_salt[] =
165 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
166
167 static bool send_client_authreq(PgSocket *client)
168 {
169         uint8_t saltlen = 0;
170         int res;
171         int auth = cf_auth_type;
172         uint8_t randbuf[2];
173
174         if (auth == AUTH_CRYPT) {
175                 saltlen = 2;
176                 get_random_bytes(randbuf, saltlen);
177                 client->tmp_login_salt[0] = valid_crypt_salt[randbuf[0] & SALT_MASK];
178                 client->tmp_login_salt[1] = valid_crypt_salt[randbuf[1] & SALT_MASK];
179                 client->tmp_login_salt[2] = 0;
180         } else if (cf_auth_type == AUTH_MD5) {
181                 saltlen = 4;
182                 get_random_bytes((void*)client->tmp_login_salt, saltlen);
183         } else if (auth == AUTH_ANY)
184                 auth = AUTH_TRUST;
185
186         SEND_generic(res, client, 'R', "ib", auth, client->tmp_login_salt, saltlen);
187         return res;
188 }
189
190 /* decide on packets of client in login phase */
191 static bool handle_client_startup(PgSocket *client, PktHdr *pkt)
192 {
193         const char *passwd;
194
195         SBuf *sbuf = &client->sbuf;
196
197         /* don't tolerate partial packets */
198         if (incomplete_pkt(pkt)) {
199                 disconnect_client(client, true, "client sent partial pkt in startup phase");
200                 return false;
201         }
202
203         if (client->wait_for_welcome) {
204                 if  (finish_client_login(client)) {
205                         /* the packet was already parsed */
206                         sbuf_prepare_skip(sbuf, pkt->len);
207                         return true;
208                 } else
209                         return false;
210         }
211
212         switch (pkt->type) {
213         case PKT_SSLREQ:
214                 slog_noise(client, "C: req SSL");
215                 slog_noise(client, "P: nak");
216
217                 /* reject SSL attempt */
218                 if (!sbuf_answer(&client->sbuf, "N", 1)) {
219                         disconnect_client(client, false, "failed to nak SSL");
220                         return false;
221                 }
222                 break;
223         case PKT_STARTUP:
224                 if (client->pool) {
225                         disconnect_client(client, true, "client re-sent startup pkt");
226                         return false;
227                 }
228
229                 if (!decide_startup_pool(client, pkt))
230                         return false;
231
232                 if (client->pool->db->admin) {
233                         if (!admin_pre_login(client))
234                                 return false;
235                 }
236
237                 if (cf_auth_type <= AUTH_TRUST || client->own_user) {
238                         if (!finish_client_login(client))
239                                 return false;
240                 } else {
241                         if (!send_client_authreq(client)) {
242                                 disconnect_client(client, false, "failed to send auth req");
243                                 return false;
244                         }
245                 }
246                 break;
247         case 'p':               /* PasswordMessage */
248                 /* haven't requested it */
249                 if (cf_auth_type <= AUTH_TRUST) {
250                         disconnect_client(client, true, "unrequested passwd pkt");
251                         return false;
252                 }
253
254                 passwd = mbuf_get_string(&pkt->data);
255                 if (passwd && check_client_passwd(client, passwd)) {
256                         if (!finish_client_login(client))
257                                 return false;
258                 } else {
259                         disconnect_client(client, true, "Auth failed");
260                         return false;
261                 }
262                 break;
263         case PKT_CANCEL:
264                 if (mbuf_avail(&pkt->data) == BACKENDKEY_LEN) {
265                         const uint8_t *key = mbuf_get_bytes(&pkt->data, BACKENDKEY_LEN);
266                         memcpy(client->cancel_key, key, BACKENDKEY_LEN);
267                         accept_cancel_request(client);
268                 } else
269                         disconnect_client(client, false, "bad cancel request");
270                 return false;
271         default:
272                 disconnect_client(client, false, "bad packet");
273                 return false;
274         }
275         sbuf_prepare_skip(sbuf, pkt->len);
276         client->request_time = get_cached_time();
277         return true;
278 }
279
280 /* decide on packets of logged-in client */
281 static bool handle_client_work(PgSocket *client, PktHdr *pkt)
282 {
283         SBuf *sbuf = &client->sbuf;
284
285         switch (pkt->type) {
286
287         /* request immidiate response from server */
288         case 'H':               /* Flush */
289         case 'S':               /* Sync */
290
291         /* one-packet queries */
292         case 'Q':               /* Query */
293         case 'F':               /* FunctionCall */
294
295         /* copy end markers */
296         case 'c':               /* CopyDone(F/B) */
297         case 'f':               /* CopyFail(F/B) */
298
299         /*
300          * extended protocol allows server (and thus pooler)
301          * to buffer packets until sync or flush is sent by client
302          */
303         case 'P':               /* Parse */
304         case 'E':               /* Execute */
305         case 'C':               /* Close */
306         case 'B':               /* Bind */
307         case 'D':               /* Describe */
308         case 'd':               /* CopyData(F/B) */
309
310                 /* update stats */
311                 if (!client->query_start) {
312                         client->pool->stats.request_count++;
313                         client->query_start = get_cached_time();
314                 }
315
316                 if (client->pool->db->admin)
317                         return admin_handle_client(client, pkt);
318
319                 /* aquire server */
320                 if (!find_server(client))
321                         return false;
322
323                 client->pool->stats.client_bytes += pkt->len;
324
325                 /* tag the server as dirty */
326                 client->link->ready = 0;
327
328                 /* forward the packet */
329                 sbuf_prepare_send(sbuf, &client->link->sbuf, pkt->len);
330                 break;
331
332         /* client wants to go away */
333         default:
334                 slog_error(client, "unknown pkt from client: %d/0x%x", pkt->type, pkt->type);
335                 disconnect_client(client, true, "unknown pkt");
336                 return false;
337         case 'X': /* Terminate */
338                 disconnect_client(client, false, "client close request");
339                 return false;
340         }
341         return true;
342 }
343
344 /* callback from SBuf */
345 bool client_proto(SBuf *sbuf, SBufEvent evtype, MBuf *data)
346 {
347         bool res = false;
348         PgSocket *client = container_of(sbuf, PgSocket, sbuf);
349         PktHdr pkt;
350
351
352         Assert(!is_server_socket(client));
353         Assert(client->sbuf.sock);
354         Assert(client->state != CL_FREE);
355
356         /* may happen if close failed */
357         if (client->state == CL_JUSTFREE)
358                 return false;
359
360         switch (evtype) {
361         case SBUF_EV_CONNECT_OK:
362         case SBUF_EV_CONNECT_FAILED:
363                 /* ^ those should not happen */
364         case SBUF_EV_RECV_FAILED:
365                 disconnect_client(client, false, "client unexpected eof");
366                 break;
367         case SBUF_EV_SEND_FAILED:
368                 disconnect_server(client->link, false, "Server connection closed");
369                 break;
370         case SBUF_EV_READ:
371                 if (mbuf_avail(data) < NEW_HEADER_LEN && client->state != CL_LOGIN) {
372                         slog_noise(client, "C: got partial header, trying to wait a bit");
373                         return false;
374                 }
375
376                 if (!get_header(data, &pkt)) {
377                         disconnect_client(client, true, "bad packet header");
378                         return false;
379                 }
380                 slog_noise(client, "pkt='%c' len=%d", pkt_desc(&pkt), pkt.len);
381
382                 client->request_time = get_cached_time();
383                 switch (client->state) {
384                 case CL_LOGIN:
385                         res = handle_client_startup(client, &pkt);
386                         break;
387                 case CL_ACTIVE:
388                         if (client->wait_for_welcome)
389                                 res = handle_client_startup(client, &pkt);
390                         else
391                                 res = handle_client_work(client, &pkt);
392                         break;
393                 case CL_WAITING:
394                         fatal("why waiting client in client_proto()");
395                 default:
396                         fatal("bad client state: %d", client->state);
397                 }
398                 break;
399         case SBUF_EV_FLUSH:
400                 /* client is not interested in it */
401                 break;
402         case SBUF_EV_PKT_CALLBACK:
403                 /* unused ATM */
404                 break;
405         }
406         return res;
407 }
408