"Fossies" - the Fresh Open Source Software Archive

Member "passwdqc-2.0.3/pam_passwdqc.c" (23 Jun 2023, 16436 Bytes) of package /linux/privat/passwdqc-2.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "pam_passwdqc.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.0.2_vs_2.0.3.

    1 /*
    2  * Copyright (c) 2000-2003,2005,2012,2016,2019,2021 by Solar Designer
    3  * Copyright (c) 2017,2018 by Dmitry V. Levin
    4  * Copyright (c) 2017,2018 by Oleg Solovyov
    5  * See LICENSE
    6  */
    7 
    8 #ifdef __FreeBSD__
    9 /* For vsnprintf(3) */
   10 #define _XOPEN_SOURCE 600
   11 #else
   12 #define _XOPEN_SOURCE 500
   13 #define _XOPEN_SOURCE_EXTENDED
   14 #define _XOPEN_VERSION 500
   15 #define _DEFAULT_SOURCE
   16 #endif
   17 #include <stdio.h>
   18 #include <stdlib.h>
   19 #include <stdarg.h>
   20 #include <string.h>
   21 #include <limits.h>
   22 #include <unistd.h>
   23 #include <pwd.h>
   24 #ifdef HAVE_SHADOW
   25 #include <shadow.h>
   26 #endif
   27 #ifdef HAVE_LIBAUDIT
   28 #include <security/pam_modutil.h>
   29 #include <libaudit.h>
   30 #endif
   31 
   32 #define PAM_SM_PASSWORD
   33 #ifndef LINUX_PAM
   34 #include <security/pam_appl.h>
   35 #endif
   36 #include <security/pam_modules.h>
   37 
   38 #include "pam_macros.h"
   39 
   40 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
   41 #define PAM_EXTERN          extern
   42 #endif
   43 
   44 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR)
   45 #define PAM_AUTHTOK_RECOVERY_ERR    PAM_AUTHTOK_RECOVER_ERR
   46 #endif
   47 
   48 #if (defined(__sun) || defined(__hpux)) && \
   49     !defined(LINUX_PAM) && !defined(_OPENPAM)
   50 /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */
   51 #define lo_const
   52 #else
   53 #define lo_const            const
   54 #endif
   55 #ifdef _OPENPAM
   56 /* OpenPAM doesn't use const here, while Linux-PAM does */
   57 #define l_const
   58 #else
   59 #define l_const             lo_const
   60 #endif
   61 typedef lo_const void *pam_item_t;
   62 
   63 #include "passwdqc.h"
   64 
   65 #include "passwdqc_i18n.h"
   66 
   67 #define PROMPT_OLDPASS \
   68     _("Enter current password: ")
   69 #define PROMPT_NEWPASS1 \
   70     _("Enter new password: ")
   71 #define PROMPT_NEWPASS2 \
   72     _("Re-type new password: ")
   73 
   74 #define MESSAGE_MISCONFIGURED \
   75     _("System configuration error.  Please contact your administrator.")
   76 #define MESSAGE_INVALID_OPTION \
   77     "pam_passwdqc: %s."
   78 #define MESSAGE_INTRO_PASSWORD \
   79     _("\nYou can now choose the new password.\n")
   80 #define MESSAGE_INTRO_BOTH \
   81     _("\nYou can now choose the new password or passphrase.\n")
   82 
   83 #define MESSAGE_EXPLAIN_PASSWORD_1_CLASS(count) \
   84     P3_( \
   85     "A good password should be a mix of upper and lower case letters, digits, and\n" \
   86     "other characters.  You can use a password containing at least %d character.\n", \
   87     \
   88     "A good password should be a mix of upper and lower case letters, digits, and\n" \
   89     "other characters.  You can use a password containing at least %d characters.\n", \
   90     count), (count)
   91 
   92 #define MESSAGE_EXPLAIN_PASSWORD_N_CLASSES(count) \
   93     P3_( \
   94     "A valid password should be a mix of upper and lower case letters, digits, and\n" \
   95     "other characters.  You can use a password containing at least %d character\n" \
   96     "from at least %d of these 4 classes.\n" \
   97     "An upper case letter that begins the password and a digit that ends it do not\n" \
   98     "count towards the number of character classes used.\n", \
   99     \
  100     "A valid password should be a mix of upper and lower case letters, digits, and\n" \
  101     "other characters.  You can use a password containing at least %d characters\n" \
  102     "from at least %d of these 4 classes.\n" \
  103     "An upper case letter that begins the password and a digit that ends it do not\n" \
  104     "count towards the number of character classes used.\n", \
  105     count), (count)
  106 
  107 #define MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES(count) \
  108     P3_( \
  109     "A valid password should be a mix of upper and lower case letters, digits, and\n" \
  110     "other characters.  You can use a password containing at least %d character\n" \
  111     "from all of these classes.\n" \
  112     "An upper case letter that begins the password and a digit that ends it do not\n" \
  113     "count towards the number of character classes used.\n", \
  114     \
  115     "A valid password should be a mix of upper and lower case letters, digits, and\n" \
  116     "other characters.  You can use a password containing at least %d characters\n" \
  117     "from all of these classes.\n" \
  118     "An upper case letter that begins the password and a digit that ends it do not\n" \
  119     "count towards the number of character classes used.\n", \
  120     count), (count)
  121 
  122 #define MESSAGE_EXPLAIN_PASSWORD_ALL_OR_3_CLASSES(count) \
  123     P3_( \
  124     "A valid password should be a mix of upper and lower case letters, digits, and\n" \
  125     "other characters.  You can use a password containing at least %d character\n" \
  126     "from all of these classes, or a password containing at least %d characters\n" \
  127     "from just 3 of these 4 classes.\n" \
  128     "An upper case letter that begins the password and a digit that ends it do not\n" \
  129     "count towards the number of character classes used.\n", \
  130     \
  131     "A valid password should be a mix of upper and lower case letters, digits, and\n" \
  132     "other characters.  You can use a password containing at least %d characters\n" \
  133     "from all of these classes, or a password containing at least %d characters\n" \
  134     "from just 3 of these 4 classes.\n" \
  135     "An upper case letter that begins the password and a digit that ends it do not\n" \
  136     "count towards the number of character classes used.\n", \
  137     count), (count)
  138 
  139 #define MESSAGE_EXPLAIN_PASSPHRASE(count) \
  140     P3_(\
  141     "A passphrase should be of at least %d word, %d to %d characters long, and\n" \
  142     "contain enough different characters.\n", \
  143     \
  144     "A passphrase should be of at least %d words, %d to %d characters long, and\n" \
  145     "contain enough different characters.\n", \
  146     count), (count)
  147 
  148 #define MESSAGE_RANDOM \
  149     _("Alternatively, if no one else can see your terminal now, you can pick this as\n" \
  150     "your password: \"%s\".\n")
  151 #define MESSAGE_RANDOMONLY \
  152     _("This system is configured to permit randomly generated passwords only.\n" \
  153     "If no one else can see your terminal now, you can pick this as your\n" \
  154     "password: \"%s\".  Otherwise come back later.\n")
  155 #define MESSAGE_RANDOMFAILED \
  156     _("This system is configured to use randomly generated passwords only,\n" \
  157     "but the attempt to generate a password has failed.  This could happen\n" \
  158     "for a number of reasons: you could have requested an impossible password\n" \
  159     "length, or the access to kernel random number pool could have failed.")
  160 #define MESSAGE_TOOLONG \
  161     _("This password may be too long for some services.  Choose another.")
  162 #define MESSAGE_TRUNCATED \
  163     _("Warning: your longer password will be truncated to 8 characters.")
  164 #define MESSAGE_WEAKPASS \
  165     _("Weak password: %s.")
  166 #define MESSAGE_NOTRANDOM \
  167     _("Sorry, you've mistyped the password that was generated for you.")
  168 #define MESSAGE_MISTYPED \
  169     _("Sorry, passwords do not match.")
  170 #define MESSAGE_RETRY \
  171     _("Try again.")
  172 
  173 static int logaudit(pam_handle_t *pamh, int status, passwdqc_params_t *params)
  174 {
  175 #ifdef HAVE_LIBAUDIT
  176     if (!(params->pam.flags & F_NO_AUDIT)) {
  177         int rc = pam_modutil_audit_write(pamh, AUDIT_USER_CHAUTHTOK, "pam_passwdqc", status);
  178         if (status == PAM_SUCCESS)
  179                status = rc;
  180     }
  181 #else /* !HAVE_LIBAUDIT */
  182     (void) pamh;
  183 #endif
  184     passwdqc_params_free(params);
  185     return status;
  186 }
  187 
  188 static int converse(pam_handle_t *pamh, int style, l_const char *text,
  189     struct pam_response **resp)
  190 {
  191     pam_item_t item;
  192     const struct pam_conv *conv;
  193     struct pam_message msg, *pmsg;
  194     int status;
  195 
  196     *resp = NULL;
  197     status = pam_get_item(pamh, PAM_CONV, &item);
  198     if (status != PAM_SUCCESS)
  199         return status;
  200     conv = item;
  201 
  202     pmsg = &msg;
  203     msg.msg_style = style;
  204     msg.msg = text;
  205 
  206     return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp,
  207         conv->appdata_ptr);
  208 }
  209 
  210 #ifdef __GNUC__
  211 __attribute__ ((format (printf, 3, 4)))
  212 #endif
  213 static int say(pam_handle_t *pamh, int style, const char *format, ...)
  214 {
  215     va_list args;
  216     char buffer[0x800];
  217     int needed;
  218     struct pam_response *resp;
  219     int status;
  220 
  221     va_start(args, format);
  222     needed = vsnprintf(buffer, sizeof(buffer), format, args);
  223     va_end(args);
  224 
  225     if ((unsigned int)needed < sizeof(buffer)) {
  226         status = converse(pamh, style, buffer, &resp);
  227         pwqc_drop_pam_reply(resp, 1);
  228     } else {
  229         status = PAM_ABORT;
  230     }
  231     _passwdqc_memzero(buffer, sizeof(buffer));
  232 
  233     return status;
  234 }
  235 
  236 static int check_max(passwdqc_params_qc_t *qc, pam_handle_t *pamh,
  237     const char *newpass)
  238 {
  239     if (strlen(newpass) > (size_t)qc->max) {
  240         if (qc->max != 8) {
  241             say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
  242             return -1;
  243         }
  244         say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
  245     }
  246 
  247     return 0;
  248 }
  249 
  250 static int check_pass(struct passwd *pw, const char *pass)
  251 {
  252     const char *hash;
  253     int retval;
  254 
  255 #ifdef HAVE_SHADOW
  256 #ifdef __hpux
  257     if (iscomsec()) {
  258 #else
  259     if (!strcmp(pw->pw_passwd, "x")) {
  260 #endif
  261         struct spwd *spw = getspnam(pw->pw_name);
  262         endspent();
  263         if (!spw)
  264             return -1;
  265         hash = NULL;
  266         if (strlen(spw->sp_pwdp) >= 13) {
  267 #ifdef __hpux
  268             hash = bigcrypt(pass, spw->sp_pwdp);
  269 #else
  270             hash = crypt(pass, spw->sp_pwdp);
  271 #endif
  272         }
  273         retval = (hash && !strcmp(hash, spw->sp_pwdp)) ? 0 : -1;
  274         _passwdqc_memzero(spw->sp_pwdp, strlen(spw->sp_pwdp));
  275         return retval;
  276     }
  277 #endif
  278 
  279     hash = NULL;
  280     if (strlen(pw->pw_passwd) >= 13)
  281         hash = crypt(pass, pw->pw_passwd);
  282     retval = (hash && !strcmp(hash, pw->pw_passwd)) ? 0 : -1;
  283     _passwdqc_memzero(pw->pw_passwd, strlen(pw->pw_passwd));
  284     return retval;
  285 }
  286 
  287 static int am_root(pam_handle_t *pamh)
  288 {
  289     pam_item_t item;
  290     const char *service;
  291 
  292     if (getuid() != 0)
  293         return 0;
  294 
  295     if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS)
  296         return 0;
  297     service = item;
  298 
  299     return !strcmp(service, "passwd") || !strcmp(service, "chpasswd");
  300 }
  301 
  302 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
  303     int argc, const char **argv)
  304 {
  305     passwdqc_params_t params;
  306     struct pam_response *resp;
  307     struct passwd *pw, fake_pw;
  308     pam_item_t item;
  309     const char *user, *oldpass, *newpass;
  310     char *trypass, *randompass;
  311     char *parse_reason;
  312     const char *check_reason;
  313     int ask_oldauthtok;
  314     int randomonly, enforce, retries_left, retry_wanted;
  315     int status;
  316 
  317     passwdqc_params_reset(&params);
  318     if (passwdqc_params_parse(&params, &parse_reason, argc, argv)) {
  319         say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
  320             MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED,
  321             parse_reason);
  322         free(parse_reason);
  323         return PAM_ABORT;
  324     }
  325     status = PAM_SUCCESS;
  326 
  327     ask_oldauthtok = 0;
  328     if (flags & PAM_PRELIM_CHECK) {
  329         if (params.pam.flags & F_ASK_OLDAUTHTOK_PRELIM)
  330             ask_oldauthtok = 1;
  331     } else if (flags & PAM_UPDATE_AUTHTOK) {
  332         if (params.pam.flags & F_ASK_OLDAUTHTOK_UPDATE)
  333             ask_oldauthtok = 1;
  334     } else {
  335         passwdqc_params_free(&params);
  336         return PAM_SERVICE_ERR;
  337     }
  338 
  339     if (ask_oldauthtok && !am_root(pamh)) {
  340         status = converse(pamh, PAM_PROMPT_ECHO_OFF,
  341             PROMPT_OLDPASS, &resp);
  342 
  343         if (status == PAM_SUCCESS) {
  344             if (resp && resp->resp) {
  345                 status = pam_set_item(pamh,
  346                     PAM_OLDAUTHTOK, resp->resp);
  347                 pwqc_drop_pam_reply(resp, 1);
  348             } else
  349                 status = PAM_AUTHTOK_RECOVERY_ERR;
  350         }
  351 
  352         if (status != PAM_SUCCESS)
  353             return logaudit(pamh, status, &params);
  354     }
  355 
  356     if (flags & PAM_PRELIM_CHECK) {
  357         passwdqc_params_free(&params);
  358         return status;
  359     }
  360 
  361     status = pam_get_item(pamh, PAM_USER, &item);
  362     if (status != PAM_SUCCESS)
  363         return logaudit(pamh, status, &params);
  364     user = item;
  365 
  366     status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
  367     if (status != PAM_SUCCESS)
  368         return logaudit(pamh, status, &params);
  369     oldpass = item;
  370 
  371     if (params.pam.flags & F_NON_UNIX) {
  372         pw = &fake_pw;
  373         memset(pw, 0, sizeof(*pw));
  374         pw->pw_name = (char *)user;
  375         pw->pw_gecos = "";
  376         pw->pw_dir = "";
  377     } else {
  378 /* As currently implemented, we don't avoid timing leaks for valid vs. not
  379  * usernames and hashes.  Normally, the username would have already been
  380  * checked and determined valid, and the check_oldauthtok option is only needed
  381  * on systems that happen to have similar timing leaks all over the place. */
  382         pw = getpwnam(user);
  383         endpwent();
  384         if (!pw)
  385             return logaudit(pamh, PAM_USER_UNKNOWN, &params);
  386         if ((params.pam.flags & F_CHECK_OLDAUTHTOK) && !am_root(pamh)
  387             && (!oldpass || check_pass(pw, oldpass)))
  388             status = PAM_AUTH_ERR;
  389         _passwdqc_memzero(pw->pw_passwd, strlen(pw->pw_passwd));
  390         if (status != PAM_SUCCESS)
  391             return logaudit(pamh, status, &params);
  392     }
  393 
  394     randomonly = params.qc.min[4] > params.qc.max;
  395 
  396     if (am_root(pamh))
  397         enforce = params.pam.flags & F_ENFORCE_ROOT;
  398     else
  399         enforce = params.pam.flags & F_ENFORCE_USERS;
  400 
  401     if (params.pam.flags & F_USE_AUTHTOK) {
  402         status = pam_get_item(pamh, PAM_AUTHTOK, &item);
  403         if (status != PAM_SUCCESS)
  404             return logaudit(pamh, status, &params);
  405         newpass = item;
  406         if (!newpass ||
  407             (check_max(&params.qc, pamh, newpass) && enforce))
  408             return logaudit(pamh, PAM_AUTHTOK_ERR, &params);
  409         check_reason =
  410             passwdqc_check(&params.qc, newpass, oldpass, pw);
  411         if (check_reason) {
  412             say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS,
  413                 check_reason);
  414             if (enforce)
  415                 status = PAM_AUTHTOK_ERR;
  416         }
  417         return logaudit(pamh, status, &params);
  418     }
  419 
  420     retries_left = params.pam.retry;
  421 
  422 retry:
  423     retry_wanted = 0;
  424 
  425     if (!randomonly &&
  426         params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
  427         status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
  428     else
  429         status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
  430     if (status != PAM_SUCCESS)
  431         return logaudit(pamh, status, &params);
  432 
  433     if (!randomonly && params.qc.min[0] == params.qc.min[4])
  434         status = say(pamh, PAM_TEXT_INFO,
  435             MESSAGE_EXPLAIN_PASSWORD_1_CLASS(params.qc.min[4]));
  436 
  437     else if (!randomonly && params.qc.min[3] == params.qc.min[4])
  438         status = say(pamh, PAM_TEXT_INFO,
  439             MESSAGE_EXPLAIN_PASSWORD_N_CLASSES(params.qc.min[4]),
  440             params.qc.min[1] != params.qc.min[3] ? 3 : 2);
  441     else if (!randomonly && params.qc.min[3] == INT_MAX)
  442         status = say(pamh, PAM_TEXT_INFO,
  443             MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES(params.qc.min[4]));
  444     else if (!randomonly) {
  445         status = say(pamh, PAM_TEXT_INFO,
  446             MESSAGE_EXPLAIN_PASSWORD_ALL_OR_3_CLASSES(params.qc.min[4]),
  447             params.qc.min[3]);
  448     }
  449     if (status != PAM_SUCCESS)
  450         return logaudit(pamh, status, &params);
  451 
  452     if (!randomonly &&
  453         params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) {
  454         status = say(pamh, PAM_TEXT_INFO,
  455             MESSAGE_EXPLAIN_PASSPHRASE(params.qc.passphrase_words),
  456             params.qc.min[2], params.qc.max);
  457         if (status != PAM_SUCCESS)
  458             return logaudit(pamh, status, &params);
  459     }
  460 
  461     randompass = passwdqc_random(&params.qc);
  462     if (randompass) {
  463         status = say(pamh, PAM_TEXT_INFO, randomonly ?
  464             MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
  465         if (status != PAM_SUCCESS) {
  466             pwqc_overwrite_string(randompass);
  467             pwqc_drop_mem(randompass);
  468         }
  469     } else if (randomonly) {
  470         say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
  471             MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED);
  472         return logaudit(pamh, PAM_AUTHTOK_ERR, &params);
  473     }
  474 
  475     status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
  476     if (status == PAM_SUCCESS && (!resp || !resp->resp))
  477         status = PAM_AUTHTOK_ERR;
  478 
  479     if (status != PAM_SUCCESS) {
  480         pwqc_overwrite_string(randompass);
  481         pwqc_drop_mem(randompass);
  482         return logaudit(pamh, status, &params);
  483     }
  484 
  485     trypass = strdup(resp->resp);
  486 
  487     pwqc_drop_pam_reply(resp, 1);
  488 
  489     if (!trypass) {
  490         pwqc_overwrite_string(randompass);
  491         pwqc_drop_mem(randompass);
  492         return logaudit(pamh, PAM_AUTHTOK_ERR, &params);
  493     }
  494 
  495     if (check_max(&params.qc, pamh, trypass) && enforce) {
  496         status = PAM_AUTHTOK_ERR;
  497         retry_wanted = 1;
  498     }
  499 
  500     check_reason = NULL; /* unused */
  501     if (status == PAM_SUCCESS &&
  502         (!randompass || !strstr(trypass, randompass)) &&
  503         (randomonly ||
  504          (check_reason = passwdqc_check(&params.qc, trypass, oldpass, pw)))) {
  505         if (randomonly)
  506             say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
  507         else
  508             say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS,
  509                 check_reason);
  510         if (enforce) {
  511             status = PAM_AUTHTOK_ERR;
  512             retry_wanted = 1;
  513         }
  514     }
  515 
  516     if (status == PAM_SUCCESS)
  517         status = converse(pamh, PAM_PROMPT_ECHO_OFF,
  518             PROMPT_NEWPASS2, &resp);
  519     if (status == PAM_SUCCESS) {
  520         if (resp && resp->resp) {
  521             if (strcmp(trypass, resp->resp)) {
  522                 status = say(pamh,
  523                     PAM_ERROR_MSG, MESSAGE_MISTYPED);
  524                 if (status == PAM_SUCCESS) {
  525                     status = PAM_AUTHTOK_ERR;
  526                     retry_wanted = 1;
  527                 }
  528             }
  529             pwqc_drop_pam_reply(resp, 1);
  530         } else
  531             status = PAM_AUTHTOK_ERR;
  532     }
  533 
  534     if (status == PAM_SUCCESS)
  535         status = pam_set_item(pamh, PAM_AUTHTOK, trypass);
  536 
  537     pwqc_overwrite_string(randompass);
  538     pwqc_drop_mem(randompass);
  539 
  540     pwqc_overwrite_string(trypass);
  541     pwqc_drop_mem(trypass);
  542 
  543     if (retry_wanted && --retries_left > 0) {
  544         status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
  545         if (status == PAM_SUCCESS)
  546             goto retry;
  547     }
  548 
  549     return logaudit(pamh, status, &params);
  550 }
  551 
  552 #ifdef PAM_MODULE_ENTRY
  553 PAM_MODULE_ENTRY("pam_passwdqc");
  554 #elif defined(PAM_STATIC)
  555 const struct pam_module _pam_passwdqc_modstruct = {
  556     "pam_passwdqc",
  557     NULL,
  558     NULL,
  559     NULL,
  560     NULL,
  561     NULL,
  562     pam_sm_chauthtok
  563 };
  564 #endif