LCOV - code coverage report
Current view: top level - contrib/passwordcheck - passwordcheck.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 28 29 96.6 %
Date: 2025-02-22 07:14:56 Functions: 3 3 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * passwordcheck.c
       4             :  *
       5             :  *
       6             :  * Copyright (c) 2009-2025, PostgreSQL Global Development Group
       7             :  *
       8             :  * Author: Laurenz Albe <laurenz.albe@wien.gv.at>
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    contrib/passwordcheck/passwordcheck.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : #include "postgres.h"
      16             : 
      17             : #include <ctype.h>
      18             : #include <limits.h>
      19             : 
      20             : #ifdef USE_CRACKLIB
      21             : #include <crack.h>
      22             : #endif
      23             : 
      24             : #include "commands/user.h"
      25             : #include "fmgr.h"
      26             : #include "libpq/crypt.h"
      27             : 
      28           2 : PG_MODULE_MAGIC;
      29             : 
      30             : /* Saved hook value */
      31             : static check_password_hook_type prev_check_password_hook = NULL;
      32             : 
      33             : /* GUC variables */
      34             : static int  min_password_length = 8;
      35             : 
      36             : /*
      37             :  * check_password
      38             :  *
      39             :  * performs checks on an encrypted or unencrypted password
      40             :  * ereport's if not acceptable
      41             :  *
      42             :  * username: name of role being created or changed
      43             :  * password: new password (possibly already encrypted)
      44             :  * password_type: PASSWORD_TYPE_* code, to indicate if the password is
      45             :  *          in plaintext or encrypted form.
      46             :  * validuntil_time: password expiration time, as a timestamptz Datum
      47             :  * validuntil_null: true if password expiration time is NULL
      48             :  *
      49             :  * This sample implementation doesn't pay any attention to the password
      50             :  * expiration time, but you might wish to insist that it be non-null and
      51             :  * not too far in the future.
      52             :  */
      53             : static void
      54          14 : check_password(const char *username,
      55             :                const char *shadow_pass,
      56             :                PasswordType password_type,
      57             :                Datum validuntil_time,
      58             :                bool validuntil_null)
      59             : {
      60          14 :     if (prev_check_password_hook)
      61           0 :         prev_check_password_hook(username, shadow_pass,
      62             :                                  password_type, validuntil_time,
      63             :                                  validuntil_null);
      64             : 
      65          14 :     if (password_type != PASSWORD_TYPE_PLAINTEXT)
      66             :     {
      67             :         /*
      68             :          * Unfortunately we cannot perform exhaustive checks on encrypted
      69             :          * passwords - we are restricted to guessing. (Alternatively, we could
      70             :          * insist on the password being presented non-encrypted, but that has
      71             :          * its own security disadvantages.)
      72             :          *
      73             :          * We only check for username = password.
      74             :          */
      75           4 :         const char *logdetail = NULL;
      76             : 
      77           4 :         if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
      78           2 :             ereport(ERROR,
      79             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      80             :                      errmsg("password must not equal user name")));
      81             :     }
      82             :     else
      83             :     {
      84             :         /*
      85             :          * For unencrypted passwords we can perform better checks
      86             :          */
      87          10 :         const char *password = shadow_pass;
      88          10 :         int         pwdlen = strlen(password);
      89             :         int         i;
      90             :         bool        pwd_has_letter,
      91             :                     pwd_has_nonletter;
      92             : #ifdef USE_CRACKLIB
      93             :         const char *reason;
      94             : #endif
      95             : 
      96             :         /* enforce minimum length */
      97          10 :         if (pwdlen < min_password_length)
      98           2 :             ereport(ERROR,
      99             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     100             :                      errmsg("password is too short"),
     101             :                      errdetail("password must be at least \"passwordcheck.min_password_length\" (%d) bytes long",
     102             :                                min_password_length)));
     103             : 
     104             :         /* check if the password contains the username */
     105           8 :         if (strstr(password, username))
     106           2 :             ereport(ERROR,
     107             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     108             :                      errmsg("password must not contain user name")));
     109             : 
     110             :         /* check if the password contains both letters and non-letters */
     111           6 :         pwd_has_letter = false;
     112           6 :         pwd_has_nonletter = false;
     113         100 :         for (i = 0; i < pwdlen; i++)
     114             :         {
     115             :             /*
     116             :              * isalpha() does not work for multibyte encodings but let's
     117             :              * consider non-ASCII characters non-letters
     118             :              */
     119          94 :             if (isalpha((unsigned char) password[i]))
     120          86 :                 pwd_has_letter = true;
     121             :             else
     122           8 :                 pwd_has_nonletter = true;
     123             :         }
     124           6 :         if (!pwd_has_letter || !pwd_has_nonletter)
     125           2 :             ereport(ERROR,
     126             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     127             :                      errmsg("password must contain both letters and nonletters")));
     128             : 
     129             : #ifdef USE_CRACKLIB
     130             :         /* call cracklib to check password */
     131             :         if ((reason = FascistCheck(password, CRACKLIB_DICTPATH)))
     132             :             ereport(ERROR,
     133             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     134             :                      errmsg("password is easily cracked"),
     135             :                      errdetail_log("cracklib diagnostic: %s", reason)));
     136             : #endif
     137             :     }
     138             : 
     139             :     /* all checks passed, password is ok */
     140           6 : }
     141             : 
     142             : /*
     143             :  * Module initialization function
     144             :  */
     145             : void
     146           2 : _PG_init(void)
     147             : {
     148             :     /* Define custom GUC variables. */
     149           2 :     DefineCustomIntVariable("passwordcheck.min_password_length",
     150             :                             "Minimum allowed password length.",
     151             :                             NULL,
     152             :                             &min_password_length,
     153             :                             8,
     154             :                             0, INT_MAX,
     155             :                             PGC_SUSET,
     156             :                             GUC_UNIT_BYTE,
     157             :                             NULL, NULL, NULL);
     158             : 
     159           2 :     MarkGUCPrefixReserved("passwordcheck");
     160             : 
     161             :     /* activate password checks when the module is loaded */
     162           2 :     prev_check_password_hook = check_password_hook;
     163           2 :     check_password_hook = check_password;
     164           2 : }

Generated by: LCOV version 1.14