Package: libpam-unix2
Version: 1.25-1

Hello,

I discovered that various screen locking programs (xlock, kcheckpass,
xscreensaver, vlock) have problems to authenticate a user when using
pam_unix2. The reason was evident for me: these programs are not setuid
root. 

But I wondered why the programs are working correctly when using
pam_unix and found out that pam_unix calls the external helper binary
/sbin/unix_chkpwd that is set setuid root to solve the problem that
normal users cannot read for example /etc/shadow. I saw that the
upstream source of pam_unix2 (I got it from the SuSE ftp server) also
provides the source code for an external binary (unix2_chkpwd.c with
man-page unix2_chkpwd.8) , but this binary is not called from pam_unix2.
So I added the support for calling the external binary to
src/unix_auth.c. Most of the code was taken from the source code of
pam_unix. Furthermore, I discovered that unix2_chkpwd has to be
installed setuid root (-r-sr-xr-x root root unix2_chkpwd, like
unix_chkpwd) and that setgid shadow (-r-xr-sr-x root shadow) is not
sufficient.

I have attached the new code of unix_auth.c. Unfortunately I could not
add support for compilation and installation of unix2_chkpwd to the
source package of libpam-unix2 because I do not know how to use automake
and autoconf.

Kind regards
  Christoph Pleger 
/*
 * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004 SuSE GmbH Nuernberg, 
Germany.
 * Author: Thorsten Kukuk <[EMAIL PROTECTED]>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * ALTERNATIVELY, this product may be distributed under the terms of
 * the GNU Public License, in which case the provisions of the GPL are
 * required INSTEAD OF the above restrictions.  (This clause is
 * necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if defined(HAVE_CONFIG_H)
#include <config.h>
#endif

#define _GNU_SOURCE

#include <grp.h>
#include <pwd.h>
#include <time.h>
#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <shadow.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <rpc/rpc.h>
#include <rpc/key_prot.h>

#define PAM_SM_AUTH
#include <security/pam_modules.h>

#if defined(HAVE_XCRYPT_H)
#include <xcrypt.h>
#elif defined(HAVE_CRYPT_H)
#include <crypt.h>
#endif

#include "public.h"

#define CHKPWD_HELPER "/sbin/unix2_chkpwd"
#define SHADOW_GROUP_ID 42

/* This module actually performs UNIX/shadow authentication

   If shadow support is available, attempt to perform authentication
   using shadow passwords. If shadow is not available, or user does not
   have a shadow password, fallback onto a normal UNIX authentication.
   If an secret key exists and "set_secrpc" is set, send private key
   to keyserv.
*/

extern int key_setnet (struct key_netstarg *arg);

static void
_cleanup_message (pam_handle_t *pamh __attribute__ ((unused)),
                  void *fl, int err __attribute__ ((unused)))
{
  if (fl)
    free (fl);
}

static void
__delete_secret_key (uid_t uid)
{
  char empty_key[HEXKEYBYTES + 1];
  uid_t saved_uid = geteuid ();

  seteuid (uid);
  memset (&empty_key, 0, HEXKEYBYTES + 1);
  key_setsecret (empty_key);
  seteuid (saved_uid);
}

