diff --git a/contrib/Makefile b/contrib/Makefile
index 8543b5287fe57c4bf267706eff389d0ef608d304..0b208851c16543ad32f3502664f1681654d04f23 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/contrib/Makefile,v 1.89 2009/08/18 10:34:39 teodor Exp $
+# $PostgreSQL: pgsql/contrib/Makefile,v 1.90 2009/11/18 21:57:56 tgl Exp $
 
 subdir = contrib
 top_builddir = ..
@@ -25,6 +25,7 @@ SUBDIRS = \
 		ltree		\
 		oid2name	\
 		pageinspect	\
+		passwordcheck	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_standby	\
diff --git a/contrib/README b/contrib/README
index a8396a5bfadf513ab5133da0d70a27a71ca5f961..ff35c08a700501cd9583218fe4c609765c21d5d1 100644
--- a/contrib/README
+++ b/contrib/README
@@ -104,6 +104,10 @@ pageinspect -
 	Allows inspection of database pages
 	Heikki Linnakangas <heikki@enterprisedb.com>
 
+passwordcheck -
+	Simple password strength checker
+	Laurenz Albe <laurenz.albe@wien.gv.at>
+
 pg_buffercache -
 	Real time queries on the shared buffer cache
 	by Mark Kirkwood <markir@paradise.net.nz>
diff --git a/contrib/passwordcheck/Makefile b/contrib/passwordcheck/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..1d2c8b1c34eb1e99b2ae5fe677d33e02f1fe9e10
--- /dev/null
+++ b/contrib/passwordcheck/Makefile
@@ -0,0 +1,19 @@
+# $PostgreSQL: pgsql/contrib/passwordcheck/Makefile,v 1.1 2009/11/18 21:57:56 tgl Exp $
+
+MODULE_big = passwordcheck
+OBJS = passwordcheck.o
+
+# uncomment the following two lines to enable cracklib support
+# PG_CPPFLAGS = -DUSE_CRACKLIB '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"'
+# SHLIB_LINK = -lcrack
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/passwordcheck
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c
new file mode 100644
index 0000000000000000000000000000000000000000..88055e374d9e873d5d102ee6150724fde1db487f
--- /dev/null
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -0,0 +1,147 @@
+/*-------------------------------------------------------------------------
+ *
+ * passwordcheck.c
+ *
+ *
+ * Copyright (c) 2009, PostgreSQL Global Development Group
+ *
+ * Author: Laurenz Albe <laurenz.albe@wien.gv.at>
+ *
+ * IDENTIFICATION
+ *	  $PostgreSQL: pgsql/contrib/passwordcheck/passwordcheck.c,v 1.1 2009/11/18 21:57:56 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+
+#ifdef USE_CRACKLIB
+#include <crack.h>
+#endif
+
+#include "commands/user.h"
+#include "fmgr.h"
+#include "libpq/md5.h"
+
+
+PG_MODULE_MAGIC;
+
+/* passwords shorter than this will be rejected */
+#define MIN_PWD_LENGTH 8
+
+extern void _PG_init(void);
+
+/*
+ * check_password
+ *
+ * performs checks on an encrypted or unencrypted password
+ * ereport's if not acceptable
+ *
+ * username: name of role being created or changed
+ * password: new password (possibly already encrypted)
+ * password_type: PASSWORD_TYPE_PLAINTEXT or PASSWORD_TYPE_MD5 (there
+ *			could be other encryption schemes in future)
+ * validuntil_time: password expiration time, as a timestamptz Datum
+ * validuntil_null: true if password expiration time is NULL
+ *
+ * This sample implementation doesn't pay any attention to the password
+ * expiration time, but you might wish to insist that it be non-null and
+ * not too far in the future.
+ */
+static void
+check_password(const char *username,
+			   const char *password,
+			   int password_type,
+			   Datum validuntil_time,
+			   bool validuntil_null)
+{
+	int			namelen = strlen(username);
+	int			pwdlen = strlen(password);
+	char		encrypted[MD5_PASSWD_LEN + 1];
+	int			i;
+	bool		pwd_has_letter,
+				pwd_has_nonletter;
+
+	switch (password_type)
+	{
+		case PASSWORD_TYPE_MD5:
+			/*
+			 * Unfortunately we cannot perform exhaustive checks on
+			 * encrypted passwords - we are restricted to guessing.
+			 * (Alternatively, we could insist on the password being
+			 * presented non-encrypted, but that has its own security
+			 * disadvantages.)
+			 *
+			 * We only check for username = password.
+			 */
+			if (!pg_md5_encrypt(username, username, namelen, encrypted))
+				elog(ERROR, "password encryption failed");
+			if (strcmp(password, encrypted) == 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("password must not contain user name")));
+			break;
+
+		case PASSWORD_TYPE_PLAINTEXT:
+			/*
+			 * For unencrypted passwords we can perform better checks
+			 */
+
+			/* enforce minimum length */
+			if (pwdlen < MIN_PWD_LENGTH)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("password is too short")));
+
+			/* check if the password contains the username */
+			if (strstr(password, username))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("password must not contain user name")));
+
+			/* check if the password contains both letters and non-letters */
+			pwd_has_letter = false;
+			pwd_has_nonletter = false;
+			for (i = 0; i < pwdlen; i++)
+			{
+				/*
+				 * isalpha() does not work for multibyte encodings
+				 * but let's consider non-ASCII characters non-letters
+				 */
+				if (isalpha((unsigned char) password[i]))
+					pwd_has_letter = true;
+				else
+					pwd_has_nonletter = true;
+			}
+			if (!pwd_has_letter || !pwd_has_nonletter)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("password must contain both letters and nonletters")));
+
+#ifdef USE_CRACKLIB
+			/* call cracklib to check password */
+			if (FascistCheck(password, CRACKLIB_DICTPATH))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("password is easily cracked")));
+#endif
+			break;
+
+		default:
+			elog(ERROR, "unrecognized password type: %d", password_type);
+			break;
+	}
+
+	/* all checks passed, password is ok */
+}
+
+/*
+ * Module initialization function
+ */
+void
+_PG_init(void)
+{
+	/* activate password checks when the module is loaded */
+	check_password_hook = check_password;
+}
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index cffbc55249c8e111cbcfa93e6abd7ed5c88d33bf..2895e6c170fcace2e6a4de00d4e6a73c961bf026 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/contrib.sgml,v 1.14 2009/08/18 10:34:39 teodor Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/contrib.sgml,v 1.15 2009/11/18 21:57:56 tgl Exp $ -->
 
 <appendix id="contrib">
  <title>Additional Supplied Modules</title>
