]> granicus.if.org Git - pgbouncer/commitdiff
auth_user support
authorCody Cutrer <cody@cutrer.us>
Wed, 18 Jan 2012 17:32:01 +0000 (10:32 -0700)
committerCody Cutrer <cody@instructure.com>
Mon, 7 Oct 2013 21:34:51 +0000 (15:34 -0600)
query the database's pg_shadow table for user information,
instead of using a local userlist.txt.  Brings transparent support
for Postgres 9.0+ which no longer has a compatible file format.

Tested with trust and md5 auth_types only.

13 files changed:
doc/config.txt
doc/todo.txt
doc/usage.txt
include/admin.h
include/bouncer.h
include/client.h
include/objects.h
src/admin.c
src/client.c
src/janitor.c
src/loader.c
src/objects.c
src/server.c

index 87ccb1b6d8fe10716e69ed0c9da75600afb23303..7e3c646307279b143170cca0ac173c9cf8e004bf 100644 (file)
@@ -527,6 +527,12 @@ for this database.
 Otherwise PgBouncer tries to log into the destination database with client
 username, meaning that there will be one pool per user.
 
+==== auth_user ====
+
+If +auth_user+ is set, any user not specified in auth_file will be
+queried from pg_shadow in the database using auth_user. Auth_user's
+password will be taken from auth_file.
+
 === Pool configuration ===
 
 ==== pool_size ====
@@ -610,7 +616,7 @@ So user `admin` with password `1234` will have MD5-hidden password
 === Minimal config ===
 
   [databases]
-  template1 = host=127.0.0.1 dbname=template1
+  template1 = host=127.0.0.1 dbname=template1 auth_user=someuser
 
   [pgbouncer]
   pool_mode = session
index 5724092704a48ee9ecae02ef89b3535d6f8a66b8..a8f6d3618dd711eb353cfb4f99eaa4625539c59e 100644 (file)
@@ -4,9 +4,6 @@
 
 Significant amount of users feel the need for those.
 
- * auth_conn - access to pg_shadow, so auth_file is not needed.
-   [ flat-text files are gone in 9.0+ ]
-
  * Protocol-level plan cache.
 
  * LISTEN/NOTIFY.  Requires strict SQL format.
index f554b1488b64df0d377b17b80e972b8da1874e1d..d0ec52deff30c01c2e4c07fe47038627711898bf 100644 (file)
@@ -149,7 +149,7 @@ database +pgbouncer+
 
 Only users listed in configuration parameters +admin_users+ or +stats_users+
 are allowed to login to the console.  (Except when `auth_mode=any`, then
-any user is allowed in as an admin.)
+any user is allowed in as a stats_user.)
 
 Additionally, the username +pgbouncer+ is allowed to log in without password,
 if the login comes via Unix socket and the client has same Unix user uid
index 9f0e338e4cedc5f621ff912dd438847a821ddd6d..188f255daa98207617efcf6dc7f90fba4439e685 100644 (file)
@@ -16,7 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 bool admin_handle_client(PgSocket *client, PktHdr *pkt)  _MUSTCHECK;
-bool admin_pre_login(PgSocket *client)  _MUSTCHECK;
+bool admin_pre_login(PgSocket *client, const char *username)  _MUSTCHECK;
+bool admin_post_login(PgSocket *client)  _MUSTCHECK;
 void admin_setup(void);
 bool admin_error(PgSocket *console, const char *fmt, ...)  _PRINTF(2, 3) /* _MUSTCHECK */;
 void admin_pause_done(void);
