]> granicus.if.org Git - linux-pam/commitdiff
Relevant BUGIDs: none
authorThorsten Kukuk <kukuk@thkukuk.de>
Mon, 16 May 2005 11:03:02 +0000 (11:03 +0000)
committerThorsten Kukuk <kukuk@thkukuk.de>
Mon, 16 May 2005 11:03:02 +0000 (11:03 +0000)
Purpose of commit: new feature

Commit summary:
---------------

Add SELinux support, based on Patch from Red Hat

14 files changed:
CHANGELOG
Make.Rules.in
_pam_aconf.h.in
configure.in
modules/pam_pwdb/Makefile
modules/pam_rootok/Makefile
modules/pam_rootok/pam_rootok.c
modules/pam_unix/Makefile
modules/pam_unix/lckpwdf.-c
modules/pam_unix/pam_unix_acct.c
modules/pam_unix/pam_unix_passwd.c
modules/pam_unix/support.c
modules/pam_unix/support.h
modules/pam_unix/unix_chkpwd.c

index ddba1ceb131e43035e0c5a798ddcdbb570037bf8..3f4578330165afa1d467fdf3fa5d5434897c41f5 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -75,6 +75,8 @@ BerliOS Bugs are marked with (BerliOS #XXXX).
 * pam_mkhomedir: Set owner/permissions of home directory after we
   created all files (Bug 1032922 - kukuk)
 * pam_rhosts: Get ride of static buffer for path (kukuk)
+* pam_selinux/pam_unix/pam_rootok: Add SELinux support based on
+  patch from Red Hat (kukuk)
 
 0.79: Thu Mar 31 16:48:45 CEST 2005
 * pam_tally: added audit option (toady)
index 9c808cd0e4a7d85b8676310aadf52a9158409fb0..d8bc36361af59bf29e8bb91d05274965fb54a1c5 100644 (file)
@@ -56,6 +56,7 @@ HAVE_LIBDB=@HAVE_LIBDB@
 HAVE_LIBFL=@HAVE_LIBFL@
 HAVE_LIBNSL=@HAVE_LIBNSL@
 HAVE_LIBPWDB=@HAVE_LIBPWDB@
+HAVE_LIBSELINUX=@HAVE_LIBSELINUX@
 
 ifeq (@HAVE_LIBFLEX@,yes)
 LINK_LIBLEX=-lfl
index ae4b16e968218abb6a80bd56c1449003ae935c73..08a9ac8f21e3e41842d94970202425e0c3d6465e 100644 (file)
@@ -45,6 +45,9 @@
 /* have libpwdb - don't expect this to be important for much longer */
 #undef HAVE_LIBPWDB
 
+/* have libselinux - SELinux is Linux special */
+#undef HAVE_LIBSELINUX
+
 /* have gethostname() declared */
 #undef HAVE_GETHOSTNAME
 
index bc26474e5a8dbf3d94abecd6a498f64caf45b7ff..f82c028ae85c1a3e3b6872cff58fcbd09ed637cd 100644 (file)
@@ -227,6 +227,13 @@ AC_CHECK_LIB(nsl, yp_maplist, HAVE_LIBNSL=yes ; AC_DEFINE(HAVE_LIBNSL),
        HAVE_LIBNSL=no)
 AC_SUBST(HAVE_LIBNSL)
 
+AC_CHECK_LIB(selinux, getfilecon, HAVE_LIBSELINUX=yes ; AC_DEFINE(HAVE_LIBSELINUX),
+       HAVE_LIBSELINUX=no)
+AC_SUBST(HAVE_LIBSELINUX)
+
+if test $HAVE_LIBSELINUX = yes ; then
+       pwdblibs="$pwdblibs -lselinux"
+fi
 if test $HAVE_LIBNSL = yes ; then
        pwdblibs="$pwdblibs -lnsl"
 fi
index fa0e1b026e5fc383013bef346f0f9efa6f89656d..f136a8200641ac444a8a1b1766a25e51f244e8fd 100644 (file)
@@ -11,9 +11,13 @@ include ../../Make.Rules
 
 ifeq ($(HAVE_LIBPWDB),yes)
 
-EXTRALS += -lpwdb 
+EXTRALS += -lpwdb
 EXTRAS += -DCHKPWD_HELPER=\"$(SUPLEMENTED)/$(CHKPWD)\"
 
+ifeq ($(HAVE_LIBSELINUX),yes)
+  EXTRALS += -lselinux
+endif
+
 ifeq ($(HAVE_LIBCRYPT),yes)
   EXTRALS += -lcrypt
 endif
index 3460c2a23412dd90f977e44d80cb2b326cf32f1e..0c868c918facdaf0d7c92808ac7dd1c4c5a4c3c7 100644 (file)
@@ -13,3 +13,9 @@ include ../../Make.Rules
 TITLE=pam_rootok
 
 include ../Simple.Rules
+
+ifeq ($(HAVE_LIBSELINUX),yes)
+CFLAGS += -DWITH_SELINUX
+LINK_PAMMODUTILS += -lselinux
+endif
+
index a7342104dea1535712b82cc3ee6761b583581978..93e3169136a4bf6eef28ecb8f8d361b73b3ce541 100644 (file)
@@ -39,6 +39,11 @@ static void _pam_log(int err, const char *format, ...)
 }
 
 
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/av_permissions.h>
+#endif
+
 /* argument parsing */
 
 #define PAM_DEBUG_ARG       01