@@ -98,6 +98,7 @@ psql -d dbname -f <replaceable>SHAREDIR</>/contrib/<replaceable>module</>.sql
  &ltree;
  &oid2name;
  &pageinspect;
+ &passwordcheck;
  &pgbench;
  &pgbuffercache;
  &pgcrypto;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index bee66008b6695a4dd99d5f48980a56ae810f052a..2ceee79cb98f14f2f970524df343d02c66e49d2f 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/filelist.sgml,v 1.64 2009/08/18 10:34:39 teodor Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/filelist.sgml,v 1.65 2009/11/18 21:57:56 tgl Exp $ -->
 
 <!entity history    SYSTEM "history.sgml">
 <!entity info       SYSTEM "info.sgml">
@@ -111,6 +111,7 @@
 <!entity ltree           SYSTEM "ltree.sgml">
 <!entity oid2name        SYSTEM "oid2name.sgml">
 <!entity pageinspect     SYSTEM "pageinspect.sgml">
+<!entity passwordcheck   SYSTEM "passwordcheck.sgml">
 <!entity pgbench         SYSTEM "pgbench.sgml">
 <!entity pgbuffercache   SYSTEM "pgbuffercache.sgml">
 <!entity pgcrypto        SYSTEM "pgcrypto.sgml">
diff --git a/doc/src/sgml/passwordcheck.sgml b/doc/src/sgml/passwordcheck.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..e46e3dfa034935ca04976d0e0e8c3a0f7e63481f
--- /dev/null
+++ b/doc/src/sgml/passwordcheck.sgml
@@ -0,0 +1,62 @@
+<!-- $PostgreSQL: pgsql/doc/src/sgml/passwordcheck.sgml,v 1.1 2009/11/18 21:57:56 tgl Exp $ -->
+
+<sect1 id="passwordcheck">
+ <title>passwordcheck</title>
+
+ <indexterm zone="passwordcheck">
+  <primary>passwordcheck</primary>
+ </indexterm>
+
+ <para>
+  The <filename>passwordcheck</filename> module checks users' passwords
+  whenever they are set with
+  <xref linkend="SQL-CREATEROLE" endterm="SQL-CREATEROLE-title"> or
+  <xref linkend="SQL-ALTERROLE" endterm="SQL-ALTERROLE-title">.
+  If a password is considered too weak, it will be rejected and
+  the command will terminate with an error.
+ </para>
+
+ <para>
+  To enable this module, add <literal>'$libdir/passwordcheck'</literal>
+  to <xref linkend="guc-shared-preload-libraries"> in
+  <filename>postgresql.conf</filename>, then restart the server.
+ </para>
+
+ <para>
+  You can adapt this module to your needs by changing the source code.
+  For example, you can use
+  <ulink url="http://sourceforge.net/projects/cracklib/">CrackLib</ulink>
+  to check passwords &mdash; this only requires uncommenting
+  two lines in the <filename>Makefile</filename> and rebuilding the
+  module.  (We cannot include <productname>CrackLib</productname>
+  by default for license reasons.)
+  Without <productname>CrackLib</productname>, the module enforces a few
+  simple rules for password strength, which you can modify or extend
+  as you see fit.
+ </para>
+
+ <caution>
+  <para>
+   To prevent unencrypted passwords from being sent across the network,
+   written to the server log or otherwise stolen by a database administrator,
+   <productname>PostgreSQL</productname> allows the user to supply
+   pre-encrypted passwords. Many client programs make use of this
+   functionality and encrypt the password before sending it to the server.
+  </para>
+  <para>
+   This limits the usefulness of the <filename>passwordcheck</filename>
+   module, because in that case it can only try to guess the password.
+   For this reason, <filename>passwordcheck</filename> is not
+   recommendable if your security requirements are high.
+   It is more secure to use an external authentication method such as Kerberos
+   (see <xref linkend="client-authentication">) than to rely on
+   passwords within the database.
+  </para>
+  <para>
+   Alternatively, you could modify <filename>passwordcheck</filename>
+   to reject pre-encrypted passwords, but forcing users to set their
+   passwords in clear text carries its own security risks.
+  </para>
+ </caution>
+
+</sect1>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index ef546cf3602b9572e8091c2f804efc724847b7fc..66560d7a5b7176413b596ce60cbc446c236bbf72 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.189 2009/10/07 22:14:19 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.190 2009/11/18 21:57:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -35,8 +35,12 @@
 #include "utils/tqual.h"
 
 
