LCOV - code coverage report
Current view: top level - contrib/passwordcheck - passwordcheck.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 26 27 96.3 %
Date: 2024-12-02 20:15:07 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-2024, 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             : 
      19             : #ifdef USE_CRACKLIB
      20             : #include <crack.h>
      21             : #endif
      22             : 
      23             : #include "commands/user.h"
      24             : #include "fmgr.h"
      25             : #include "libpq/crypt.h"
      26             : 
      27           2 : PG_MODULE_MAGIC;
      28             : 
      29             : /* Saved hook value in case of unload */
      30             : static check_password_hook_type prev_check_password_hook = NULL;
      31             : 
      32             : /* passwords shorter than this will be rejected */
      33             : #define MIN_PWD_LENGTH 8
      34             : 
      35             : /*
      36             :  * check_password
      37             :  *
      38             :  * performs checks on an encrypted or unencrypted password
      39             :  * ereport's if not acceptable
      40             :  *
      41             :  * username: name of role being created or changed
      42             :  * password: new password (possibly already encrypted)
      43             :  * password_type: PASSWORD_TYPE_* code, to indicate if the password is
      44             :  *          in plaintext or encrypted form.
      45             :  * validuntil_time: password expiration time, as a timestamptz Datum
      46             :  * validuntil_null: true if password expiration time is NULL
      47             :  *
      48             :  * This sample implementation doesn't pay any attention to the password
      49             :  * expiration time, but you might wish to insist that it be non-null and
      50             :  * not too far in the future.
      51             :  */
      52             : static void
      53          12 : check_password(const char *username,
      54             :                const char *shadow_pass,
      55             :                PasswordType password_type,
      56             :                Datum validuntil_time,
      57             :                bool validuntil_null)
      58             : {
      59          12 :     if (prev_check_password_hook)
      60           0 :         prev_check_password_hook(username, shadow_pass,
      61             :                                  password_type, validuntil_time,
      62             :                                  validuntil_null);
      63             : 
      64          12 :     if (password_type != PASSWORD_TYPE_PLAINTEXT)
      65             :     {
      66             :         /*
      67             :          * Unfortunately we cannot perform exhaustive checks on encrypted
      68             :          * passwords - we are restricted to guessing. (Alternatively, we could
      69             :          * insist on the password being presented non-encrypted, but that has
      70             :          * its own security disadvantages.)
      71             :          *
      72             :          * We only check for username = password.
      73             :          */
      74           4 :         const char *logdetail = NULL;
      75             : 
      76           4 :         if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
      77           2 :             ereport(ERROR,
      78             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      79             :                      errmsg("password must not equal user name")));
      80             :     }
      81             :     else
      82             :     {
      83             :         /*
      84             :          * For unencrypted passwords we can perform better checks
      85             :          */
      86           8 :         const char *password = shadow_pass;
      87           8 :         int         pwdlen = strlen(password);
      88             :         int         i;
      89             :         bool        pwd_has_letter,
      90             :                     pwd_has_nonletter;
      91             : #ifdef USE_CRACKLIB
      92             :         const char *reason;
      93             : #endif
      94             : 
      95             :         /* enforce minimum length */
      96           8 :         if (pwdlen < MIN_PWD_LENGTH)
      97           2 :             ereport(ERROR,
      98             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      99             :                      errmsg("password is too short")));
     100             : 
     101             :         /* check if the password contains the username */
     102           6 :         if (strstr(password, username))
     103           2 :             ereport(ERROR,
     104             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     105             :                      errmsg("password must not contain user name")));
     106             : 
     107             :         /* check if the password contains both letters and non-letters */
     108           4 :         pwd_has_letter = false;
     109           4 :         pwd_has_nonletter = false;
     110          86 :         for (i = 0; i < pwdlen; i++)
     111             :         {
     112             :             /*
     113             :              * isalpha() does not work for multibyte encodings but let's
     114             :              * consider non-ASCII characters non-letters
     115             :              */
     116          82 :             if (isalpha((unsigned char) password[i]))
     117          76 :                 pwd_has_letter = true;
     118             :             else
     119           6 :                 pwd_has_nonletter = true;
     120             :         }
     121           4 :         if (!pwd_has_letter || !pwd_has_nonletter)
     122           2 :             ereport(ERROR,
     123             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     124             :                      errmsg("password must contain both letters and nonletters")));
     125             : 
     126             : #ifdef USE_CRACKLIB
     127             :         /* call cracklib to check password */
     128             :         if ((reason = FascistCheck(password, CRACKLIB_DICTPATH)))
     129             :             ereport(ERROR,
     130             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     131             :                      errmsg("password is easily cracked"),
     132             :                      errdetail_log("cracklib diagnostic: %s", reason)));
     133             : #endif
     134             :     }
     135             : 
     136             :     /* all checks passed, password is ok */
     137           4 : }
     138             : 
     139             : /*
     140             :  * Module initialization function
     141             :  */
     142             : void
     143           2 : _PG_init(void)
     144             : {
     145             :     /* activate password checks when the module is loaded */
     146           2 :     prev_check_password_hook = check_password_hook;
     147           2 :     check_password_hook = check_password;
     148           2 : }

Generated by: LCOV version 1.14