@@ -73,6 +78,9 @@ int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
 
     ctrl = _pam_parse(argc, argv);
     if (getuid() == 0)
+#ifdef WITH_SELINUX
+      if (is_selinux_enabled()<1 || checkPasswdAccess(PASSWD__ROOTOK)==0)
+#endif
        retval = PAM_SUCCESS;
 
     if (ctrl & PAM_DEBUG_ARG) {
index abeb7eefc220d8c888a0b4af30f1577bfe79f480..749982a179f11bb1e43bc5f46879282b0bb7fc47 100644 (file)
@@ -40,6 +40,11 @@ ifeq ($(HAVE_LCKPWDF),no)
 endif
 endif
 
+ifeq ($(HAVE_LIBSELINUX),yes)
+  USE_SELINUX=-D"WITH_SELINUX"
+  EXTRALS += -lselinux
+endif
+
 ifeq ($(HAVE_LIBNSL),yes)
   LIBNSL = -lnsl
 endif
@@ -60,7 +65,7 @@ INCLUDE_PAMMODUTILS = -I../pammodutil/include
 ########################################################################
 
 CFLAGS += $(USE_CRACKLIB) $(USE_LCKPWDF) $(NEED_LCKPWDF) $(EXTRAS) \
-        $(INCLUDE_PAMMODUTILS)
+        $(INCLUDE_PAMMODUTILS) $(USE_SELINUX)
 
 LDLIBS = $(EXTRALS) $(LINK_PAMMODUTILS)
 
@@ -186,6 +191,5 @@ clean:
        rm -f *~ *.a *.out *.bak
        rm -rf dynamic static
 
-.c.o:  
+.c.o:
        $(CC) -c $(CFLAGS) $<
-
index b5ff45850b4bcdc51ff9a6527df41ab4c1d25b90..7145617e0143ecd351768f54506554330f8ede15 100644 (file)
@@ -26,6 +26,9 @@
 
 #include <fcntl.h>
 #include <signal.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#endif
 
 #define LOCKFILE "/etc/.pwd.lock"
 #define TIMEOUT 15
@@ -64,6 +67,28 @@ static int lckpwdf(void)
        if (lockfd != -1)
                return -1;
 
+#ifdef WITH_SELINUX
+       if(is_selinux_enabled()>0)
+       {
+               lockfd = open(LOCKFILE, O_WRONLY);
+               if(lockfd == -1 && errno == ENOENT)
+               {
+                       security_context_t create_context;
+                       int rc;
+
+                       if(getfilecon("/etc/passwd", &create_context))
+                               return -1;
+                       rc = setfscreatecon(create_context);
+                       freecon(create_context);
+                       if(rc)
+                               return -1;
+                       lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600);
+                       if(setfscreatecon(NULL))
+                               return -1;
+               }
+       }
+       else
+#endif
        lockfd = open(LOCKFILE, O_CREAT | O_WRONLY, 0600);
        if (lockfd == -1)
                return -1;
index 02e07ba67d96f85c0a5632cf1ff680d1dcf6f454..9330a551b786672947a43a6ab2ca7766f552db2f 100644 (file)
  * 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
 #include <pwd.h>
 #include <shadow.h>
 #include <time.h>              /* for time() */