+/* GUC parameter */
 extern bool Password_encryption;
 
+/* Hook to check passwords in CreateRole() and AlterRole() */
+check_password_hook_type check_password_hook = NULL;
+
 static List *roleNamesToIds(List *memberNames);
 static void AddRoleMems(const char *rolename, Oid roleid,
 			List *memberNames, List *memberIds,
@@ -96,6 +100,8 @@ CreateRole(CreateRoleStmt *stmt)
 	List	   *rolemembers = NIL;		/* roles to be members of this role */
 	List	   *adminmembers = NIL;		/* roles to be admins of this role */
 	char	   *validUntil = NULL;		/* time the login is valid until */
+	Datum		validUntil_datum;		/* same, as timestamptz Datum */
+	bool		validUntil_null;
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
@@ -298,6 +304,31 @@ CreateRole(CreateRoleStmt *stmt)
 				 errmsg("role \"%s\" already exists",
 						stmt->role)));
 
+	/* Convert validuntil to internal form */
+	if (validUntil)
+	{
+		validUntil_datum = DirectFunctionCall3(timestamptz_in,
+											   CStringGetDatum(validUntil),
+											   ObjectIdGetDatum(InvalidOid),
+											   Int32GetDatum(-1));
+		validUntil_null = false;
+	}
+	else
+	{
+		validUntil_datum = (Datum) 0;
+		validUntil_null = true;
+	}
+
+	/*
+	 * Call the password checking hook if there is one defined
+	 */
+	if (check_password_hook && password)
+		(*check_password_hook) (stmt->role,
+								password,
+								isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								validUntil_datum,
+								validUntil_null);
+
 	/*
 	 * Build a tuple to insert
 	 */
