Package: libpam-unix2 Version: 1.25-1 Hello,
I discovered that various screen locking programs (xlock, kcheckpass, xscreensaver, vlock) have problems to authenticate a user when using pam_unix2. The reason was evident for me: these programs are not setuid root. But I wondered why the programs are working correctly when using pam_unix and found out that pam_unix calls the external helper binary /sbin/unix_chkpwd that is set setuid root to solve the problem that normal users cannot read for example /etc/shadow. I saw that the upstream source of pam_unix2 (I got it from the SuSE ftp server) also provides the source code for an external binary (unix2_chkpwd.c with man-page unix2_chkpwd.8) , but this binary is not called from pam_unix2. So I added the support for calling the external binary to src/unix_auth.c. Most of the code was taken from the source code of pam_unix. Furthermore, I discovered that unix2_chkpwd has to be installed setuid root (-r-sr-xr-x root root unix2_chkpwd, like unix_chkpwd) and that setgid shadow (-r-xr-sr-x root shadow) is not sufficient. I have attached the new code of unix_auth.c. Unfortunately I could not add support for compilation and installation of unix2_chkpwd to the source package of libpam-unix2 because I do not know how to use automake and autoconf. Kind regards Christoph Pleger
/* * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004 SuSE GmbH Nuernberg, Germany. * Author: Thorsten Kukuk <[EMAIL PROTECTED]> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #if defined(HAVE_CONFIG_H) #include <config.h> #endif #define _GNU_SOURCE #include <grp.h> #include <pwd.h> #include <time.h> #include <ctype.h> #include <dlfcn.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <shadow.h> #include <sys/wait.h> #include <sys/types.h> #include <rpc/rpc.h> #include <rpc/key_prot.h> #define PAM_SM_AUTH #include <security/pam_modules.h> #if defined(HAVE_XCRYPT_H) #include <xcrypt.h> #elif defined(HAVE_CRYPT_H) #include <crypt.h> #endif #include "public.h" #define CHKPWD_HELPER "/sbin/unix2_chkpwd" #define SHADOW_GROUP_ID 42 /* This module actually performs UNIX/shadow authentication If shadow support is available, attempt to perform authentication using shadow passwords. If shadow is not available, or user does not have a shadow password, fallback onto a normal UNIX authentication. If an secret key exists and "set_secrpc" is set, send private key to keyserv. */ extern int key_setnet (struct key_netstarg *arg); static void _cleanup_message (pam_handle_t *pamh __attribute__ ((unused)), void *fl, int err __attribute__ ((unused))) { if (fl) free (fl); } static void __delete_secret_key (uid_t uid) { char empty_key[HEXKEYBYTES + 1]; uid_t saved_uid = geteuid (); seteuid (uid); memset (&empty_key, 0, HEXKEYBYTES + 1); key_setsecret (empty_key); seteuid (saved_uid); } static int __set_secret_key (pam_handle_t *pamh, uid_t uid, char *password) { struct key_netstarg net; char domain[MAXNETNAMELEN + 1]; char netname[MAXNETNAMELEN + 1]; getdomainname (domain, MAXNETNAMELEN); /* nomalization of the domain name for secure RPC without the ending dot of the NIS+ domain. */ if (domain[strlen (domain) - 1] == '.') domain[strlen (domain) - 1] = '\0'; if (uid == 0) { char hostname[MAXHOSTNAMELEN + 1]; gethostname (hostname, MAXHOSTNAMELEN); snprintf (netname, MAXNETNAMELEN, "[EMAIL PROTECTED]", hostname, domain); } else snprintf (netname, MAXNETNAMELEN, "[EMAIL PROTECTED]", uid, domain); /* encrypt the secret key with the users password */ if (!getsecretkey (netname, (char *) &net.st_priv_key, password)) { char *mtmp = alloca (strlen (netname) + 30); if (!mtmp) return PAM_IGNORE; #if 0 /* Don't print this, maybe no SecureRPC is used ? */ sprintf (mtmp, "Can't find %s's secret key", netname); pam_set_data (pamh, "pam_unix_auth_keylogin_msg", mtmp, _cleanup_message); #endif return PAM_IGNORE; } /* check, if we have encrypt the secret key */ if (net.st_priv_key[0] == 0) { char *mtmp = alloca (strlen (netname) + 40); if (!mtmp) return PAM_IGNORE; sprintf (mtmp, "Secure-RPC password incorrect for %s", netname); pam_set_data (pamh, "pam_unix_auth_keylogin_msg", mtmp, _cleanup_message); return PAM_IGNORE; } /* give unencrypted secret key to keyserv */ net.st_pub_key[0] = 0; net.st_netname = (char *) &netname; /* give unencrypted secret key to keyserv */ if (key_setnet (&net) < 0) { char *mtmp = alloca (strlen (netname) + 65); if (!mtmp) return PAM_IGNORE; sprintf (mtmp, "Could not set %s's secret key,\nmaybe the keyserver is down?", netname); pam_set_data (pamh, "pam_unix_auth_keylogin_msg", mtmp, _cleanup_message); return PAM_IGNORE; } return PAM_IGNORE; } static int _unix2_run_helper_binary(pam_handle_t *pamh, const char *passwd, const char *user, const options_t *options) { int retval, child, fds[2]; sigset_t sigset; char *service; pam_get_item (pamh, PAM_SERVICE, (void *) &service); if (options->debug) __pam_log (LOG_DEBUG, "_unix2_run_helper_binary called."); /* create a pipe for the password */ if (pipe(fds) != 0) { if (options->debug) __pam_log (LOG_DEBUG, "could not make pipe"); return PAM_AUTH_ERR; } /* Block SIGCHLD */ sigemptyset(&sigset); sigaddset(&sigset, SIGCHLD); sigprocmask(SIG_BLOCK, &sigset, 0); /* fork */ child = fork(); if (child == 0) { char *args[] = { NULL, NULL, NULL, NULL }; static char *envp[] = { NULL }; /* XXX - should really tidy up PAM here too */ /* reopen stdin as pipe */ close(fds[1]); dup2(fds[0], STDIN_FILENO); /* exec binary helper */ args[0] = x_strdup(CHKPWD_HELPER); args[1] = x_strdup(service); args[2] = x_strdup(user); execve(CHKPWD_HELPER, args, envp); /* should not get here: exit with error */ if (options->debug) __pam_log (LOG_DEBUG, "helper binary is not available"); exit(PAM_AUTHINFO_UNAVAIL); } else if (child > 0) { if (passwd != NULL) { /* send the password to the child */ write(fds[1], passwd, strlen(passwd)+1); passwd = NULL; } else { write(fds[1], "", 1); /* blank password */ } close(fds[0]); /* close here to avoid possible SIGPIPE above */ close(fds[1]); (void) waitpid(child, &retval, 0); /* wait for helper to complete */ retval = (retval == 0) ? PAM_SUCCESS:PAM_AUTH_ERR; } else { if (options->debug) __pam_log (LOG_DEBUG, "fork failed"); retval = PAM_AUTH_ERR; } /* Unblock SIGCHLD */ sigprocmask(SIG_BLOCK, &sigset, 0); if (options->debug) __pam_log (LOG_DEBUG, "returning %d", retval); return retval; } int pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc, const char **argv) { struct crypt_data output; int retval; int sp_buflen = 256; char *sp_buffer = alloca (sp_buflen); struct spwd sp_resultbuf; struct spwd *sp = NULL; int pw_buflen = 256; char *pw_buffer = alloca (pw_buflen); struct passwd pw_resultbuf; struct passwd *pw; const char *name = NULL; char *service, *p = NULL; const char *salt; int dont_delete_seckey = 1; uid_t save_uid = geteuid (); options_t options; memset (&output, 0, sizeof (output)); memset (&options, 0, sizeof (options)); if (get_options (&options, "auth", argc, argv) < 0) { __pam_log (LOG_ERR, "cannot get options"); return PAM_BUF_ERR; } if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_authenticate() called"); /* get the user name */ if ((retval = pam_get_user (pamh, &name, NULL)) != PAM_SUCCESS) { if (options.debug) __pam_log (LOG_DEBUG, "pam_get_user failed: return %d", retval); return (retval == PAM_CONV_AGAIN ? PAM_INCOMPLETE:retval); } if (name == NULL || name[0] == '\0') { if (name) { __pam_log (LOG_ERR, "bad username [%s]", name); return PAM_USER_UNKNOWN; } else if (options.debug) __pam_log (LOG_DEBUG, "name == NULL, return PAM_SERVICE_ERR"); return PAM_SERVICE_ERR; } else if (options.debug) __pam_log (LOG_DEBUG, "username=[%s]", name); /* Get the password entry for this user. */ while (getpwnam_r (name, &pw_resultbuf, pw_buffer, pw_buflen, &pw) != 0 && errno == ERANGE) { errno = 0; pw_buflen += 256; pw_buffer = alloca (pw_buflen); } /* If we call another PAM module, handle the module like "sufficient". If it returns success, we should also return success. Else ignore the call. This PAM modules will not be called, if the user to authenticate is root. */ if (options.use_other_modules && (pw == NULL || pw->pw_uid != 0)) { unsigned int i; for (i = 0; options.use_other_modules[i] != NULL; i++) { int retval; retval = __call_other_module(pamh, flags, options.use_other_modules[i], "pam_sm_authenticate", &options); if (retval == PAM_SUCCESS) { pam_get_item (pamh, PAM_SERVICE, (void *) &service); if (strcasecmp (service, "chsh") == 0 || strcasecmp (service, "chfn") == 0) { char *p; pam_get_item (pamh, PAM_AUTHTOK, (void *)&p); if (p != NULL) { char *msg = alloca (strlen (p) + 13); sprintf (msg, "PAM_AUTHTOK=%s", p); pam_putenv (pamh, msg); } } return retval; } } } /* Check at first for empty password, then prompt for user password */ /* Get shadow entry. We don't bail out if user does not exists. Ask for an password in this case and bail out then. */ if (pw) while (getspnam_r (pw->pw_name, &sp_resultbuf, sp_buffer, sp_buflen, &sp) != 0 && errno == ERANGE) { errno = 0; sp_buflen += 256; sp_buffer = alloca (sp_buflen); } if ((pw && (pw->pw_passwd == NULL || strlen (pw->pw_passwd) == 0)) || (sp && pw && strcmp (pw->pw_passwd, "x") == 0 && (sp->sp_pwdp == NULL || strlen (sp->sp_pwdp) == 0))) { if (flags & PAM_DISALLOW_NULL_AUTHTOK || !options.nullok) { if (options.debug) __pam_log (LOG_DEBUG, "return PAM_AUTH_ERR"); return PAM_AUTH_ERR; } if (options.debug) __pam_log (LOG_DEBUG, "return PAM_SUCCESS"); return PAM_SUCCESS; } retval = pam_get_item (pamh, PAM_AUTHTOK, (void *) &p); if (retval != PAM_SUCCESS) { if (options.debug) __pam_log (LOG_DEBUG, "pam_get_item (PAM_AUTHTOK) failed, return %d", retval); return retval; } if (p == NULL) { if (options.use_first_pass) { if (options.debug) __pam_log (LOG_DEBUG, "Cannot get stacked password, return PAM_AUTHTOK_RECOVER_ERR"); return PAM_AUTHTOK_RECOVER_ERR; } retval = __get_authtok (pamh, options.not_set_pass); if (retval != PAM_SUCCESS) { if (options.debug) __pam_log (LOG_DEBUG, "__get_authtok failed with %d, exit", retval); return retval; } /* We have to call pam_get_item() again because value of p should changed to a password. */ pam_get_item (pamh, PAM_AUTHTOK, (void *) &p); } pam_get_item (pamh, PAM_SERVICE, (void *) &service); if (strcasecmp (service, "chsh") == 0 || strcasecmp (service, "chfn") == 0) { char *p; pam_get_item (pamh, PAM_AUTHTOK, (void *)&p); if (p != NULL) { char *msg = alloca (strlen (p) + 13); sprintf (msg, "PAM_AUTHTOK=%s", p); pam_putenv (pamh, msg); } } /* Now bail out if we cannot find the account. */ if (pw == NULL) { if (options.debug) __pam_log (LOG_DEBUG, "Cannot find passwd entry for %s", name); return PAM_USER_UNKNOWN; } /* If authtok is empty again, check if this is allowed */ if (p == NULL) { if (flags & PAM_DISALLOW_NULL_AUTHTOK || !options.nullok) { if (options.debug) __pam_log (LOG_DEBUG, "empty authtok, return PAM_AUTH_ERR"); return PAM_AUTH_ERR; } if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_authenticate: PAM_SUCCESS"); return PAM_SUCCESS; } /* For NIS+, root cannot get password for lesser user. So we change our uid to this of the user, try to set his secret key and get the password a second time. This only works if we are allowed ot set the secret key. */ if (options.secrpc_flag) { if (seteuid (pw->pw_uid) < 0) { __pam_log (LOG_ERR, "auth: seteuid(%d) faild", pw->pw_uid); return PAM_PERM_DENIED; } dont_delete_seckey = key_secretkey_is_set(); __set_secret_key (pamh, pw->pw_uid, p); if (strcmp (pw->pw_passwd, "*NP*") == 0) while (getpwnam_r (name, &pw_resultbuf, pw_buffer, pw_buflen, &pw) != 0 && errno == ERANGE) { errno = 0; pw_buflen += 256; pw_buffer = alloca (pw_buflen); } if (sp && strcmp (sp->sp_pwdp, "*NP*") == 0) while (getspnam_r (pw->pw_name, &sp_resultbuf, sp_buffer, sp_buflen, &sp) != 0 && errno == ERANGE) { errno = 0; sp_buflen += 256; sp_buffer = alloca (sp_buflen); } if (seteuid (save_uid) < 0) { __pam_log (LOG_ERR, "auth: seteuid(%d) faild", save_uid); if (!dont_delete_seckey) __delete_secret_key (pw->pw_uid); return PAM_PERM_DENIED; } /* No we should have the correct password. */ /* Check a second time, if the password is empty. It could have changed since we now had the correct permissions. */ if ((pw->pw_passwd == NULL || strlen (pw->pw_passwd) == 0) || (sp && strcmp (pw->pw_passwd, "x") == 0 && (sp->sp_pwdp == NULL || strlen (sp->sp_pwdp) == 0)) || p == NULL) { if (flags & PAM_DISALLOW_NULL_AUTHTOK || !options.nullok) { if (!dont_delete_seckey) __delete_secret_key (pw->pw_uid); return PAM_AUTH_ERR; } if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_authenticate: PAM_SUCCESS"); return PAM_SUCCESS; } } if (pw->pw_passwd[0] != '!') { if (sp) salt = strdupa (sp->sp_pwdp); else salt = strdupa (pw->pw_passwd); } else { if (!dont_delete_seckey && options.secrpc_flag) __delete_secret_key (pw->pw_uid); return PAM_PERM_DENIED; } /* This is for HP-UX password aging (why couldn't they use shadow ?) */ if (strchr (salt, ',') != NULL) { char *cp = alloca (strlen (salt) + 1); strcpy (cp, salt); salt = cp; cp = strchr (salt, ','); *cp = '\0'; } if (strcmp(crypt_r(p, salt, &output), salt) != 0) { retval = PAM_AUTH_ERR; if (geteuid()) { /* we are not root, perhaps this is the reason? Run helper */ if (options.debug) __pam_log (LOG_DEBUG, "running helper binary"); retval = _unix2_run_helper_binary(pamh, p, name, &options); } if (retval != PAM_SUCCESS) { if (!dont_delete_seckey && options.secrpc_flag) __delete_secret_key (pw->pw_uid); if (options.debug) __pam_log (LOG_DEBUG, "wrong password, return PAM_AUTH_ERR"); return PAM_AUTH_ERR; } } if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_authenticate: PAM_SUCCESS"); return PAM_SUCCESS; } int pam_sm_setcred (pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval; void *msg = NULL; options_t options; int pw_buflen = 256; char *pw_buffer = alloca (pw_buflen); struct passwd pw_resultbuf; struct passwd *pw; const char *name = NULL; memset (&options, 0, sizeof (options)); if (get_options (&options, "auth", argc, argv) < 0) { __pam_log (LOG_ERR, "cannot get options"); return PAM_BUF_ERR; } if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_setcred() called"); /* get the user name */ if ((retval = pam_get_user (pamh, &name, NULL)) != PAM_SUCCESS) { if (options.debug) __pam_log (LOG_DEBUG, "pam_get_user failed: return %d", retval); return (retval == PAM_CONV_AGAIN ? PAM_INCOMPLETE:retval); } if (name == NULL || name[0] == '\0') { if (name) { __pam_log (LOG_ERR, "bad username [%s]", name); return PAM_USER_UNKNOWN; } else if (options.debug) __pam_log (LOG_DEBUG, "name == NULL, return PAM_SERVICE_ERR"); return PAM_SERVICE_ERR; } else if (options.debug) __pam_log (LOG_DEBUG, "username=[%s]", name); /* Get the password entry for this user. */ while (getpwnam_r (name, &pw_resultbuf, pw_buffer, pw_buflen, &pw) != 0 && errno == ERANGE) { errno = 0; pw_buflen += 256; pw_buffer = alloca (pw_buflen); } if (pw == NULL) { if (options.debug) __pam_log (LOG_DEBUG, "Cannot find passwd entry for %s", name); return PAM_USER_UNKNOWN; } /* If we call another PAM module, handle the module like "sufficient". If it returns success, we should also return success. Else ignore the call. This PAM modules will not be called, if the user to authenticate is root. */ if (options.use_other_modules && pw->pw_uid != 0) { unsigned int i; for (i = 0; options.use_other_modules[i] != NULL; i++) { retval = __call_other_module(pamh, flags, options.use_other_modules[i], "pam_sm_setcred", &options); if (retval != PAM_SUCCESS && retval != PAM_IGNORE && retval != PAM_CRED_UNAVAIL) { if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_setcred: %d", retval); return retval; } } } pam_get_data (pamh, "pam_unix_auth_keylogin_msg", (const void **) &msg); if (msg != NULL) __write_message (pamh, flags, PAM_TEXT_INFO, (char *)msg); if (options.debug) __pam_log (LOG_DEBUG, "pam_sm_setcred: PAM_SUCCESS"); return PAM_SUCCESS; }