mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
Add a hook to CREATE/ALTER ROLE to allow an external module to check the
strength of database passwords, and create a sample implementation of such a hook as a new contrib module "passwordcheck". Laurenz Albe, reviewed by Takahiro Itagaki
This commit is contained in:
@@ -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 \
|
||||
|
@@ -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>
|
||||
|
19
contrib/passwordcheck/Makefile
Normal file
19
contrib/passwordcheck/Makefile
Normal file
@@ -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
|
147
contrib/passwordcheck/passwordcheck.c
Normal file
147
contrib/passwordcheck/passwordcheck.c
Normal file
@@ -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;
|
||||
}
|
Reference in New Issue
Block a user