static int
__set_secret_key (pam_handle_t *pamh, uid_t uid, char *password)
{
  struct key_netstarg net;
  char domain[MAXNETNAMELEN + 1];
  char netname[MAXNETNAMELEN + 1];

  getdomainname (domain, MAXNETNAMELEN);

  /* nomalization of the domain name for secure RPC without the ending
     dot of the NIS+ domain. */
  if (domain[strlen (domain) - 1] == '.')
    domain[strlen (domain) - 1] = '\0';

  if (uid == 0)
    {
      char hostname[MAXHOSTNAMELEN + 1];

      gethostname (hostname, MAXHOSTNAMELEN);
      snprintf (netname, MAXNETNAMELEN, "[EMAIL PROTECTED]", hostname, domain);
    }
  else
    snprintf (netname, MAXNETNAMELEN, "[EMAIL PROTECTED]", uid, domain);

  /* encrypt the secret key with the users password */
  if (!getsecretkey (netname, (char *) &net.st_priv_key, password))
    {
      char *mtmp = alloca (strlen (netname) + 30);
      if (!mtmp)
        return PAM_IGNORE;
#if 0
      /* Don't print this, maybe no SecureRPC is used ? */
      sprintf (mtmp, "Can't find %s's secret key", netname);
      pam_set_data (pamh, "pam_unix_auth_keylogin_msg", mtmp,
                    _cleanup_message);
#endif
      return PAM_IGNORE;
    }

  /* check, if we have encrypt the secret key */
  if (net.st_priv_key[0] == 0)
    {
      char *mtmp = alloca (strlen (netname) + 40);
      if (!mtmp)
        return PAM_IGNORE;
      sprintf (mtmp, "Secure-RPC password incorrect for %s", netname);
      pam_set_data (pamh, "pam_unix_auth_keylogin_msg", mtmp,
                    _cleanup_message);
      return PAM_IGNORE;
    }

  /* give unencrypted secret key to keyserv */
  net.st_pub_key[0] = 0;
  net.st_netname = (char *) &netname;
  /* give unencrypted secret key to keyserv */
  if (key_setnet (&net) < 0)
    {
      char *mtmp = alloca (strlen (netname) + 65);
      if (!mtmp)
        return PAM_IGNORE;
      sprintf (mtmp,
             "Could not set %s's secret key,\nmaybe the keyserver is down?",
               netname);
      pam_set_data (pamh, "pam_unix_auth_keylogin_msg", mtmp,
                    _cleanup_message);
      return PAM_IGNORE;
    }

  return PAM_IGNORE;
}

static int _unix2_run_helper_binary(pam_handle_t *pamh, const char *passwd,
                                    const char *user, const options_t *options)
{
    int retval, child, fds[2];
    sigset_t sigset;
    char *service;

    pam_get_item (pamh, PAM_SERVICE, (void *) &service);

    if (options->debug)
      __pam_log (LOG_DEBUG, "_unix2_run_helper_binary called.");
    /* create a pipe for the password */
    if (pipe(fds) != 0) {
        if (options->debug)
          __pam_log (LOG_DEBUG, "could not make pipe");
        return PAM_AUTH_ERR;
    }

    /* Block SIGCHLD */
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGCHLD);
    sigprocmask(SIG_BLOCK, &sigset, 0);

    /* fork */
    child = fork();
    if (child == 0) {
        char *args[] = { NULL, NULL, NULL, NULL };
        static char *envp[] = { NULL };

        /* XXX - should really tidy up PAM here too */

        /* reopen stdin as pipe */
        close(fds[1]);
        dup2(fds[0], STDIN_FILENO);

        /* exec binary helper */
        args[0] = x_strdup(CHKPWD_HELPER);
        args[1] = x_strdup(service);
        args[2] = x_strdup(user);
        
        execve(CHKPWD_HELPER, args, envp);

        /* should not get here: exit with error */
        if (options->debug)
          __pam_log (LOG_DEBUG, "helper binary is not available");
        exit(PAM_AUTHINFO_UNAVAIL);
    } else if (child > 0) {
        if (passwd != NULL) {            /* send the password to the child */
            write(fds[1], passwd, strlen(passwd)+1);
            passwd = NULL;
        } else {
            write(fds[1], "", 1);                        /* blank password */
        }
        close(fds[0]);       /* close here to avoid possible SIGPIPE above */
        close(fds[1]);
        (void) waitpid(child, &retval, 0);  /* wait for helper to complete */
        retval = (retval == 0) ? PAM_SUCCESS:PAM_AUTH_ERR;
    } else {
        if (options->debug)
          __pam_log (LOG_DEBUG, "fork failed");
        retval = PAM_AUTH_ERR;
    }

    /* Unblock SIGCHLD */
    sigprocmask(SIG_BLOCK, &sigset, 0);

    if (options->debug)
      __pam_log (LOG_DEBUG, "returning %d", retval);
    return retval;
}

