/*
- * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
+ * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
* Copyright (C) 1996.
- * Copyright (c) Jan Rêkorajski, 1999.
+ * Copyright (c) Jan Rêkorajski, 1999.
+ * Copyright (c) Red Hat, Inc., 2007, 2008.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <security/_pam_aconf.h>
+#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/stat.h>
-#include <rpc/rpc.h>
-#include <rpcsvc/yp_prot.h>
-#include <rpcsvc/ypclnt.h>
-#ifdef USE_CRACKLIB
-#include <crack.h>
-#endif
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
#include <security/_pam_macros.h>
#define PAM_SM_PASSWORD
#include <security/pam_modules.h>
+#include <security/pam_ext.h>
+#include <security/pam_modutil.h>
-#ifndef LINUX_PAM
-#include <security/pam_appl.h>
-#endif /* LINUX_PAM */
-
-#include "yppasswd.h"
#include "md5.h"
#include "support.h"
+#include "passverify.h"
+#include "bigcrypt.h"
-#if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
-extern int getrpcport(const char *host, unsigned long prognum,
- unsigned long versnum, unsigned int proto);
-#endif /* GNU libc 2.1 */
+#if (HAVE_YP_GET_DEFAULT_DOMAIN || HAVE_GETDOMAINNAME) && HAVE_YP_MASTER
+# define HAVE_NIS
+#endif
-/*
- * PAM framework looks for these entry-points to pass control to the
- * password changing module.
- */
+#ifdef HAVE_NIS
+# include <rpc/rpc.h>
-#ifdef NEED_LCKPWDF
-#include "./lckpwdf.-c"
-#endif
+# if HAVE_RPCSVC_YP_PROT_H
+# include <rpcsvc/yp_prot.h>
+# endif
+
+# if HAVE_RPCSVC_YPCLNT_H
+# include <rpcsvc/ypclnt.h>
+# endif
-extern char *bigcrypt(const char *key, const char *salt);
+# include "yppasswd.h"
+
+# if !HAVE_DECL_GETRPCPORT &&!HAVE_RPCB_GETADDR
+extern int getrpcport(const char *host, unsigned long prognum,
+ unsigned long versnum, unsigned int proto);
+# endif /* GNU libc 2.1 */
+#endif
/*
How it works:
Sets it.
*/
-/* passwd/salt conversion macros */
-
-#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
-#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
-
-/* data tokens */
-
-#define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
-#define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
-
#define MAX_PASSWD_TRIES 3
-#define PW_TMPFILE "/etc/npasswd"
-#define SH_TMPFILE "/etc/nshadow"
-#define CRACKLIB_DICTS "/usr/share/dict/cracklib_dict"
-#define OPW_TMPFILE "/etc/security/nopasswd"
-#define OLD_PASSWORDS_FILE "/etc/security/opasswd"
-/*
- * i64c - convert an integer to a radix 64 character
- */
-static int i64c(int i)
+#ifdef HAVE_NIS
+#ifdef HAVE_RPCB_GETADDR
+static unsigned short
+__taddr2port (const struct netconfig *nconf, const struct netbuf *nbuf)
{
- if (i < 0)
- return ('.');
- else if (i > 63)
- return ('z');
- if (i == 0)
- return ('.');
- if (i == 1)
- return ('/');
- if (i >= 2 && i <= 11)
- return ('0' - 2 + i);
- if (i >= 12 && i <= 37)
- return ('A' - 12 + i);
- if (i >= 38 && i <= 63)
- return ('a' - 38 + i);
- return ('\0');
-}
-
-static char *crypt_md5_wrapper(const char *pass_new)
-{
- /*
- * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
- * removed use of static variables (AGM)
- */
-
- struct timeval tv;
- MD5_CTX ctx;
- unsigned char result[16];
- char *cp = (char *) result;
- unsigned char tmp[16];
- int i;
- char *x, *e = NULL;
-
- GoodMD5Init(&ctx);
- gettimeofday(&tv, (struct timezone *) 0);
- GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
- i = getpid();
- GoodMD5Update(&ctx, (void *) &i, sizeof i);
- i = clock();
- GoodMD5Update(&ctx, (void *) &i, sizeof i);
- GoodMD5Update(&ctx, result, sizeof result);
- GoodMD5Final(tmp, &ctx);
- strcpy(cp, "$1$"); /* magic for the MD5 */
- cp += strlen(cp);
- for (i = 0; i < 8; i++)
- *cp++ = i64c(tmp[i] & 077);
- *cp = '\0';
-
- /* no longer need cleartext */
- e = Goodcrypt_md5(pass_new, (const char *) result);
- x = x_strdup(e); /* put e in malloc()ed memory */
- _pam_overwrite(e); /* clean up */
-
- return x;
+ unsigned short port = 0;
+ struct __rpc_sockinfo si;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ if (!__rpc_nconf2sockinfo(nconf, &si))
+ return 0;
+
+ switch (si.si_af)
+ {
+ case AF_INET:
+ sin = nbuf->buf;
+ port = sin->sin_port;
+ break;
+ case AF_INET6:
+ sin6 = nbuf->buf;
+ port = sin6->sin6_port;
+ break;
+ default:
+ break;
+ }
+
+ return htons (port);
}
+#endif
-static char *getNISserver(pam_handle_t *pamh)
+static char *getNISserver(pam_handle_t *pamh, unsigned int ctrl)
{
char *master;
char *domainname;
int port, err;
+#if defined(HAVE_RPCB_GETADDR)
+ struct netconfig *nconf;
+ struct netbuf svcaddr;
+ char addrbuf[INET6_ADDRSTRLEN];
+ void *handle;
+ int found;
+#endif
+
+#ifdef HAVE_YP_GET_DEFAULT_DOMAIN
if ((err = yp_get_default_domain(&domainname)) != 0) {
- _log_err(LOG_WARNING, pamh, "can't get local yp domain: %s\n",
+ pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
yperr_string(err));
return NULL;
}
+#elif defined(HAVE_GETDOMAINNAME)
+ char domainname_res[256];
+
+ if (getdomainname (domainname_res, sizeof (domainname_res)) == 0)
+ {
+ if (strcmp (domainname_res, "(none)") == 0)
+ {
+ /* If domainname is not set, some systems will return "(none)" */
+ domainname_res[0] = '\0';
+ }
+ domainname = domainname_res;
+ }
+ else domainname = NULL;
+#endif
+
if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
- _log_err(LOG_WARNING, pamh, "can't find the master ypserver: %s\n",
+ pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
yperr_string(err));
return NULL;
}
+#ifdef HAVE_RPCB_GETADDR
+ svcaddr.len = 0;
+ svcaddr.maxlen = sizeof (addrbuf);
+ svcaddr.buf = addrbuf;
+ port = 0;
+ found = 0;
+
+ handle = setnetconfig();
+ while ((nconf = getnetconfig(handle)) != NULL) {
+ if (!strcmp(nconf->nc_proto, "udp")) {
+ if (rpcb_getaddr(YPPASSWDPROG, YPPASSWDPROC_UPDATE,
+ nconf, &svcaddr, master)) {
+ port = __taddr2port (nconf, &svcaddr);
+ endnetconfig (handle);
+ found=1;
+ break;
+ }
+
+ if (rpc_createerr.cf_stat != RPC_UNKNOWNHOST) {
+ clnt_pcreateerror (master);
+ pam_syslog (pamh, LOG_ERR,
+ "rpcb_getaddr (%s) failed!", master);
+ return NULL;
+ }
+ }
+ }
+
+ if (!found) {
+ pam_syslog (pamh, LOG_ERR,
+ "Cannot find suitable transport for protocol 'udp'");
+ return NULL;
+ }
+#else
port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
+#endif
if (port == 0) {
- _log_err(LOG_WARNING, pamh,
- "yppasswdd not running on NIS master host\n");
+ pam_syslog(pamh, LOG_WARNING,
+ "yppasswdd not running on NIS master host");
return NULL;
}
if (port >= IPPORT_RESERVED) {
- _log_err(LOG_WARNING, pamh,
- "yppasswd daemon running on illegal port.\n");
+ pam_syslog(pamh, LOG_WARNING,
+ "yppasswd daemon running on illegal port");
return NULL;
}
+ if (on(UNIX_DEBUG, ctrl)) {
+ pam_syslog(pamh, LOG_DEBUG, "Use NIS server on %s with port %d",
+ master, port);
+ }
return master;
}
+#endif
+
+#ifdef WITH_SELINUX
+
+static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user,
+ const char *fromwhat, const char *towhat, int remember)
+{
+ int retval, child, fds[2];
+ struct sigaction newsa, oldsa;
+
+ D(("called."));
+ /* create a pipe for the password */
+ if (pipe(fds) != 0) {
+ D(("could not make pipe"));
+ return PAM_AUTH_ERR;
+ }
+
+ if (off(UNIX_NOREAP, ctrl)) {
+ /*
+ * This code arranges that the demise of the child does not cause
+ * the application to receive a signal it is not expecting - which
+ * may kill the application or worse.
+ *
+ * The "noreap" module argument is provided so that the admin can
+ * override this behavior.
+ */
+ memset(&newsa, '\0', sizeof(newsa));
+ newsa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &newsa, &oldsa);
+ }
+
+ /* fork */
+ child = fork();
+ if (child == 0) {
+ static char *envp[] = { NULL };
+ const char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
+ char buffer[16];
+
+ /* XXX - should really tidy up PAM here too */
+
+ /* reopen stdin as pipe */
+ if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO) {
+ pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
+ _exit(PAM_AUTHINFO_UNAVAIL);
+ }
+
+ if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
+ PAM_MODUTIL_PIPE_FD,
+ PAM_MODUTIL_PIPE_FD) < 0) {
+ _exit(PAM_AUTHINFO_UNAVAIL);
+ }
+
+ /* exec binary helper */
+ args[0] = UPDATE_HELPER;
+ args[1] = user;
+ args[2] = "update";
+ if (on(UNIX_SHADOW, ctrl))
+ args[3] = "1";
+ else
+ args[3] = "0";
+
+ snprintf(buffer, sizeof(buffer), "%d", remember);
+ args[4] = buffer;
+
+ execve(UPDATE_HELPER, (char *const *) args, envp);
+
+ /* should not get here: exit with error */
+ D(("helper binary is not available"));
+ _exit(PAM_AUTHINFO_UNAVAIL);
+ } else if (child > 0) {
+ /* wait for child */
+ /* if the stored password is NULL */
+ int rc=0;
+ if (fromwhat) {
+ int len = strlen(fromwhat);
+
+ if (len > PAM_MAX_RESP_SIZE)
+ len = PAM_MAX_RESP_SIZE;
+ pam_modutil_write(fds[1], fromwhat, len);
+ }
+ pam_modutil_write(fds[1], "", 1);
+ if (towhat) {
+ int len = strlen(towhat);
+
+ if (len > PAM_MAX_RESP_SIZE)
+ len = PAM_MAX_RESP_SIZE;
+ pam_modutil_write(fds[1], towhat, len);
+ }
+ pam_modutil_write(fds[1], "", 1);
+
+ close(fds[0]); /* close here to avoid possible SIGPIPE above */
+ close(fds[1]);
+ /* wait for helper to complete: */
+ while ((rc=waitpid(child, &retval, 0)) < 0 && errno == EINTR);
+ if (rc<0) {
+ pam_syslog(pamh, LOG_ERR, "unix_update waitpid failed: %m");
+ retval = PAM_AUTHTOK_ERR;
+ } else if (!WIFEXITED(retval)) {
+ pam_syslog(pamh, LOG_ERR, "unix_update abnormal exit: %d", retval);
+ retval = PAM_AUTHTOK_ERR;
+ } else {
+ retval = WEXITSTATUS(retval);
+ }
+ } else {
+ D(("fork failed"));
+ close(fds[0]);
+ close(fds[1]);
+ retval = PAM_AUTH_ERR;
+ }
+
+ if (off(UNIX_NOREAP, ctrl)) {
+ sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
+ }
+
+ return retval;
+}
+#endif
static int check_old_password(const char *forwho, const char *newpass)
{
char *s_luser, *s_uid, *s_npas, *s_pas;
int retval = PAM_SUCCESS;
FILE *opwfile;
+ size_t len = strlen(forwho);
opwfile = fopen(OLD_PASSWORDS_FILE, "r");
if (opwfile == NULL)
- return PAM_AUTHTOK_ERR;
+ return PAM_ABORT;
while (fgets(buf, 16380, opwfile)) {
- if (!strncmp(buf, forwho, strlen(forwho))) {
+ if (!strncmp(buf, forwho, len) && (buf[len] == ':' ||
+ buf[len] == ',')) {
+ char *sptr;
buf[strlen(buf) - 1] = '\0';
- s_luser = strtok(buf, ":,");
- s_uid = strtok(NULL, ":,");
- s_npas = strtok(NULL, ":,");
- s_pas = strtok(NULL, ":,");
+ s_luser = strtok_r(buf, ":,", &sptr);
+ s_uid = strtok_r(NULL, ":,", &sptr);
+ s_npas = strtok_r(NULL, ":,", &sptr);
+ s_pas = strtok_r(NULL, ":,", &sptr);
while (s_pas != NULL) {
- if (!strcmp(Goodcrypt_md5(newpass, s_pas), s_pas)) {
+ char *md5pass = Goodcrypt_md5(newpass, s_pas);
+ if (md5pass == NULL || !strcmp(md5pass, s_pas)) {
+ _pam_delete(md5pass);
retval = PAM_AUTHTOK_ERR;
break;
}
- s_pas = strtok(NULL, ":,");
- }
- break;
- }
- }
- fclose(opwfile);
-
- return retval;
-}
-
-static int save_old_password(const char *forwho, const char *oldpass, int howmany)
-{
- static char buf[16384];
- static char nbuf[16384];
- char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
- int retval = 0, npas;
- FILE *pwfile, *opwfile;
- int err = 0;
- int oldmask;
- int found = 0;
- struct passwd *pwd = NULL;
-
- if (howmany < 0)
- return retval;
-
- if (oldpass == NULL)
- return retval;
-
- oldmask = umask(077);
- pwfile = fopen(OPW_TMPFILE, "w");
- umask(oldmask);
- opwfile = fopen(OLD_PASSWORDS_FILE, "r");
- if (pwfile == NULL || opwfile == NULL)
- return PAM_AUTHTOK_ERR;
- chown(OPW_TMPFILE, 0, 0);
- chmod(OPW_TMPFILE, 0600);
-
- while (fgets(buf, 16380, opwfile)) {
- if (!strncmp(buf, forwho, strlen(forwho))) {
- buf[strlen(buf) - 1] = '\0';
- s_luser = strtok(buf, ":");
- s_uid = strtok(NULL, ":");
- s_npas = strtok(NULL, ":");
- s_pas = strtok(NULL, ":");
- npas = strtol(s_npas, NULL, 10) + 1;
- while (npas > howmany) {
- s_pas = strpbrk(s_pas, ",");
- if (s_pas != NULL)
- s_pas++;
- npas--;
- }
- pass = crypt_md5_wrapper(oldpass);
- if (s_pas == NULL)
- sprintf(nbuf, "%s:%s:%d:%s\n", s_luser, s_uid, npas, pass);
- else
- sprintf(nbuf, "%s:%s:%d:%s,%s\n", s_luser, s_uid, npas, s_pas, pass);
- if (fputs(nbuf, pwfile) < 0) {
- retval = PAM_AUTHTOK_ERR;
- err = 1;
- break;
- }
- found = 1;
- } else if (fputs(buf, pwfile) < 0) {
- retval = PAM_AUTHTOK_ERR;
- err = 1;
- break;
- }
- }
- fclose(opwfile);
- if (!found) {
- pwd = getpwnam(forwho);
- if (pwd == NULL) {
- retval = PAM_AUTHTOK_ERR;
- err = 1;
- } else {
- pass = crypt_md5_wrapper(oldpass);
- sprintf(nbuf, "%s:%d:1:%s\n", forwho, pwd->pw_uid, pass);
- if (fputs(nbuf, pwfile) < 0) {
- retval = PAM_AUTHTOK_ERR;
- err = 1;
+ s_pas = strtok_r(NULL, ":,", &sptr);
+ _pam_delete(md5pass);
}
- }
- }
- if (fclose(pwfile)) {
- fprintf(stderr, "error writing entries to old passwords file: %s\n",
- strerror(errno));
- retval = PAM_AUTHTOK_ERR;
- err = 1;
- }
- if (!err)
- rename(OPW_TMPFILE, OLD_PASSWORDS_FILE);
- else
- unlink(OPW_TMPFILE);
-
- return retval;
-}
-
-static int _update_passwd(const char *forwho, char *towhat)
-{
- struct passwd *tmpent = NULL;
- FILE *pwfile, *opwfile;
- int retval = 0;
- int err = 0;
- int oldmask;
-
- oldmask = umask(077);
- pwfile = fopen(PW_TMPFILE, "w");
- umask(oldmask);
- opwfile = fopen("/etc/passwd", "r");
- if (pwfile == NULL || opwfile == NULL)
- return PAM_AUTHTOK_ERR;
- chown(PW_TMPFILE, 0, 0);
- chmod(PW_TMPFILE, 0644);
- tmpent = fgetpwent(opwfile);
- while (tmpent) {
- if (!strcmp(tmpent->pw_name, forwho)) {
- tmpent->pw_passwd = towhat;
- }
- if (putpwent(tmpent, pwfile)) {
- fprintf(stderr, "error writing entry to password file: %s\n",
- strerror(errno));
- err = 1;
- retval = PAM_AUTHTOK_ERR;
- break;
- }
- tmpent = fgetpwent(opwfile);
- }
- fclose(opwfile);
-
- if (fclose(pwfile)) {
- fprintf(stderr, "error writing entries to password file: %s\n",
- strerror(errno));
- retval = PAM_AUTHTOK_ERR;
- err = 1;
- }
- if (!err)
- rename(PW_TMPFILE, "/etc/passwd");
- else
- unlink(PW_TMPFILE);
-
- return retval;
-}
-
-static int _update_shadow(const char *forwho, char *towhat)
-{
- struct spwd *spwdent = NULL, *stmpent = NULL;
- FILE *pwfile, *opwfile;
- int retval = 0;
- int err = 0;
- int oldmask;
-
- spwdent = getspnam(forwho);
- if (spwdent == NULL)
- return PAM_USER_UNKNOWN;
- oldmask = umask(077);
- pwfile = fopen(SH_TMPFILE, "w");
- umask(oldmask);
- opwfile = fopen("/etc/shadow", "r");
- if (pwfile == NULL || opwfile == NULL)
- return PAM_AUTHTOK_ERR;
- chown(SH_TMPFILE, 0, 0);
- chmod(SH_TMPFILE, 0600);
- stmpent = fgetspent(opwfile);
- while (stmpent) {
- if (!strcmp(stmpent->sp_namp, forwho)) {
- stmpent->sp_pwdp = towhat;
- stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
-
- D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
- }
- if (putspent(stmpent, pwfile)) {
- fprintf(stderr, "error writing entry to shadow file: %s\n",
- strerror(errno));
- err = 1;
- retval = PAM_AUTHTOK_ERR;
break;
}
- stmpent = fgetspent(opwfile);
}
fclose(opwfile);
- if (fclose(pwfile)) {
- fprintf(stderr, "error writing entries to shadow file: %s\n",
- strerror(errno));
- retval = PAM_AUTHTOK_ERR;
- err = 1;
- }
- if (!err)
- rename(SH_TMPFILE, "/etc/shadow");
- else
- unlink(SH_TMPFILE);
-
return retval;
}
-static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
+static int _do_setpass(pam_handle_t* pamh, const char *forwho,
+ const char *fromwhat,
char *towhat, unsigned int ctrl, int remember)
{
struct passwd *pwd = NULL;
int retval = 0;
+ int unlocked = 0;
+ char *master = NULL;
D(("called"));
- setpwent();
pwd = getpwnam(forwho);
- endpwent();
- if (pwd == NULL)
- return PAM_AUTHTOK_ERR;
+ if (pwd == NULL) {
+ retval = PAM_AUTHTOK_ERR;
+ goto done;
+ }
- if (on(UNIX_NIS, ctrl)) {
+ if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
+#ifdef HAVE_NIS
+ if ((master=getNISserver(pamh, ctrl)) != NULL) {
struct timeval timeout;
struct yppasswd yppwd;
CLIENT *clnt;
- char *master;
int status;
- int err = 0;
+ enum clnt_stat err;
- /* Make RPC call to NIS server */
- if ((master = getNISserver(pamh)) == NULL)
- return PAM_TRY_AGAIN;
+ /* Unlock passwd file to avoid deadlock */
+ unlock_pwdf();
+ unlocked = 1;
/* Initialize password information */
yppwd.newpw.pw_passwd = pwd->pw_passwd;
yppwd.newpw.pw_gecos = pwd->pw_gecos;
yppwd.newpw.pw_dir = pwd->pw_dir;
yppwd.newpw.pw_shell = pwd->pw_shell;
- yppwd.oldpass = fromwhat;
+ yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
yppwd.newpw.pw_passwd = towhat;
D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
(xdrproc_t) xdr_int, (char *) &status,
timeout);
+ free (yppwd.oldpass);
+
if (err) {
- clnt_perrno(err);
- retval = PAM_TRY_AGAIN;
+ _make_remark(pamh, ctrl, PAM_TEXT_INFO,
+ clnt_sperrno(err));
} else if (status) {
- fprintf(stderr, "Error while changing NIS password.\n");
- retval = PAM_TRY_AGAIN;
+ D(("Error while changing NIS password.\n"));
}
- printf("\nThe password has%s been changed on %s.\n",
- (err || status) ? " not" : "", master);
+ D(("The password has%s been changed on %s.",
+ (err || status) ? " not" : "", master));
+ pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s",
+ (err || status) ? " not" : "", pwd->pw_name, master);
auth_destroy(clnt->cl_auth);
clnt_destroy(clnt);
- if ((err || status) != 0) {
+ if (err || status) {
+ _make_remark(pamh, ctrl, PAM_TEXT_INFO,
+ _("NIS password could not be changed."));
retval = PAM_TRY_AGAIN;
}
-#ifdef DEBUG
+#ifdef PAM_DEBUG
sleep(5);
#endif
- return retval;
- }
- /* first, save old password */
- if (save_old_password(forwho, fromwhat, remember)) {
- return PAM_AUTHTOK_ERR;
+ } else {
+ retval = PAM_TRY_AGAIN;
+ }
+#else
+ if (on(UNIX_DEBUG, ctrl)) {
+ pam_syslog(pamh, LOG_DEBUG, "No NIS support available");
+ }
+
+ retval = PAM_TRY_AGAIN;
+#endif
}
- if (on(UNIX_SHADOW, ctrl) || (strcmp(pwd->pw_passwd, "x") == 0)) {
- retval = _update_shadow(forwho, towhat);
- if (retval == PAM_SUCCESS)
- retval = _update_passwd(forwho, "x");
- } else {
- retval = _update_passwd(forwho, towhat);
+
+ if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
+ if(unlocked) {
+ if (lock_pwdf() != PAM_SUCCESS) {
+ return PAM_AUTHTOK_LOCK_BUSY;
+ }
+ }
+#ifdef WITH_SELINUX
+ if (unix_selinux_confined())
+ return _unix_run_update_binary(pamh, ctrl, forwho, fromwhat, towhat, remember);
+#endif
+ /* first, save old password */
+ if (save_old_password(pamh, forwho, fromwhat, remember)) {
+ retval = PAM_AUTHTOK_ERR;
+ goto done;
+ }
+ if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) {
+ retval = unix_update_shadow(pamh, forwho, towhat);
+ if (retval == PAM_SUCCESS)
+ if (!is_pwd_shadowed(pwd))
+ retval = unix_update_passwd(pamh, forwho, "x");
+ } else {
+ retval = unix_update_passwd(pamh, forwho, towhat);
+ }
}
+
+done:
+ unlock_pwdf();
+
return retval;
}
-static int _unix_verify_shadow(const char *user, unsigned int ctrl)
+static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
{
- struct passwd *pwd = NULL; /* Password and shadow password */
- struct spwd *spwdent = NULL; /* file entries for the user */
- time_t curdays;
- int retval = PAM_SUCCESS;
+ struct passwd *pwent = NULL; /* Password and shadow password */
+ struct spwd *spent = NULL; /* file entries for the user */
+ int daysleft;
+ int retval;
- /* UNIX passwords area */
- setpwent();
- pwd = getpwnam(user); /* Get password file entry... */
- endpwent();
- if (pwd == NULL)
- return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */
-
- if (strcmp(pwd->pw_passwd, "x") == 0) {
- /* ...and shadow password file entry for this user, if shadowing
- is enabled */
- setspent();
- spwdent = getspnam(user);
- endspent();
-
- if (spwdent == NULL)
- return PAM_AUTHINFO_UNAVAIL;
- } else {
- if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */
- uid_t save_uid;
-
- save_uid = geteuid();
- seteuid (pwd->pw_uid);
- spwdent = getspnam( user );
- seteuid (save_uid);
-
- if (spwdent == NULL)
- return PAM_AUTHINFO_UNAVAIL;
- } else
- spwdent = NULL;
+ retval = get_account_info(pamh, user, &pwent, &spent);
+ if (retval == PAM_USER_UNKNOWN) {
+ return retval;
}
- if (spwdent != NULL) {
- /* We have the user's information, now let's check if their account
- has expired (60 * 60 * 24 = number of seconds in a day) */
-
- if (off(UNIX__IAMROOT, ctrl)) {
- /* Get the current number of days since 1970 */
- curdays = time(NULL) / (60 * 60 * 24);
- if ((curdays < (spwdent->sp_lstchg + spwdent->sp_min))
- && (spwdent->sp_min != -1))
- retval = PAM_AUTHTOK_ERR;
- else if ((curdays > (spwdent->sp_lstchg + spwdent->sp_max + spwdent->sp_inact))
- && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1)
- && (spwdent->sp_lstchg != 0))
- /*
- * Their password change has been put off too long,
- */
- retval = PAM_ACCT_EXPIRED;
- else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1)
- && (spwdent->sp_lstchg != 0))
- /*
- * OR their account has just plain expired
- */
- retval = PAM_ACCT_EXPIRED;
- }
+ if (retval == PAM_SUCCESS && spent == NULL)
+ return PAM_SUCCESS;
+
+ if (retval == PAM_UNIX_RUN_HELPER) {
+ retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft);
+ if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN)
+ return retval;
}
+ else if (retval == PAM_SUCCESS)
+ retval = check_shadow_expiry(pamh, spent, &daysleft);
+
+ if (on(UNIX__IAMROOT, ctrl) || retval == PAM_NEW_AUTHTOK_REQD)
+ return PAM_SUCCESS;
+
return retval;
}
static int _pam_unix_approve_pass(pam_handle_t * pamh
,unsigned int ctrl
,const char *pass_old
- ,const char *pass_new)
+ ,const char *pass_new,
+ int pass_min_len)
{
- const char *user;
- char *remark = NULL;
+ const void *user;
+ const char *remark = NULL;
int retval = PAM_SUCCESS;
D(("&new=%p, &old=%p", pass_old, pass_new));
if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
if (on(UNIX_DEBUG, ctrl)) {
- _log_err(LOG_DEBUG, pamh, "bad authentication token");
+ pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
}
_make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
- "No password supplied" : "Password unchanged");
+ _("No password has been supplied.") :
+ _("The password has not been changed."));
return PAM_AUTHTOK_ERR;
}
/*
* checking this would be the place - AGM
*/
- retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
+ retval = pam_get_item(pamh, PAM_USER, &user);
if (retval != PAM_SUCCESS) {
if (on(UNIX_DEBUG, ctrl)) {
- _log_err(LOG_ERR, pamh, "Can not get username");
+ pam_syslog(pamh, LOG_ERR, "Can not get username");
return PAM_AUTHTOK_ERR;
}
}
if (off(UNIX__IAMROOT, ctrl)) {
-#ifdef USE_CRACKLIB
- remark = FascistCheck(pass_new, CRACKLIB_DICTS);
- D(("called cracklib [%s]", remark));
-#else
- if (strlen(pass_new) < 6)
- remark = "You must choose a longer password";
- D(("lenth check [%s]", remark));
-#endif
- if (on(UNIX_REMEMBER_PASSWD, ctrl))
- if ((retval = check_old_password(user, pass_new)) != PAM_SUCCESS)
- remark = "Password has been already used. Choose another.";
+ if (strlen(pass_new) < pass_min_len)
+ remark = _("You must choose a longer password.");
+ D(("length check [%s]", remark));
+ if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
+ if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
+ remark = _("Password has been already used. Choose another.");
+ if (retval == PAM_ABORT) {
+ pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords",
+ OLD_PASSWORDS_FILE);
+ return retval;
+ }
+ }
}
if (remark) {
_make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
return retval;
}
-
-PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
- int argc, const char **argv)
+int
+pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
unsigned int ctrl, lctrl;
- int retval, i;
+ int retval;
int remember = -1;
+ int rounds = -1;
+ int pass_min_len = 0;
/* <DO NOT free() THESE> */
const char *user;
- char *pass_old, *pass_new;
+ const void *item;
+ const char *pass_old, *pass_new;
/* </DO NOT free() THESE> */
D(("called."));
-#ifdef USE_LCKPWDF
- /* our current locking system requires that we lock the
- entire password database. This avoids both livelock
- and deadlock. */
- /* These values for the number of attempts and the sleep time
- are, of course, completely arbitrary.
- My reading of the PAM docs is that, once pam_chauthtok() has been
- called with PAM_UPDATE_AUTHTOK, we are obliged to take any
- reasonable steps to make sure the token is updated; so retrying
- for 1/10 sec. isn't overdoing it.
- The other possibility is to call lckpwdf() on the first
- pam_chauthtok() pass, and hold the lock until released in the
- second pass--but is this guaranteed to work? -SRL */
- i=0;
- while((retval = lckpwdf()) != 0 && i < 100) {
- usleep(1000);
- }
- if(retval != 0) {
- return PAM_AUTHTOK_LOCK_BUSY;
- }
-#endif
- ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
+ ctrl = _set_ctrl(pamh, flags, &remember, &rounds, &pass_min_len,
+ argc, argv);
/*
* First get the name of a user
*/
- retval = pam_get_user(pamh, &user, "Username: ");
+ retval = pam_get_user(pamh, &user, NULL);
if (retval == PAM_SUCCESS) {
/*
* Various libraries at various times have had bugs related to
- * '+' or '-' as the first character of a user name. Don't take
- * any chances here. Require that the username starts with an
- * alphanumeric character.
+ * '+' or '-' as the first character of a user name. Don't
+ * allow them.
*/
- if (user == NULL || !isalnum(*user)) {
- _log_err(LOG_ERR, pamh, "bad username [%s]", user);
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ if (user == NULL || user[0] == '-' || user[0] == '+') {
+ pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
return PAM_USER_UNKNOWN;
}
if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
- _log_err(LOG_DEBUG, pamh, "username [%s] obtained",
+ pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
user);
} else {
if (on(UNIX_DEBUG, ctrl))
- _log_err(LOG_DEBUG, pamh,
+ pam_syslog(pamh, LOG_DEBUG,
"password - could not identify user");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
return retval;
}
D(("Got username of %s", user));
+ /*
+ * Before we do anything else, check to make sure that the user's
+ * info is in one of the databases we can modify from this module,
+ * which currently is 'files' and 'nis'. We have to do this because
+ * getpwnam() doesn't tell you *where* the information it gives you
+ * came from, nor should it. That's our job.
+ */
+ if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "user \"%s\" does not exist in /etc/passwd%s",
+ user, on(UNIX_NIS, ctrl) ? " or NIS" : "");
+ return PAM_USER_UNKNOWN;
+ } else {
+ struct passwd *pwd;
+ _unix_getpwnam(pamh, user, 1, 1, &pwd);
+ if (pwd == NULL) {
+ pam_syslog(pamh, LOG_DEBUG,
+ "user \"%s\" has corrupted passwd entry",
+ user);
+ return PAM_USER_UNKNOWN;
+ }
+ }
+
/*
* This is not an AUTH module!
*/
* obtain and verify the current password (OLDAUTHTOK) for
* the user.
*/
- char *Announce;
-
D(("prelim check"));
- if (_unix_blankpasswd(ctrl, user)) {
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ if (_unix_blankpasswd(pamh, ctrl, user)) {
return PAM_SUCCESS;
- } else if (off(UNIX__IAMROOT, ctrl)) {
-
+ } else if (off(UNIX__IAMROOT, ctrl) ||
+ (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, user, 0, 1))) {
/* instruct user what is happening */
-#define greeting "Changing password for "
- Announce = (char *) malloc(sizeof(greeting) + strlen(user));
- if (Announce == NULL) {
- _log_err(LOG_CRIT, pamh,
- "password - out of memory");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
- return PAM_BUF_ERR;
+ if (off(UNIX__QUIET, ctrl)) {
+ retval = pam_info(pamh, _("Changing password for %s."), user);
+ if (retval != PAM_SUCCESS)
+ return retval;
}
- (void) strcpy(Announce, greeting);
- (void) strcpy(Announce + sizeof(greeting) - 1, user);
-#undef greeting
-
- lctrl = ctrl;
- set(UNIX__OLD_PASSWD, lctrl);
- retval = _unix_read_password(pamh, lctrl
- ,Announce
- ,"(current) UNIX password: "
- ,NULL
- ,_UNIX_OLD_AUTHTOK
- ,(const char **) &pass_old);
- free(Announce);
+ retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass_old, NULL);
if (retval != PAM_SUCCESS) {
- _log_err(LOG_NOTICE, pamh
- ,"password - (old) token not obtained");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ pam_syslog(pamh, LOG_NOTICE,
+ "password - (old) token not obtained");
return retval;
}
/* verify that this is the password for this user */
if (retval != PAM_SUCCESS) {
D(("Authentication failed"));
pass_old = NULL;
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
return retval;
}
- retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
pass_old = NULL;
- if (retval != PAM_SUCCESS) {
- _log_err(LOG_CRIT, pamh,
- "failed to set PAM_OLDAUTHTOK");
- }
- retval = _unix_verify_shadow(user, ctrl);
+ retval = _unix_verify_shadow(pamh,user, ctrl);
if (retval == PAM_AUTHTOK_ERR) {
if (off(UNIX__IAMROOT, ctrl))
_make_remark(pamh, ctrl, PAM_ERROR_MSG,
- "You must wait longer to change your password");
+ _("You must wait longer to change your password."));
else
retval = PAM_SUCCESS;
}
* previous call to this function].
*/
- if (off(UNIX_NOT_SET_PASS, ctrl)) {
- retval = pam_get_item(pamh, PAM_OLDAUTHTOK
- ,(const void **) &pass_old);
- } else {
- retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
- ,(const void **) &pass_old);
- if (retval == PAM_NO_MODULE_DATA) {
- retval = PAM_SUCCESS;
- pass_old = NULL;
- }
- }
- D(("pass_old [%s]", pass_old));
+ retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
if (retval != PAM_SUCCESS) {
- _log_err(LOG_NOTICE, pamh, "user not authenticated");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
- return retval;
- }
- retval = _unix_verify_shadow(user, ctrl);
- if (retval != PAM_SUCCESS) {
- _log_err(LOG_NOTICE, pamh, "user not authenticated 2");
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
+ pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
return retval;
}
+ pass_old = item;
+ D(("pass_old [%s]", pass_old));
+
D(("get new password now"));
lctrl = ctrl;
if (on(UNIX_USE_AUTHTOK, lctrl)) {
set(UNIX_USE_FIRST_PASS, lctrl);
}
- retry = 0;
+ if (on(UNIX_USE_FIRST_PASS, lctrl)) {
+ retry = MAX_PASSWD_TRIES-1;
+ }
retval = PAM_AUTHTOK_ERR;
while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
/*
* password -- needed for pluggable password strength checking
*/
- retval = _unix_read_password(pamh, lctrl
- ,NULL
- ,"Enter new UNIX password: "
- ,"Retype new UNIX password: "
- ,_UNIX_NEW_AUTHTOK
- ,(const char **) &pass_new);
+ retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass_new, NULL);
if (retval != PAM_SUCCESS) {
if (on(UNIX_DEBUG, ctrl)) {
- _log_err(LOG_ALERT, pamh
- ,"password - new password not obtained");
+ pam_syslog(pamh, LOG_ERR,
+ "password - new password not obtained");
}
pass_old = NULL; /* tidy up */
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
return retval;
}
D(("returned to _unix_chauthtok"));
* password is acceptable.
*/
- if (pass_new[0] == '\0') { /* "\0" password = NULL */
+ if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */
pass_new = NULL;
}
- retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
+ retval = _pam_unix_approve_pass(pamh, ctrl, pass_old,
+ pass_new, pass_min_len);
+
+ if (retval != PAM_SUCCESS) {
+ pam_set_item(pamh, PAM_AUTHTOK, NULL);
+ }
}
if (retval != PAM_SUCCESS) {
- _log_err(LOG_NOTICE, pamh,
+ pam_syslog(pamh, LOG_NOTICE,
"new password not acceptable");
- _pam_overwrite(pass_new);
- _pam_overwrite(pass_old);
pass_new = pass_old = NULL; /* tidy up */
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
return retval;
}
+ if (lock_pwdf() != PAM_SUCCESS) {
+ return PAM_AUTHTOK_LOCK_BUSY;
+ }
+
+ if (pass_old) {
+ retval = _unix_verify_password(pamh, user, pass_old, ctrl);
+ if (retval != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
+ unlock_pwdf();
+ return retval;
+ }
+ }
+
+ retval = _unix_verify_shadow(pamh, user, ctrl);
+ if (retval != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_NOTICE, "user shadow entry expired");
+ unlock_pwdf();
+ return retval;
+ }
+
+ retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new,
+ pass_min_len);
+ if (retval != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_NOTICE,
+ "new password not acceptable 2");
+ pass_new = pass_old = NULL; /* tidy up */
+ unlock_pwdf();
+ return retval;
+ }
+
/*
* By reaching here we have approved the passwords and must now
* rebuild the password database file.
* First we encrypt the new password.
*/
- if (on(UNIX_MD5_PASS, ctrl)) {
- tpass = crypt_md5_wrapper(pass_new);
- } else {
- /*
- * Salt manipulation is stolen from Rick Faith's passwd
- * program. Sorry Rick :) -- alex
- */
-
- time_t tm;
- char salt[3];
-
- time(&tm);
- salt[0] = bin_to_ascii(tm & 0x3f);
- salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
- salt[2] = '\0';
-
- if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
- /*
- * to avoid using the _extensions_ of the bigcrypt()
- * function we truncate the newly entered password
- */
- char *temp = malloc(9);
- char *e;
-
- if (temp == NULL) {
- _log_err(LOG_CRIT, pamh,
- "out of memory for password");
- _pam_overwrite(pass_new);
- _pam_overwrite(pass_old);
- pass_new = pass_old = NULL; /* tidy up */
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
- return PAM_BUF_ERR;
- }
- /* copy first 8 bytes of password */
- strncpy(temp, pass_new, 8);
- temp[8] = '\0';
-
- /* no longer need cleartext */
- e = bigcrypt(temp, salt);
- tpass = x_strdup(e);
-
- _pam_overwrite(e);
- _pam_delete(temp); /* tidy up */
- } else {
- char *e;
-
- /* no longer need cleartext */
- e = bigcrypt(pass_new, salt);
- tpass = x_strdup(e);
-
- _pam_overwrite(e);
- }
+ tpass = create_password_hash(pamh, pass_new, ctrl, rounds);
+ if (tpass == NULL) {
+ pam_syslog(pamh, LOG_CRIT,
+ "crypt() failure or out of memory for password");
+ pass_new = pass_old = NULL; /* tidy up */
+ unlock_pwdf();
+ return PAM_BUF_ERR;
}
D(("password processed"));
retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
remember);
- _pam_overwrite(pass_new);
- _pam_overwrite(pass_old);
+ /* _do_setpass has called unlock_pwdf for us */
+
_pam_delete(tpass);
pass_old = pass_new = NULL;
} else { /* something has broken with the module */
- _log_err(LOG_ALERT, pamh,
+ pam_syslog(pamh, LOG_CRIT,
"password received unknown request");
retval = PAM_ABORT;
}
D(("retval was %d", retval));
-#ifdef USE_LCKPWDF
- ulckpwdf();
-#endif
return retval;
}
-
-
-/* static module data */
-#ifdef PAM_STATIC
-struct pam_module _pam_unix_passwd_modstruct = {
- "pam_unix_passwd",
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- pam_sm_chauthtok,
-};
-#endif
-