]> granicus.if.org Git - linux-pam/blobdiff - modules/pam_unix/unix_chkpwd.c
Relevant BUGIDs: none
[linux-pam] / modules / pam_unix / unix_chkpwd.c
index 66c0ad7fd734489918d1d8b5471864a840b45ad4..5e4b0eaed299d2d29bc0aff3c0a428e3066c2533 100644 (file)
  *
  */
 
-#define _BSD_SOURCE
-#ifdef linux
-# define _GNU_SOURCE
-# include <features.h>
-#endif
+#include "config.h"
+
+#ifdef MEMORY_DEBUG
+# undef exit
+# undef strdup
+# undef free
+#endif /* MEMORY_DEBUG */
 
 #include <stdarg.h>
 #include <stdio.h>
 #include <syslog.h>
 #include <unistd.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <pwd.h>
 #include <shadow.h>
 #include <signal.h>
+#include <time.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
+static security_context_t prev_context=NULL;
+static int selinux_enabled=-1;
+#else
+#define SELINUX_ENABLED 0
+#endif
 
 #define MAXPASS                200     /* the maximum length of a password */
 
+#include <security/_pam_types.h>
 #include <security/_pam_macros.h>
 
 #include "md5.h"
@@ -39,9 +52,6 @@
 extern char *crypt(const char *key, const char *salt);
 extern char *bigcrypt(const char *key, const char *salt);
 
-#define UNIX_PASSED    0
-#define UNIX_FAILED    1
-
 /* syslogging function for errors and other information */
 
 static void _log_err(int err, const char *format,...)
@@ -55,8 +65,31 @@ static void _log_err(int err, const char *format,...)
        closelog();
 }
 