+#include <errno.h>
+#include <sys/wait.h>
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#define SELINUX_ENABLED is_selinux_enabled()>0
+#endif
 
 #include <security/_pam_macros.h>
 
 #endif                         /* LINUX_PAM */
 
 #include "support.h"
+
+#ifdef WITH_SELINUX
+
+struct spwd spwd;
+
+struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user)
+{
+  int retval=0, child, fds[2];
+  void (*sighandler)(int) = NULL;
+  D(("running verify_binary"));
+
+  /* create a pipe for the messages */
+  if (pipe(fds) != 0) {
+    D(("could not make pipe"));
+    _log_err(LOG_ERR, pamh, "Could not make pipe %s",strerror(errno));
+    return NULL;
+  }
+  D(("called."));
+
+  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.
+     */
+    sighandler = signal(SIGCHLD, SIG_DFL);
+  }
+
+  /* fork */
+  child = fork();
+  if (child == 0) {
+    int i=0;
+    struct rlimit rlim;
+    static char *envp[] = { NULL };
+    char *args[] = { NULL, NULL, NULL, NULL };
+
+    close(0); close(1);
+    /* reopen stdin as pipe */
+    close(fds[0]);
+    dup2(fds[1], STDOUT_FILENO);
+
+    /* XXX - should really tidy up PAM here too */
+
+    if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
+      for (i=2; i < rlim.rlim_max; i++) {
+       if (fds[1] != i) {
+         close(i);
+       }
+      }
+    }
+    /* exec binary helper */
+    args[0] = x_strdup(CHKPWD_HELPER);
+    args[1] = x_strdup(user);
+    args[2] = x_strdup("verify");
+
+    execve(CHKPWD_HELPER, args, envp);
+
+    _log_err(LOG_ERR, pamh, "helper binary execve failed: %s",strerror(errno));
+    /* should not get here: exit with error */
+    close (fds[1]);
+    D(("helper binary is not available"));
+    exit(PAM_AUTHINFO_UNAVAIL);
+  } else {
+    close(fds[1]);
+    if (child > 0) {
+      char buf[1024];
+      int rc=0;
+      rc=waitpid(child, &retval, 0);  /* wait for helper to complete */
+      if (rc<0) {
+       _log_err(LOG_ERR, pamh, "unix_chkpwd waitpid returned %d: %s", rc, strerror(errno));
+       retval = PAM_AUTH_ERR;
+      } else {
+       retval = WEXITSTATUS(retval);
+       if (retval != PAM_AUTHINFO_UNAVAIL) {
+          rc = _pammodutil_read(fds[0], buf, sizeof(buf) - 1);
+         if(rc > 0) {
+             buf[rc] = '\0';
+             if (sscanf(buf,"%ld:%ld:%ld:%ld:%ld:%ld",
+                    &spwd.sp_lstchg, /* last password change */
+                    &spwd.sp_min, /* days until change allowed. */
+                    &spwd.sp_max, /* days before change required */
+                    &spwd.sp_warn, /* days warning for expiration */
+                    &spwd.sp_inact, /* days before account inactive */
+                    &spwd.sp_expire) /* date when account expires */ != 6 ) retval = PAM_AUTH_ERR;
+           }
+         else {
+           _log_err(LOG_ERR, pamh, " ERROR %d:%s \n",rc, strerror(errno)); retval = PAM_AUTH_ERR;
+         }
+       }
+      }
+    } else {
+      _log_err(LOG_ERR, pamh, "Fork failed %s \n",strerror(errno));
+      D(("fork failed"));
+      retval = PAM_AUTH_ERR;
+    }
+    close(fds[0]);
+  }
+  if (sighandler != NULL) {
+    (void) signal(SIGCHLD, sighandler);   /* restore old signal handler */
+  }
+  D(("Returning %d",retval));
+  if (retval != PAM_SUCCESS) {
+    return NULL;
+  }
+  return &spwd;
+}
+
+#endif
+
+
 /*
  * PAM framework looks for this entry-point to pass control to the
  * account management module.
@@ -128,6 +246,11 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags,
        else
                return PAM_SUCCESS;
 
+#ifdef WITH_SELINUX
+       if (!spent && SELINUX_ENABLED )
+           spent = _unix_run_verify_binary(pamh, ctrl, uname);
+#endif
+
        if (!spent)
                if (on(UNIX_BROKEN_SHADOW,ctrl))
                        return PAM_SUCCESS;
index b48539a0e5a82a94d8204a99186d651e832d3aea..9c7cb07cf1d56bb77278e4f56fbff503c9cbba24 100644 (file)
 #include <rpcsvc/yp_prot.h>
 #include <rpcsvc/ypclnt.h>
 
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+#ifdef WITH_SELINUX
+static int selinux_enabled=-1;
+#include <selinux/selinux.h>
+static security_context_t prev_context=NULL;
+#define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
+#endif
+
 #ifdef USE_CRACKLIB
 #include <crack.h>
 #endif
@@ -210,6 +220,101 @@ static char *getNISserver(pam_handle_t *pamh)
        return master;
 }
 
+#ifdef WITH_SELINUX
+
+static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
+{
+    int retval, child, fds[2];
+    void (*sighandler)(int) = NULL;
+
+    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.
+        */
+       sighandler = signal(SIGCHLD, SIG_DFL);
+    }
+
+    /* fork */
+    child = fork();
+    if (child == 0) {
+        int i=0;
+        struct rlimit rlim;
+       static char *envp[] = { NULL };
+       char *args[] = { NULL, NULL, NULL, NULL };
+
+       /* XXX - should really tidy up PAM here too */
+
+       close(0); close(1);
+       /* reopen stdin as pipe */
+       close(fds[1]);
+       dup2(fds[0], STDIN_FILENO);
+
+       if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
+         for (i=2; i < rlim.rlim_max; i++) {
+               if (fds[0] != i)
+                  close(i);
+         }
+       }
+       /* exec binary helper */
+       args[0] = x_strdup(CHKPWD_HELPER);
+       args[1] = x_strdup(user);
+       args[2] = x_strdup("shadow");
+
+       execve(CHKPWD_HELPER, 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)
+         _pammodutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
+       else
+         _pammodutil_write(fds[1], "", 1);
+       if (towhat) {
+         _pammodutil_write(fds[1], towhat, strlen(towhat)+1);
+       }
+       else
+         _pammodutil_write(fds[1], "", 1);
+
+       close(fds[0]);       /* close here to avoid possible SIGPIPE above */
+       close(fds[1]);
+       rc=waitpid(child, &retval, 0);  /* wait for helper to complete */
+       if (rc<0) {
+         _log_err(LOG_ERR, pamh, "unix_chkpwd waitpid returned %d: %s", rc, strerror(errno));
+         retval = PAM_AUTH_ERR;
+       } else {
+         retval = WEXITSTATUS(retval);
+       }
+    } else {
+       D(("fork failed"));
+       close(fds[0]);
+       close(fds[1]);
+       retval = PAM_AUTH_ERR;
+    }
+
+    if (sighandler != NULL) {
+        (void) signal(SIGCHLD, sighandler);   /* restore old signal handler */
+    }
+
+    return retval;
+}
+#endif
+
 static int check_old_password(const char *forwho, const char *newpass)
 {
        static char buf[16384];
@@ -270,37 +375,58 @@ static int save_old_password(pam_handle_t *pamh,
     }
 
     oldmask = umask(077);
+
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      security_context_t passwd_context=NULL;
+      if (getfilecon("/etc/passwd",&passwd_context)<0) {
+        return PAM_AUTHTOK_ERR;
+      };
+      if (getfscreatecon(&prev_context)<0) {
+        freecon(passwd_context);
+        return PAM_AUTHTOK_ERR;
+      }
+      if (setfscreatecon(passwd_context)) {
+        freecon(passwd_context);
+        freecon(prev_context);
+        return PAM_AUTHTOK_ERR;
+      }
+      freecon(passwd_context);
+    }
+#endif
     pwfile = fopen(OPW_TMPFILE, "w");
     umask(oldmask);
     if (pwfile == NULL) {
-       return PAM_AUTHTOK_ERR;
+      err = 1;
+      goto done;
     }
 
     opwfile = fopen(OLD_PASSWORDS_FILE, "r");
     if (opwfile == NULL) {
        fclose(pwfile);
-       return PAM_AUTHTOK_ERR;
+      err = 1;
+      goto done;
     }
 
