2 * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
4 * Copyright (c) Jan Rêkorajski, 1999.
5 * Copyright (c) Red Hat, Inc., 2007, 2008.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, and the entire permission notice in its entirety,
12 * including the disclaimer of warranties.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote
17 * products derived from this software without specific prior
20 * ALTERNATIVELY, this product may be distributed under the terms of
21 * the GNU Public License, in which case the provisions of the GPL are
22 * required INSTEAD OF the above restrictions. (This clause is
23 * necessary due to a potential bad interaction between the GPL and
24 * the restrictions contained in a BSD-style copyright.)
26 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
27 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 * OF THE POSSIBILITY OF SUCH DAMAGE.
48 #include <sys/types.h>
52 #include <time.h> /* for time() */
58 #ifdef HAVE_RPCSVC_YP_PROT_H
59 #include <rpcsvc/yp_prot.h>
61 #ifdef HAVE_RPCSVC_YPCLNT_H
62 #include <rpcsvc/ypclnt.h>
69 #include <security/_pam_macros.h>
71 /* indicate the following groups are defined */
73 #define PAM_SM_PASSWORD
75 #include <security/pam_modules.h>
76 #include <security/pam_ext.h>
77 #include <security/pam_modutil.h>
82 #include "passverify.h"
85 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
86 extern int getrpcport(const char *host, unsigned long prognum,
87 unsigned long versnum, unsigned int proto);
88 #endif /* GNU libc 2.1 */
92 Gets in username (has to be done) from the calling program
93 Does authentication of user (only if we are not running as root)
94 Gets new password/checks for sanity
100 #define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
101 #define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
103 #define MAX_PASSWD_TRIES 3
105 static char *getNISserver(pam_handle_t *pamh, unsigned int ctrl)
107 #if (defined(HAVE_YP_GET_DEFAULT_DOMAIN) || defined(HAVE_GETDOMAINNAME)) && defined(HAVE_YP_MASTER)
112 #ifdef HAVE_YP_GET_DEFAULT_DOMAIN
113 if ((err = yp_get_default_domain(&domainname)) != 0) {
114 pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
118 #elif defined(HAVE_GETDOMAINNAME)
119 char domainname_res[256];
121 if (getdomainname (domainname_res, sizeof (domainname_res)) == 0)
123 if (strcmp (domainname_res, "(none)") == 0)
125 /* If domainname is not set, some systems will return "(none)" */
126 domainname_res[0] = '\0';
128 domainname = domainname_res;
130 else domainname = NULL;
133 if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
134 pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
138 port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
140 pam_syslog(pamh, LOG_WARNING,
141 "yppasswdd not running on NIS master host");
144 if (port >= IPPORT_RESERVED) {
145 pam_syslog(pamh, LOG_WARNING,
146 "yppasswd daemon running on illegal port");
149 if (on(UNIX_DEBUG, ctrl)) {
150 pam_syslog(pamh, LOG_DEBUG, "Use NIS server on %s with port %d",
155 if (on(UNIX_DEBUG, ctrl)) {
156 pam_syslog(pamh, LOG_DEBUG, "getNISserver: No NIS support available");
165 static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user,
166 const char *fromwhat, const char *towhat, int remember)
168 int retval, child, fds[2];
169 struct sigaction newsa, oldsa;
172 /* create a pipe for the password */
173 if (pipe(fds) != 0) {
174 D(("could not make pipe"));
178 if (off(UNIX_NOREAP, ctrl)) {
180 * This code arranges that the demise of the child does not cause
181 * the application to receive a signal it is not expecting - which
182 * may kill the application or worse.
184 * The "noreap" module argument is provided so that the admin can
185 * override this behavior.
187 memset(&newsa, '\0', sizeof(newsa));
188 newsa.sa_handler = SIG_DFL;
189 sigaction(SIGCHLD, &newsa, &oldsa);
197 static char *envp[] = { NULL };
198 char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
201 /* XXX - should really tidy up PAM here too */
203 /* reopen stdin as pipe */
204 dup2(fds[0], STDIN_FILENO);
206 if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
207 if (rlim.rlim_max >= MAX_FD_NO)
208 rlim.rlim_max = MAX_FD_NO;
209 for (i=0; i < (int)rlim.rlim_max; i++) {
210 if (i != STDIN_FILENO)
215 /* exec binary helper */
216 args[0] = x_strdup(UPDATE_HELPER);
217 args[1] = x_strdup(user);
218 args[2] = x_strdup("update");
219 if (on(UNIX_SHADOW, ctrl))
220 args[3] = x_strdup("1");
222 args[3] = x_strdup("0");
224 snprintf(buffer, sizeof(buffer), "%d", remember);
225 args[4] = x_strdup(buffer);
227 execve(UPDATE_HELPER, args, envp);
229 /* should not get here: exit with error */
230 D(("helper binary is not available"));
231 _exit(PAM_AUTHINFO_UNAVAIL);
232 } else if (child > 0) {
234 /* if the stored password is NULL */
237 pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
239 pam_modutil_write(fds[1], "", 1);
241 pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
244 pam_modutil_write(fds[1], "", 1);
246 close(fds[0]); /* close here to avoid possible SIGPIPE above */
248 rc=waitpid(child, &retval, 0); /* wait for helper to complete */
250 pam_syslog(pamh, LOG_ERR, "unix_update waitpid failed: %m");
251 retval = PAM_AUTHTOK_ERR;
252 } else if (!WIFEXITED(retval)) {
253 pam_syslog(pamh, LOG_ERR, "unix_update abnormal exit: %d", retval);
254 retval = PAM_AUTHTOK_ERR;
256 retval = WEXITSTATUS(retval);
262 retval = PAM_AUTH_ERR;
265 if (off(UNIX_NOREAP, ctrl)) {
266 sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
273 static int check_old_password(const char *forwho, const char *newpass)
275 static char buf[16384];
276 char *s_luser, *s_uid, *s_npas, *s_pas;
277 int retval = PAM_SUCCESS;
280 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
284 while (fgets(buf, 16380, opwfile)) {
285 if (!strncmp(buf, forwho, strlen(forwho))) {
287 buf[strlen(buf) - 1] = '\0';
288 s_luser = strtok_r(buf, ":,", &sptr);
289 s_uid = strtok_r(NULL, ":,", &sptr);
290 s_npas = strtok_r(NULL, ":,", &sptr);
291 s_pas = strtok_r(NULL, ":,", &sptr);
292 while (s_pas != NULL) {
293 char *md5pass = Goodcrypt_md5(newpass, s_pas);
294 if (!strcmp(md5pass, s_pas)) {
295 _pam_delete(md5pass);
296 retval = PAM_AUTHTOK_ERR;
299 s_pas = strtok_r(NULL, ":,", &sptr);
300 _pam_delete(md5pass);
310 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
311 const char *fromwhat,
312 char *towhat, unsigned int ctrl, int remember)
314 struct passwd *pwd = NULL;
321 pwd = getpwnam(forwho);
324 retval = PAM_AUTHTOK_ERR;
328 if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
329 if ((master=getNISserver(pamh, ctrl)) != NULL) {
330 struct timeval timeout;
331 struct yppasswd yppwd;
336 /* Unlock passwd file to avoid deadlock */
340 /* Initialize password information */
341 yppwd.newpw.pw_passwd = pwd->pw_passwd;
342 yppwd.newpw.pw_name = pwd->pw_name;
343 yppwd.newpw.pw_uid = pwd->pw_uid;
344 yppwd.newpw.pw_gid = pwd->pw_gid;
345 yppwd.newpw.pw_gecos = pwd->pw_gecos;
346 yppwd.newpw.pw_dir = pwd->pw_dir;
347 yppwd.newpw.pw_shell = pwd->pw_shell;
348 yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
349 yppwd.newpw.pw_passwd = towhat;
351 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
353 /* The yppasswd.x file said `unix authentication required',
354 * so I added it. This is the only reason it is in here.
355 * My yppasswdd doesn't use it, but maybe some others out there
358 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
359 clnt->cl_auth = authunix_create_default();
360 memset((char *) &status, '\0', sizeof(status));
363 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
364 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
365 (xdrproc_t) xdr_int, (char *) &status,
368 free (yppwd.oldpass);
371 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
374 D(("Error while changing NIS password.\n"));
376 D(("The password has%s been changed on %s.",
377 (err || status) ? " not" : "", master));
378 pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s",
379 (err || status) ? " not" : "", pwd->pw_name, master);
381 auth_destroy(clnt->cl_auth);
384 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
385 _("NIS password could not be changed."));
386 retval = PAM_TRY_AGAIN;
392 retval = PAM_TRY_AGAIN;
396 if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
398 if (lock_pwdf() != PAM_SUCCESS) {
399 return PAM_AUTHTOK_LOCK_BUSY;
403 if (unix_selinux_confined())
404 return _unix_run_update_binary(pamh, ctrl, forwho, fromwhat, towhat, remember);
406 /* first, save old password */
407 if (save_old_password(pamh, forwho, fromwhat, remember)) {
408 retval = PAM_AUTHTOK_ERR;
411 if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) {
412 retval = unix_update_shadow(pamh, forwho, towhat);
413 if (retval == PAM_SUCCESS)
414 if (!is_pwd_shadowed(pwd))
415 retval = unix_update_passwd(pamh, forwho, "x");
417 retval = unix_update_passwd(pamh, forwho, towhat);
428 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
430 struct passwd *pwent = NULL; /* Password and shadow password */
431 struct spwd *spent = NULL; /* file entries for the user */
435 retval = get_account_info(pamh, user, &pwent, &spent);
436 if (retval == PAM_USER_UNKNOWN) {
440 if (retval == PAM_SUCCESS && spent == NULL)
443 if (retval == PAM_UNIX_RUN_HELPER) {
444 retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft);
445 if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN)
448 else if (retval == PAM_SUCCESS)
449 retval = check_shadow_expiry(pamh, spent, &daysleft);
451 if (on(UNIX__IAMROOT, ctrl) || retval == PAM_NEW_AUTHTOK_REQD)
457 static int _pam_unix_approve_pass(pam_handle_t * pamh
459 ,const char *pass_old
460 ,const char *pass_new)
463 const char *remark = NULL;
464 int retval = PAM_SUCCESS;
466 D(("&new=%p, &old=%p", pass_old, pass_new));
467 D(("new=[%s]", pass_new));
468 D(("old=[%s]", pass_old));
470 if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
471 if (on(UNIX_DEBUG, ctrl)) {
472 pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
474 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
475 _("No password supplied") : _("Password unchanged"));
476 return PAM_AUTHTOK_ERR;
479 * if one wanted to hardwire authentication token strength
480 * checking this would be the place - AGM
483 retval = pam_get_item(pamh, PAM_USER, &user);
484 if (retval != PAM_SUCCESS) {
485 if (on(UNIX_DEBUG, ctrl)) {
486 pam_syslog(pamh, LOG_ERR, "Can not get username");
487 return PAM_AUTHTOK_ERR;
490 if (off(UNIX__IAMROOT, ctrl)) {
491 if (strlen(pass_new) < 6)
492 remark = _("You must choose a longer password");
493 D(("length check [%s]", remark));
494 if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
495 if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
496 remark = _("Password has been already used. Choose another.");
497 if (retval == PAM_ABORT) {
498 pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords",
505 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
506 retval = PAM_AUTHTOK_ERR;
512 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
513 int argc, const char **argv)
515 unsigned int ctrl, lctrl;
520 /* <DO NOT free() THESE> */
522 const void *pass_old, *pass_new;
523 /* </DO NOT free() THESE> */
527 ctrl = _set_ctrl(pamh, flags, &remember, &rounds, argc, argv);
530 * First get the name of a user
532 retval = pam_get_user(pamh, &user, NULL);
533 if (retval == PAM_SUCCESS) {
535 * Various libraries at various times have had bugs related to
536 * '+' or '-' as the first character of a user name. Don't
539 if (user == NULL || user[0] == '-' || user[0] == '+') {
540 pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
541 return PAM_USER_UNKNOWN;
543 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
544 pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
547 if (on(UNIX_DEBUG, ctrl))
548 pam_syslog(pamh, LOG_DEBUG,
549 "password - could not identify user");
553 D(("Got username of %s", user));
556 * Before we do anything else, check to make sure that the user's
557 * info is in one of the databases we can modify from this module,
558 * which currently is 'files' and 'nis'. We have to do this because
559 * getpwnam() doesn't tell you *where* the information it gives you
560 * came from, nor should it. That's our job.
562 if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) {
563 pam_syslog(pamh, LOG_DEBUG,
564 "user \"%s\" does not exist in /etc/passwd%s",
565 user, on(UNIX_NIS, ctrl) ? " or NIS" : "");
566 return PAM_USER_UNKNOWN;
569 _unix_getpwnam(pamh, user, 1, 1, &pwd);
571 pam_syslog(pamh, LOG_DEBUG,
572 "user \"%s\" has corrupted passwd entry",
574 return PAM_USER_UNKNOWN;
579 * This is not an AUTH module!
581 if (on(UNIX__NONULL, ctrl))
582 set(UNIX__NULLOK, ctrl);
584 if (on(UNIX__PRELIM, ctrl)) {
586 * obtain and verify the current password (OLDAUTHTOK) for
593 if (_unix_blankpasswd(pamh, ctrl, user)) {
595 } else if (off(UNIX__IAMROOT, ctrl)) {
596 /* instruct user what is happening */
597 if (asprintf(&Announce, _("Changing password for %s."),
599 pam_syslog(pamh, LOG_CRIT,
600 "password - out of memory");
605 set(UNIX__OLD_PASSWD, lctrl);
606 retval = _unix_read_password(pamh, lctrl
608 ,_("(current) UNIX password: ")
614 if (retval != PAM_SUCCESS) {
615 pam_syslog(pamh, LOG_NOTICE,
616 "password - (old) token not obtained");
619 /* verify that this is the password for this user */
621 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
623 D(("process run by root so do nothing this time around"));
625 retval = PAM_SUCCESS; /* root doesn't have too */
628 if (retval != PAM_SUCCESS) {
629 D(("Authentication failed"));
633 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
635 if (retval != PAM_SUCCESS) {
636 pam_syslog(pamh, LOG_CRIT,
637 "failed to set PAM_OLDAUTHTOK");
639 retval = _unix_verify_shadow(pamh,user, ctrl);
640 if (retval == PAM_AUTHTOK_ERR) {
641 if (off(UNIX__IAMROOT, ctrl))
642 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
643 _("You must wait longer to change your password"));
645 retval = PAM_SUCCESS;
647 } else if (on(UNIX__UPDATE, ctrl)) {
649 * tpass is used below to store the _pam_md() return; it
650 * should be _pam_delete()'d.
657 * obtain the proposed password
663 * get the old token back. NULL was ok only if root [at this
664 * point we assume that this has already been enforced on a
665 * previous call to this function].
668 if (off(UNIX_NOT_SET_PASS, ctrl)) {
669 retval = pam_get_item(pamh, PAM_OLDAUTHTOK
672 retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
674 if (retval == PAM_NO_MODULE_DATA) {
675 retval = PAM_SUCCESS;
679 D(("pass_old [%s]", pass_old));
681 if (retval != PAM_SUCCESS) {
682 pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
686 D(("get new password now"));
690 if (on(UNIX_USE_AUTHTOK, lctrl)) {
691 set(UNIX_USE_FIRST_PASS, lctrl);
694 retval = PAM_AUTHTOK_ERR;
695 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
697 * use_authtok is to force the use of a previously entered
698 * password -- needed for pluggable password strength checking
701 retval = _unix_read_password(pamh, lctrl
703 ,_("Enter new UNIX password: ")
704 ,_("Retype new UNIX password: ")
708 if (retval != PAM_SUCCESS) {
709 if (on(UNIX_DEBUG, ctrl)) {
710 pam_syslog(pamh, LOG_ALERT,
711 "password - new password not obtained");
713 pass_old = NULL; /* tidy up */
716 D(("returned to _unix_chauthtok"));
719 * At this point we know who the user is and what they
720 * propose as their new password. Verify that the new
721 * password is acceptable.
724 if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */
727 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
729 if (retval != PAM_SUCCESS && off(UNIX_NOT_SET_PASS, ctrl)) {
730 pam_set_item(pamh, PAM_AUTHTOK, NULL);
734 if (retval != PAM_SUCCESS) {
735 pam_syslog(pamh, LOG_NOTICE,
736 "new password not acceptable");
737 pass_new = pass_old = NULL; /* tidy up */
740 if (lock_pwdf() != PAM_SUCCESS) {
741 return PAM_AUTHTOK_LOCK_BUSY;
745 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
746 if (retval != PAM_SUCCESS) {
747 pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
753 retval = _unix_verify_shadow(pamh, user, ctrl);
754 if (retval != PAM_SUCCESS) {
755 pam_syslog(pamh, LOG_NOTICE, "user shadow entry expired");
760 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
761 if (retval != PAM_SUCCESS) {
762 pam_syslog(pamh, LOG_NOTICE,
763 "new password not acceptable 2");
764 pass_new = pass_old = NULL; /* tidy up */
770 * By reaching here we have approved the passwords and must now
771 * rebuild the password database file.
775 * First we encrypt the new password.
778 tpass = create_password_hash(pamh, pass_new, ctrl, rounds);
780 pam_syslog(pamh, LOG_CRIT,
781 "out of memory for password");
782 pass_new = pass_old = NULL; /* tidy up */
787 D(("password processed"));
789 /* update the password database(s) -- race conditions..? */
791 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
793 /* _do_setpass has called unlock_pwdf for us */
796 pass_old = pass_new = NULL;
797 } else { /* something has broken with the module */
798 pam_syslog(pamh, LOG_ALERT,
799 "password received unknown request");
803 D(("retval was %d", retval));
809 /* static module data */
811 struct pam_module _pam_unix_passwd_modstruct = {