+static int _unix_shadowed(const struct passwd *pwd)
+{
+       char hashpass[1024];
+       if (pwd != NULL) {
+               if (strcmp(pwd->pw_passwd, "x") == 0) {
+                       return 1;
+               }
+               if (strlen(pwd->pw_name) < sizeof(hashpass) - 2) {
+                       strcpy(hashpass, "##");
+                       strcpy(hashpass + 2, pwd->pw_name);
+                       if (strcmp(pwd->pw_passwd, hashpass) == 0) {
+                               return 1;
+                       }
+               }
+       }
+       return 0;
+}
+
 static void su_sighandler(int sig)
 {
+#ifndef SA_RESETHAND
+       /* emulate the behaviour of the SA_RESETHAND flag */
+       if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
+               signal(sig, SIG_DFL);
+#endif
        if (sig > 0) {
                _log_err(LOG_NOTICE, "caught signal %d.", sig);
                exit(sig);
@@ -72,7 +105,9 @@ static void setup_signals(void)
         */
        (void) memset((void *) &action, 0, sizeof(action));
        action.sa_handler = su_sighandler;
+#ifdef SA_RESETHAND
        action.sa_flags = SA_RESETHAND;
+#endif
        (void) sigaction(SIGILL, &action, NULL);
        (void) sigaction(SIGTRAP, &action, NULL);
        (void) sigaction(SIGBUS, &action, NULL);
@@ -85,20 +120,48 @@ static void setup_signals(void)
        (void) sigaction(SIGQUIT, &action, NULL);
 }
 
-static int _unix_verify_password(const char *name, const char *p, int opt)
+static int _verify_account(const char * const uname)
+{
+       struct spwd *spent;
+       struct passwd *pwent;
+
+       pwent = getpwnam(uname);
+       if (!pwent) {
+               _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
+               return PAM_USER_UNKNOWN;
+       }
+
+       spent = getspnam( uname );
+       if (!spent) {
+               _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
+               return PAM_AUTHINFO_UNAVAIL;    /* Couldn't get username from shadow */
+       }
+       printf("%ld:%ld:%ld:%ld:%ld:%ld",
+                spent->sp_lstchg, /* last password change */
+                 spent->sp_min, /* days until change allowed. */
+                 spent->sp_max, /* days before change required */
+                 spent->sp_warn, /* days warning for expiration */
+                 spent->sp_inact, /* days before account inactive */
+                 spent->sp_expire); /* date when account expires */
+
+       return PAM_SUCCESS;
+}
+
+static int _unix_verify_password(const char *name, const char *p, int nullok)
 {
        struct passwd *pwd = NULL;
        struct spwd *spwdent = NULL;
        char *salt = NULL;
        char *pp = NULL;
-       int retval = UNIX_FAILED;
+       int retval = PAM_AUTH_ERR;
+       int salt_len;
 
        /* UNIX passwords area */
        setpwent();
        pwd = getpwnam(name);   /* Get password file entry... */
        endpwent();
        if (pwd != NULL) {
-               if (strcmp(pwd->pw_passwd, "x") == 0) {
+               if (_unix_shadowed(pwd)) {
                        /*
                         * ...and shadow password file entry for this user,
                         * if shadowing is enabled
@@ -128,27 +191,43 @@ static int _unix_verify_password(const char *name, const char *p, int opt)
        if (pwd == NULL || salt == NULL) {
                _log_err(LOG_ALERT, "check pass; user unknown");
                p = NULL;
-               return retval;
+               return PAM_USER_UNKNOWN;
        }
 
-       if (strlen(salt) == 0)
-               return (opt == 0) ? UNIX_FAILED : UNIX_PASSED;
+       salt_len = strlen(salt);
+       if (salt_len == 0) {
+               return (nullok == 0) ? PAM_AUTH_ERR : PAM_SUCCESS;
+       }
+       if (p == NULL || strlen(p) == 0) {
+               return PAM_AUTHTOK_ERR;
+       }
 
        /* the moment of truth -- do we agree with the password? */
-       retval = UNIX_FAILED;
+       retval = PAM_AUTH_ERR;
        if (!strncmp(salt, "$1$", 3)) {
                pp = Goodcrypt_md5(p, salt);
                if (strcmp(pp, salt) == 0) {
-                       retval = UNIX_PASSED;
+                       retval = PAM_SUCCESS;
                } else {
                        pp = Brokencrypt_md5(p, salt);
                        if (strcmp(pp, salt) == 0)
-                               retval = UNIX_PASSED;
+                               retval = PAM_SUCCESS;
                }
+       } else if ((*salt == '*') || (salt_len < 13)) {
+           retval = PAM_AUTH_ERR;
        } else {
                pp = bigcrypt(p, salt);
-               if (strcmp(pp, salt) == 0) {
-                       retval = UNIX_PASSED;
+               /*
+                * Note, we are comparing the bigcrypt of the password with
+                * the contents of the password field. If the latter was
+                * encrypted with regular crypt (and not bigcrypt) it will
+                * have been truncated for storage relative to the output
+                * of bigcrypt here. As such we need to compare only the
+                * stored string with the subset of bigcrypt's result.
+                * Bug 521314: the strncmp comparison is for legacy support.
+                */
+               if (strncmp(pp, salt, salt_len) == 0) {
+                       retval = PAM_SUCCESS;
                }
        }
        p = NULL;               /* no longer needed here */
@@ -159,6 +238,7 @@ static int _unix_verify_password(const char *name, const char *p, int opt)
                if (pp != NULL) {
                        while (tp && *tp)
                                *tp++ = '\0';
+                       free(pp);
                }
                pp = tp = NULL;
        }
@@ -169,42 +249,186 @@ static int _unix_verify_password(const char *name, const char *p, int opt)
 static char *getuidname(uid_t uid)
 {
        struct passwd *pw;
-#if 0
-       char *envname;
-
-       envname = getenv("LOGNAME");
-       if (envname == NULL)
-               return NULL;
+       static char username[32];
 
        pw = getpwuid(uid);
        if (pw == NULL)
                return NULL;
 
-       if (strcmp(envname, pw->pw_name))
-               return NULL;
+       strncpy(username, pw->pw_name, sizeof(username));
+       username[sizeof(username) - 1] = '\0';
 
-       return envname;
-#else
-       static char username[32];
+       return username;
+}
 
-       pw = getpwuid(uid);
-       if (pw == NULL)
-               return NULL;
+#define SH_TMPFILE             "/etc/nshadow"
+static int _update_shadow(const char *forwho)
+{
+    struct spwd *spwdent = NULL, *stmpent = NULL;
+    FILE *pwfile, *opwfile;
+    int err = 1;
+    int oldmask;
+    struct stat st;
+    char pass[MAXPASS + 1];
+    char towhat[MAXPASS + 1];
+    int npass=0;
 
-       memset(username, 0, 32);
-       strncpy(username, pw->pw_name, 32);
-       username[31] = '\0';
-       
-       return username;
+    /* read the password from stdin (a pipe from the pam_unix module) */
+
+    npass = read(STDIN_FILENO, pass, MAXPASS);
+
+    if (npass < 0) {   /* is it a valid password? */
+
+      _log_err(LOG_DEBUG, "no password supplied");
+      return PAM_AUTHTOK_ERR;
+
+    } else if (npass >= MAXPASS) {
+
+      _log_err(LOG_DEBUG, "password too long");
+      return PAM_AUTHTOK_ERR;
+
+    } else {
+      /* does pass agree with the official one? */
+      int retval=0;
+      pass[npass] = '\0';      /* NUL terminate */
+      retval = _unix_verify_password(forwho, pass, 0);
+      if (retval != PAM_SUCCESS) {
+       return retval;
+      }
+    }
+
+    /* read the password from stdin (a pipe from the pam_unix module) */
+
+    npass = read(STDIN_FILENO, towhat, MAXPASS);
+
+    if (npass < 0) {   /* is it a valid password? */
+
+      _log_err(LOG_DEBUG, "no new password supplied");
+      return PAM_AUTHTOK_ERR;
+
+    } else if (npass >= MAXPASS) {
+
+      _log_err(LOG_DEBUG, "new password too long");
+      return PAM_AUTHTOK_ERR;
+
+    }
+
+    towhat[npass] = '\0';      /* NUL terminate */
+    spwdent = getspnam(forwho);
+    if (spwdent == NULL) {
+       return PAM_USER_UNKNOWN;
+    }
+    oldmask = umask(077);
+
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      security_context_t shadow_context=NULL;
+      if (getfilecon("/etc/shadow",&shadow_context)<0) {
+       return PAM_AUTHTOK_ERR;
+      };
+      if (getfscreatecon(&prev_context)<0) {
+       freecon(shadow_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      if (setfscreatecon(shadow_context)) {
+       freecon(shadow_context);
+       freecon(prev_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      freecon(shadow_context);
+    }
+#endif
+    pwfile = fopen(SH_TMPFILE, "w");
+    umask(oldmask);
+    if (pwfile == NULL) {
+       err = 1;
+       goto done;
+    }
+
+    opwfile = fopen("/etc/shadow", "r");
+    if (opwfile == NULL) {
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    if (fstat(fileno(opwfile), &st) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+    if (fchmod(fileno(pwfile), st.st_mode) == -1) {
+       fclose(opwfile);
+       fclose(pwfile);
+       err = 1;
+       goto done;
+    }
+
+    stmpent = fgetspent(opwfile);
+    while (stmpent) {
+
+       if (!strcmp(stmpent->sp_namp, forwho)) {
+           stmpent->sp_pwdp = towhat;
+           stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
+           err = 0;
+           D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
+       }
+
+       if (putspent(stmpent, pwfile)) {
+           D(("error writing entry to shadow file: %s\n", strerror(errno)));
+           err = 1;
+           break;
+       }
+
+       stmpent = fgetspent(opwfile);
+    }
+    fclose(opwfile);
+
+    if (fclose(pwfile)) {
+       D(("error writing entries to shadow file: %s\n", strerror(errno)));
+       err = 1;
+    }
+
+ done:
+    if (!err) {
+       if (rename(SH_TMPFILE, "/etc/shadow"))
+           err = 1;
+    }
+
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      if (setfscreatecon(prev_context)) {
+       err = 1;
+      }
+      if (prev_context)
+       freecon(prev_context);
+      prev_context=NULL;
+    }
 #endif
+
+    if (!err) {
+       return PAM_SUCCESS;
+    } else {
+       unlink(SH_TMPFILE);
+       return PAM_AUTHTOK_ERR;
+    }
 }
 
 int main(int argc, char *argv[])
 {
        char pass[MAXPASS + 1];
-       char option[8];
-       int npass, opt;
-       int retval = UNIX_FAILED;
+       char *option;
+       int npass, nullok;
+       int force_failure = 0;
+       int retval = PAM_AUTH_ERR;
        char *user;
 
        /*
@@ -221,8 +445,7 @@ int main(int argc, char *argv[])
         * account).
         */
 
-       if (isatty(STDIN_FILENO)) {
-
+       if (isatty(STDIN_FILENO) || argc != 3 ) {
                _log_err(LOG_NOTICE
                      ,"inappropriate use of Unix helper binary [UID=%d]"
                         ,getuid());
@@ -230,30 +453,46 @@ int main(int argc, char *argv[])
                 ,"This binary is not designed for running in this way\n"
                      "-- the system administrator has been informed\n");
                sleep(10);      /* this should discourage/annoy the user */
-               return UNIX_FAILED;
+               return PAM_SYSTEM_ERR;
        }
+
        /*
-        * determine the current user's name is
-        * 1. supplied as a environment variable as LOGNAME
-        * 2. the uid has to match the one associated with the LOGNAME.
+        * determine the current user's name is.
+        * On a SELinux enabled system, policy will prevent third parties from using
+        * unix_chkpwd as a password guesser.  Leaving the existing check prevents
+        * su from working,  Since the current uid is the users and the password is
+        * for root.
         */
-       user = getuidname(getuid());
+       if (SELINUX_ENABLED) {
+         user=argv[1];
+       }
+       else {
+         user = getuidname(getuid());
+         /* if the caller specifies the username, verify that user
+            matches it */
+         if (strcmp(user, argv[1])) {
+           return PAM_AUTH_ERR;
+         }
+       }
 
-       /* read the nollok/nonull option */
+       option=argv[2];
 
-       npass = read(STDIN_FILENO, option, 8);
+       if (strncmp(argv[2], "verify", 8) == 0) {
+         /* Get the account information from the shadow file */
+         return _verify_account(argv[1]);
+       }
 
-       if (npass < 0) {
-               _log_err(LOG_DEBUG, "no option supplied");
-               return UNIX_FAILED;
-       } else {
-               option[7] = '\0';
-               if (strncmp(option, "nullok", 8) == 0)
-                       opt = 1;
-               else
-                       opt = 0;
+       if (strncmp(option, "shadow", 8) == 0) {
+         /* Attempting to change the password */
+         return _update_shadow(argv[1]);
        }
 
+       /* read the nullok/nonull option */
+       if (strncmp(option, "nullok", 8) == 0)
+         nullok = 1;
+       else
+         nullok = 0;
+
        /* read the password from stdin (a pipe from the pam_unix module) */
 
        npass = read(STDIN_FILENO, pass, MAXPASS);
@@ -270,13 +509,13 @@ int main(int argc, char *argv[])
                if (npass == 0) {
                        /* the password is NULL */
 
-                       retval = _unix_verify_password(user, NULL, opt);
+                       retval = _unix_verify_password(user, NULL, nullok);
 
                } else {
                        /* does pass agree with the official one? */
 
                        pass[npass] = '\0';     /* NUL terminate */
-                       retval = _unix_verify_password(user, pass, opt);
+                       retval = _unix_verify_password(user, pass, nullok);
 
                }
        }
@@ -285,7 +524,11 @@ int main(int argc, char *argv[])
 
        /* return pass or fail */
 
-       return retval;
+       if ((retval != PAM_SUCCESS) || force_failure) {
+           return PAM_AUTH_ERR;
+       } else {
+           return PAM_SUCCESS;
+       }
 }
 
 /*
@@ -303,13 +546,13 @@ int main(int argc, char *argv[])
  * 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