diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/commands/user.c | 55 | ||||
-rw-r--r-- | src/backend/libpq/auth.c | 60 | ||||
-rw-r--r-- | src/backend/libpq/crypt.c | 8 | ||||
-rw-r--r-- | src/test/regress/expected/password.out | 14 | ||||
-rw-r--r-- | src/test/regress/sql/password.sql | 7 |
5 files changed, 106 insertions, 38 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 0a72c2ecb37..f2941352d7c 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -384,13 +384,36 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) if (password) { - /* Encrypt the password to the requested format. */ char *shadow_pass; + char *logdetail; - shadow_pass = encrypt_password(Password_encryption, stmt->role, - password); - new_record[Anum_pg_authid_rolpassword - 1] = - CStringGetTextDatum(shadow_pass); + /* + * Don't allow an empty password. Libpq treats an empty password the + * same as no password at all, and won't even try to authenticate. But + * other clients might, so allowing it would be confusing. By clearing + * the password when an empty string is specified, the account is + * consistently locked for all clients. + * + * Note that this only covers passwords stored in the database itself. + * There are also checks in the authentication code, to forbid an + * empty password from being used with authentication methods that + * fetch the password from an external system, like LDAP or PAM. + */ + if (password[0] == '\0' || + plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK) + { + ereport(NOTICE, + (errmsg("empty string is not a valid password, clearing password"))); + new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; + } + else + { + /* Encrypt the password to the requested format. */ + shadow_pass = encrypt_password(Password_encryption, stmt->role, + password); + new_record[Anum_pg_authid_rolpassword - 1] = + CStringGetTextDatum(shadow_pass); + } } else new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; @@ -782,13 +805,25 @@ AlterRole(AlterRoleStmt *stmt) /* password */ if (password) { - /* Encrypt the password to the requested format. */ char *shadow_pass; + char *logdetail; - shadow_pass = encrypt_password(Password_encryption, rolename, - password); - new_record[Anum_pg_authid_rolpassword - 1] = - CStringGetTextDatum(shadow_pass); + /* Like in CREATE USER, don't allow an empty password. */ + if (password[0] == '\0' || + plain_crypt_verify(rolename, password, "", &logdetail) == STATUS_OK) + { + ereport(NOTICE, + (errmsg("empty string is not a valid password, clearing password"))); + new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; + } + else + { + /* Encrypt the password to the requested format. */ + shadow_pass = encrypt_password(Password_encryption, rolename, + password); + new_record[Anum_pg_authid_rolpassword - 1] = + CStringGetTextDatum(shadow_pass); + } new_record_repl[Anum_pg_authid_rolpassword - 1] = true; } diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index dd7de7c3a4e..cb30fc7b714 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -688,6 +688,24 @@ recv_password_packet(Port *port) (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("invalid password packet size"))); + /* + * Don't allow an empty password. Libpq treats an empty password the same + * as no password at all, and won't even try to authenticate. But other + * clients might, so allowing it would be confusing. + * + * Note that this only catches an empty password sent by the client in + * plaintext. There's also a check in CREATE/ALTER USER that prevents an + * empty string from being stored as a user's password in the first place. + * We rely on that for MD5 and SCRAM authentication, but we still need + * this check here, to prevent an empty password from being used with + * authentication methods that check the password against an external + * system, like PAM, LDAP and RADIUS. + */ + if (buf.len == 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PASSWORD), + errmsg("empty password returned by client"))); + /* Do not echo password to logs, for security. */ elog(DEBUG5, "received password packet"); @@ -2081,12 +2099,6 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message **msg, */ goto fail; } - if (strlen(passwd) == 0) - { - ereport(LOG, - (errmsg("empty password returned by client"))); - goto fail; - } } if ((reply[i].resp = strdup(passwd)) == NULL) goto fail; @@ -2277,6 +2289,8 @@ CheckBSDAuth(Port *port, char *user) */ retval = auth_userokay(user, NULL, "auth-postgresql", passwd); + pfree(passwd); + if (!retval) return STATUS_ERROR; @@ -2407,16 +2421,12 @@ CheckLDAPAuth(Port *port) if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - if (strlen(passwd) == 0) - { - ereport(LOG, - (errmsg("empty password returned by client"))); - return STATUS_ERROR; - } - if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR) + { /* Error message already sent */ + pfree(passwd); return STATUS_ERROR; + } if (port->hba->ldapbasedn) { @@ -2448,6 +2458,7 @@ CheckLDAPAuth(Port *port) { ereport(LOG, (errmsg("invalid character in user name for LDAP authentication"))); + pfree(passwd); return STATUS_ERROR; } } @@ -2464,6 +2475,7 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s", port->hba->ldapbinddn, port->hba->ldapserver, ldap_err2string(r)))); + pfree(passwd); return STATUS_ERROR; } @@ -2488,6 +2500,7 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s", filter, port->hba->ldapserver, ldap_err2string(r)))); + pfree(passwd); pfree(filter); return STATUS_ERROR; } @@ -2508,6 +2521,7 @@ CheckLDAPAuth(Port *port) count, filter, port->hba->ldapserver, count))); + pfree(passwd); pfree(filter); ldap_msgfree(search_message); return STATUS_ERROR; @@ -2523,6 +2537,7 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s", filter, port->hba->ldapserver, ldap_err2string(error)))); + pfree(passwd); pfree(filter); ldap_msgfree(search_message); return STATUS_ERROR; @@ -2543,6 +2558,7 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s", fulluser, port->hba->ldapserver, ldap_err2string(error)))); + pfree(passwd); pfree(fulluser); return STATUS_ERROR; } @@ -2553,6 +2569,7 @@ CheckLDAPAuth(Port *port) */ if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR) { + pfree(passwd); pfree(fulluser); /* Error message already sent */ @@ -2573,10 +2590,12 @@ CheckLDAPAuth(Port *port) ereport(LOG, (errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s", fulluser, port->hba->ldapserver, ldap_err2string(r)))); + pfree(passwd); pfree(fulluser); return STATUS_ERROR; } + pfree(passwd); pfree(fulluser); return STATUS_OK; @@ -2720,17 +2739,11 @@ CheckRADIUSAuth(Port *port) if (passwd == NULL) return STATUS_EOF; /* client wouldn't send password */ - if (strlen(passwd) == 0) - { - ereport(LOG, - (errmsg("empty password returned by client"))); - return STATUS_ERROR; - } - if (strlen(passwd) > RADIUS_MAX_PASSWORD_LENGTH) { ereport(LOG, (errmsg("RADIUS authentication does not support passwords longer than %d characters", RADIUS_MAX_PASSWORD_LENGTH))); + pfree(passwd); return STATUS_ERROR; } @@ -2756,9 +2769,15 @@ CheckRADIUSAuth(Port *port) *------ */ if (ret == STATUS_OK) + { + pfree(passwd); return STATUS_OK; + } else if (ret == STATUS_EOF) + { + pfree(passwd); return STATUS_ERROR; + } /* * secret, port and identifiers either have length 0 (use default), @@ -2775,6 +2794,7 @@ CheckRADIUSAuth(Port *port) } /* No servers left to try, so give up */ + pfree(passwd); return STATUS_ERROR; } diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c index 0013ee38786..1715c524622 100644 --- a/src/backend/libpq/crypt.c +++ b/src/backend/libpq/crypt.c @@ -71,14 +71,6 @@ get_role_password(const char *role, char **logdetail) ReleaseSysCache(roleTup); - if (*shadow_pass == '\0') - { - *logdetail = psprintf(_("User \"%s\" has an empty password."), - role); - pfree(shadow_pass); - return NULL; /* empty password */ - } - /* * Password OK, but check to be sure we are not past rolvaliduntil */ diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out index bb25ad0c2cf..393d836eada 100644 --- a/src/test/regress/expected/password.out +++ b/src/test/regress/expected/password.out @@ -75,11 +75,25 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+ regress_passwd5 | md5e73a4b11df52a6068f8b39f90be36023 (5 rows) +-- An empty password is not allowed, in any form +CREATE ROLE regress_passwd_empty PASSWORD ''; +NOTICE: empty string is not a valid password, clearing password +ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a'; +NOTICE: empty string is not a valid password, clearing password +ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4='; +NOTICE: empty string is not a valid password, clearing password +SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty'; + rolpassword +------------- + +(1 row) + DROP ROLE regress_passwd1; DROP ROLE regress_passwd2; DROP ROLE regress_passwd3; DROP ROLE regress_passwd4; DROP ROLE regress_passwd5; +DROP ROLE regress_passwd_empty; -- all entries should have been removed SELECT rolname, rolpassword FROM pg_authid diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql index f1682437254..8f8252d127f 100644 --- a/src/test/regress/sql/password.sql +++ b/src/test/regress/sql/password.sql @@ -59,11 +59,18 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+ WHERE rolname LIKE 'regress_passwd%' ORDER BY rolname, rolpassword; +-- An empty password is not allowed, in any form +CREATE ROLE regress_passwd_empty PASSWORD ''; +ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a'; +ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4='; +SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty'; + DROP ROLE regress_passwd1; DROP ROLE regress_passwd2; DROP ROLE regress_passwd3; DROP ROLE regress_passwd4; DROP ROLE regress_passwd5; +DROP ROLE regress_passwd_empty; -- all entries should have been removed SELECT rolname, rolpassword |