-    if (fstat (fileno (opwfile), &st) == -1)
-      {
-       fclose (opwfile);
-       fclose (pwfile);
-       return PAM_AUTHTOK_ERR;
-      }
+    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);
-       return PAM_AUTHTOK_ERR;
-      }
-    if (fchmod (fileno (pwfile), st.st_mode) == -1)
-      {
-       fclose (opwfile);
-       fclose (pwfile);
-       return PAM_AUTHTOK_ERR;
-      }
+    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;
+    }
 
     while (fgets(buf, 16380, opwfile)) {
        if (!strncmp(buf, forwho, strlen(forwho))) {
@@ -357,14 +483,27 @@ static int save_old_password(pam_handle_t *pamh,
        err = 1;
     }
 
+done:
     if (!err) {
-       if (!rename(OPW_TMPFILE, OLD_PASSWORDS_FILE)) {
-           return PAM_SUCCESS;
-       }
+       if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
+           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(OPW_TMPFILE);
+       return PAM_AUTHTOK_ERR;
     }
-
-    unlink(OPW_TMPFILE);
-    return PAM_AUTHTOK_ERR;
 }
 
 static int _update_passwd(pam_handle_t *pamh,
@@ -377,38 +516,59 @@ static int _update_passwd(pam_handle_t *pamh,
     int oldmask;
 
     oldmask = umask(077);
+#ifdef WITH_SELINUX
+    if (SELINUX_ENABLED) {
+      security_context_t passwd_context=NULL;
+      if (getfilecon("/etc/passwd",&passwd_context)<0) {
+       return PAM_AUTHTOK_ERR;
+      };
+      if (getfscreatecon(&prev_context)<0) {
+       freecon(passwd_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      if (setfscreatecon(passwd_context)) {
+       freecon(passwd_context);
+       freecon(prev_context);
+       return PAM_AUTHTOK_ERR;
+      }
+      freecon(passwd_context);
+    }
+#endif
     pwfile = fopen(PW_TMPFILE, "w");
     umask(oldmask);
     if (pwfile == NULL) {
-       return PAM_AUTHTOK_ERR;
+      err = 1;
+      goto done;
     }
 
     opwfile = fopen("/etc/passwd", "r");
     if (opwfile == NULL) {
        fclose(pwfile);
-       return PAM_AUTHTOK_ERR;
+       err = 1;
+       goto done;
     }
 
-    if (fstat (fileno (opwfile), &st) == -1)
-      {
-       fclose (opwfile);
-       fclose (pwfile);
-       return PAM_AUTHTOK_ERR;
-      }
+    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);
-       return PAM_AUTHTOK_ERR;
-      }
-    if (fchmod (fileno (pwfile), st.st_mode) == -1)
-      {
-       fclose (opwfile);
-       fclose (pwfile);
-      }
+    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;
+    }
 
-    tmpent = fgetpwent (opwfile);
+    tmpent = fgetpwent(opwfile);
     while (tmpent) {
        if (!strcmp(tmpent->pw_name, forwho)) {
            /* To shut gcc up */
@@ -435,15 +595,29 @@ static int _update_passwd(pam_handle_t *pamh,
        err = 1;
     }
 
+done:
     if (!err) {
-       if (!rename(PW_TMPFILE, "/etc/passwd")) {
+       if (!rename(PW_TMPFILE, "/etc/passwd"))
            _log_err(LOG_NOTICE, pamh, "password changed for %s", forwho);
-           return PAM_SUCCESS;
-       }
+       else
+           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(PW_TMPFILE);
+       return PAM_AUTHTOK_ERR;
     }
-
-    unlink(PW_TMPFILE);
-    return PAM_AUTHTOK_ERR;
 }
 
 static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
@@ -459,37 +633,58 @@ static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
        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) {
-       return PAM_AUTHTOK_ERR;
+       err = 1;
+       goto done;
     }
 
     opwfile = fopen("/etc/shadow", "r");
     if (opwfile == NULL) {
        fclose(pwfile);
-       return PAM_AUTHTOK_ERR;
+       err = 1;
+       goto done;
     }
 
-    if (fstat (fileno (opwfile), &st) == -1)
-      {
-       fclose (opwfile);
-       fclose (pwfile);
-       return PAM_AUTHTOK_ERR;
-      }
+    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);
-       return PAM_AUTHTOK_ERR;
-      }
-    if (fchmod (fileno (pwfile), st.st_mode) == -1)
-      {
-       fclose (opwfile);
-       fclose (pwfile);
-       return PAM_AUTHTOK_ERR;
-      }
+    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) {
@@ -516,15 +711,31 @@ static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
        err = 1;
     }
 
