Skip to content

Commit

Permalink
Fixed build issues
Browse files Browse the repository at this point in the history
  • Loading branch information
anermakov authored Oct 16, 2024
1 parent 54711b8 commit ef952c6
Show file tree
Hide file tree
Showing 4 changed files with 401 additions and 17 deletions.
21 changes: 16 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# passwordcheck_extra/Makefile
# arenadata_password_check/Makefile

MODULE_big = passwordcheck_extra
OBJS = passwordcheck_extra.o $(WIN32RES)
PGFILEDESC = "passwordcheck_extra - strengthen user password checks"
MODULE_big = arenadata_password_check
OBJS = arenadata_password_check.o $(WIN32RES)
PGFILEDESC = "arenadata_password_check - strengthen user password checks"

REGRESS = passwordcheck_extra
REGRESS = arenadata_password_check

# uncomment the following two lines to enable cracklib support
# PG_CPPFLAGS = -DUSE_CRACKLIB '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"'
# SHLIB_LINK = -lcrack

#PG_CONFIG = pg_config
#PGXS := $(shell $(PG_CONFIG) --pgxs)
#include $(PGXS)

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
24 changes: 12 additions & 12 deletions README
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
passwordcheck_extra
arenadata_password_check
===================

Development
-----------

passwordcheck_extra is a fork of passwordcheck, contrib module to check
arenadata_password_check is a fork of passwordcheck, contrib module to check
a password quality using a fork of PostgreSQL core.

There are two ways to compile and install the code:
1) Copy it as contrib/passwordcheck_extra in PostgreSQL code and use the
1) Copy it as contrib/arenadata_password_check in PostgreSQL code and use the
following command:
make install
2) Include PostgreSQL libraries in LD_LIBRARY_PATH and use the following
Expand All @@ -25,24 +25,24 @@ make installcheck USE_PGXS=1 # Run on existing server

In order to install it, install the library to server, add the following
parameter value to postgresql.conf and restart server.
shared_preload_libraries = '$libdir/passwordcheck_extra'
shared_preload_libraries = '$libdir/arenadata_password_check'

Features
--------

This module strengthens the minimum password requirement it should have
at creation with a user-defined policy:
- passwordcheck_extra.special_chars, to define a list of special characters
- arenadata_password_check.special_chars, to define a list of special characters
with the password needing at least one. Default is "!@#$%^&*()_+{}|<>?=".
- passwordcheck_extra.restrict_lower, to enforce the use of at least one
- arenadata_password_check.restrict_lower, to enforce the use of at least one
lower-case character.
- passwordcheck_extra.restrict_upper, to enforce the use of at least one
- arenadata_password_check.restrict_upper, to enforce the use of at least one
upper-case character.
- passwordcheck_extra.restrict_numbers, to enforce the use of at least
- arenadata_password_check.restrict_numbers, to enforce the use of at least
one number.
- passwordcheck_extra.restrict_special, to enforce the use of at least
one special character listed in \"passwordcheck_extra.special_chars\".
- passwordcheck_extra.minimum_length, minimum length of password allowed.
- arenadata_password_check.restrict_special, to enforce the use of at least
one special character listed in \"arenadata_password_check.special_chars\".
- arenadata_password_check.minimum_length, minimum length of password allowed.
Default is 8, which likely sucks.
- passwordcheck_extra.maximum_length, maximum length of password allowed.
- arenadata_password_check.maximum_length, maximum length of password allowed.
Default is 15, which definitely sucks, but it is useful for tests.
298 changes: 298 additions & 0 deletions arenadata_password_check.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*-------------------------------------------------------------------------
*
* arenadata_password_check.c
*
* Copyright (c) 2024, Arenadata Software LLC
*
* Author: Alexander Ermakov <[email protected]>
*
* IDENTIFICATION
* arenadata_password_check/arenadata_password_check.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"

#include <ctype.h>

#ifdef USE_CRACKLIB
#include <crack.h>
#endif

#include "commands/user.h"
#include "fmgr.h"
#include "libpq/md5.h"
#include "lib/stringinfo.h"
#include "utils/guc.h"

PG_MODULE_MAGIC;

/* GUC variables */
static int password_min_len = 8;
static int password_max_len = 15;
static bool password_lower_case = true;
static bool password_upper_case = true;
static bool password_special = true;
static bool password_numbers = true;
static char *password_special_chars = "!@#$%^&*()_+{}|<>?=";

/*
* Flags to check contents of plain text passwords, which allow tracking
* of restrictions more easily.
*/
#define PASSWORD_HAS_LOWER 0x0001 /* Lower-case character */
#define PASSWORD_HAS_UPPER 0x0002 /* Upper-case character */
#define PASSWORD_HAS_SPECIAL 0x0004 /* Special character */
#define PASSWORD_HAS_NUMBER 0x0008 /* Number */

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,
PasswordType password_type,
Datum validuntil_time,
bool validuntil_null)
{
int pwdlen = strlen(password);
char encrypted[MD5_PASSWD_LEN + 1];
int i;
int password_flag = 0;

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, &errstr))
elog(ERROR, "password encryption failed: %s", errstr); */
if (strcmp(password, encrypted) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));
break;

