6 * 0.9. switch to using a distance algorithm in similar()
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.
42 #elif defined(HAVE_CRYPT_H)
50 #include <sys/types.h>
55 #include <security/pam_modutil.h>
60 extern char *FascistCheck(char *pw, const char *dictpath);
63 #ifndef CRACKLIB_DICTS
64 #define CRACKLIB_DICTS NULL
67 /* For Translators: "%s%s" could be replaced with "<service> " or "". */
68 #define PROMPT1 _("New %s%spassword: ")
69 /* For Translators: "%s%s" could be replaced with "<service> " or "". */
70 #define PROMPT2 _("Retype new %s%spassword: ")
71 #define MISTYPED_PASS _("Sorry, passwords do not match.")
76 #define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b))
79 * here, we make a definition for the externally accessible function
80 * in this file (this definition is required for static a module
81 * but strongly encouraged generally) it is used to instruct the
82 * modules include file to define the function prototypes.
85 #define PAM_SM_PASSWORD
87 #include <security/pam_modules.h>
88 #include <security/_pam_macros.h>
89 #include <security/pam_ext.h>
91 /* argument parsing */
92 #define PAM_DEBUG_ARG 0x0001
94 struct cracklib_options {
105 int max_class_repeat;
108 int enforce_for_root;
109 const char *cracklib_dictpath;
112 #define CO_RETRY_TIMES 1
114 #define CO_MIN_LENGTH 9
115 # define CO_MIN_LENGTH_BASE 5
116 #define CO_DIG_CREDIT 1
117 #define CO_UP_CREDIT 1
118 #define CO_LOW_CREDIT 1
119 #define CO_OTH_CREDIT 1
120 #define CO_MIN_WORD_LENGTH 4
123 _pam_parse (pam_handle_t *pamh, struct cracklib_options *opt,
124 int argc, const char **argv)
128 /* step through arguments */
129 for (ctrl=0; argc-- > 0; ++argv) {
132 /* generic options */
134 if (!strcmp(*argv,"debug"))
135 ctrl |= PAM_DEBUG_ARG;
136 else if (!strncmp(*argv,"type=",5))
137 pam_set_item (pamh, PAM_AUTHTOK_TYPE, *argv+5);
138 else if (!strncmp(*argv,"retry=",6)) {
139 opt->retry_times = strtol(*argv+6,&ep,10);
140 if (!ep || (opt->retry_times < 1))
141 opt->retry_times = CO_RETRY_TIMES;
142 } else if (!strncmp(*argv,"difok=",6)) {
143 opt->diff_ok = strtol(*argv+6,&ep,10);
144 if (!ep || (opt->diff_ok < 0))
145 opt->diff_ok = CO_DIFF_OK;
146 } else if (!strncmp(*argv,"difignore=",10)) {
148 } else if (!strncmp(*argv,"minlen=",7)) {
149 opt->min_length = strtol(*argv+7,&ep,10);
150 if (!ep || (opt->min_length < CO_MIN_LENGTH_BASE))
151 opt->min_length = CO_MIN_LENGTH_BASE;
152 } else if (!strncmp(*argv,"dcredit=",8)) {
153 opt->dig_credit = strtol(*argv+8,&ep,10);
156 } else if (!strncmp(*argv,"ucredit=",8)) {
157 opt->up_credit = strtol(*argv+8,&ep,10);
160 } else if (!strncmp(*argv,"lcredit=",8)) {
161 opt->low_credit = strtol(*argv+8,&ep,10);
164 } else if (!strncmp(*argv,"ocredit=",8)) {
165 opt->oth_credit = strtol(*argv+8,&ep,10);
168 } else if (!strncmp(*argv,"minclass=",9)) {
169 opt->min_class = strtol(*argv+9,&ep,10);
172 if (opt->min_class > 4)
174 } else if (!strncmp(*argv,"maxrepeat=",10)) {
175 opt->max_repeat = strtol(*argv+10,&ep,10);
178 } else if (!strncmp(*argv,"maxsequence=",12)) {
179 opt->max_sequence = strtol(*argv+12,&ep,10);
181 opt->max_sequence = 0;
182 } else if (!strncmp(*argv,"maxclassrepeat=",15)) {
183 opt->max_class_repeat = strtol(*argv+15,&ep,10);
185 opt->max_class_repeat = 0;
186 } else if (!strncmp(*argv,"reject_username",15)) {
187 opt->reject_user = 1;
188 } else if (!strncmp(*argv,"gecoscheck",10)) {
189 opt->gecos_check = 1;
190 } else if (!strncmp(*argv,"enforce_for_root",16)) {
191 opt->enforce_for_root = 1;
192 } else if (!strncmp(*argv,"authtok_type",12)) {
193 /* for pam_get_authtok, ignore */;
194 } else if (!strncmp(*argv,"use_authtok",11)) {
195 /* for pam_get_authtok, ignore */;
196 } else if (!strncmp(*argv,"use_first_pass",14)) {
197 /* for pam_get_authtok, ignore */;
198 } else if (!strncmp(*argv,"try_first_pass",14)) {
199 /* for pam_get_authtok, ignore */;
200 } else if (!strncmp(*argv,"dictpath=",9)) {
201 opt->cracklib_dictpath = *argv+9;
202 if (!*(opt->cracklib_dictpath)) {
203 opt->cracklib_dictpath = CRACKLIB_DICTS;
206 pam_syslog(pamh,LOG_ERR,"pam_parse: unknown option; %s",*argv);
213 /* Helper functions */
216 * can't be a palindrome - like `R A D A R' or `M A D A M'
218 static int palindrome(const char *new)
224 for (j = 0;j < i;j++)
225 if (new[i - j - 1] != new[j])
232 * Calculate how different two strings are in terms of the number of
233 * character removals, additions, and changes needed to go from one to
237 static int distdifferent(const char *old, const char *new,
242 if ((i == 0) || (strlen(old) < i)) {
247 if ((j == 0) || (strlen(new) < j)) {
255 static int distcalculate(int **distances, const char *old, const char *new,
260 if (distances[i][j] != -1) {
261 return distances[i][j];
264 tmp = distcalculate(distances, old, new, i - 1, j - 1);
265 tmp = MIN(tmp, distcalculate(distances, old, new, i, j - 1));
266 tmp = MIN(tmp, distcalculate(distances, old, new, i - 1, j));
267 tmp += distdifferent(old, new, i, j);
269 distances[i][j] = tmp;
274 static int distance(const char *old, const char *new)
276 int **distances = NULL;
277 size_t m, n, i, j, r;
281 distances = malloc(sizeof(int*) * (m + 1));
283 for (i = 0; i <= m; i++) {
284 distances[i] = malloc(sizeof(int) * (n + 1));
285 for(j = 0; j <= n; j++) {
286 distances[i][j] = -1;
289 for (i = 0; i <= m; i++) {
292 for (j = 0; j <= n; j++) {
297 r = distcalculate(distances, old, new, m, n);
299 for (i = 0; i <= m; i++) {
300 memset(distances[i], 0, sizeof(int) * (n + 1));
308 static int similar(struct cracklib_options *opt,
309 const char *old, const char *new)
311 if (distance(old, new) >= opt->diff_ok) {
315 if (strlen(new) >= (strlen(old) * 2)) {
319 /* passwords are too similar */
324 * enough classes of charecters
327 static int minclass (struct cracklib_options *opt,
339 for (i = 0; new[i]; i++)
341 if (isdigit (new[i]))
343 else if (isupper (new[i]))
345 else if (islower (new[i]))
351 total_class = digits + uppers + lowers + others;
353 D (("total class: %d\tmin_class: %d", total_class, opt->min_class));
355 if (total_class >= opt->min_class)
365 * a nice mix of characters.
367 static int simple(struct cracklib_options *opt, const char *new)
375 enum { NONE, DIGIT, UCASE, LCASE, OTHER } prevclass = NONE;
378 for (i = 0;new[i];i++) {
379 if (isdigit (new[i])) {
381 if (prevclass != DIGIT) {
387 else if (isupper (new[i])) {
389 if (prevclass != UCASE) {
395 else if (islower (new[i])) {
397 if (prevclass != LCASE) {
405 if (prevclass != OTHER) {
411 if (opt->max_class_repeat > 1 && sameclass > opt->max_class_repeat) {
417 * The scam was this - a password of only one character type
418 * must be 8 letters long. Two types, 7, and so on.
419 * This is now changed, the base size and the credits or defaults
420 * see the docs on the module for info on these parameters, the
421 * defaults cause the effect to be the same as before the change
424 if ((opt->dig_credit >= 0) && (digits > opt->dig_credit))
425 digits = opt->dig_credit;
427 if ((opt->up_credit >= 0) && (uppers > opt->up_credit))
428 uppers = opt->up_credit;
430 if ((opt->low_credit >= 0) && (lowers > opt->low_credit))
431 lowers = opt->low_credit;
433 if ((opt->oth_credit >= 0) && (others > opt->oth_credit))
434 others = opt->oth_credit;
436 size = opt->min_length;
438 if (opt->dig_credit >= 0)
440 else if (digits < opt->dig_credit * -1)
443 if (opt->up_credit >= 0)
445 else if (uppers < opt->up_credit * -1)
448 if (opt->low_credit >= 0)
450 else if (lowers < opt->low_credit * -1)
453 if (opt->oth_credit >= 0)
455 else if (others < opt->oth_credit * -1)
464 static int consecutive(struct cracklib_options *opt, const char *new)
470 if (opt->max_repeat == 0)
473 for (i = 0; new[i]; i++) {
474 if (i > 0 && new[i] == c) {
476 if (same > opt->max_repeat)
486 static int sequence(struct cracklib_options *opt, const char *new)
493 if (opt->max_sequence == 0)
499 for (i = 1; new[i]; i++) {
503 if (sequp > opt->max_sequence)
506 } else if (new[i] == c-1) {
508 if (seqdown > opt->max_sequence)
519 static int wordcheck(const char *new, char *word)
523 if (strstr(new, word) != NULL)
526 /* now reverse the word, we can do that in place
527 as it is strdup-ed */
529 b = word+strlen(word)-1;
540 if (strstr(new, word) != NULL)
545 static int usercheck(struct cracklib_options *opt, const char *new,
548 if (!opt->reject_user)
551 return wordcheck(new, user);
554 static char * str_lower(char *string)
561 for (cp = string; *cp; cp++)
566 static int gecoscheck(pam_handle_t *pamh, struct cracklib_options *opt, const char *new,
574 if (!opt->gecos_check)
577 if ((pwd = pam_modutil_getpwnam(pamh, user)) == NULL) {
581 list = strdup(pwd->pw_gecos);
583 if (list == NULL || *list == '\0') {
588 for (p = list;;p = next + 1) {
589 next = strchr(p, ' ');
593 if (strlen(p) >= CO_MIN_WORD_LENGTH) {
595 if (wordcheck(new, p)) {
609 static const char *password_check(pam_handle_t *pamh, struct cracklib_options *opt,
610 const char *old, const char *new,
613 const char *msg = NULL;
614 char *oldmono = NULL, *newmono, *wrapped = NULL;
615 char *usermono = NULL;
617 if (old && strcmp(new, old) == 0) {
618 msg = _("is the same as the old one");
622 newmono = str_lower(x_strdup(new));
624 msg = _("memory allocation error");
626 usermono = str_lower(x_strdup(user));
628 msg = _("memory allocation error");
631 oldmono = str_lower(x_strdup(old));
633 wrapped = malloc(strlen(oldmono) * 2 + 1);
635 strcpy (wrapped, oldmono);
636 strcat (wrapped, oldmono);
638 msg = _("memory allocation error");
642 if (!msg && palindrome(newmono))
643 msg = _("is a palindrome");
645 if (!msg && oldmono && strcmp(oldmono, newmono) == 0)
646 msg = _("case changes only");
648 if (!msg && oldmono && similar(opt, oldmono, newmono))
649 msg = _("is too similar to the old one");
651 if (!msg && simple(opt, new))
652 msg = _("is too simple");
654 if (!msg && wrapped && strstr(wrapped, newmono))
655 msg = _("is rotated");
657 if (!msg && minclass (opt, new))
658 msg = _("not enough character classes");
660 if (!msg && consecutive(opt, new))
661 msg = _("contains too many same characters consecutively");
663 if (!msg && sequence(opt, new))
664 msg = _("contains too long of a monotonic character sequence");
666 if (!msg && (usercheck(opt, newmono, usermono) || gecoscheck(pamh, opt, newmono, user)))
667 msg = _("contains the user name in some form");
671 memset(newmono, 0, strlen(newmono));
675 memset(oldmono, 0, strlen(oldmono));
679 memset(wrapped, 0, strlen(wrapped));
687 static int _pam_unix_approve_pass(pam_handle_t *pamh,
689 struct cracklib_options *opt,
690 const char *pass_old,
691 const char *pass_new)
693 const char *msg = NULL;
697 if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
698 if (ctrl & PAM_DEBUG_ARG)
699 pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
700 pam_error(pamh, "%s", pass_new == NULL ?
701 _("No password supplied"):_("Password unchanged"));
702 return PAM_AUTHTOK_ERR;
705 retval = pam_get_user(pamh, &user, NULL);
706 if (retval != PAM_SUCCESS || user == NULL) {
707 if (ctrl & PAM_DEBUG_ARG)
708 pam_syslog(pamh,LOG_ERR,"Can not get username");
709 return PAM_AUTHTOK_ERR;
712 * if one wanted to hardwire authentication token strength
713 * checking this would be the place
715 msg = password_check(pamh, opt, pass_old, pass_new, user);
718 if (ctrl & PAM_DEBUG_ARG)
719 pam_syslog(pamh, LOG_NOTICE,
720 "new passwd fails strength check: %s", msg);
721 pam_error(pamh, _("BAD PASSWORD: %s"), msg);
722 return PAM_AUTHTOK_ERR;
728 /* The Main Thing (by Cristian Gafton, CEO at this module :-)
729 * (stolen from http://home.netscape.com)
731 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
732 int argc, const char **argv)
735 struct cracklib_options options;
739 memset(&options, 0, sizeof(options));
740 options.retry_times = CO_RETRY_TIMES;
741 options.diff_ok = CO_DIFF_OK;
742 options.min_length = CO_MIN_LENGTH;
743 options.dig_credit = CO_DIG_CREDIT;
744 options.up_credit = CO_UP_CREDIT;
745 options.low_credit = CO_LOW_CREDIT;
746 options.oth_credit = CO_OTH_CREDIT;
747 options.cracklib_dictpath = CRACKLIB_DICTS;
749 ctrl = _pam_parse(pamh, &options, argc, argv);
751 if (flags & PAM_PRELIM_CHECK) {
752 /* Check for passwd dictionary */
753 /* We cannot do that, since the original path is compiled
754 into the cracklib library and we don't know it. */
756 } else if (flags & PAM_UPDATE_AUTHTOK) {
758 const void *oldtoken;
764 retval = pam_get_item (pamh, PAM_OLDAUTHTOK, &oldtoken);
765 if (retval != PAM_SUCCESS) {
766 if (ctrl & PAM_DEBUG_ARG)
767 pam_syslog(pamh,LOG_ERR,"Can not get old passwd");
772 while (tries < options.retry_times) {
773 const char *crack_msg;
774 const char *newtoken = NULL;
779 /* Planned modus operandi:
781 * Verify it against cracklib.
782 * If okay get it a second time.
783 * Check to be the same with the first one.
784 * set PAM_AUTHTOK and return
787 retval = pam_get_authtok_noverify (pamh, &newtoken, NULL);
788 if (retval != PAM_SUCCESS) {
789 pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s",
790 pam_strerror (pamh, retval));
792 } else if (newtoken == NULL) { /* user aborted password change, quit */
793 return PAM_AUTHTOK_ERR;
796 D(("testing password"));
797 /* now test this passwd against cracklib */
799 D(("against cracklib"));
800 if ((crack_msg = FascistCheck (newtoken, options.cracklib_dictpath))) {
801 if (ctrl & PAM_DEBUG_ARG)
802 pam_syslog(pamh,LOG_DEBUG,"bad password: %s",crack_msg);
803 pam_error (pamh, _("BAD PASSWORD: %s"), crack_msg);
804 if (getuid() || options.enforce_for_root || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
806 pam_set_item (pamh, PAM_AUTHTOK, NULL);
807 retval = PAM_AUTHTOK_ERR;
812 /* check it for strength too... */
814 retval = _pam_unix_approve_pass (pamh, ctrl, &options,
816 if (retval != PAM_SUCCESS) {
817 if (getuid() || options.enforce_for_root || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
819 pam_set_item(pamh, PAM_AUTHTOK, NULL);
820 retval = PAM_AUTHTOK_ERR;
825 retval = pam_get_authtok_verify (pamh, &newtoken, NULL);
826 if (retval != PAM_SUCCESS) {
827 pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s",
828 pam_strerror (pamh, retval));
829 pam_set_item(pamh, PAM_AUTHTOK, NULL);
831 } else if (newtoken == NULL) { /* user aborted password change, quit */
832 return PAM_AUTHTOK_ERR;
838 D(("returning because maxtries reached"));
840 pam_set_item (pamh, PAM_AUTHTOK, NULL);
842 /* if we have only one try, we can use the real reason,
843 else say that there were too many tries. */
844 if (options.retry_times > 1)
850 if (ctrl & PAM_DEBUG_ARG)
851 pam_syslog(pamh, LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
852 return PAM_SERVICE_ERR;
856 return PAM_SERVICE_ERR;
862 /* static module data */
863 struct pam_module _pam_cracklib_modstruct = {
875 * Copyright (c) Cristian Gafton <gafton@redhat.com>, 1996.
876 * All rights reserved
878 * Redistribution and use in source and binary forms, with or without
879 * modification, are permitted provided that the following conditions
881 * 1. Redistributions of source code must retain the above copyright
882 * notice, and the entire permission notice in its entirety,
883 * including the disclaimer of warranties.
884 * 2. Redistributions in binary form must reproduce the above copyright
885 * notice, this list of conditions and the following disclaimer in the
886 * documentation and/or other materials provided with the distribution.
887 * 3. The name of the author may not be used to endorse or promote
888 * products derived from this software without specific prior
889 * written permission.
891 * ALTERNATIVELY, this product may be distributed under the terms of
892 * the GNU Public License, in which case the provisions of the GPL are
893 * required INSTEAD OF the above restrictions. (This clause is
894 * necessary due to a potential bad interaction between the GPL and
895 * the restrictions contained in a BSD-style copyright.)
897 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
898 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
899 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
900 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
901 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
902 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
903 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
904 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
905 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
906 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
907 * OF THE POSSIBILITY OF SUCH DAMAGE.
909 * The following copyright was appended for the long password support
910 * added with the libpam 0.58 release:
912 * Modificaton Copyright (c) Philip W. Dalrymple III <pwd@mdtsoft.com>
913 * 1997. All rights reserved
915 * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO
916 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
917 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
918 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
919 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
920 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
921 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
922 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
923 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
924 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
925 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
926 * OF THE POSSIBILITY OF SUCH DAMAGE.