+ done:
     if (!err) {
-       if (!rename(SH_TMPFILE, "/etc/shadow")) {
+       if (!rename(SH_TMPFILE, "/etc/shadow"))
            _log_err(LOG_NOTICE, pamh, "password changed for %s", forwho);
-           return PAM_SUCCESS;
-       }
+       else
+           err = 1;
     }
 
-    unlink(SH_TMPFILE);
-    return PAM_AUTHTOK_ERR;
+#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;
+    }
 }
 
 static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
@@ -638,6 +849,10 @@ static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
                }
                if (on(UNIX_SHADOW, ctrl) || _unix_shadowed(pwd)) {
                        retval = _update_shadow(pamh, forwho, towhat);
+#ifdef WITH_SELINUX
+                       if (retval != PAM_SUCCESS && SELINUX_ENABLED)
+                         retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
+#endif
                        if (retval == PAM_SUCCESS)
                                if (!_unix_shadowed(pwd))
                                        retval = _update_passwd(pamh, forwho, "x");
@@ -655,7 +870,7 @@ done:
        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 */
@@ -674,6 +889,10 @@ static int _unix_verify_shadow(const char *user, unsigned int ctrl)
                spwdent = getspnam(user);
                endspent();
 
+#ifdef WITH_SELINUX
+               if (spwdent == NULL && SELINUX_ENABLED )
+                   spwdent = _unix_run_verify_binary(pamh, ctrl, user);
+#endif
                if (spwdent == NULL)
                        return PAM_AUTHINFO_UNAVAIL;
        } else {
@@ -918,7 +1137,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
                        _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,
@@ -1043,7 +1262,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
                        }
                }
 
