-# $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 = ..
ltree \
oid2name \
pageinspect \
+ passwordcheck \
pg_buffercache \
pg_freespacemap \
pg_standby \
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>
--- /dev/null
+# $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
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+}
-<!-- $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>
<ree;
&oid2name;
&pageinspect;
+ &passwordcheck;
&pgbench;
&pgbuffercache;
&pgcrypto;
-<!-- $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">
<!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">
--- /dev/null
+<!-- $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 — 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>
* 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 $
*
*-------------------------------------------------------------------------
*/
#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,
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;
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
*/
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);
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;
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
*/
}
/* 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);
* 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 $
*
*-------------------------------------------------------------------------
*/
#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);