int
pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc,
                     const char **argv)
{
  struct crypt_data output;
  int retval;
  int sp_buflen = 256;
  char *sp_buffer = alloca (sp_buflen);
  struct spwd sp_resultbuf;
  struct spwd *sp = NULL;
  int pw_buflen = 256;
  char *pw_buffer = alloca (pw_buflen);
  struct passwd pw_resultbuf;
  struct passwd *pw;
  const char *name = NULL;
  char *service, *p = NULL;
  const char *salt;
  int dont_delete_seckey = 1;
  uid_t save_uid = geteuid ();
  options_t options;

  memset (&output, 0, sizeof (output));
  memset (&options, 0, sizeof (options));

  if (get_options (&options, "auth", argc, argv) < 0)
    {
      __pam_log (LOG_ERR, "cannot get options");
      return PAM_BUF_ERR;
    }

  if (options.debug)
    __pam_log (LOG_DEBUG, "pam_sm_authenticate() called");

  /* get the user name */
  if ((retval = pam_get_user (pamh, &name, NULL)) != PAM_SUCCESS)
    {
      if (options.debug)
        __pam_log (LOG_DEBUG, "pam_get_user failed: return %d", retval);
      return (retval == PAM_CONV_AGAIN ? PAM_INCOMPLETE:retval);
    }

  if (name == NULL || name[0] == '\0')
    {
      if (name)
        {
          __pam_log (LOG_ERR, "bad username [%s]", name);
          return PAM_USER_UNKNOWN;
        }
      else if (options.debug)
        __pam_log (LOG_DEBUG, "name == NULL, return PAM_SERVICE_ERR");
      return PAM_SERVICE_ERR;
    }
  else if (options.debug)
    __pam_log (LOG_DEBUG, "username=[%s]", name);

  /* Get the password entry for this user. */
  while (getpwnam_r (name, &pw_resultbuf, pw_buffer, pw_buflen, &pw) != 0
         && errno == ERANGE)
    {
      errno = 0;
      pw_buflen += 256;
      pw_buffer = alloca (pw_buflen);
    }

  /* If we call another PAM module, handle the module like "sufficient".
     If it returns success, we should also return success. Else ignore
     the call. This PAM modules will not be called, if the user to
     authenticate is root.  */
  if (options.use_other_modules && (pw == NULL || pw->pw_uid != 0))
    {
      unsigned int i;

      for (i = 0; options.use_other_modules[i] != NULL; i++)
        {
          int retval;

          retval = __call_other_module(pamh, flags,
                                options.use_other_modules[i],
                                "pam_sm_authenticate",
                                &options);

          if (retval == PAM_SUCCESS)
            {
              pam_get_item (pamh, PAM_SERVICE, (void *) &service);
              if (strcasecmp (service, "chsh") == 0 ||
                  strcasecmp (service, "chfn") == 0)
                {
                  char *p;

                  pam_get_item (pamh, PAM_AUTHTOK, (void *)&p);
                  if (p != NULL)
                    {
                      char *msg = alloca (strlen (p) + 13);
                      sprintf (msg, "PAM_AUTHTOK=%s", p);
                      pam_putenv (pamh, msg);
                    }
                }
              return retval;
            }
        }
    }

  /* Check at first for empty password, then prompt for user password */

  /* Get shadow entry. We don't bail out if user does not exists.
     Ask for an password in this case and bail out then. */
  if (pw)
    while (getspnam_r (pw->pw_name, &sp_resultbuf, sp_buffer,
                       sp_buflen, &sp) != 0 && errno == ERANGE)
      {
        errno = 0;
        sp_buflen += 256;
        sp_buffer = alloca (sp_buflen);
      }

  if ((pw && (pw->pw_passwd == NULL || strlen (pw->pw_passwd) == 0)) ||
      (sp && pw && strcmp (pw->pw_passwd, "x") == 0 &&
       (sp->sp_pwdp == NULL || strlen (sp->sp_pwdp) == 0)))
    {
      if (flags & PAM_DISALLOW_NULL_AUTHTOK || !options.nullok)
        {
          if (options.debug)
            __pam_log (LOG_DEBUG, "return PAM_AUTH_ERR");
          return PAM_AUTH_ERR;
        }
      if (options.debug)
        __pam_log (LOG_DEBUG, "return PAM_SUCCESS");
      return PAM_SUCCESS;
    }

  retval = pam_get_item (pamh, PAM_AUTHTOK, (void *) &p);
  if (retval != PAM_SUCCESS)
    {
      if (options.debug)
        __pam_log (LOG_DEBUG, "pam_get_item (PAM_AUTHTOK) failed, return %d",
                   retval);
      return retval;
    }
  if (p == NULL)
    {
      if (options.use_first_pass)
        {
          if (options.debug)
            __pam_log (LOG_DEBUG, "Cannot get stacked password, return 
PAM_AUTHTOK_RECOVER_ERR");
          return PAM_AUTHTOK_RECOVER_ERR;
        }

      retval = __get_authtok (pamh, options.not_set_pass);
      if (retval != PAM_SUCCESS)
        {
          if (options.debug)
            __pam_log (LOG_DEBUG, "__get_authtok failed with %d, exit",
                       retval);
          return retval;
        }

      /* We have to call pam_get_item() again because value of p should
         changed to a password. */
      pam_get_item (pamh, PAM_AUTHTOK, (void *) &p);
    }

  pam_get_item (pamh, PAM_SERVICE, (void *) &service);
  if (strcasecmp (service, "chsh") == 0 ||
      strcasecmp (service, "chfn") == 0)
    {
      char *p;

      pam_get_item (pamh, PAM_AUTHTOK, (void *)&p);
      if (p != NULL)
        {
          char *msg = alloca (strlen (p) + 13);
          sprintf (msg, "PAM_AUTHTOK=%s", p);
          pam_putenv (pamh, msg);
        }
    }

  /* Now bail out if we cannot find the account.  */
  if (pw == NULL)
    {
      if (options.debug)
        __pam_log (LOG_DEBUG, "Cannot find passwd entry for %s", name);
      return PAM_USER_UNKNOWN;
    }

  /* If authtok is empty again, check if this is allowed */
  if (p == NULL)
    {
      if (flags & PAM_DISALLOW_NULL_AUTHTOK || !options.nullok)
        {
          if (options.debug)
            __pam_log (LOG_DEBUG, "empty authtok, return PAM_AUTH_ERR");
          return PAM_AUTH_ERR;
        }
      if (options.debug)
        __pam_log (LOG_DEBUG, "pam_sm_authenticate: PAM_SUCCESS");
      return PAM_SUCCESS;
    }

  /* For NIS+, root cannot get password for lesser user.
     So we change our uid to this of the user, try to set
     his secret key and get the password a second time.
     This only works if we are allowed ot set the secret key. */
  if (options.secrpc_flag)
    {
      if (seteuid (pw->pw_uid) < 0)
        {
          __pam_log (LOG_ERR, "auth: seteuid(%d) faild", pw->pw_uid);
          return PAM_PERM_DENIED;
        }
      dont_delete_seckey = key_secretkey_is_set();
      __set_secret_key (pamh, pw->pw_uid, p);

      if (strcmp (pw->pw_passwd, "*NP*") == 0)
        while (getpwnam_r (name, &pw_resultbuf, pw_buffer,
                           pw_buflen, &pw) != 0 && errno == ERANGE)
          {
            errno = 0;
            pw_buflen += 256;
            pw_buffer = alloca (pw_buflen);
          }

      if (sp && strcmp (sp->sp_pwdp, "*NP*") == 0)
        while (getspnam_r (pw->pw_name, &sp_resultbuf, sp_buffer,
                           sp_buflen, &sp) != 0 && errno == ERANGE)
          {
            errno = 0;
            sp_buflen += 256;
            sp_buffer = alloca (sp_buflen);
          }

      if (seteuid (save_uid) < 0)
        {
          __pam_log (LOG_ERR, "auth: seteuid(%d) faild", save_uid);
          if (!dont_delete_seckey)
            __delete_secret_key (pw->pw_uid);
          return PAM_PERM_DENIED;
        }
      /* No we should have the correct password. */

      /* Check a second time, if the password is empty. It could
         have changed since we now had the correct permissions. */
      if ((pw->pw_passwd == NULL || strlen (pw->pw_passwd) == 0) ||
          (sp && strcmp (pw->pw_passwd, "x") == 0 &&
           (sp->sp_pwdp == NULL || strlen (sp->sp_pwdp) == 0)) || p == NULL)
        {
          if (flags & PAM_DISALLOW_NULL_AUTHTOK || !options.nullok)
            {
              if (!dont_delete_seckey)
                __delete_secret_key (pw->pw_uid);
              return PAM_AUTH_ERR;
            }
          if (options.debug)
            __pam_log (LOG_DEBUG, "pam_sm_authenticate: PAM_SUCCESS");
          return PAM_SUCCESS;
        }
    }


  if (pw->pw_passwd[0] != '!')
    {
      if (sp)
        salt = strdupa (sp->sp_pwdp);
      else
        salt = strdupa (pw->pw_passwd);
    }
  else
    {
      if (!dont_delete_seckey && options.secrpc_flag)
        __delete_secret_key (pw->pw_uid);
      return PAM_PERM_DENIED;
    }


  /* This is for HP-UX password aging (why couldn't they use shadow ?) */
  if (strchr (salt, ',') != NULL)
    {
      char *cp = alloca (strlen (salt) + 1);
      strcpy (cp, salt);
      salt = cp;
      cp = strchr (salt, ',');
      *cp = '\0';
    }

  if (strcmp(crypt_r(p, salt, &output), salt) != 0)
    {
      retval = PAM_AUTH_ERR;
      
      if (geteuid())
        {
          /* we are not root, perhaps this is the reason?  Run helper */
          if (options.debug)
            __pam_log (LOG_DEBUG, "running helper binary");
          
          retval = _unix2_run_helper_binary(pamh, p, name, &options);
        }
        
      if (retval != PAM_SUCCESS)
        {
          if (!dont_delete_seckey && options.secrpc_flag)
            __delete_secret_key (pw->pw_uid);
          
          if (options.debug)
                __pam_log (LOG_DEBUG, "wrong password, return PAM_AUTH_ERR");
          return PAM_AUTH_ERR;
        }
    }
  
  if (options.debug)
    __pam_log (LOG_DEBUG, "pam_sm_authenticate: PAM_SUCCESS");
  return PAM_SUCCESS;
}

int
pam_sm_setcred (pam_handle_t *pamh, int flags, int argc, const char **argv)
{
  int retval;
  void *msg = NULL;
  options_t options;
  int pw_buflen = 256;
  char *pw_buffer = alloca (pw_buflen);
  struct passwd pw_resultbuf;
  struct passwd *pw;
  const char *name = NULL;

  memset (&options, 0, sizeof (options));

  if (get_options (&options, "auth", argc, argv) < 0)
    {
      __pam_log (LOG_ERR, "cannot get options");
      return PAM_BUF_ERR;
    }

  if (options.debug)
    __pam_log (LOG_DEBUG, "pam_sm_setcred() called");

  /* get the user name */
  if ((retval = pam_get_user (pamh, &name, NULL)) != PAM_SUCCESS)
    {
      if (options.debug)
        __pam_log (LOG_DEBUG, "pam_get_user failed: return %d", retval);
      return (retval == PAM_CONV_AGAIN ? PAM_INCOMPLETE:retval);
    }

  if (name == NULL || name[0] == '\0')
    {
      if (name)
        {
          __pam_log (LOG_ERR, "bad username [%s]", name);
          return PAM_USER_UNKNOWN;
        }
      else if (options.debug)
        __pam_log (LOG_DEBUG, "name == NULL, return PAM_SERVICE_ERR");
      return PAM_SERVICE_ERR;
    }
  else if (options.debug)
    __pam_log (LOG_DEBUG, "username=[%s]", name);

  /* Get the password entry for this user. */
  while (getpwnam_r (name, &pw_resultbuf, pw_buffer, pw_buflen, &pw) != 0
         && errno == ERANGE)
    {
      errno = 0;
      pw_buflen += 256;
      pw_buffer = alloca (pw_buflen);
    }

  if (pw == NULL)
    {
      if (options.debug)
        __pam_log (LOG_DEBUG, "Cannot find passwd entry for %s", name);
      return PAM_USER_UNKNOWN;
    }

  /* If we call another PAM module, handle the module like "sufficient".
     If it returns success, we should also return success. Else ignore
     the call. This PAM modules will not be called, if the user to
     authenticate is root.  */
  if (options.use_other_modules && pw->pw_uid != 0)
    {
      unsigned int i;

      for (i = 0; options.use_other_modules[i] != NULL; i++)
        {
          retval = __call_other_module(pamh, flags,
                                       options.use_other_modules[i],
                                       "pam_sm_setcred",
                                       &options);

          if (retval != PAM_SUCCESS && retval != PAM_IGNORE &&
              retval != PAM_CRED_UNAVAIL)
            {
              if (options.debug)
                __pam_log (LOG_DEBUG, "pam_sm_setcred: %d", retval);
              return retval;
            }
        }
    }


  pam_get_data (pamh, "pam_unix_auth_keylogin_msg", (const void **) &msg);

  if (msg != NULL)
    __write_message (pamh, flags, PAM_TEXT_INFO, (char *)msg);

  if (options.debug)
    __pam_log (LOG_DEBUG, "pam_sm_setcred: PAM_SUCCESS");
  return PAM_SUCCESS;
}

Reply via email to