-               retval = _unix_verify_shadow(user, ctrl);
+               retval = _unix_verify_shadow(pamh, user, ctrl);
                if (retval != PAM_SUCCESS) {
                        _log_err(LOG_NOTICE, pamh, "user not authenticated 2");
 #ifdef USE_LCKPWDF
index a9df0c5feb855daaf57d2161a118c739a7b14ea3..bb74987b82d7675e7c939911aac98f163b9ae2a6 100644 (file)
 
 #include "md5.h"
 #include "support.h"
-
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#define SELINUX_ENABLED is_selinux_enabled()>0
+#else
+#define SELINUX_ENABLED 0
+#endif
 extern char *crypt(const char *key, const char *salt);
 extern char *bigcrypt(const char *key, const char *salt);
 
@@ -562,18 +567,32 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
     /* fork */
     child = fork();
     if (child == 0) {
+        int i=0;
+        struct rlimit rlim;
        static char *envp[] = { NULL };
-       char *args[] = { NULL, NULL, NULL };
+       char *args[] = { NULL, NULL, NULL, NULL };
 
        /* XXX - should really tidy up PAM here too */
 
+       close(0); close(1);
        /* reopen stdin as pipe */
        close(fds[1]);
        dup2(fds[0], STDIN_FILENO);
 
+       if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
+         for (i=2; i < rlim.rlim_max; i++) {
+               if (fds[0] != i)
+                  close(i);
+         }     
+       }
        /* exec binary helper */
        args[0] = x_strdup(CHKPWD_HELPER);
        args[1] = x_strdup(user);
+       if (off(UNIX__NONULL, ctrl)) {  /* this means we've succeeded */
+         args[2]=x_strdup("nullok");
+       } else {
+         args[2]=x_strdup("nonull");
+       }
 
        execve(CHKPWD_HELPER, args, envp);
 
@@ -583,11 +602,7 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
     } else if (child > 0) {
        /* wait for child */
        /* if the stored password is NULL */
-       if (off(UNIX__NONULL, ctrl)) {  /* this means we've succeeded */
-           write(fds[1], "nullok\0\0", 8);
-       } else {
-           write(fds[1], "nonull\0\0", 8);
-       }
+        int rc=0;
        if (passwd != NULL) {            /* send the password to the child */
            write(fds[1], passwd, strlen(passwd)+1);
            passwd = NULL;
@@ -596,10 +611,17 @@ static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
        }
        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;
+       rc=waitpid(child, &retval, 0);  /* wait for helper to complete */
+       if (rc<0) {
+         _log_err(LOG_ERR, pamh, "unix_chkpwd waitpid returned %d: %s", rc, strerror(errno));
+         retval = PAM_AUTH_ERR;
+       } else {
+         retval = WEXITSTATUS(retval);
+       }
     } else {
        D(("fork failed"));
+       close(fds[0]);
+       close(fds[1]);
        retval = PAM_AUTH_ERR;
     }
 