case PASSWORD_TYPE_PLAINTEXT:
{
StringInfoData buf;
bool set_comma = false;

/*
* For unencrypted passwords we can perform better checks.
*/

/* enforce minimum length */
if (pwdlen < password_min_len)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password is too short")));

/* enforce maximum length */
if (pwdlen > password_max_len)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password is too long")));

/* 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")));

/* Scan password characters and check contents */
for (i = 0; i < pwdlen; i++)
{
/* Check character validity */
if (isupper((unsigned char) password[i]))
password_flag |= PASSWORD_HAS_UPPER;
else if (islower((unsigned char) password[i]))
password_flag |= PASSWORD_HAS_LOWER;
else if (isdigit((unsigned char) password[i]))
password_flag |= PASSWORD_HAS_NUMBER;
else if (strchr(password_special_chars,
(unsigned char) password[i]) != NULL)
password_flag |= PASSWORD_HAS_SPECIAL;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password contains invalid characters")));
}

/* Initialize error message */
initStringInfo(&buf);

/* Lower-case character missing? */
if ((password_flag & PASSWORD_HAS_LOWER) == 0 &&
password_lower_case)
{
appendStringInfo(&buf, "lower-case character missing");
set_comma = true;
}

/* Upper-case character missing? */
if ((password_flag & PASSWORD_HAS_UPPER) == 0 &&
password_upper_case)
{
if (set_comma)
appendStringInfo(&buf, ", ");
appendStringInfo(&buf, "upper-case character missing");
set_comma = true;
}

/* Number missing? */
if ((password_flag & PASSWORD_HAS_NUMBER) == 0 &&
password_numbers)
{
if (set_comma)
appendStringInfo(&buf, ", ");
appendStringInfo(&buf, "number missing");
set_comma = true;
}

/* Special character missing */
if ((password_flag & PASSWORD_HAS_SPECIAL) == 0 &&
password_special)
{
if (set_comma)
appendStringInfo(&buf, ", ");
appendStringInfo(&buf, "special character missing "
"(needs to be one listed in \"%s\")",
password_special_chars);
}

/*
* Complain with everything lacking if anything has been
* found.
*/
if (buf.len != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Incorrect password format: %s",
buf.data)));

#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 */
}

/*
* Entry point for parameter loading
*/
static void
arenadata_password_check_load_params(void)
{
/* Special character set */
DefineCustomStringVariable("arenadata_password_check.special_chars",
"Special characters defined.",
"Default value is \"!@#$%^&*()_+{}|<>?=\".",
&password_special_chars,
"!@#$%^&*()_+{}|<>?=",
PGC_SUSET,
0, NULL, NULL, NULL);

/* Restrict use of lower-case characters */
DefineCustomBoolVariable("arenadata_password_check.restrict_lower",
"Enforce use of lower-case characters.",
NULL,
&password_lower_case,
true,
PGC_SUSET,
0, NULL, NULL, NULL);

/* Restrict use of upper-case characters */
DefineCustomBoolVariable("arenadata_password_check.restrict_upper",
"Enforce use of upper-case characters.",
NULL,
&password_upper_case,
true,
PGC_SUSET,
0, NULL, NULL, NULL);

/* Restrict use of numbers */
DefineCustomBoolVariable("arenadata_password_check.restrict_numbers",
"Enforce use of numbers.",
NULL,
&password_numbers,
true,
PGC_SUSET,
0, NULL, NULL, NULL);

/* Restrict use of special characters */
DefineCustomBoolVariable("arenadata_password_check.restrict_special",
"Enforce use of special characters.",
NULL,
&password_special,
true,
PGC_SUSET,
0, NULL, NULL, NULL);

/* Minimum password length */
DefineCustomIntVariable("arenadata_password_check.minimum_length",
"Minimum length of password allowed",
"Default value set to 8.",
&password_min_len,
8, 1, 10000,
PGC_SUSET,
0, NULL, NULL, NULL);

/* Maximum password length */
DefineCustomIntVariable("arenadata_password_check.maximum_length",
"Maximum length of password allowed",
"Default value set to 15, which actually sucks.",
&password_max_len,
15, 1, 10000,
PGC_SUSET,
0, NULL, NULL, NULL);

/* MarkGUCPrefixReserved("arenadata_password_check"); */
}

/*
* Module initialization function
*/
void
_PG_init(void)
{
/* Load library parameters */
arenadata_password_check_load_params();

/* activate password checks when the module is loaded */
check_password_hook = check_password;
}
Loading

0 comments on commit ef952c6

Please sign in to comment.