/* -*-pgsql-c-*- */ /* * $Header$ * * pgpool: a language independent connection pool server for PostgreSQL * written by Tatsuo Ishii * * Copyright (c) 2003-2013 PgPool Global Development Group * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that copyright notice and this permission * notice appear in supporting documentation, and that the name of the * author not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior * permission. The author makes no representations about the * suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * pool_auth.c: authentication stuff * */ #include "pool.h" #include "pool_stream.h" #include "pool_config.h" #include "pool_passwd.h" #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_PARAM_H #include #endif #include #include #include #define AUTHFAIL_ERRORCODE "28000" static POOL_STATUS pool_send_backend_key_data(POOL_CONNECTION *frontend, int pid, int key, int protoMajor); static int do_clear_text_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); static void pool_send_auth_fail(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp); static int do_crypt(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); static int do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); //static int send_md5auth_request(POOL_CONNECTION *frontend, int protoMajor, char *salt); //static int read_password_packet(POOL_CONNECTION *frontend, int protoMajor, char *password, int *pwdSize); static int send_password_packet(POOL_CONNECTION *backend, int protoMajor, char *password); static int send_auth_ok(POOL_CONNECTION *frontend, int protoMajor); /* * After sending the start up packet to the backend, do the * authentication against backend. if success return 0 otherwise non * 0. */ int pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) { signed char kind; int pid; int key; int protoMajor; int length; int authkind; int i; StartupPacket *sp; protoMajor = MAJOR(cp); kind = pool_read_kind(cp); if (kind < 0) { return -1; } /* error response? */ if (kind == 'E') { /* we assume error response at this stage is likely version * protocol mismatch (v3 frontend vs. v2 backend). So we throw * a V2 protocol error response in the hope that v3 frontend * will negotiate again using v2 protocol. */ pool_log("pool_do_auth: maybe protocol version mismatch (current version %d)", protoMajor); ErrorResponse(frontend, cp); return -1; } else if (kind != 'R') { pool_error("pool_do_auth: expect \"R\" got %c", kind); return -1; } /* * message length (v3 only) */ if (protoMajor == PROTO_MAJOR_V3 && pool_read_message_length(cp) < 0) { pool_error("Failed to read the authentication packet length. \ This is likely caused by the inconsistency of auth method among DB nodes. \ In this case you can check the previous error messages (hint: length field) \ from pool_read_message_length and recheck the pg_hba.conf settings."); return -1; } /* * read authentication request kind. * * 0: authentication ok * 1: kerberos v4 * 2: kerberos v5 * 3: clear text password * 4: crypt password * 5: md5 password * 6: scm credential * * in replication mode, we only support kind = 0, 3. this is because "salt" * cannot be replicated. * in non replication mode, we support kind = 0, 3, 4, 5 */ authkind = pool_read_int(cp); if (authkind < 0) { pool_error("pool_do_auth: read auth kind failed"); return -1; } authkind = ntohl(authkind); pool_debug("pool_do_auth: auth kind:%d", authkind); /* trust? */ if (authkind == 0) { int msglen; pool_write(frontend, "R", 1); if (protoMajor == PROTO_MAJOR_V3) { msglen = htonl(8); pool_write(frontend, &msglen, sizeof(msglen)); } msglen = htonl(0); if (pool_write_and_flush(frontend, &msglen, sizeof(msglen)) < 0) { return -1; } MASTER(cp)->auth_kind = 0; } /* clear text password authentication? */ else if (authkind == 3) { for (i=0;iauth_method != uaMD5 && !RAW_MODE && NUM_BACKENDS > 1) { pool_send_error_message(frontend, protoMajor, AUTHFAIL_ERRORCODE, "MD5 authentication is unsupported in replication, master-slave and parallel modes.", "", "check pg_hba.conf", __FILE__, __LINE__); return -1; } for (i=0;isp; pid = -1; for (i=0;iinfo[i]:%p pid:%u", &cp->info[i], ntohl(pid)); CONNECTION_SLOT(cp, i)->pid = cp->info[i].pid = pid; /* read key */ if (pool_read(CONNECTION(cp, i), &key, sizeof(key)) < 0) { pool_error("pool_do_auth: failed to read key in slot %d", i); return -1; } CONNECTION_SLOT(cp, i)->key = cp->info[i].key = key; cp->info[i].major = sp->major; cp->info[i].minor = sp->minor; strlcpy(cp->info[i].database, sp->database, sizeof(cp->info[i].database)); strlcpy(cp->info[i].user, sp->user, sizeof(cp->info[i].user)); cp->info[i].counter = 1; } } if (pid == -1) { pool_error("pool_do_auth: all backends are down"); return -1; } return pool_send_backend_key_data(frontend, pid, key, protoMajor); } /* * do re-authentication for reused connection. if success return 0 otherwise non 0. */ int pool_do_reauth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) { int status; int protoMajor; protoMajor = MAJOR(cp); switch(MASTER(cp)->auth_kind) { case 0: /* trust */ status = 0; break; case 3: /* clear text password */ status = do_clear_text_password(MASTER(cp), frontend, 1, protoMajor); break; case 4: /* crypt password */ status = do_crypt(MASTER(cp), frontend, 1, protoMajor); break; case 5: /* md5 password */ status = do_md5(MASTER(cp), frontend, 1, protoMajor); break; default: pool_error("pool_do_reauth: unknown authentication request code %d", MASTER(cp)->auth_kind); return -1; } if (status == 0) { int msglen; pool_write(frontend, "R", 1); if (protoMajor == PROTO_MAJOR_V3) { msglen = htonl(8); pool_write(frontend, &msglen, sizeof(msglen)); } msglen = htonl(0); if (pool_write_and_flush(frontend, &msglen, sizeof(msglen)) < 0) { return -1; } } else { pool_debug("pool_do_reauth: authentication failed"); pool_send_auth_fail(frontend, cp); return -1; } return (pool_send_backend_key_data(frontend, MASTER_CONNECTION(cp)->pid, MASTER_CONNECTION(cp)->key, protoMajor) != POOL_CONTINUE); } /* * send authentication failure message text to frontend */ static void pool_send_auth_fail(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) { int messagelen; char *errmessage; int protoMajor; bool send_error_to_frontend = true; protoMajor = MAJOR(cp); messagelen = strlen(MASTER_CONNECTION(cp)->sp->user) + 100; if ((errmessage = (char *)malloc(messagelen+1)) == NULL) { pool_error("pool_send_auth_fail_failed: malloc failed: %s", strerror(errno)); child_exit(1); } snprintf(errmessage, messagelen, "password authentication failed for user \"%s\"", MASTER_CONNECTION(cp)->sp->user); if (send_error_to_frontend) pool_send_fatal_message(frontend, protoMajor, "XX000", errmessage, "", "", __FILE__, __LINE__); free(errmessage); } /* * Send backend key data to frontend. if success return 0 otherwise non 0. */ static POOL_STATUS pool_send_backend_key_data(POOL_CONNECTION *frontend, int pid, int key, int protoMajor) { char kind; int len; /* Send backend key data */ kind = 'K'; pool_write(frontend, &kind, 1); if (protoMajor == PROTO_MAJOR_V3) { len = htonl(12); pool_write(frontend, &len, sizeof(len)); } pool_debug("pool_send_auth_ok: send pid %d to frontend", ntohl(pid)); pool_write(frontend, &pid, sizeof(pid)); if (pool_write_and_flush(frontend, &key, sizeof(key)) < 0) { return -1; } return 0; } /* * perform clear text password authentication */ static int do_clear_text_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) { static int size; static char password[MAX_PASSWORD_SIZE]; char response; int kind; int len; /* master? */ if (IS_MASTER_NODE_ID(backend->db_node_id)) { pool_write(frontend, "R", 1); /* authentication */ if (protoMajor == PROTO_MAJOR_V3) { len = htonl(8); pool_write(frontend, &len, sizeof(len)); } kind = htonl(3); /* clear text password authentication */ pool_write_and_flush(frontend, &kind, sizeof(kind)); /* indicating clear text password authentication */ /* read password packet */ if (protoMajor == PROTO_MAJOR_V2) { if (pool_read(frontend, &size, sizeof(size))) { pool_debug("do_clear_text_password: failed to read password packet size"); return -1; } } else { char k; if (pool_read(frontend, &k, sizeof(k))) { pool_debug("do_clear_text_password: failed to read password packet \"p\""); return -1; } if (k != 'p') { pool_error("do_clear_text_password: password packet does not start with \"p\""); return -1; } if (pool_read(frontend, &size, sizeof(size))) { pool_error("do_clear_text_password: failed to read password packet size"); return -1; } } if ((ntohl(size) - 4) > sizeof(password)) { pool_error("do_clear_text_password: password is too long (size: %d)", ntohl(size) - 4); return -1; } if (pool_read(frontend, password, ntohl(size) - 4)) { pool_error("do_clear_text_password: failed to read password (size: %d)", ntohl(size) - 4); return -1; } } /* connection reusing? */ if (reauth) { if ((ntohl(size) - 4) != backend->pwd_size) { pool_debug("do_clear_text_password; password size does not match in re-authentication"); return -1; } if (memcmp(password, backend->password, backend->pwd_size) != 0) { pool_debug("do_clear_text_password; password does not match in re-authentication"); return -1; } return 0; } /* send password packet to backend */ if (protoMajor == PROTO_MAJOR_V3) pool_write(backend, "p", 1); pool_write(backend, &size, sizeof(size)); pool_write_and_flush(backend, password, ntohl(size) -4); if (pool_read(backend, &response, sizeof(response))) { pool_error("do_clear_text_password: failed to read authentication response"); return -1; } if (response != 'R') { pool_debug("do_clear_text_password: backend does not return R while processing clear text password authentication"); return -1; } if (protoMajor == PROTO_MAJOR_V3) { if (pool_read(backend, &len, sizeof(len))) { pool_error("do_clear_text_password: failed to read authentication packet size"); return -1; } if (ntohl(len) != 8) { pool_error("do_clear_text_password: incorrect authentication packet size (%d)", ntohl(len)); return -1; } } /* expect to read "Authentication OK" response. kind should be 0... */ if (pool_read(backend, &kind, sizeof(kind))) { pool_debug("do_clear_text_password: failed to read Authentication OK response"); return -1; } /* if authenticated, save info */ if (!reauth && kind == 0) { if (IS_MASTER_NODE_ID(backend->db_node_id)) { int msglen; pool_write(frontend, "R", 1); if (protoMajor == PROTO_MAJOR_V3) { msglen = htonl(8); pool_write(frontend, &msglen, sizeof(msglen)); } msglen = htonl(0); if (pool_write_and_flush(frontend, &msglen, sizeof(msglen)) < 0) { return -1; } } backend->auth_kind = 3; backend->pwd_size = ntohl(size) - 4; memcpy(backend->password, password, backend->pwd_size); } return kind; } /* * perform crypt authentication */ static int do_crypt(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) { char salt[2]; static int size; static char password[MAX_PASSWORD_SIZE]; char response; int kind; int len; if (!reauth) { /* read salt */ if (pool_read(backend, salt, sizeof(salt))) { pool_error("do_crypt: failed to read salt"); return -1; } } else { memcpy(salt, backend->salt, sizeof(salt)); } /* master? */ if (IS_MASTER_NODE_ID(backend->db_node_id)) { pool_write(frontend, "R", 1); /* authentication */ if (protoMajor == PROTO_MAJOR_V3) { len = htonl(10); pool_write(frontend, &len, sizeof(len)); } kind = htonl(4); /* crypt authentication */ pool_write(frontend, &kind, sizeof(kind)); /* indicating crypt authentication */ pool_write_and_flush(frontend, salt, sizeof(salt)); /* salt */ /* read password packet */ if (protoMajor == PROTO_MAJOR_V2) { if (pool_read(frontend, &size, sizeof(size))) { pool_error("do_crypt: failed to read password packet size"); return -1; } } else { char k; if (pool_read(frontend, &k, sizeof(k))) { pool_debug("do_crypt_password: failed to read password packet \"p\""); return -1; } if (k != 'p') { pool_error("do_crypt_password: password packet does not start with \"p\""); return -1; } if (pool_read(frontend, &size, sizeof(size))) { pool_error("do_crypt_password: failed to read password packet size"); return -1; } } if ((ntohl(size) - 4) > sizeof(password)) { pool_error("do_crypt: password is too long(size: %d)", ntohl(size) - 4); return -1; } if (pool_read(frontend, password, ntohl(size) - 4)) { pool_error("do_crypt: failed to read password (size: %d)", ntohl(size) - 4); return -1; } } /* connection reusing? */ if (reauth) { pool_debug("size: %d saved_size: %d", (ntohl(size) - 4), backend->pwd_size); if ((ntohl(size) - 4) != backend->pwd_size) { pool_debug("do_crypt: password size does not match in re-authentication"); return -1; } if (memcmp(password, backend->password, backend->pwd_size) != 0) { pool_debug("do_crypt: password does not match in re-authentication"); return -1; } return 0; } /* send password packet to backend */ if (protoMajor == PROTO_MAJOR_V3) pool_write(backend, "p", 1); pool_write(backend, &size, sizeof(size)); pool_write_and_flush(backend, password, ntohl(size) -4); if (pool_read(backend, &response, sizeof(response))) { pool_error("do_crypt: failed to read authentication response"); return -1; } if (response != 'R') { pool_debug("do_crypt: backend does not return R while processing crypt authentication(%02x) DB node id: %d", response, backend->db_node_id); return -1; } if (protoMajor == PROTO_MAJOR_V3) { if (pool_read(backend, &len, sizeof(len))) { pool_error("do_crypt: failed to read authentication packet size"); return -1; } if (ntohl(len) != 8) { pool_error("do_crypt: incorrect authentication packet size (%d)", ntohl(len)); return -1; } } /* expect to read "Authentication OK" response. kind should be 0... */ if (pool_read(backend, &kind, sizeof(kind))) { pool_debug("do_crypt: failed to read Authentication OK response"); return -1; } /* if authenticated, save info */ if (!reauth && kind == 0) { int msglen; pool_write(frontend, "R", 1); if (protoMajor == PROTO_MAJOR_V3) { msglen = htonl(8); pool_write(frontend, &msglen, sizeof(msglen)); } msglen = htonl(0); if (pool_write_and_flush(frontend, &msglen, sizeof(msglen)) < 0) { return -1; } backend->auth_kind = 4; backend->pwd_size = ntohl(size) - 4; memcpy(backend->password, password, backend->pwd_size); memcpy(backend->salt, salt, sizeof(salt)); } return kind; } /* * perform MD5 authentication */ static int do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) { char salt[4]; static int size; static char password[MAX_PASSWORD_SIZE]; int kind; char encbuf[POOL_PASSWD_LEN+1]; char *pool_passwd = NULL; if (NUM_BACKENDS > 1) { /* Read password entry from pool_passwd */ pool_passwd = pool_get_passwd(frontend->username); if (!pool_passwd) { pool_debug("do_md5: %s does not exist in pool_passwd", frontend->username); return -1; } /* master? */ if (IS_MASTER_NODE_ID(backend->db_node_id)) { /* Send md5 auth request to frontend with my own salt */ pool_random_salt(salt); if (send_md5auth_request(frontend, protoMajor, salt)) { pool_error("do_md5: send_md5auth_request failed"); return -1; } /* Read password packet */ if (read_password_packet(frontend, protoMajor, password, &size)) { pool_debug("do_md5: read_password_packet failed"); return -1; } /* Check the password using my salt + pool_passwd */ pg_md5_encrypt(pool_passwd+strlen("md5"), salt, sizeof(salt), encbuf); if (strcmp(password, encbuf)) { /* Password does not match */ pool_debug("password does not match: frontend:%s pgpool:%s", password, encbuf); return -1; } } kind = 0; if (!reauth) { /* * If ok, authenticate against backends using pool_passwd */ /* Read salt */ if (pool_read(backend, salt, sizeof(salt))) { pool_error("do_md5: failed to read salt"); return -1; } pool_debug("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id, salt[0], salt[1], salt[2], salt[3]); /* Encrypt password in pool_passwd using the salt */ pg_md5_encrypt(pool_passwd+strlen("md5"), salt, sizeof(salt), encbuf); /* Send password packet to backend and receive auth response */ kind = send_password_packet(backend, protoMajor, encbuf); if (kind < 0) { return -1; } } if (!reauth && kind == 0) { if (IS_MASTER_NODE_ID(backend->db_node_id)) { /* Send auth ok to frontend */ if (send_auth_ok(frontend, protoMajor) < 0) { pool_error("do_md5: send_auth_ok failed"); return -1; } } /* Save the auth info */ backend->auth_kind = 5; } return kind; } /* * Followings are NUM_BACKEND == 1 case. */ if (!reauth) { /* read salt */ if (pool_read(backend, salt, sizeof(salt))) { pool_error("do_md5: failed to read salt"); return -1; } pool_debug("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id, salt[0], salt[1], salt[2], salt[3]); } else { memcpy(salt, backend->salt, sizeof(salt)); } /* master? */ if (IS_MASTER_NODE_ID(backend->db_node_id)) { /* Send md5 auth request to frontend */ if (send_md5auth_request(frontend, protoMajor, salt)) { pool_error("do_md5: send_md5auth_request failed"); return -1; } /* Read password packet */ if (read_password_packet(frontend, protoMajor, password, &size)) { pool_debug("do_md5: read_password_packet failed"); return -1; } } /* connection reusing? */ if (reauth) { if (size != backend->pwd_size) { pool_debug("do_md5; password size does not match in re-authentication"); return -1; } if (memcmp(password, backend->password, backend->pwd_size) != 0) { pool_debug("do_md5; password does not match in re-authentication"); return -1; } return 0; } /* Send password packet to backend and receive auth response */ kind = send_password_packet(backend, protoMajor, password); if (kind < 0) { return -1; } /* If authenticated, reply back to frontend and save info */ if (!reauth && kind == 0) { if (send_auth_ok(frontend, protoMajor) < 0) { pool_error("do_md5: send_auth_ok failed"); return -1; } backend->auth_kind = 5; backend->pwd_size = size; memcpy(backend->password, password, backend->pwd_size); memcpy(backend->salt, salt, sizeof(salt)); } return kind; } /* * Send md5 authentication request packet to frontend */ int send_md5auth_request(POOL_CONNECTION *frontend, int protoMajor, char *salt) { int len; int kind; pool_write(frontend, "R", 1); /* authentication */ if (protoMajor == PROTO_MAJOR_V3) { len = htonl(12); pool_write(frontend, &len, sizeof(len)); } kind = htonl(5); pool_write(frontend, &kind, sizeof(kind)); /* indicating MD5 */ pool_write_and_flush(frontend, salt, 4); /* salt */ return 0; } /* * Read password packet from frontend */ int read_password_packet(POOL_CONNECTION *frontend, int protoMajor, char *password, int *pwdSize) { int size; /* Read password packet */ if (protoMajor == PROTO_MAJOR_V2) { if (pool_read(frontend, &size, sizeof(size))) { pool_error("read_password_packet: failed to read password packet size"); return -1; } } else { char k; if (pool_read(frontend, &k, sizeof(k))) { pool_debug("read_password_packet: failed to read password packet \"p\""); return -1; } if (k != 'p') { pool_error("read_password_packet: password packet does not start with \"p\""); return -1; } if (pool_read(frontend, &size, sizeof(size))) { pool_error("read_password_packet: failed to read password packet size"); return -1; } } *pwdSize = ntohl(size) - 4; if (*pwdSize > MAX_PASSWORD_SIZE) { pool_error("read_password_packet: too long password string (size: %d)", *pwdSize); /* * We do not read to throw away packet here. Since it is possible that * it's a denial of service attack. */ return -1; } else if (*pwdSize <= 0) { pool_error("read_password_packet: invalid password string size (size: %d)", *pwdSize); return -1; } if (pool_read(frontend, password, *pwdSize)) { pool_error("read_password_packet: failed to read password (size: %d)", *pwdSize); return -1; } password[*pwdSize] = '\0'; return 0; } /* * Send password packet to backend and receive authentication response * packet. Return value is the last field of authentication * response. If it's 0, authentication was successful. * "password" must be null-terminated. */ static int send_password_packet(POOL_CONNECTION *backend, int protoMajor, char *password) { int size; int len; int kind; char response; /* Send password packet to backend */ if (protoMajor == PROTO_MAJOR_V3) pool_write(backend, "p", 1); size = htonl(sizeof(size) + strlen(password)+1); pool_write(backend, &size, sizeof(size)); pool_write_and_flush(backend, password, strlen(password)+1); if (pool_read(backend, &response, sizeof(response))) { pool_error("send_password_packet: failed to read authentication response"); return -1; } if (response != 'R') { pool_debug("send_password_packet: backend does not return R"); return -1; } if (protoMajor == PROTO_MAJOR_V3) { if (pool_read(backend, &len, sizeof(len))) { pool_error("send_password_packet: failed to read authentication packet size"); return -1; } if (ntohl(len) != 8) { pool_error("send_password_packet: incorrect authentication packet size (%d)", ntohl(len)); return -1; } } /* Expect to read "Authentication OK" response. kind should be 0... */ if (pool_read(backend, &kind, sizeof(kind))) { pool_debug("send_password_packet: failed to read Authentication OK response"); return -1; } return kind; } /* * Send auth ok to frontend */ static int send_auth_ok(POOL_CONNECTION *frontend, int protoMajor) { int msglen; pool_write(frontend, "R", 1); if (protoMajor == PROTO_MAJOR_V3) { msglen = htonl(8); pool_write(frontend, &msglen, sizeof(msglen)); } msglen = htonl(0); if (pool_write_and_flush(frontend, &msglen, sizeof(msglen)) < 0) { return -1; } return 0; } /* * read message length (V3 only) */ int pool_read_message_length(POOL_CONNECTION_POOL *cp) { int status; int length, length0; int i; /* read message from master node */ status = pool_read(CONNECTION(cp, MASTER_NODE_ID), &length0, sizeof(length0)); if (status < 0) { pool_error("pool_read_message_length: error while reading message length in slot %d", MASTER_NODE_ID); return -1; } length0 = ntohl(length0); pool_debug("pool_read_message_length: slot: %d length: %d", MASTER_NODE_ID, length0); for (i=0;i