Index: doc/src/sgml/config.sgml
===================================================================
RCS file: /usr/local/cvsroot/pgsql/doc/src/sgml/config.sgml,v
retrieving revision 1.98
diff -c -p -r1.98 config.sgml
*** doc/src/sgml/config.sgml 30 Nov 2006 20:50:44 -0000 1.98
--- doc/src/sgml/config.sgml 4 Dec 2006 13:46:34 -0000
*************** SET ENABLE_SEQSCAN TO OFF;
*** 587,592 ****
--- 587,627 ----
+
+ test_weak_passwords (boolean)
+ test_weak_passwords> configuration parameter
+
+
+
+ When a password is specified in or
+
+ and the password is unencrypted, the password's strength will be
+ tested if this parameter is turned on.
+
+
+
+ If the password supplied is marked ENCRYPTED> or
+ password_encryption> is switched on, the password is not
+ tested.
+
+
+
+
+
+ min_password_length (integer)
+
+ min_password_length> configuration
+ paramater
+
+
+
+ Determines the minimum length in bytes for a password to be tested
+ when test_weak_passwords> is turned on.
+
+
+
+
krb_server_keyfile (string)
Index: doc/src/sgml/libpq.sgml
===================================================================
RCS file: /usr/local/cvsroot/pgsql/doc/src/sgml/libpq.sgml,v
retrieving revision 1.220
diff -c -p -r1.220 libpq.sgml
*** doc/src/sgml/libpq.sgml 10 Nov 2006 22:15:26 -0000 1.220
--- doc/src/sgml/libpq.sgml 5 Dec 2006 12:40:47 -0000
*************** the result when done with it.
*** 3888,3893 ****
--- 3888,3915 ----
+
+
+ PQpasswordCheckPQpasswordCheck>>
+
+
+ Check whether a password could be considered strong.
+
+ int PQpasswordCheck(const char *passwd);
+
+ This function tests whether a password is of at least 6 bytes and
+ has a mix of upper case characters, lower case characters, numbers and
+ other symbols (such as punctuation marks). The function works as follows:
+ any password of less than 6 bytes is rejected. For each class of character
+ missing from the strong, the password length is hypothetically shortened. That
+ is, the checker assumes that the password is easier to guess. So, to
+ be accepted, a password consisting only of lower case letters would have to
+ be 8 bytes long; one with with upper and lower case letters, 7 bytes; one with
+ upper and lower case letters and numbers, 6 bytes. The function is
+ designed for SQL_ASCII> environments.
+
+
+
Index: src/backend/commands/user.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/commands/user.c,v
retrieving revision 1.174
diff -c -p -r1.174 user.c
*** src/backend/commands/user.c 4 Oct 2006 00:29:51 -0000 1.174
--- src/backend/commands/user.c 4 Dec 2006 13:47:01 -0000
***************
*** 29,38 ****
--- 29,40 ----
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
+ #include "utils/passwd_chk.h"
#include "utils/syscache.h"
extern bool Password_encryption;
+ extern bool test_weak_passwords;
static List *roleNamesToIds(List *memberNames);
static void AddRoleMems(const char *rolename, Oid roleid,
*************** CreateRole(CreateRoleStmt *stmt)
*** 309,314 ****
--- 311,324 ----
if (password)
{
+ /* test the strength of the password, if we can */
+ if(!isMD5(password) && test_weak_passwords)
+ {
+ if(pg_password_weak(password))
+ elog(ERROR, "password is too weak");
+
+ }
+
if (!encrypt_password || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
DirectFunctionCall1(textin, CStringGetDatum(password));
*************** AlterRole(AlterRoleStmt *stmt)
*** 637,642 ****
--- 647,677 ----
/* password */
if (password)
{
+ if(!isMD5(password) && test_weak_passwords)
+ {
+ /* get the old tuple */
+ bool isnull;
+ Datum datum;
+ const char *oldpasswd;
+
+ datum = heap_getattr(tuple, Anum_pg_authid_rolpassword,
+ pg_authid_dsc, &isnull);
+ if(isnull)
+ oldpasswd = "";
+ else
+ oldpasswd = DatumGetCString(DirectFunctionCall1(textout, datum));
+ if(!isMD5(oldpasswd))
+ {
+ if(!pg_password_chg_aprv(oldpasswd, password))
+ elog(ERROR, "password is too weak");
+ }
+ else
+ {
+ if(pg_password_weak(password))
+ elog(ERROR, "password is too weak");
+ }
+ }
+
if (!encrypt_password || isMD5(password))
new_record[Anum_pg_authid_rolpassword - 1] =
DirectFunctionCall1(textin, CStringGetDatum(password));
Index: src/backend/libpq/auth.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/libpq/auth.c,v
retrieving revision 1.146
diff -c -p -r1.146 auth.c
*** src/backend/libpq/auth.c 6 Nov 2006 01:27:52 -0000 1.146
--- src/backend/libpq/auth.c 5 Dec 2006 13:06:00 -0000
*************** char *pg_krb_server_keyfile;
*** 41,46 ****
--- 41,47 ----
char *pg_krb_srvnam;
bool pg_krb_caseins_users;
char *pg_krb_server_hostname = NULL;
+ extern int auth_failure_delay;
#ifdef USE_PAM
#ifdef HAVE_PAM_PAM_APPL_H
*************** pg_krb5_recvauth(Port *port)
*** 216,221 ****
--- 217,225 ----
krb5_ticket *ticket;
char *kusername;
+ if (get_role_line(port->user_name) == NULL)
+ return STATUS_ERROR;
+
ret = pg_krb5_init();
if (ret != STATUS_OK)
return ret;
*************** auth_failed(Port *port, int status)
*** 357,362 ****
--- 361,369 ----
break;
}
+ if(auth_failure_delay)
+ pg_usleep(auth_failure_delay * 1000L);
+
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg(errstr, port->user_name)));
Index: src/backend/utils/misc/Makefile
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/Makefile,v
retrieving revision 1.26
diff -c -p -r1.26 Makefile
*** src/backend/utils/misc/Makefile 25 Jul 2006 03:51:21 -0000 1.26
--- src/backend/utils/misc/Makefile 5 Dec 2006 12:58:25 -0000
*************** include $(top_builddir)/src/Makefile.glo
*** 14,20 ****
override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
! OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o
# This location might depend on the installation directories. Therefore
# we can't subsitute it into pg_config.h.
--- 14,21 ----
override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
! OBJS = guc.o help_config.o passwd_chk.o pg_rusage.o ps_status.o superuser.o \
! tzparser.o
# This location might depend on the installation directories. Therefore
# we can't subsitute it into pg_config.h.
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.360
diff -c -p -r1.360 guc.c
*** src/backend/utils/misc/guc.c 29 Nov 2006 14:50:07 -0000 1.360
--- src/backend/utils/misc/guc.c 5 Dec 2006 12:46:14 -0000
***************
*** 56,61 ****
--- 56,62 ----
#include "utils/builtins.h"
#include "utils/guc_tables.h"
#include "utils/memutils.h"
+ #include "utils/passwd_chk.h"
#include "utils/pg_locale.h"
#include "utils/ps_status.h"
#include "utils/tzparser.h"
*************** bool default_with_oids = false;
*** 175,180 ****
--- 176,184 ----
bool SQL_inheritance = true;
bool Password_encryption = true;
+ bool test_weak_passwords = false;
+ int min_password_length = PASSWD_MIN_LEN;
+ int auth_failure_delay = 0;
int log_min_error_statement = ERROR;
int log_min_messages = NOTICE;
*************** static struct config_bool ConfigureNames
*** 827,832 ****
--- 831,846 ----
true, NULL, NULL
},
{
+ {"test_weak_passwords", PGC_SUSET, CONN_AUTH_SECURITY,
+ gettext_noop("Test for weak passwords."),
+ gettext_noop("When turned on, unencrypted passwords supplied to "
+ "the CREATE/ALTER ROLE/USER commands will be tested for "
+ "strength.")
+ },
+ &test_weak_passwords,
+ false, NULL, NULL
+ },
+ {
{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
gettext_noop("When turned on, expressions of the form expr = NULL "
*************** static struct config_int ConfigureNamesI
*** 1658,1664 ****
&server_version_num,
PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL
},
!
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
--- 1672,1699 ----
&server_version_num,
PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL
},
! {
! {"min_password_length", PGC_SUSET, CONN_AUTH_SECURITY,
! gettext_noop("Minimum password length."),
! gettext_noop("When unencrypted passwords are sent to "
! "CREATE/ALTER ROLE/USER, check that they are at least "
! "this long. Only used if test_weak_passwords is on.")
! },
! &min_password_length,
! PASSWD_MIN_LEN, 0, NAMEDATALEN - 1, NULL, NULL
! },
! {
! {"auth_failure_delay", PGC_SIGHUP, CONN_AUTH_SECURITY,
! gettext_noop("Time to sleep after an authentication failure."),
! gettext_noop("When authentication of a user fails, sleep for "
! "a period, measured in milliseconds, before notifying the "
! "client that the failure has occured. This can assist with "
! "mitigating systematic attacks which try and guess passwords."),
! GUC_UNIT_MS
! },
! &auth_failure_delay,
! 0, 0, INT_MAX / 1000, NULL, NULL
! },
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
Index: src/backend/utils/misc/passwd_chk.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/Attic/passwd_chk.c,v
retrieving revision 1.1.2.1
diff -c -p -r1.1.2.1 passwd_chk.c
*** src/backend/utils/misc/passwd_chk.c 2 Nov 2006 21:51:11 -0000 1.1.2.1
--- src/backend/utils/misc/passwd_chk.c 27 Oct 2006 05:36:13 -0000
***************
*** 0 ****
--- 1,217 ----
+ /*-------------------------------------------------------------------------
+ *
+ * passwd_check.c
+ * Functions to check the strength of passwords
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #ifndef FRONTEND
+ #include "postgres.h"
+ #else
+ #include "postgres_fe.h"
+ #endif
+
+ #include
+
+ #include "utils/passwd_chk.h"
+
+ #ifndef FRONTEND
+ extern bool test_weak_passwords;
+ extern int min_password_length;
+ #endif
+
+
+ /*
+ * Check if the password is too simple. To do this, we check that characters
+ * -- be they digits, upper case alpha, lower case alpha or other -- meet
+ * the thresholds defined in passwd_chk.h.
+ *
+ * This is very much specific to C locale settings -- i.e., doesn't play
+ * address multibyte issues. For example, six bytes can be taken up by
+ * two Thai utf-8 characters, which is hardly a strong password. Hmmm...
+ * what to do?
+ *
+ * There are a few different things we could do:
+ * - have a password_min_bytes GUC (seems kinda lame)
+ * - have a password_min_len GUC and measure characters basic on current
+ * encoding
+ * - do the isupper, islower, etc, but with iswupper, etc. But, I don't know
+ * if all languages have a concept of upper and lower case...
+ *
+ * Returns true if password is simple, otherwise false.
+ */
+
+ static bool
+ passwd_smpl(const char *passwd)
+ {
+ int i = 0;
+ int o = 0;
+ int d = 0;
+ int u = 0;
+ int l = 0;
+
+ while(passwd[i] != '\0')
+ {
+ int c = (int)passwd[i];
+ if(isdigit(c))
+ d++;
+ else if(isupper(c))
+ u++;
+ else if(islower(c))
+ l++;
+ else
+ o++;
+ i++;
+ }
+
+ /*
+ * For every missing class of character, we're going to subtract 1 byte
+ * from the measured length of the password. If we're too short after
+ * this, complain that the password is too simple.
+ */
+ i -= d > 0 ? 0 : 1;
+ i -= o > 0 ? 0 : 1;
+ i -= u > 0 ? 0 : 1;
+ i -= l > 0 ? 0 : 1;
+
+ #ifdef FRONTEND
+ return i < PASSWD_MIN_LEN;
+ #else
+ return i < min_password_length;
+ #endif
+ }
+
+ #ifndef FRONTEND
+
+ /*
+ * Sometimes users change their passwords in silly ways: such as
+ * 'foo' -> 'oof' or 'foo' to 'FOO'. See if that's the case.
+ *
+ * If the passwords are sufficiently different, return true, otherwise
+ * false.
+ */
+ static bool
+ passwd_diff(const char *old, const char *new)
+ {
+ int ol;
+ int nl;
+ int i;
+ bool found = false;
+ char oldl[NAMEDATALEN];
+ char newl[NAMEDATALEN];
+
+ /* we want to ignore case, so fold each to lower case */
+ ol = strlen(old);
+ nl = strlen(new);
+
+ for(i = 0; i < ol; i++)
+ {
+ oldl[i] = tolower(old[i]);
+ }
+ oldl[i] = '\0';
+
+ for(i = 0; i < nl; i++)
+ {
+ newl[i] = tolower(new[i]);
+ }
+ newl[i] = '\0';
+
+ /* are they the same? */
+ if(strcmp(oldl, newl) == 0)
+ return false;
+
+ /* is the string just reversed? */
+ if(ol == nl)
+ {
+ for(i = 0; i < ol && i < nl; i++)
+ {
+ /* they might have changed case too */
+ if(oldl[i] != newl[nl - i - 1])
+ {
+ found = true;
+ break;
+ }
+ }
+ if(!found)
+ return false;
+ }
+
+ /* is one password a sub/super set of the other? */
+ if(strstr(oldl, newl))
+ return false;
+ if(strstr(newl, oldl))
+ return false;
+
+ return true;
+ }
+
+ #endif
+
+ /*
+ * public functions
+ */
+
+ bool
+ pg_password_weak(const char *passwd)
+ {
+ #ifndef FRONTEND
+ if(!test_weak_passwords)
+ return false;
+ #endif
+ return passwd_smpl(passwd);
+ }
+
+ /* We have no use for testing password changes in the front end */
+ #ifndef FRONTEND
+ bool
+ pg_password_chg_aprv(const char *old, const char *new)
+ {
+ if(!test_weak_passwords)
+ return true;
+
+ if(pg_password_weak(new))
+ return false;
+ if(!passwd_diff(old, new))
+ return false;
+ return true;
+ }
+
+ #endif /* !FRONTEND */
+
+ #ifdef UNIT_TEST
+
+ #include
+
+ #define MYA(condition) \
+ if(!(condition)) \
+ fprintf(stderr, "Test '%s' failed at %i\n", #condition, __LINE__);
+
+ int
+ main(void)
+ {
+
+ MYA(passwd_smpl("foo") == true);
+ MYA(passwd_smpl("Foo6") == true);
+ MYA(passwd_smpl("gavins") == true);
+
+ MYA(passwd_diff("foo", "foo") == false);
+ MYA(passwd_diff("foo", "oof") == false);
+ MYA(passwd_diff("foofoo", "foo") == false);
+ MYA(passwd_diff("FOO", "foo") == false);
+ MYA(passwd_diff("dsiifm0239u", "5sd|[+09s2") == true);
+
+ #ifndef FRONTEND
+ MYA(pg_password_chg_aprv("oldold", "ddd") == false);
+ MYA(pg_password_chg_aprv("newview", "sdfi0238j") == true);
+ #endif
+
+ return 0;
+ }
+ #endif /* UNIT_TEST */
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.199
diff -c -p -r1.199 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample 21 Nov 2006 01:23:37 -0000 1.199
--- src/backend/utils/misc/postgresql.conf.sample 5 Dec 2006 13:04:33 -0000
***************
*** 72,77 ****
--- 72,81 ----
#authentication_timeout = 1min # 1s-600s
#ssl = off # (change requires restart)
#password_encryption = on
+ #auth_failure_delay = 0ms # sleep this long after an authentication failure
+ #test_weak_passwords = off # whether to test for weak passwords
+ #min_password_length = 0 # minimum length of passwords, in bytes.
+ # only used if test_weak_passwords is on.
#db_user_namespace = off
# Kerberos
Index: src/include/utils/passwd_chk.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/include/utils/Attic/passwd_chk.h,v
retrieving revision 1.1.2.1
diff -c -p -r1.1.2.1 passwd_chk.h
*** src/include/utils/passwd_chk.h 2 Nov 2006 21:52:14 -0000 1.1.2.1
--- src/include/utils/passwd_chk.h 27 Oct 2006 05:12:32 -0000
***************
*** 0 ****
--- 1,24 ----
+ /*-------------------------------------------------------------------------
+ *
+ * passwd_chk.h
+ * Routines to check password strength
+ *
+ * See passwd_chk.c for details.
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #ifndef PASSWD_CHK_H
+ #define PASSWD_CHK_H
+
+ #define PASSWD_MIN_LEN 6
+
+ extern bool pg_password_weak(const char *passwd);
+ extern bool pg_password_chg_aprv(const char *old, const char *new);
+
+ #endif
Index: src/interfaces/libpq/Makefile
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/interfaces/libpq/Makefile,v
retrieving revision 1.149
diff -c -p -r1.149 Makefile
*** src/interfaces/libpq/Makefile 27 Sep 2006 21:29:17 -0000 1.149
--- src/interfaces/libpq/Makefile 5 Dec 2006 12:42:39 -0000
*************** LIBS := $(LIBS:-lpgport=)
*** 34,39 ****
--- 34,40 ----
OBJS= fe-auth.o fe-connect.o fe-exec.o fe-misc.o fe-print.o fe-lobj.o \
fe-protocol2.o fe-protocol3.o pqexpbuffer.o pqsignal.o fe-secure.o \
md5.o ip.o wchar.o encnames.o noblock.o pgstrcasecmp.o thread.o \
+ passwd_chk.o \
$(filter crypt.o getaddrinfo.o inet_aton.o open.o snprintf.o strerror.o strlcpy.o, $(LIBOBJS))
ifeq ($(PORTNAME), cygwin)
*************** md5.c ip.c: % : $(backend_src)/libpq/%
*** 89,94 ****
--- 90,98 ----
encnames.c wchar.c : % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
+ passwd_chk.c: % : $(backend_src)/utils/misc/%
+ rm -f $@ && $(LN_S) $< .
+
# We need several not-quite-identical variants of .DEF files to build libpq
# DLLs for Windows. These are made from the single source file exports.txt.
Index: src/interfaces/libpq/fe-auth.c
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v
retrieving revision 1.121
diff -c -p -r1.121 fe-auth.c
*** src/interfaces/libpq/fe-auth.c 4 Oct 2006 00:30:12 -0000 1.121
--- src/interfaces/libpq/fe-auth.c 5 Dec 2006 12:43:24 -0000
***************
*** 49,54 ****
--- 49,55 ----
#include "libpq-fe.h"
#include "fe-auth.h"
+ #include "utils/passwd_chk.h"
#include "libpq/md5.h"
*************** pg_fe_getauthname(char *PQerrormsg)
*** 581,586 ****
--- 582,605 ----
return authn;
}
+ /*
+ * PQpasswordCheck() - test the strength of a password
+ *
+ * We can do this in the backend when a role is created or altered and a
+ * password is supplied. In fact, pg_password_weak() is exported from the
+ * backend code. The reason we provide it here is that passwords can be
+ * sent to the backend encrypted (see PQencryptPassword() above). Obvious
+ * we cannot test the strength of a password encrypted with md5 so we
+ * allow users to do it in the front end.
+ *
+ * Returns true if the password is sufficiently strong, otherwise false.
+ */
+
+ int
+ PQpasswordCheck(const char *passwd)
+ {
+ return !pg_password_weak(passwd);
+ }
/*
* PQencryptPassword -- exported routine to encrypt a password
Index: src/interfaces/libpq/libpq-fe.h
===================================================================
RCS file: /usr/local/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.134
diff -c -p -r1.134 libpq-fe.h
*** src/interfaces/libpq/libpq-fe.h 4 Oct 2006 00:30:13 -0000 1.134
--- src/interfaces/libpq/libpq-fe.h 5 Dec 2006 12:43:46 -0000
*************** extern int PQenv2encoding(void);
*** 506,511 ****
--- 506,512 ----
/* === in fe-auth.c === */
+ extern int PQpasswordCheck(const char *passwd);
extern char *PQencryptPassword(const char *passwd, const char *user);
#ifdef __cplusplus