index 88f66f72054bd59fff65562d30140f65ec4b5cf7..bfeb0b28b20ed62f116f9b498d7021c42968990c 100644 (file)
@@ -49,6 +49,7 @@ enum SocketState {
        CL_JUSTFREE,            /* justfree_client_list */
        CL_LOGIN,               /* login_client_list */
        CL_WAITING,             /* pool->waiting_client_list */
+       CL_WAITING_LOGIN,       /*   - but return to CL_LOGIN instead of CL_ACTIVE */
        CL_ACTIVE,              /* pool->active_client_list */
        CL_CANCEL,              /* pool->cancel_req_list */
 
@@ -270,6 +271,7 @@ struct PgDatabase {
        struct PktBuf *startup_params; /* partial StartupMessage (without user) be sent to server */
 
        PgUser *forced_user;    /* if not NULL, the user/psw is forced */
+       PgUser *auth_user;      /* if not NULL, users not in userlist.txt will be looked up on the server */
 
        const char *host;       /* host or unix socket name */
        int port;
@@ -285,6 +287,8 @@ struct PgDatabase {
 
        usec_t inactive_time;   /* when auto-database became inactive (to kill it after timeout) */
        unsigned active_stamp;  /* set if autodb has connections */
+
+       struct AATree user_tree;        /* users that have been queried on this database */
 };
 
 
@@ -309,6 +313,8 @@ struct PgSocket {
        bool exec_on_connect:1; /* server: executing connect_query */
 
        bool wait_for_welcome:1;/* client: no server yet in pool, cannot send welcome msg */
+       bool wait_for_user_conn:1;/* client: waiting for auth_conn server connection */
+       bool wait_for_user:1;   /* client: waiting for auth_conn query results */
 
        bool suspended:1;       /* client/server: if the socket is suspended */
 
@@ -324,7 +330,10 @@ struct PgSocket {
        PgAddr remote_addr;     /* ip:port for remote endpoint */
        PgAddr local_addr;      /* ip:port for local endpoint */
 
-       struct DNSToken *dns_token;     /* ongoing request */
+       union {
+               struct DNSToken *dns_token;     /* ongoing request */
+               PgDatabase *db;                 /* cache db while doing auth query */
+       };
 
        VarCache vars;          /* state of interesting server parameters */
 
index aaa143144b3e75c0212e83e1bef1f8ede9e02c13..32535321a0b2ecdee558da34ff4f3418adfe9d92 100644 (file)
@@ -17,6 +17,5 @@
  */
 
 bool client_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *pkt)  _MUSTCHECK;
-bool set_pool(PgSocket *client, const char *dbname, const char *username) _MUSTCHECK;
-
-
+bool set_pool(PgSocket *client, const char *dbname, const char *username, bool takeover) _MUSTCHECK;
+bool handle_auth_response(PgSocket *client, PktHdr *pkt);
index 218244b5c297a6c0a3180dbab575e3e21f210934..37355650dbb97afef5413c2c86706d8a30e0d821 100644 (file)
@@ -44,6 +44,7 @@ void disconnect_client(PgSocket *client, bool notify, const char *reason, ...) _
 PgDatabase * add_database(const char *name) _MUSTCHECK;
 PgDatabase *register_auto_database(const char *name);
 PgUser * add_user(const char *name, const char *passwd) _MUSTCHECK;
+PgUser * add_db_user(PgDatabase *db, const char *name, const char *passwd) _MUSTCHECK;
 PgUser * force_user(PgDatabase *db, const char *username, const char *passwd) _MUSTCHECK;
 
 void accept_cancel_request(PgSocket *req);
index cb19476eec965cda78a48395bedecacd54a3198c..945669a99a675512abe175276a42ae6e4d545a88 100644 (file)
@@ -1316,12 +1316,11 @@ bool admin_handle_client(PgSocket *admin, PktHdr *pkt)
  * Client is unauthenticated, look if it wants to connect
  * to special "pgbouncer" user.
  */
-bool admin_pre_login(PgSocket *client)
+bool admin_pre_login(PgSocket *client, const char *username)
 {
        uid_t peer_uid = -1;
        gid_t peer_gid = -1;
        int res;
-       const char *username = client->auth_user->name;
 
        client->admin_user = 0;
        client->own_user = 0;
@@ -1332,6 +1331,7 @@ bool admin_pre_login(PgSocket *client)
                if (res >= 0 && peer_uid == getuid()
                        && strcmp("pgbouncer", username) == 0)
                {
+                       client->auth_user = admin_pool->db->forced_user;
                        client->own_user = 1;
                        client->admin_user = 1;
                        slog_info(client, "pgbouncer access from unix socket");
@@ -1341,21 +1341,33 @@ bool admin_pre_login(PgSocket *client)
 
        /*
         * auth_mode=any does not keep original username around,
-        * so username based checks do not work.
+        * so username based check has to take place here
         */
        if (cf_auth_type == AUTH_ANY) {
-               if (cf_log_connections)
-                       slog_info(client, "auth_mode=any: allowing anybody in as admin");
-               client->admin_user = 1;
-               return true;
+               if (strlist_contains(cf_admin_users, username)) {
+                       client->admin_user = 1;
+                       return true;
+               } else if (strlist_contains(cf_stats_users, username)) {
+                       return true;
+               }
        }
+       return false;
+}
+
+bool admin_post_login(PgSocket *client)
+{
+       const char *username = client->auth_user->name;
 
-       if (strlist_contains(cf_admin_users, username)) {
+       if (cf_auth_type == AUTH_ANY)
+               return true;
+
+       if (client->admin_user || strlist_contains(cf_admin_users, username)) {
                client->admin_user = 1;
                return true;
        } else if (strlist_contains(cf_stats_users, username)) {
                return true;
        }
+
        disconnect_client(client, true, "not allowed");
        return false;
 }
index 4a8faf5ce919960af96907d73169f16e118f2c46..4ea6d724b469ab753071549cbd6f609f4eb73ce5 100644 (file)
@@ -22,6 +22,8 @@
 
 #include "bouncer.h"
 
+#include <usual/pgutil.h>
+
 static const char *hdr2hex(const struct MBuf *data, char *buf, unsigned buflen)
 {
        const uint8_t *bin = data->data + data->read_pos;
@@ -58,17 +60,113 @@ static bool check_client_passwd(PgSocket *client, const char *passwd)
        return false;
 }
 
-bool set_pool(PgSocket *client, const char *dbname, const char *username)
+/* mask to get offset into valid_crypt_salt[] */
+#define SALT_MASK  0x3F
+
+static const char valid_crypt_salt[] =
+"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static bool send_client_authreq(PgSocket *client)
+{
+       uint8_t saltlen = 0;
+       int res;
+       int auth = cf_auth_type;
+       uint8_t randbuf[2];
+
+       if (auth == AUTH_CRYPT) {
+               saltlen = 2;
+               get_random_bytes(randbuf, saltlen);
+               client->tmp_login_salt[0] = valid_crypt_salt[randbuf[0] & SALT_MASK];
+               client->tmp_login_salt[1] = valid_crypt_salt[randbuf[1] & SALT_MASK];
+               client->tmp_login_salt[2] = 0;
+       } else if (cf_auth_type == AUTH_MD5) {
+               saltlen = 4;
+               get_random_bytes((void*)client->tmp_login_salt, saltlen);
+       } else if (auth == AUTH_ANY)
+               auth = AUTH_TRUST;
+
+       SEND_generic(res, client, 'R', "ib", auth, client->tmp_login_salt, saltlen);
+       return res;
+}
+
+static void start_auth_request(PgSocket *client, const char *username)
+{
+       int res;
+       char quoted_username[64], query[128];
+
+       client->auth_user = client->db->auth_user;
+       /* have to fetch user info from db */
+       client->pool = get_pool(client->db, client->db->auth_user);
+       if (!find_server(client)) {
+               client->wait_for_user_conn = true;
+               return;
+       }
+       slog_noise(client, "Doing auth_conn query");
+       client->wait_for_user_conn = false;
+       client->wait_for_user = true;
+       if (!sbuf_pause(&client->sbuf)) {
+               release_server(client->link);
+               disconnect_client(client, true, "pause failed");
+               return;
+       }
+       client->link->ready = 0;
+
+       pg_quote_literal(quoted_username, username, sizeof(quoted_username));
+       snprintf(query, sizeof(query), "SELECT usename, passwd FROM pg_shadow WHERE usename=%s", quoted_username);
+       SEND_generic(res, client->link, 'Q', "s", query);
+       if (!res)
+               disconnect_server(client->link, false, "unable to send login query");
+}
+
+static bool finish_set_pool(PgSocket *client, bool takeover)
 {
-       PgDatabase *db;
-       PgUser *user;
+       PgUser *user = client->auth_user;
+       /* pool user may be forced */
+       if (client->db->forced_user) {
+               user = client->db->forced_user;
+       }
+       client->pool = get_pool(client->db, user);
+       if (!client->pool) {
+               disconnect_client(client, true, "no memory for pool");
+               return false;
+       }
+
+       if (cf_log_connections)
+               slog_info(client, "login attempt: db=%s user=%s", client->db->name, client->auth_user->name);
+
+       if (!check_fast_fail(client))
+               return false;
+
+       if (takeover)
+               return true;
+
+       if (client->pool->db->admin) {
+               if (!admin_post_login(client))
+                       return false;
+       }
+
+       if (cf_auth_type <= AUTH_TRUST || client->own_user) {
+               if (!finish_client_login(client))
+                       return false;
+       } else {
+               if (!send_client_authreq(client)) {
+                       disconnect_client(client, false, "failed to send auth req");
+                       return false;
+               }
+       }
+       return true;
+}
 
+bool set_pool(PgSocket *client, const char *dbname, const char *username, bool takeover)
+{
        /* find database */
-       db = find_database(dbname);
-       if (!db) {
-               db = register_auto_database(dbname);
-               if (!db) {
+       client->db = find_database(dbname);
+       if (!client->db) {
+               client->db = register_auto_database(dbname);
+               if (!client->db) {
                        disconnect_client(client, true, "No such database: %s", dbname);
+                       if (cf_log_connections)
+                               slog_info(client, "login failed: db=%s user=%s", dbname, username);
                        return false;
                }
                else {
@@ -77,42 +175,128 @@ bool set_pool(PgSocket *client, const char *dbname, const char *username)
        }
 
        /* are new connections allowed? */
-       if (db->db_disabled) {
+       if (client->db->db_disabled) {
                disconnect_client(client, true, "database does not allow connections: %s", dbname);
                return false;
        }
 
+       if (client->db->admin) {
+               if (admin_pre_login(client, username))
+                       return finish_set_pool(client, takeover);
+       }
+
        /* find user */
        if (cf_auth_type == AUTH_ANY) {
                /* ignore requested user */
-               user = NULL;
-
-               if (db->forced_user == NULL) {
+               if (client->db->forced_user == NULL) {
                        slog_error(client, "auth_type=any requires forced user");
                        disconnect_client(client, true, "bouncer config error");
                        return false;
                }
-               client->auth_user = db->forced_user;
+               client->auth_user = client->db->forced_user;
        } else {
                /* the user clients wants to log in as */
-               user = find_user(username);
-               if (!user) {
+               client->auth_user = find_user(username);
+               if (!client->auth_user && client->db->auth_user) {
+                       if (takeover) {
+                               client->auth_user = add_db_user(client->db, username, "");
+                               return finish_set_pool(client, takeover);
+                       }
+                       start_auth_request(client, username);
+                       return false;
+               }
+               if (!client->auth_user) {
                        disconnect_client(client, true, "No such user: %s", username);
+                       if (cf_log_connections)
+                               slog_info(client, "login failed: db=%s user=%s", dbname, username);
                        return false;
                }
-               client->auth_user = user;
        }
+       return finish_set_pool(client, takeover);
+}
 
-       /* pool user may be forced */
-       if (db->forced_user)
-               user = db->forced_user;
-       client->pool = get_pool(db, user);
-       if (!client->pool) {
-               disconnect_client(client, true, "no memory for pool");
+bool handle_auth_response(PgSocket *client, PktHdr *pkt) {
+       uint16_t columns;
+       uint32_t length;
+       const char *username, *password;
+       PgUser user;
+
+       switch(pkt->type) {
+       case 'T':       /* RowDescription */
+               if (!mbuf_get_uint16be(&pkt->data, &columns)) {
+                       disconnect_server(client->link, false, "bad packet");
+                       return false;
+               }
+               if (columns != 2u) {
+                       disconnect_server(client->link, false, "expected 1 column from login query, not %hu", columns);
+                       return false;
+               }
+               break;
+       case 'D':       /* DataRow */
+               memset(&user, 0, sizeof(user));
+               if (!mbuf_get_uint16be(&pkt->data, &columns)) {
+                       disconnect_server(client->link, false, "bad packet");
+                       return false;
+               }
+               if (columns != 2u) {
+                       disconnect_server(client->link, false, "expected 1 column from login query, not %hu", columns);
+                       return false;
+               }
+               if (!mbuf_get_uint32be(&pkt->data, &length)) {
+                       disconnect_server(client->link, false, "bad packet");
+                       return false;
+               }
+               if (!mbuf_get_chars(&pkt->data, length, &username)) {
+                       disconnect_server(client->link, false, "bad packet");
+                       return false;
+               }
+               if (sizeof(user.name) - 1 < length)
+                       length = sizeof(user.name) - 1;
+               memcpy(user.name, username, length);
+               if (!mbuf_get_uint32be(&pkt->data, &length)) {
+                       disconnect_server(client->link, false, "bad packet");
+                       return false;
+               }
+               if (length == (uint32_t)-1) {
+                       // NULL - set an md5 password with an impossible value,
+                       // so that nothing will ever match
+                       password = "md5";
+                       length = 3;
+               } else {
+                       if (!mbuf_get_chars(&pkt->data, length, &password)) {
+                               disconnect_server(client->link, false, "bad packet");
+                               return false;
+                       }
+               }
+               if (sizeof(user.passwd)  - 1 < length)
+                       length = sizeof(user.passwd) - 1;
+               memcpy(user.passwd, password, length);
+
+               client->auth_user = add_db_user(client->db, user.name, user.passwd);
+               if (!client->auth_user) {
+                       disconnect_server(client->link, false, "unable to allocate new user for auth");
+                       return false;
+               }
+               break;
+       case 'C':       /* CommandComplete */
+               break;
+       case 'Z':       /* ReadyForQuery */
+               sbuf_prepare_skip(&client->link->sbuf, pkt->len);
+               if (!client->auth_user) {
+                       if (cf_log_connections)
+                               slog_info(client, "login failed: db=%s", client->db->name);
+                       disconnect_client(client, true, "No such user");
+               } else {
+                       slog_noise(client, "auth query complete");
+                       sbuf_continue(&client->sbuf);
+               }
+               return true;
+       default:
+               disconnect_server(client->link, false, "unexpected response from login query");
                return false;
        }
-
-       return check_fast_fail(client);
+       sbuf_prepare_skip(&client->link->sbuf, pkt->len);
+       return true;
 }
 
 static bool decide_startup_pool(PgSocket *client, PktHdr *pkt)
@@ -164,45 +348,8 @@ static bool decide_startup_pool(PgSocket *client, PktHdr *pkt)
                }
        }
 
-       /* find pool and log about it */
-       if (set_pool(client, dbname, username)) {
-               if (cf_log_connections)
-                       slog_info(client, "login attempt: db=%s user=%s", dbname, username);
-               return true;
-       } else {
-               if (cf_log_connections)
-                       slog_info(client, "login failed: db=%s user=%s", dbname, username);
-               return false;
-       }
-}
-
-/* mask to get offset into valid_crypt_salt[] */
-#define SALT_MASK  0x3F
-
-static const char valid_crypt_salt[] =
-"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-static bool send_client_authreq(PgSocket *client)
-{
-       uint8_t saltlen = 0;
-       int res;
-       int auth = cf_auth_type;
-       uint8_t randbuf[2];
-
-       if (auth == AUTH_CRYPT) {
-               saltlen = 2;
-               get_random_bytes(randbuf, saltlen);
-               client->tmp_login_salt[0] = valid_crypt_salt[randbuf[0] & SALT_MASK];
-               client->tmp_login_salt[1] = valid_crypt_salt[randbuf[1] & SALT_MASK];
-               client->tmp_login_salt[2] = 0;
-       } else if (cf_auth_type == AUTH_MD5) {
-               saltlen = 4;
-               get_random_bytes((void*)client->tmp_login_salt, saltlen);
-       } else if (auth == AUTH_ANY)
-               auth = AUTH_TRUST;
-
-       SEND_generic(res, client, 'R', "ib", auth, client->tmp_login_salt, saltlen);
-       return res;
+       /* find pool */
+       return set_pool(client, dbname, username, false);
 }
 
 /* decide on packets of client in login phase */
@@ -244,28 +391,19 @@ static bool handle_client_startup(PgSocket *client, PktHdr *pkt)
                disconnect_client(client, true, "Old V2 protocol not supported");
                return false;
        case PKT_STARTUP:
-               if (client->pool) {
+               if (client->pool && !client->wait_for_user_conn && !client->wait_for_user) {
                        disconnect_client(client, true, "client re-sent startup pkt");
                        return false;
                }
 
-               if (!decide_startup_pool(client, pkt))
-                       return false;
-
-               if (client->pool->db->admin) {
-                       if (!admin_pre_login(client))
+               if (client->wait_for_user) {
+                       client->wait_for_user = false;
+                       if (!finish_set_pool(client, false))
                                return false;
+               } else if (!decide_startup_pool(client, pkt)) {
+                       return false;
                }
 
-               if (cf_auth_type <= AUTH_TRUST || client->own_user) {
-                       if (!finish_client_login(client))
-                               return false;
-               } else {
-                       if (!send_client_authreq(client)) {
-                               disconnect_client(client, false, "failed to send auth req");
-                               return false;
-                       }
-               }
                break;
        case 'p':               /* PasswordMessage */
                /* haven't requested it */
index 26c7c57fd1a05d42dff235217a2af1131180bc77..b51fbdd5965b039fa805f8e5292b7dcd2246af87 100644 (file)
@@ -331,7 +331,7 @@ static void pool_client_maint(PgPool *pool)
        if (cf_query_timeout > 0 || cf_query_wait_timeout > 0) {
                statlist_for_each_safe(item, &pool->waiting_client_list, tmp) {
                        client = container_of(item, PgSocket, head);
-                       Assert(client->state == CL_WAITING);
+                       Assert(client->state == CL_WAITING || client->state == CL_WAITING_LOGIN);
                        if (client->query_start == 0) {
                                age = now - client->request_time;
                                //log_warning("query_start==0");
@@ -649,6 +649,7 @@ static void kill_database(PgDatabase *db)
                statlist_remove(&autodatabase_idle_list, &db->head);
        else
                statlist_remove(&database_list, &db->head);
+       aatree_destroy(&db->user_tree);
        slab_free(db_cache, db);
 }
 
index 870ddf8b879bfc234c595e1a276ded9fb4c33830..6b83774c3dc9e676efe7974758c7a1b2484fd253 100644 (file)
@@ -189,6 +189,7 @@ bool parse_database(void *base, const char *name, const char *connstr)
        char *port = "5432";
        char *username = NULL;
        char *password = "";
+       char *auth_username = NULL;
        char *client_encoding = NULL;
        char *datestyle = NULL;
        char *timezone = NULL;
@@ -228,6 +229,8 @@ bool parse_database(void *base, const char *name, const char *connstr)
                        username = val;
                else if (strcmp("password", key) == 0)
                        password = val;
+               else if (strcmp("auth_user", key) == 0)
+                       auth_username = val;
                else if (strcmp("client_encoding", key) == 0)
                        client_encoding = val;
                else if (strcmp("datestyle", key) == 0)
@@ -357,6 +360,15 @@ bool parse_database(void *base, const char *name, const char *connstr)
                pktbuf_put_string(msg, appname);
        }
 
+       if (auth_username != NULL) {
+               db->auth_user = find_user(auth_username);
+               if (!db->auth_user) {
+                       db->auth_user = add_user(auth_username, "");
+               }
+       } else if (db->auth_user) {
+               db->auth_user = NULL;
+       }
+
        /* if user is forces, create fake object for it */
        if (username != NULL) {
                if (!force_user(db, username, password))
index b48f7740040981d2e58468550db8740a14a21f82..1bac9ce24b70ea594fe9d28885920d286176f606 100644 (file)
@@ -94,6 +94,13 @@ static int user_node_cmp(uintptr_t userptr, struct AANode *node)
        return strcmp(name, user->name);
 }
 
+/* destroy PgUser, for usage with btree */
+static void user_node_release(struct AANode *node, void *arg)
+{
+       PgUser *user = container_of(node, PgUser, tree_node);
+       slab_free(user_cache, user);
+}
+
 /* initialization before config loading */
 void init_objects(void)
 {
@@ -133,8 +140,13 @@ void change_client_state(PgSocket *client, SocketState newstate)
                statlist_remove(&justfree_client_list, &client->head);
                break;
        case CL_LOGIN:
+               if (newstate == CL_WAITING)
+                       newstate = CL_WAITING_LOGIN;
                statlist_remove(&login_client_list, &client->head);
                break;
+       case CL_WAITING_LOGIN:
+               if (newstate == CL_ACTIVE)
+                       newstate = CL_LOGIN;
        case CL_WAITING:
                statlist_remove(&pool->waiting_client_list, &client->head);
                break;
@@ -163,6 +175,7 @@ void change_client_state(PgSocket *client, SocketState newstate)
                statlist_append(&login_client_list, &client->head);
                break;
        case CL_WAITING:
+       case CL_WAITING_LOGIN:
                statlist_append(&pool->waiting_client_list, &client->head);
                break;
        case CL_ACTIVE:
@@ -308,6 +321,7 @@ PgDatabase *add_database(const char *name)
                        slab_free(db_cache, db);
                        return NULL;
                }
+               aatree_init(&db->user_tree, user_node_cmp, user_node_release);
                put_in_order(&db->head, &database_list, cmp_database);
        }
 
@@ -367,6 +381,31 @@ PgUser *add_user(const char *name, const char *passwd)
        return user;
 }
 
+/* add or update db users */
+PgUser *add_db_user(PgDatabase *db, const char *name, const char *passwd)
+{
+       PgUser *user = NULL;
+       struct AANode *node;
+
+       node = aatree_search(&db->user_tree, (uintptr_t)name);
+       user = node ? container_of(node, PgUser, tree_node) : NULL;
+
+       if (user == NULL) {
+               user = slab_alloc(user_cache);
+               if (!user)
+                       return NULL;
+
+               list_init(&user->head);
+               list_init(&user->pool_list);
+               safe_strcpy(user->name, name, sizeof(user->name));
+
+               aatree_insert(&db->user_tree, (uintptr_t)user->name, &user->tree_node);
+               user->pool_mode = POOL_INHERIT;
+       }
+       safe_strcpy(user->passwd, passwd, sizeof(user->passwd));
+       return user;
+}
+
 /* create separate user object for storing server user info */
 PgUser *force_user(PgDatabase *db, const char *name, const char *passwd)
 {
@@ -472,7 +511,7 @@ PgPool *get_pool(PgDatabase *db, PgUser *user)
 /* deactivate socket and put into wait queue */
 static void pause_client(PgSocket *client)
 {
-       Assert(client->state == CL_ACTIVE);
+       Assert(client->state == CL_ACTIVE || client->state == CL_LOGIN);
 
        slog_debug(client, "pause_client");
        change_client_state(client, CL_WAITING);
@@ -483,7 +522,7 @@ static void pause_client(PgSocket *client)
 /* wake client from wait */
 void activate_client(PgSocket *client)
 {
-       Assert(client->state == CL_WAITING);
+       Assert(client->state == CL_WAITING || client->state == CL_WAITING_LOGIN);
 
        slog_debug(client, "activate_client");
        change_client_state(client, CL_ACTIVE);
@@ -531,7 +570,7 @@ bool find_server(PgSocket *client)
        bool res;
        bool varchange = false;
 
-       Assert(client->state == CL_ACTIVE);
+       Assert(client->state == CL_ACTIVE || client->state == CL_LOGIN);
 
        if (client->link)
                return true;
@@ -799,6 +838,7 @@ void disconnect_client(PgSocket *client, bool notify, const char *reason, ...)
                }
        case CL_LOGIN:
        case CL_WAITING:
+       case CL_WAITING_LOGIN:
        case CL_CANCEL:
                break;
        default:
@@ -1158,7 +1198,7 @@ bool use_client_socket(int fd, PgAddr *addr,
                return false;
        client->suspended = 1;
 
-       if (!set_pool(client, dbname, username))
+       if (!set_pool(client, dbname, username, true))
                return false;
 
        change_client_state(client, CL_ACTIVE);
index f21ba7af393f51f1c22aec0b3cb7e95a694059c4..9a1e3e96bf20a225701e75eeefe78facca3eb770 100644 (file)
@@ -300,15 +300,19 @@ static bool handle_server_work(PgSocket *server, PktHdr *pkt)
                Assert(client);
                sbuf_prepare_skip(sbuf, pkt->len);
        } else if (client) {
-               sbuf_prepare_send(sbuf, &client->sbuf, pkt->len);
-               if (ready && client->query_start) {
-                       usec_t total;
-                       total = get_cached_time() - client->query_start;
-                       client->query_start = 0;
-                       server->pool->stats.query_time += total;
-                       slog_debug(client, "query time: %d us", (int)total);
-               } else if (ready) {
-                       slog_warning(client, "FIXME: query end, but query_start == 0");
+               if (client->state == CL_LOGIN) {
+                       return handle_auth_response(client, pkt);
+               } else {
+                       sbuf_prepare_send(sbuf, &client->sbuf, pkt->len);
+                       if (ready && client->query_start) {
+                               usec_t total;
+                               total = get_cached_time() - client->query_start;
+                               client->query_start = 0;
+                               server->pool->stats.query_time += total;
+                               slog_debug(client, "query time: %d us", (int)total);
+                       } else if (ready) {
+                               slog_warning(client, "FIXME: query end, but query_start == 0");
+                       }
                }
        } else {
                if (server->state != SV_TESTED)