7 * 0.86. added support for setting minimum numbers of digits, uppers,
9 * 0.85. added six new options to use this with long passwords.
10 * 0.8. tidied output and improved D(()) usage for debugging.
11 * 0.7. added support for more obscure checks for new passwd.
12 * 0.6. root can reset user passwd to any values (it's only warned)
13 * 0.5. supports retries - 'retry=N' argument
14 * 0.4. added argument 'type=XXX' for 'New XXX password' prompt
15 * 0.3. Added argument 'debug'
16 * 0.2. new password is feeded to cracklib for verify after typed once
21 * Written by Cristian Gafton <gafton@redhat.com> 1996/09/10
22 * Long password support by Philip W. Dalrymple <pwd@mdtsoft.com> 1997/07/18
23 * See the end of the file for Copyright Information
25 * Modification for long password systems (>8 chars). The original
26 * module had problems when used in a md5 password system in that it
27 * allowed too short passwords but required that at least half of the
28 * bytes in the new password did not appear in the old one. this
29 * action is still the default and the changes should not break any
30 * current user. This modification adds 6 new options, one to set the
31 * number of bytes in the new password that are not in the old one,
32 * the other five to control the length checking, these are all
33 * documented (or will be before anyone else sees this code) in the PAM
34 * S.A.G. in the section on the cracklib module.
37 #include <security/_pam_aconf.h>
48 #include <sys/types.h>
52 extern char *FascistCheck(char *pw, const char *dictpath);
54 #ifndef CRACKLIB_DICTPATH
55 #define CRACKLIB_DICTPATH "/usr/share/dict/cracklib_dict"
58 #define PROMPT1 "New %s%spassword: "
59 #define PROMPT2 "Retype new %s%spassword: "
60 #define MISTYPED_PASS "Sorry, passwords do not match"
63 * here, we make a definition for the externally accessible function
64 * in this file (this definition is required for static a module
65 * but strongly encouraged generally) it is used to instruct the
66 * modules include file to define the function prototypes.
69 #define PAM_SM_PASSWORD
71 #include <security/pam_modules.h>
72 #include <security/_pam_macros.h>
75 #include <security/pam_appl.h>
76 #endif /* LINUX_PAM */
80 static void _pam_log(int err, const char *format, ...)
84 va_start(args, format);
85 openlog("PAM-Cracklib", LOG_CONS|LOG_PID, LOG_AUTH);
86 vsyslog(err, format, args);
91 /* argument parsing */
92 #define PAM_DEBUG_ARG 0x0001
94 struct cracklib_options {
104 char prompt_type[BUFSIZ];
107 #define CO_RETRY_TIMES 1
108 #define CO_DIFF_OK 10
109 #define CO_DIFF_IGNORE 23
110 #define CO_MIN_LENGTH 9
111 # define CO_MIN_LENGTH_BASE 5
112 #define CO_DIG_CREDIT 1
113 #define CO_UP_CREDIT 1
114 #define CO_LOW_CREDIT 1
115 #define CO_OTH_CREDIT 1
116 #define CO_USE_AUTHTOK 0
118 static int _pam_parse(struct cracklib_options *opt, int argc, const char **argv)
122 /* step through arguments */
123 for (ctrl=0; argc-- > 0; ++argv) {
126 /* generic options */
128 if (!strcmp(*argv,"debug"))
129 ctrl |= PAM_DEBUG_ARG;
130 else if (!strncmp(*argv,"type=",5))
131 strncpy(opt->prompt_type, *argv+5, sizeof(opt->prompt_type) - 1);
132 else if (!strncmp(*argv,"retry=",6)) {
133 opt->retry_times = strtol(*argv+6,&ep,10);
134 if (!ep || (opt->retry_times < 1))
135 opt->retry_times = CO_RETRY_TIMES;
136 } else if (!strncmp(*argv,"difok=",6)) {
137 opt->diff_ok = strtol(*argv+6,&ep,10);
138 if (!ep || (opt->diff_ok < 0))
139 opt->diff_ok = CO_DIFF_OK;
140 } else if (!strncmp(*argv,"difignore=",10)) {
141 opt->diff_ignore = strtol(*argv+10,&ep,10);
142 if (!ep || (opt->diff_ignore < 0))
143 opt->diff_ignore = CO_DIFF_IGNORE;
144 } else if (!strncmp(*argv,"minlen=",7)) {
145 opt->min_length = strtol(*argv+7,&ep,10);
146 if (!ep || (opt->min_length < CO_MIN_LENGTH_BASE))
147 opt->min_length = CO_MIN_LENGTH_BASE;
148 } else if (!strncmp(*argv,"dcredit=",8)) {
149 opt->dig_credit = strtol(*argv+8,&ep,10);
152 } else if (!strncmp(*argv,"ucredit=",8)) {
153 opt->up_credit = strtol(*argv+8,&ep,10);
156 } else if (!strncmp(*argv,"lcredit=",8)) {
157 opt->low_credit = strtol(*argv+8,&ep,10);
160 } else if (!strncmp(*argv,"ocredit=",8)) {
161 opt->oth_credit = strtol(*argv+8,&ep,10);
164 } else if (!strncmp(*argv,"use_authtok",11)) {
165 opt->use_authtok = 1;
167 _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
170 opt->prompt_type[sizeof(opt->prompt_type) - 1] = '\0';
175 /* Helper functions */
177 /* this is a front-end for module-application conversations */
178 static int converse(pam_handle_t *pamh, int ctrl, int nargs,
179 struct pam_message **message,
180 struct pam_response **response)
183 struct pam_conv *conv;
185 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
187 if ( retval == PAM_SUCCESS ) {
188 retval = conv->conv(nargs, (const struct pam_message **)message,
189 response, conv->appdata_ptr);
190 if (retval != PAM_SUCCESS && (ctrl && PAM_DEBUG_ARG)) {
191 _pam_log(LOG_DEBUG, "conversation failure [%s]",
192 pam_strerror(pamh, retval));
195 _pam_log(LOG_ERR, "couldn't obtain coversation function [%s]",
196 pam_strerror(pamh, retval));
199 return retval; /* propagate error status */
202 static int make_remark(pam_handle_t *pamh, unsigned int ctrl,
203 int type, const char *text)
205 struct pam_message *pmsg[1], msg[1];
206 struct pam_response *resp;
211 msg[0].msg_style = type;
214 retval = converse(pamh, ctrl, 1, pmsg, &resp);
215 if (retval == PAM_SUCCESS)
216 _pam_drop_reply(resp, 1);
221 /* use this to free strings. ESPECIALLY password strings */
222 static char *_pam_delete(register char *xx)
230 * can't be a palindrome - like `R A D A R' or `M A D A M'
232 static int palindrome(const char *old, const char *new)
238 for (j = 0;j < i;j++)
239 if (new[i - j - 1] != new[j])
246 * This is a reasonably severe check for a different selection of characters
247 * in the old and new passwords.
250 static int similar(struct cracklib_options *opt,
251 const char *old, const char *new)
255 for (i = j = 0; old[i]; i++) {
256 if (strchr (new, old[i])) {
261 if (((i-j) >= opt->diff_ok)
262 || (strlen(new) >= (j * 2))
263 || (strlen(new) >= opt->diff_ignore)) {
264 /* passwords are not very similar */
268 /* passwords are too similar */
273 * a nice mix of characters.
275 static int simple(struct cracklib_options *opt, const char *old, const char *new)
284 for (i = 0;new[i];i++) {
285 if (isdigit (new[i]))
287 else if (isupper (new[i]))
289 else if (islower (new[i]))
296 * The scam was this - a password of only one character type
297 * must be 8 letters long. Two types, 7, and so on.
298 * This is now changed, the base size and the credits or defaults
299 * see the docs on the module for info on these parameters, the
300 * defaults cause the effect to be the same as before the change
303 if ((opt->dig_credit >= 0) && (digits > opt->dig_credit))
304 digits = opt->dig_credit;
306 if ((opt->up_credit >= 0) && (uppers > opt->up_credit))
307 uppers = opt->up_credit;
309 if ((opt->low_credit >= 0) && (lowers > opt->low_credit))
310 lowers = opt->low_credit;
312 if ((opt->oth_credit >= 0) && (others > opt->oth_credit))
313 others = opt->oth_credit;
315 size = opt->min_length;
317 if (opt->dig_credit >= 0)
319 else if (digits < opt->dig_credit * -1)
322 if (opt->up_credit >= 0)
324 else if (uppers < opt->up_credit * -1)
327 if (opt->low_credit >= 0)
329 else if (lowers < opt->low_credit * -1)
332 if (opt->oth_credit >= 0)
334 else if (others < opt->oth_credit * -1)
343 static char * str_lower(char *string)
347 for (cp = string; *cp; cp++)
352 static const char * password_check(struct cracklib_options *opt, const char *old, const char *new)
354 const char *msg = NULL;
355 char *oldmono, *newmono, *wrapped;
357 if (strcmp(new, old) == 0) {
358 msg = "is the same as the old one";
362 newmono = str_lower(x_strdup(new));
363 oldmono = str_lower(x_strdup(old));
364 wrapped = malloc(strlen(oldmono) * 2 + 1);
365 strcpy (wrapped, oldmono);
366 strcat (wrapped, oldmono);
368 if (palindrome(oldmono, newmono))
369 msg = "is a palindrome";
371 if (!msg && strcmp(oldmono, newmono) == 0)
372 msg = "case changes only";
374 if (!msg && similar(opt, oldmono, newmono))
375 msg = "is too similar to the old one";
377 if (!msg && simple(opt, old, new))
378 msg = "is too simple";
380 if (!msg && strstr(wrapped, newmono))
383 memset(newmono, 0, strlen(newmono));
384 memset(oldmono, 0, strlen(oldmono));
385 memset(wrapped, 0, strlen(wrapped));
394 #define OLD_PASSWORDS_FILE "/etc/security/opasswd"
396 static const char * check_old_password(const char *forwho, const char *newpass)
398 static char buf[16384];
399 char *s_luser, *s_uid, *s_npas, *s_pas;
400 const char *msg = NULL;
403 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
407 while (fgets(buf, 16380, opwfile)) {
408 if (!strncmp(buf, forwho, strlen(forwho))) {
409 buf[strlen(buf)-1] = '\0';
410 s_luser = strtok(buf, ":,");
411 s_uid = strtok(NULL, ":,");
412 s_npas = strtok(NULL, ":,");
413 s_pas = strtok(NULL, ":,");
414 while (s_pas != NULL) {
415 if (!strcmp(crypt(newpass, s_pas), s_pas)) {
416 msg = "has been already used";
419 s_pas = strtok(NULL, ":,");
430 static int _pam_unix_approve_pass(pam_handle_t *pamh,
432 struct cracklib_options *opt,
433 const char *pass_old,
434 const char *pass_new)
436 const char *msg = NULL;
440 if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
441 if (ctrl && PAM_DEBUG_ARG)
442 _pam_log(LOG_DEBUG, "bad authentication token");
443 make_remark(pamh, ctrl, PAM_ERROR_MSG,
445 "No password supplied":"Password unchanged" );
446 return PAM_AUTHTOK_ERR;
450 * if one wanted to hardwire authentication token strength
451 * checking this would be the place
453 msg = password_check(opt, pass_old,pass_new);
455 retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
456 if (retval != PAM_SUCCESS) {
457 if (ctrl & PAM_DEBUG_ARG) {
458 _pam_log(LOG_ERR,"Can not get username");
459 return PAM_AUTHTOK_ERR;
462 msg = check_old_password(user, pass_new);
468 memset(remark,0,sizeof(remark));
469 snprintf(remark,sizeof(remark),"BAD PASSWORD: %s",msg);
470 if (ctrl && PAM_DEBUG_ARG)
471 _pam_log(LOG_NOTICE, "new passwd fails strength check: %s",
473 make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
474 return PAM_AUTHTOK_ERR;
480 /* The Main Thing (by Cristian Gafton, CEO at this module :-)
481 * (stolen from http://home.netscape.com)
483 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
484 int argc, const char **argv)
487 struct cracklib_options options;
489 options.retry_times = CO_RETRY_TIMES;
490 options.diff_ok = CO_DIFF_OK;
491 options.diff_ignore = CO_DIFF_IGNORE;
492 options.min_length = CO_MIN_LENGTH;
493 options.dig_credit = CO_DIG_CREDIT;
494 options.up_credit = CO_UP_CREDIT;
495 options.low_credit = CO_LOW_CREDIT;
496 options.oth_credit = CO_OTH_CREDIT;
497 options.use_authtok = CO_USE_AUTHTOK;
498 memset(options.prompt_type, 0, BUFSIZ);
500 ctrl = _pam_parse(&options, argc, argv);
503 if (!options.prompt_type[0])
504 strcpy(options.prompt_type,"UNIX");
506 if (flags & PAM_PRELIM_CHECK) {
507 /* Check for passwd dictionary */
509 char buf[sizeof(CRACKLIB_DICTPATH)+10];
513 memset(buf,0,sizeof(buf)); /* zero the buffer */
514 snprintf(buf,sizeof(buf),"%s.pwd",CRACKLIB_DICTPATH);
516 if (!stat(buf,&st) && st.st_size)
519 if (ctrl & PAM_DEBUG_ARG)
520 _pam_log(LOG_NOTICE,"dict path '%s'[.pwd] is invalid",
526 return PAM_SERVICE_ERR;
528 } else if (flags & PAM_UPDATE_AUTHTOK) {
530 char *token1, *token2, *oldtoken;
531 struct pam_message msg[1],*pmsg[1];
532 struct pam_response *resp;
533 const char *cracklib_dictpath = CRACKLIB_DICTPATH;
537 retval = pam_get_item(pamh, PAM_OLDAUTHTOK,
538 (const void **)&oldtoken);
539 if (retval != PAM_SUCCESS) {
540 if (ctrl & PAM_DEBUG_ARG)
541 _pam_log(LOG_ERR,"Can not get old passwd");
543 retval = PAM_SUCCESS;
548 * make sure nothing inappropriate gets returned
550 token1 = token2 = NULL;
552 if (!options.retry_times) {
553 D(("returning %s because maxtries reached",
554 pam_strerror(pamh, retval)));
558 /* Planned modus operandi:
560 * Verify it against cracklib.
561 * If okay get it a second time.
562 * Check to be the same with the first one.
563 * set PAM_AUTHTOK and return
566 if (options.use_authtok == 1) {
567 const char *item = NULL;
569 retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &item);
570 if (retval != PAM_SUCCESS) {
573 ,"pam_get_item returned error to pam_cracklib"
575 } else if (item != NULL) { /* we have a password! */
576 token1 = x_strdup(item);
579 retval = PAM_AUTHTOK_RECOVER_ERR; /* didn't work */
583 /* Prepare to ask the user for the first time */
584 memset(prompt,0,sizeof(prompt));
585 snprintf(prompt,sizeof(prompt),PROMPT1,
586 options.prompt_type, options.prompt_type[0]?" ":"");
588 msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
592 retval = converse(pamh, ctrl, 1, pmsg, &resp);
594 /* interpret the response */
595 if (retval == PAM_SUCCESS) { /* a good conversation */
596 token1 = x_strdup(resp[0].resp);
597 if (token1 == NULL) {
599 "could not recover authentication token 1");
600 retval = PAM_AUTHTOK_RECOVER_ERR;
604 * tidy up the conversation (resp_retcode) is ignored
606 _pam_drop_reply(resp, 1);
608 retval = (retval == PAM_SUCCESS) ?
609 PAM_AUTHTOK_RECOVER_ERR:retval ;
613 if (retval != PAM_SUCCESS) {
614 if (ctrl && PAM_DEBUG_ARG)
615 _pam_log(LOG_DEBUG,"unable to obtain a password");
619 D(("testing password, retval = %s", pam_strerror(pamh, retval)));
620 /* now test this passwd against cracklib */
625 bzero(remark,sizeof(remark));
626 D(("against cracklib"));
627 if ((crack_msg = FascistCheck(token1, cracklib_dictpath))) {
628 if (ctrl && PAM_DEBUG_ARG)
629 _pam_log(LOG_DEBUG,"bad password: %s",crack_msg);
630 snprintf(remark,sizeof(remark),"BAD PASSWORD: %s", crack_msg);
631 make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
632 if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
633 retval = PAM_AUTHTOK_ERR;
635 retval = PAM_SUCCESS;
637 /* check it for strength too... */
640 retval = _pam_unix_approve_pass(pamh,ctrl,&options,
642 if (retval != PAM_SUCCESS) {
643 if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
644 retval = PAM_AUTHTOK_ERR;
646 retval = PAM_SUCCESS;
652 D(("after testing: retval = %s", pam_strerror(pamh, retval)));
653 /* if cracklib/strength check said it is a bad passwd... */
654 if ((retval != PAM_SUCCESS) && (retval != PAM_IGNORE)) {
657 temp_unused = pam_set_item(pamh, PAM_AUTHTOK, NULL);
658 token1 = _pam_delete(token1);
662 /* Now we have a good passwd. Ask for it once again */
664 if (options.use_authtok == 0) {
665 bzero(prompt,sizeof(prompt));
666 sprintf(prompt,sizeof(prompt),PROMPT2,
667 options.prompt_type, options.prompt_type[0]?" ":"");
669 msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
673 retval = converse(pamh, ctrl, 1, pmsg, &resp);
675 /* interpret the response */
676 if (retval == PAM_SUCCESS) { /* a good conversation */
677 token2 = x_strdup(resp[0].resp);
678 if (token2 == NULL) {
680 "could not recover authentication token 2");
681 retval = PAM_AUTHTOK_RECOVER_ERR;
685 * tidy up the conversation (resp_retcode) is ignored
687 _pam_drop_reply(resp, 1);
689 retval = (retval == PAM_SUCCESS) ?
690 PAM_AUTHTOK_RECOVER_ERR:retval ;
693 if (retval != PAM_SUCCESS) {
694 if (ctrl && PAM_DEBUG_ARG)
696 ,"unable to obtain the password a second time");
700 /* Hopefully now token1 and token2 the same password ... */
701 if (strcmp(token1,token2) != 0) {
703 make_remark(pamh, ctrl, PAM_ERROR_MSG, MISTYPED_PASS);
704 token1 = _pam_delete(token1);
705 token2 = _pam_delete(token2);
706 pam_set_item(pamh, PAM_AUTHTOK, NULL);
707 if (ctrl & PAM_DEBUG_ARG)
708 _pam_log(LOG_NOTICE,"Password mistyped");
709 retval = PAM_AUTHTOK_RECOVER_ERR;
713 /* Yes, the password was typed correct twice
714 * we store this password as an item
718 const char *item = NULL;
720 retval = pam_set_item(pamh, PAM_AUTHTOK, token1);
723 token1 = _pam_delete(token1);
724 token2 = _pam_delete(token2);
726 if ( (retval != PAM_SUCCESS) ||
727 ((retval = pam_get_item(pamh, PAM_AUTHTOK,
728 (const void **)&item)
729 ) != PAM_SUCCESS) ) {
730 _pam_log(LOG_CRIT, "error manipulating password");
733 item = NULL; /* break link to password */
738 } while (options.retry_times--);
741 if (ctrl & PAM_DEBUG_ARG)
742 _pam_log(LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
743 return PAM_SERVICE_ERR;
747 return PAM_SERVICE_ERR;
753 /* static module data */
754 struct pam_module _pam_cracklib_modstruct = {
766 * Copyright (c) Cristian Gafton <gafton@redhat.com>, 1996.
767 * All rights reserved
769 * Redistribution and use in source and binary forms, with or without
770 * modification, are permitted provided that the following conditions
772 * 1. Redistributions of source code must retain the above copyright
773 * notice, and the entire permission notice in its entirety,
774 * including the disclaimer of warranties.
775 * 2. Redistributions in binary form must reproduce the above copyright
776 * notice, this list of conditions and the following disclaimer in the
777 * documentation and/or other materials provided with the distribution.
778 * 3. The name of the author may not be used to endorse or promote
779 * products derived from this software without specific prior
780 * written permission.
782 * ALTERNATIVELY, this product may be distributed under the terms of
783 * the GNU Public License, in which case the provisions of the GPL are
784 * required INSTEAD OF the above restrictions. (This clause is
785 * necessary due to a potential bad interaction between the GPL and
786 * the restrictions contained in a BSD-style copyright.)
788 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
789 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
790 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
791 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
792 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
793 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
794 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
795 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
796 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
797 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
798 * OF THE POSSIBILITY OF SUCH DAMAGE.
800 * The following copyright was appended for the long password support
801 * added with the libpam 0.58 release:
803 * Modificaton Copyright (c) Philip W. Dalrymple III <pwd@mdtsoft.com>
804 * 1997. All rights reserved
806 * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO
807 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
808 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
809 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
810 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
811 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
812 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
813 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
814 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
815 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
816 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
817 * OF THE POSSIBILITY OF SUCH DAMAGE.