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