@@ -621,6 +643,7 @@ int _unix_verify_password(pam_handle_t * pamh, const char *name
        char *data_name;
        int retval;
 
+
        D(("called"));
 
 #ifdef HAVE_PAM_FAIL_DELAY
@@ -687,7 +710,8 @@ int _unix_verify_password(pam_handle_t * pamh, const char *name
 
        retval = PAM_SUCCESS;
        if (pwd == NULL || salt == NULL || !strcmp(salt, "x") || ((salt[0] == '#') && (salt[1] == '#') && !strcmp(salt + 2, name))) {
-               if (geteuid()) {
+         
+               if (geteuid() || SELINUX_ENABLED) {
                        /* we are not root perhaps this is the reason? Run helper */
                        D(("running helper binary"));
                        retval = _unix_run_helper_binary(pamh, p, ctrl, name);
index 956ef59e5e0d3d72ee0820fd5788648b08041df5..5f55911a1c8db068c64ad83c0e78afb67ce785b0 100644 (file)
@@ -152,4 +152,5 @@ extern int _unix_read_password(pam_handle_t * pamh
                        ,const char **pass);
 extern int _unix_shadowed(const struct passwd *pwd);
 
+extern struct spwd *_unix_run_verify_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user);
 #endif /* _PAM_UNIX_SUPPORT_H */
index ff1d1bffa000d7c4e1f65df5a2971796ab9cbdd3..2ad9536250e057eb298fdc6e1b3b84610b680c0b 100644 (file)
 #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"
@@ -41,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,...)
@@ -112,13 +120,40 @@ static void setup_signals(void)
        (void) sigaction(SIGQUIT, &action, NULL);
 }
 
+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 */
@@ -156,28 +191,30 @@ static int _unix_verify_password(const char *name, const char *p, int nullok)
        if (pwd == NULL || salt == NULL) {
                _log_err(LOG_ALERT, "check pass; user unknown");
                p = NULL;
-               return retval;
+               return PAM_USER_UNKNOWN;
        }
 
        salt_len = strlen(salt);
-       if (salt_len == 0)
-               return (nullok == 0) ? UNIX_FAILED : UNIX_PASSED;
-       else if (p == NULL || strlen(p) == 0)
-               return UNIX_FAILED;
+       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 = UNIX_FAILED;
+           retval = PAM_AUTH_ERR;
        } else {
                pp = bigcrypt(p, salt);
                /*
@@ -190,7 +227,7 @@ static int _unix_verify_password(const char *name, const char *p, int nullok)
                 * Bug 521314: the strncmp comparison is for legacy support.
                 */
                if (strncmp(pp, salt, salt_len) == 0) {
-                       retval = UNIX_PASSED;
+                       retval = PAM_SUCCESS;
                }
        }
        p = NULL;               /* no longer needed here */
@@ -220,17 +257,178 @@ static char *getuidname(uid_t uid)
 
        strncpy(username, pw->pw_name, sizeof(username));
        username[sizeof(username) - 1] = '\0';
-       
+
        return username;
 }
 
+#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;
+
+    /* 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];
+       char *option;
        int npass, nullok;
        int force_failure = 0;
-       int retval = UNIX_FAILED;
+       int retval = PAM_AUTH_ERR;
        char *user;
 
        /*
@@ -247,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());
@@ -256,36 +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
+        * 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 (argc == 2) {
-           /* if the caller specifies the username, verify that user
-              matches it */
-           if (strcmp(user, argv[1])) {
-               force_failure = 1;
-           }
+       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 nullok/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)
-                       nullok = 1;
-               else
-                       nullok = 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);
@@ -317,10 +524,10 @@ int main(int argc, char *argv[])
 
        /* return pass or fail */
 
-       if ((retval != UNIX_PASSED) || force_failure) {
-           return UNIX_FAILED;
+       if ((retval != PAM_SUCCESS) || force_failure) {
+           return PAM_AUTH_ERR;
        } else {
-           return UNIX_PASSED;
+           return PAM_SUCCESS;
        }
 }
 
@@ -339,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