@@ -333,15 +364,8 @@ CreateRole(CreateRoleStmt *stmt)
 	else
 		new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
 
-	if (validUntil)
-		new_record[Anum_pg_authid_rolvaliduntil - 1] =
-			DirectFunctionCall3(timestamptz_in,
-								CStringGetDatum(validUntil),
-								ObjectIdGetDatum(InvalidOid),
-								Int32GetDatum(-1));
-
-	else
-		new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = true;
+	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
+	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
 	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
 
@@ -419,6 +443,8 @@ AlterRole(AlterRoleStmt *stmt)
 	int			connlimit = -1; /* maximum connections allowed */
 	List	   *rolemembers = NIL;		/* roles to be added/removed */
 	char	   *validUntil = NULL;		/* time the login is valid until */
+	Datum		validUntil_datum;		/* same, as timestamptz Datum */
+	bool		validUntil_null;
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
@@ -587,6 +613,33 @@ AlterRole(AlterRoleStmt *stmt)
 					 errmsg("permission denied")));
 	}
 
+	/* Convert validuntil to internal form */
+	if (validUntil)
+	{
+		validUntil_datum = DirectFunctionCall3(timestamptz_in,
+											   CStringGetDatum(validUntil),
+											   ObjectIdGetDatum(InvalidOid),
+											   Int32GetDatum(-1));
+		validUntil_null = false;
+	}
+	else
+	{
+		/* fetch existing setting in case hook needs it */
+		validUntil_datum = SysCacheGetAttr(AUTHNAME, tuple,
+										   Anum_pg_authid_rolvaliduntil,
+										   &validUntil_null);
+	}
+
+	/*
+	 * Call the password checking hook if there is one defined
+	 */
+	if (check_password_hook && password)
+		(*check_password_hook) (stmt->role,
+								password,
+								isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								validUntil_datum,
+								validUntil_null);
+
 	/*
 	 * Build an updated tuple, perusing the information just obtained
 	 */
@@ -666,15 +719,9 @@ AlterRole(AlterRoleStmt *stmt)
 	}
 
 	/* valid until */
-	if (validUntil)
-	{
-		new_record[Anum_pg_authid_rolvaliduntil - 1] =
-			DirectFunctionCall3(timestamptz_in,
-								CStringGetDatum(validUntil),
-								ObjectIdGetDatum(InvalidOid),
-								Int32GetDatum(-1));
-		new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
-	}
+	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
+	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
+	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
 
 	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
 								  new_record_nulls, new_record_repl);
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 01fb92c354619db4d58d2d55f4cf524596dfc484..ffef486b8362a864d866c1c8aaf4cc36a9873706 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -4,7 +4,7 @@
  *	  Commands for manipulating roles (formerly called users).
  *
  *
- * $PostgreSQL: pgsql/src/include/commands/user.h,v 1.30 2006/10/04 00:30:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/user.h,v 1.31 2009/11/18 21:57:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -14,6 +14,14 @@
 #include "nodes/parsenodes.h"
 
 
+/* Hook to check passwords in CreateRole() and AlterRole() */
+#define PASSWORD_TYPE_PLAINTEXT		0
+#define PASSWORD_TYPE_MD5			1
+
+typedef void (*check_password_hook_type) (const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null);
+
+extern PGDLLIMPORT check_password_hook_type check_password_hook;
+
 extern void CreateRole(CreateRoleStmt *stmt);
 extern void AlterRole(AlterRoleStmt *stmt);
 extern void AlterRoleSet(AlterRoleSetStmt *stmt);