1 /* pam_cracklib module */
4 * 0.85. added six new options to use this with long passwords.
5 * 0.8. tidied output and improved D(()) usage for debugging.
6 * 0.7. added support for more obscure checks for new passwd.
7 * 0.6. root can reset user passwd to any values (it's only warned)
8 * 0.5. supports retries - 'retry=N' argument
9 * 0.4. added argument 'type=XXX' for 'New XXX password' prompt
10 * 0.3. Added argument 'debug'
11 * 0.2. new password is feeded to cracklib for verify after typed once
16 * Written by Cristian Gafton <gafton@redhat.com> 1996/09/10
17 * Long password support by Philip W. Dalrymple <pwd@mdtsoft.com> 1997/07/18
18 * See the end of the file for Copyright Information
20 * Modification for long password systems (>8 chars). The original
21 * module had problems when used in a md5 password system in that it
22 * allowed too short passwords but required that at least half of the
23 * bytes in the new password did not appear in the old one. this
24 * action is still the default and the changes should not break any
25 * current user. This modification adds 6 new options, one to set the
26 * number of bytes in the new password that are not in the old one,
27 * the other five to control the length checking, these are all
28 * documented (or will be before anyone else sees this code) in the PAM
29 * S.A.G. in the section on the cracklib module.
39 #include <sys/types.h>
43 extern char *FascistCheck(char *pw, const char *dictpath);
45 #ifndef CRACKLIB_DICTPATH
46 #define CRACKLIB_DICTPATH "/usr/lib/cracklib_dict"
49 #define PROMPT1 "New %s password: "
50 #define PROMPT2 "Retype new %s password: "
51 #define MISTYPED_PASS "Sorry, passwords do not match"
54 * here, we make a definition for the externally accessible function
55 * in this file (this definition is required for static a module
56 * but strongly encouraged generally) it is used to instruct the
57 * modules include file to define the function prototypes.
60 #define PAM_SM_PASSWORD
62 #include <security/pam_modules.h>
63 #include <security/_pam_macros.h>
66 #include <security/pam_appl.h>
67 #endif /* LINUX_PAM */
71 static void _pam_log(int err, const char *format, ...)
75 va_start(args, format);
76 openlog("PAM-Cracklib", LOG_CONS|LOG_PID, LOG_AUTH);
77 vsyslog(err, format, args);
82 /* argument parsing */
83 #define PAM_DEBUG_ARG 0x0001
85 /* module data - AGM: please remove these static variables... PAM was
86 * designed to be reentrant based soley on a unique pamh... this
89 static int retry_times = 0;
90 static int diff_ok = 10;
91 static int min_length = 9;
92 static int dig_credit = 1;
93 static int up_credit = 1;
94 static int low_credit = 1;
95 static int oth_credit = 1;
96 static char prompt_type[BUFSIZ];
98 static int _pam_parse(int argc, const char **argv)
102 /* step through arguments */
103 for (ctrl=0; argc-- > 0; ++argv) {
106 /* generic options */
108 if (!strcmp(*argv,"debug"))
109 ctrl |= PAM_DEBUG_ARG;
110 else if (!strncmp(*argv,"type=",5))
111 strcpy(prompt_type, *argv+5);
112 else if (!strncmp(*argv,"retry=",6)) {
113 retry_times = strtol(*argv+6,&ep,10);
114 if (!ep || (retry_times < 1))
116 } else if (!strncmp(*argv,"difok=",6)) {
117 diff_ok = strtol(*argv+6,&ep,10);
118 if (!ep || (diff_ok < 0))
120 } else if (!strncmp(*argv,"minlen=",7)) {
121 min_length = strtol(*argv+7,&ep,10);
122 if (!ep || (min_length < 5))
124 } else if (!strncmp(*argv,"dcredit=",8)) {
125 dig_credit = strtol(*argv+8,&ep,10);
126 if (!ep || (dig_credit < 0))
128 } else if (!strncmp(*argv,"ucredit=",8)) {
129 up_credit = strtol(*argv+8,&ep,10);
130 if (!ep || (up_credit < 0))
132 } else if (!strncmp(*argv,"lcredit=",8)) {
133 low_credit = strtol(*argv+8,&ep,10);
134 if (!ep || (low_credit < 0))
136 } else if (!strncmp(*argv,"ocredit=",8)) {
137 oth_credit = strtol(*argv+8,&ep,10);
138 if (!ep || (oth_credit < 0))
141 _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
148 /* Helper functions */
150 /* this is a front-end for module-application conversations */
151 static int converse(pam_handle_t *pamh, int ctrl, int nargs,
152 struct pam_message **message,
153 struct pam_response **response)
156 struct pam_conv *conv;
158 retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
160 if ( retval == PAM_SUCCESS ) {
161 retval = conv->conv(nargs, (const struct pam_message **)message,
162 response, conv->appdata_ptr);
163 if (retval != PAM_SUCCESS && (ctrl && PAM_DEBUG_ARG)) {
164 _pam_log(LOG_DEBUG, "conversation failure [%s]",
165 pam_strerror(pamh, retval));
168 _pam_log(LOG_ERR, "couldn't obtain coversation function [%s]",
169 pam_strerror(pamh, retval));
172 return retval; /* propagate error status */
175 static int make_remark(pam_handle_t *pamh, unsigned int ctrl,
176 int type, const char *text)
178 struct pam_message *pmsg[1], msg[1];
179 struct pam_response *resp;
184 msg[0].msg_style = type;
187 retval = converse(pamh, ctrl, 1, pmsg, &resp);
188 if (retval == PAM_SUCCESS)
189 _pam_drop_reply(resp, 1);
194 /* use this to free strings. ESPECIALLY password strings */
195 static char *_pam_delete(register char *xx)
203 * can't be a palindrome - like `R A D A R' or `M A D A M'
205 static int palindrome(const char *old, const char *new)
211 for (j = 0;j < i;j++)
212 if (new[i - j - 1] != new[j])
219 * more than half of the characters are different ones.
220 * or at least diff_ok are different
221 * NOTE that the defaults are NOT the same as befor this
222 * change. as long as there are at least 10 different bytes
223 * in a new password it will now pass even if the password
224 * is longer than 20 bytes (MD5)
227 static int similiar(const char *old, const char *new)
231 for (i = j = 0;new[i] && old[i];i++)
232 if (strchr (new, old[i]))
235 if (j >= diff_ok || i >= j * 2)
242 * a nice mix of characters.
244 static int simple(const char *old, const char *new)
253 for (i = 0;new[i];i++) {
254 if (isdigit (new[i]))
256 else if (isupper (new[i]))
258 else if (islower (new[i]))
265 * The scam was this - a password of only one character type
266 * must be 8 letters long. Two types, 7, and so on.
267 * This is now changed, the base size and the credits or defaults
268 * see the docs on the module for info on these parameters, the
269 * defaults cause the effect to be the same as before the change
272 if (digits > dig_credit)
275 if (uppers > up_credit)
278 if (lowers > low_credit)
281 if (others > oth_credit)
296 static char * str_lower(char *string)
300 for (cp = string; *cp; cp++)
305 static const char * password_check(const char *old, const char *new)
307 const char *msg = NULL;
308 char *oldmono, *newmono, *wrapped;
310 if (strcmp(new, old) == 0) {
311 msg = "is the same as the old one";
315 newmono = str_lower(x_strdup(new));
316 oldmono = str_lower(x_strdup(old));
317 wrapped = malloc(strlen(oldmono) * 2 + 1);
318 strcpy (wrapped, oldmono);
319 strcat (wrapped, oldmono);
321 if (palindrome(oldmono, newmono))
322 msg = "is a palindrome";
324 if (!msg && strcmp(oldmono, newmono) == 0)
325 msg = "case changes only";
327 if (!msg && similiar(oldmono, newmono))
328 msg = "is too similiar to the old one";
330 if (!msg && simple(old, new))
331 msg = "is too simple";
333 if (!msg && strstr(wrapped, newmono))
336 memset(newmono, 0, strlen(newmono));
337 memset(oldmono, 0, strlen(oldmono));
338 memset(wrapped, 0, strlen(wrapped));
347 static int _pam_unix_approve_pass(pam_handle_t *pamh,
349 const char *pass_old,
350 const char *pass_new)
352 const char *msg = NULL;
354 if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
355 if (ctrl && PAM_DEBUG_ARG)
356 _pam_log(LOG_DEBUG, "bad authentication token");
357 make_remark(pamh, ctrl, PAM_ERROR_MSG,
359 "No password supplied":"Password unchanged" );
360 return PAM_AUTHTOK_ERR;
364 * if one wanted to hardwire authentication token strength
365 * checking this would be the place
367 msg = password_check(pass_old,pass_new);
371 memset(remark,0,sizeof(remark));
372 sprintf(remark,"BAD PASSWORD: %s",msg);
373 if (ctrl && PAM_DEBUG_ARG)
374 _pam_log(LOG_NOTICE, "new passwd fails strength check: %s",
376 make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
377 return PAM_AUTHTOK_ERR;
383 /* The Main Thing (by Cristian Gafton, CEO at this module :-)
384 * (stolen from http://home.netscape.com)
386 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
387 int argc, const char **argv)
392 memset(prompt_type,0,sizeof(prompt_type));
393 ctrl = _pam_parse(argc, argv);
397 strcpy(prompt_type,"UNIX");
399 if (flags & PAM_PRELIM_CHECK) {
400 /* Check for passwd dictionary */
402 char buf[sizeof(CRACKLIB_DICTPATH)+10];
406 memset(buf,0,sizeof(buf)); /* zero the buffer */
407 sprintf(buf,"%s.pwd",CRACKLIB_DICTPATH);
409 if (!stat(buf,&st) && st.st_size)
412 if (ctrl & PAM_DEBUG_ARG)
413 _pam_log(LOG_NOTICE,"dict path '%s'[.pwd] is invalid",
419 return PAM_SERVICE_ERR;
421 } else if (flags & PAM_UPDATE_AUTHTOK) {
423 char *token1, *token2, *oldtoken;
425 struct pam_message msg[1],*pmsg[1];
426 struct pam_response *resp;
427 const char *cracklib_dictpath = CRACKLIB_DICTPATH;
431 retval = pam_get_item(pamh, PAM_OLDAUTHTOK,
432 (const void **)&oldtoken);
433 if (retval != PAM_SUCCESS) {
434 if (ctrl & PAM_DEBUG_ARG)
435 _pam_log(LOG_ERR,"Can not get old passwd");
437 retval = PAM_SUCCESS;
442 * make sure nothing inappropriate gets returned
444 token1 = token2 = NULL;
447 D(("returning %s because maxtries reached",
448 pam_strerror(pamh, retval)));
452 /* Planned modus operandi:
454 * Verify it against cracklib.
455 * If okay get it a second time.
456 * Check to be the same with the first one.
457 * set PAM_AUTHTOK and return
460 /* Prepare to ask the user for the first time */
461 memset(prompt,0,sizeof(prompt));
462 sprintf(prompt,PROMPT1,prompt_type);
464 msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
468 retval = converse(pamh, ctrl, 1, pmsg, &resp);
470 /* interpret the response */
471 if (retval == PAM_SUCCESS) { /* a good conversation */
472 token1 = x_strdup(resp[0].resp);
473 if (token1 == NULL) {
475 "could not recover authentication token 1");
476 retval = PAM_AUTHTOK_RECOVER_ERR;
480 * tidy up the conversation (resp_retcode) is ignored
482 _pam_drop_reply(resp, 1);
484 retval = (retval == PAM_SUCCESS) ?
485 PAM_AUTHTOK_RECOVER_ERR:retval ;
488 if (retval != PAM_SUCCESS) {
489 if (ctrl && PAM_DEBUG_ARG)
490 _pam_log(LOG_DEBUG,"unable to obtain a password");
494 D(("testing password, retval = %s", pam_strerror(pamh, retval)));
495 /* now test this passwd against cracklib */
500 bzero(remark,sizeof(remark));
501 D(("against cracklib"));
502 if ((crack_msg = FascistCheck(token1, cracklib_dictpath))) {
503 if (ctrl && PAM_DEBUG_ARG)
504 _pam_log(LOG_DEBUG,"bad password: %s",crack_msg);
505 sprintf(remark,"BAD PASSWORD: %s", crack_msg);
506 make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
507 if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
508 retval = PAM_AUTHTOK_ERR;
510 retval = PAM_SUCCESS;
512 /* check it for strength too... */
515 retval = _pam_unix_approve_pass(pamh,ctrl,
517 if (retval != PAM_SUCCESS)
518 if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
519 retval = PAM_AUTHTOK_ERR;
521 retval = PAM_SUCCESS;
526 D(("after testing: retval = %s", pam_strerror(pamh, retval)));
527 /* if cracklib/strength check said it is a bad passwd... */
528 if ((retval != PAM_SUCCESS) && (retval != PAM_IGNORE)) {
531 temp_unused = pam_set_item(pamh, PAM_AUTHTOK, NULL);
532 token1 = _pam_delete(token1);
536 /* Now we have a good passwd. Ask for it once again */
538 bzero(prompt,sizeof(prompt));
539 sprintf(prompt,PROMPT2,prompt_type);
541 msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
545 retval = converse(pamh, ctrl, 1, pmsg, &resp);
547 /* interpret the response */
548 if (retval == PAM_SUCCESS) { /* a good conversation */
549 token2 = x_strdup(resp[0].resp);
550 if (token2 == NULL) {
552 "could not recover authentication token 2");
553 retval = PAM_AUTHTOK_RECOVER_ERR;
557 * tidy up the conversation (resp_retcode) is ignored
559 _pam_drop_reply(resp, 1);
561 retval = (retval == PAM_SUCCESS) ?
562 PAM_AUTHTOK_RECOVER_ERR:retval ;
565 if (retval != PAM_SUCCESS) {
566 if (ctrl && PAM_DEBUG_ARG)
568 ,"unable to obtain the password a second time");
572 /* Hopefully now token1 and token2 the same password ... */
573 if (strcmp(token1,token2) != 0) {
575 make_remark(pamh, ctrl, PAM_ERROR_MSG, MISTYPED_PASS);
576 token1 = _pam_delete(token1);
577 token2 = _pam_delete(token2);
578 pam_set_item(pamh, PAM_AUTHTOK, NULL);
579 if (ctrl & PAM_DEBUG_ARG)
580 _pam_log(LOG_NOTICE,"Password mistyped");
581 retval = PAM_AUTHTOK_RECOVER_ERR;
585 /* Yes, the password was typed correct twice
586 * we store this password as an item
589 retval = pam_set_item(pamh, PAM_AUTHTOK, token1);
591 token1 = _pam_delete(token1);
592 token2 = _pam_delete(token2);
594 (retval != PAM_SUCCESS) ||
597 retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&item)
601 _pam_log(LOG_CRIT, "error manipulating password");
604 item = NULL; /* break link to password */
607 } while (retry_times--);
610 if (ctrl & PAM_DEBUG_ARG)
611 _pam_log(LOG_NOTICE, "UNKNOWN flags setting %02X",flags);
612 return PAM_SERVICE_ERR;
616 return PAM_SERVICE_ERR;
622 /* static module data */
623 struct pam_module _pam_cracklib_modstruct = {
635 * Copyright (c) Cristian Gafton <gafton@redhat.com>, 1996.
636 * All rights reserved
638 * Redistribution and use in source and binary forms, with or without
639 * modification, are permitted provided that the following conditions
641 * 1. Redistributions of source code must retain the above copyright
642 * notice, and the entire permission notice in its entirety,
643 * including the disclaimer of warranties.
644 * 2. Redistributions in binary form must reproduce the above copyright
645 * notice, this list of conditions and the following disclaimer in the
646 * documentation and/or other materials provided with the distribution.
647 * 3. The name of the author may not be used to endorse or promote
648 * products derived from this software without specific prior
649 * written permission.
651 * ALTERNATIVELY, this product may be distributed under the terms of
652 * the GNU Public License, in which case the provisions of the GPL are
653 * required INSTEAD OF the above restrictions. (This clause is
654 * necessary due to a potential bad interaction between the GPL and
655 * the restrictions contained in a BSD-style copyright.)
657 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
658 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
659 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
660 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
661 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
662 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
663 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
664 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
665 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
666 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
667 * OF THE POSSIBILITY OF SUCH DAMAGE.
669 * The following copyright was appended for the long password support
670 * added with the libpam 0.58 release:
672 * Modificaton Copyright (c) Philip W. Dalrymple III <pwd@mdtsoft.com>
673 * 1997. All rights reserved
675 * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO
676 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
677 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
678 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
679 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
680 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
681 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
682 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
683 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
684 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
685 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
686 * OF THE POSSIBILITY OF SUCH DAMAGE.