[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Linux PAM fixes



Hello!  I'm sending this mail to addresses I found in documentation
for several Linux packages or in changelog entries in their spec files
for Red Hat or Mandrake Linux.  Feel free to forward this message to
anyone interested that I missed.

I've spent the past two weeks adding PAM support to utilities from MIT krb5
and FreeBSD.  In the process, I've done some comparisons with Solaris
and Linux PAM support and read the DCE RFC for PAM.
I found a nicely HTML formatted copy of the PAM RFC at
http://www.opengroup.org/tech/rfc/rfc86.0.html

To help with testing and comparisons, I made several modified versions
of the pam_permit module, in which each function prints its module
name and function name and then returns success or, in a few cases for
comparisons, failure.

I've discovered several problems in the Linux PAM support.
Following is a summary of the problems and my proposed fixes.
My work involved libpam, pam_stack, pam_krb5, login, rshd, and su.
(I was the original author of GNU/Linux su, but I wasn't involved in
adding PAM support to it.)

1. The Linux utilities call pam_setcred() to create credentials,
   but don't call it to delete them at the end.
2. The utilities call pam_setcred() before, instead of after,
   pam_open_session().  According to the Linux-PAM docs, the RFC
   example code, and the Solaris man page, pam_setcred() should be
   called after pam_open_session().
3. su calls pam_{open,close}_session().  My test modules show that the
   Solaris su doesn't.  The Linux-PAM docs and the RFC indicate that
   sessions are intended to support things like making utmp entries,
   mounting and unmounting home directories, and making login time
   accounting records.  I'm not sure, but PAM sessions seem to be
   login sessions, not authentication sessions, in which case su
   shouldn't use them.  If PAM modules and the API are to have the
   same effect as under Solaris, then that is the case.
4. rshd can attempt an authorization conversation with the client
   using misc_conv, but I don't believe the rcmd/rsh protocol supports
   that.  When I tried adding pam_unix as an auth module for rsh, I
   got a mess of incomplete prompts and the program locked up.
   I replaced misc_conv with a function that just returns an error.
   Modules like pam_rhosts don't try to hold a conversation, so
   they are ok.
5. The utilities don't support PAM template users, where pam_authenticate() may
   set the local user name, which you retrieve using pam_get_item(PAM_USER).
   You mustn't call getpwnam() on the user name until after then.
   This useful facility is described in the FreeBSD man page for
   pam_authenticate():
      Following successful completion, the name of the authenti-
      cated user will be present in the PAM item PAM_USER.  This
      item may be recovered with a call to pam_get_item(3).
   A comment in the FreeBSD login.c elaborates:
      * With PAM we support the concept of a "template"
      * user.  The user enters a login name which is
      * authenticated by PAM, usually via a remote service
      * such as RADIUS or TACACS+.  If authentication
      * succeeds, a different but related "template" name
      * is used for setting the credentials, shell, and
      * home directory.  The name the user enters need only
      * exist on the remote authentication server, but the
      * template name must be present in the local password
      * database.  The name the user enters need only
      * exist on the remote authentication server, but the
      * template name must be present in the local password
      * database.
   See problem 12 below for another example of how to use this.
6. The pam_sm_setcred() functions for modules that authenticated
   successfully might not get called.  This problem was discovered by
   one of the FreeBSD developers, Jacques A. Vidrine <n@nectar.com>.
   Consider the following pam.conf section:

login auth sufficient pam_skey.so
login auth sufficient pam_krb5.so
login auth required   pam_unix.so

   Here's what happens with Linux-PAM when you enter a krb5 password:

      application:  pam_authenticate()
           libpam:      pam_dispatch()
         pam_skey:          pam_sm_authenticate() /* fail */
         pam_krb5:          pam_sm_authenticate() /* success */
      application:  pam_setcred()
           libpam:      pam_dispatch()
         pam_skey:          pam_sm_setcred() /* success */
         pam_krb5:          /* not called */

   In contrast, my experiments show that the Solaris 8 libpam calls the
   pam_sm_setcred() function for all of the auth modules defined in
   pam.conf for the current service.  A module should store whatever state
   it needs using pam_set_item() in pam_sm_authenticate(), and in
   pam_sm_setcred() if it can't pam_get_item() that information back,
   just return PAM_SUCCESS (or perhaps PAM_IGNORE) indicating that it's
   punting, no-op, nothing to do here move along now....

7. pam_krb5 makes the pam_sm_{open,close}_session() calls be
   duplicates of the pam_sm_setcred() calls, probably to work around
   bugs 1 and 6.  Once they are fixed, pam_krb5 can be corrected, and
   the pam_sm_open_session() and pam_sm_close_session() reduced to
   no-ops that return PAM_SUCCESS, as they should be.
8. pam_krb5 lacks an account management function to check .k5login for
   authorization.
9. pam_krb5 doesn't check some malloc calls for failure.
10. pam_krb5 can't be used for the simple case of "su", because it
   tries to authenticate as "root@realm", rather than "user/root@realm",
   which is the standard krb5 approach as I infer from its docs.
11. pam_krb5 confuses PAM_SUCCESS with KRB5_SUCCESS.  I haven't fixed this.
   It's a good thing they're both 0 and not going to change!
12. pam_krb5 pam_open_session() failed when pam_krb5 hadn't done the
   authentication, instead of checking for that and trivially succeeding.
   If listed first with "required", it would prevent session modules
   listed later from running, even if those modules had done the
   successful authentication.  This is fixed as a side effect of
   nulling out the session functions in the fix for problem 7, and
   also of changing the setcred dispatch result handling for problem 6,
   but I've also included a patch that fixes it even if that's not done.
13. pam_krb5 doesn't call krb5_aname_to_localname() to support PAM
   template users or cross-realm authentication.  For example, in
   krb5.conf I can have:

[realms]
    WEB.WCOM.NET = {
        kdc = agamemnon.test.pubnix.com:88
        admin_server = agamemnon.test.pubnix.com:749
        default_domain = test.pubnix.com
        auth_to_local_names = {
            djm/root = root
            djm/operator = operator
        }
    }

   Then, on a secure terminal I can login as "djm/root"
   and it should authenticate against the djm/root principal
   and log me in as root.

   Similarly for "su djm/operator" if I have the ability
   to become "operator" using my own instance.

   I could also specify a more general translation using an
   "auth_to_local" section for a regexp substitution or a DBM lookup.
   (Although, stupidly, the krb5 regexp substitution code insists on
   using slashes to separate the pattern from the replacement text,
   and you can't escape slashes in them, either.  Maybe I'll fix that.)
14. pam_stack also breaks template users, by refusing to pass back the
   modified value of PAM_USER.

Patches follow, organized by package:

==============================================================================
patch for util-linux-2.10o release 6mdk
==============================================================================

--- util-linux-2.10o/login-utils/login.c~	Sat Jan  6 19:38:46 2001
+++ util-linux-2.10o/login-utils/login.c	Mon Jan 22 01:59:05 2001
@@ -145,8 +145,11 @@
        syslog(LOG_ERR,"%s",pam_strerror(pamh, retcode)); \
        pam_end(pamh, retcode); exit(1); \
    }
-#  define PAM_END { retcode = pam_close_session(pamh,0); \
-		    pam_end(pamh,retcode); }
+#  define PAM_END { \
+	pam_setcred(pamh, PAM_DELETE_CRED); \
+	retcode = pam_close_session(pamh,0); \
+	pam_end(pamh,retcode); \
+}
 #endif
 
 #ifndef __linux__
@@ -625,14 +628,21 @@
        First get the username that we are actually using, though.
     */
     retcode = pam_get_item(pamh, PAM_USER, (const void **) &username);
-    setpwent();
-    pwd = getpwnam(username);
+    if (retcode == PAM_SUCCESS && username && *username) {
+      pwd = getpwnam(username);
+    }
+
+    /*
+     * Initialize the supplementary group list.
+     * This should be done before pam_setcred because
+     * the PAM modules might add groups during pam_setcred.
+     */
     if (pwd) initgroups(username, pwd->pw_gid);
 
-    retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+    retcode = pam_open_session(pamh, 0);
     PAM_FAIL_CHECK;
 
-    retcode = pam_open_session(pamh, 0);
+    retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
     PAM_FAIL_CHECK;
 
 #else /* ! USE_PAM */

==============================================================================
patch for sh-utils 2.0 release 12mdk
==============================================================================

--- sh-utils-2.0/src/su.c~	Sat Jan 13 02:27:21 2001
+++ sh-utils-2.0/src/su.c	Mon Jan 22 01:36:40 2001
@@ -41,11 +41,8 @@
 #ifdef USE_PAM
 
    Actually, with PAM, su has nothing to do with whether or not a
-   wheel group is enforced by su.  RMS tries to restrict your access
-   to a su which implements the wheel group, but PAM considers that
-   to be fascist, and gives the user/sysadmin the opportunity to
-   enforce a wheel group by proper editing of /etc/pam.conf
-
+   wheel group is enforced by su.  You can use a pam_wheel module to
+   enforce a wheel group.  [Obnoxious sarcasm removed.]
 #endif
 
    Options:
@@ -307,21 +304,21 @@
 }
 #endif
 
-/* Ask the user for a password.
-   If PAM is in use, let PAM ask for the password if necessary.
-   Return 1 if the user gives the correct password for entry PW,
+/* Ask the user for a password for NEW_USER;
+   or if PAM is in use, let PAM ask for the password if necessary.
+   Return 1 if the user gives the correct password,
    0 if not.  Return 1 without asking for a password if run by UID 0
-   or if PW has an empty password.  */
+   or if the correct password is empty. */
 
 static int
-correct_password (const struct passwd *pw)
+correct_password (char *new_user)
 {
 #ifdef USE_PAM
   /* root always succeeds; this isn't an authentication question (no
    * extra privs are being granted) so it shouldn't authenticate with PAM.
    * However, we want to create the pam_handle so that proper credentials
    * are created later with pam_setcred(). */
-  retval = pam_start(PROGRAM_NAME, pw->pw_name, &conv, &pamh);
+  retval = pam_start(PROGRAM_NAME, new_user, &conv, &pamh);
   PAM_BAIL_P;
   if (getuid () == 0)
     return 1;
@@ -331,9 +328,9 @@
 	exit(1);
   }
 
-
   retval = pam_authenticate(pamh, 0);
   PAM_BAIL_P;
+
   retval = pam_acct_mgmt(pamh, 0);
   if (retval == PAM_NEW_AUTHTOK_REQD) {
     /* password has expired.  Offer option to change it. */
@@ -343,17 +340,23 @@
   PAM_BAIL_P;
   /* must be authenticated if this point was reached */
   return 1;
+
 #else /* !USE_PAM */
+
+  struct passwd *pw = getpwnam (new_user);
   char *unencrypted, *encrypted, *correct;
 #ifdef HAVE_SHADOW_H
   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
-  struct spwd *sp = getspnam (pw->pw_name);
+  struct spwd *sp = getspnam (new_user);
 
   endspent ();
   if (sp)
     correct = sp->sp_pwdp;
   else
 #endif
+  if (pw == 0)
+    error (1, 0, _("user %s does not exist"), new_user);
+  else
     correct = pw->pw_passwd;
 
   if (getuid () == 0 || correct == 0 || correct[0] == '\0')
@@ -478,15 +481,8 @@
   sigset_t ourset;
   int status;
 
-  retval = pam_open_session(pamh,0);
-  if (retval != PAM_SUCCESS) {
-    fprintf (stderr, "could not open session\n");
-    exit (1);
-  }
-
-/* do this at the last possible moment, because environment variables may
-   be passed even in the session phase
-*/
+  /* Do this at the last possible moment, because environment variables may
+     be passed in every phase. */
   if(pam_copyenv(pamh) != PAM_SUCCESS)
      fprintf (stderr, "error copying PAM environment\n");
 
@@ -495,6 +491,9 @@
   change_identity (pw);
   pam_end(pamh, 0);
 #endif
+
+  endpwent ();
+
   if (additional_args)
     args = (const char **) xmalloc (sizeof (char *)
 				    * (10 + elements (additional_args)));
@@ -570,7 +569,7 @@
     fprintf(stderr, "\nSession terminated, killing shell...");
     kill (child, SIGTERM);
   }
-  retval = pam_close_session(pamh, 0);
+  retval = pam_setcred(pamh, PAM_DELETE_CRED);
   PAM_BAIL_P;
   retval = pam_end(pamh, PAM_SUCCESS);
   PAM_BAIL_P;
@@ -636,12 +635,16 @@
 main (int argc, char **argv)
 {
   int optc;
-  const char *new_user = DEFAULT_USER;
+  char *new_user = DEFAULT_USER;
   char *command = 0;
   char **additional_args = 0;
   char *shell = 0;
   struct passwd *pw;
   struct passwd pw_copy;
+  int success;
+#ifdef USE_PAM
+  char *real_user;
+#endif
 
   program_name = argv[0];
   setlocale (LC_ALL, "");
@@ -699,27 +702,35 @@
   if (optind < argc)
     additional_args = argv + optind;
 
+  success = correct_password (new_user);
+
+#ifdef USE_PAM
+  /* Get the username that we are actually using. */
+  if (success
+      && pam_get_item(pamh, PAM_USER, (const void **) &real_user) == PAM_SUCCESS
+      && real_user && *real_user)
+  new_user = real_user;
+#endif
+
   pw = getpwnam (new_user);
   if (pw == 0)
     error (1, 0, _("user %s does not exist"), new_user);
-  endpwent ();
-
-  /* Make sure pw->pw_shell is non-NULL.  It may be NULL when NEW_USER
-     is a username that is retrieved via NIS (YP), but that doesn't have
-     a default shell listed.  */
-  if (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
-    pw->pw_shell = (char *) DEFAULT_SHELL;
 
   /* Make a copy of the password information and point pw at the local
-     copy instead.  Otherwise, some systems (e.g. Linux) would clobber
-     the static data through the getlogin call from log_su.  */
+     copy instead.  Otherwise, some calls (e.g. getlogin or PAM) could clobber
+     the static data. */
   pw_copy = *pw;
   pw = &pw_copy;
   pw->pw_name = xstrdup (pw->pw_name);
   pw->pw_dir = xstrdup (pw->pw_dir);
+  /* Make sure pw->pw_shell is non-NULL.  It may be NULL when NEW_USER
+     is a username that is retrieved via NIS (YP), but that doesn't have
+     a default shell listed.  */
+  if (pw->pw_shell == NULL || pw->pw_shell[0] == '\0')
+    pw->pw_shell = (char *) DEFAULT_SHELL;
   pw->pw_shell = xstrdup (pw->pw_shell);
 
-  if (!correct_password (pw))
+  if (!success)
     {
 #ifdef SYSLOG_FAILURE
       log_su (pw, 0);
@@ -745,11 +756,9 @@
       shell = 0;
     }
   if (shell == 0)
-    {
-      shell = xstrdup (pw->pw_shell);
-    }
-  modify_environment (pw, shell);
+    shell = pw->pw_shell;
 
+  modify_environment (pw, shell);
 
 #ifdef USE_PAM
   setfsuid(pw->pw_uid);

==============================================================================
patch for rsh-0.17-pre20000412 release 4mdk
==============================================================================

--- netkit-rsh-0.17-pre20000412/rshd/rshd.c~	Fri Jan 12 11:21:08 2001
+++ netkit-rsh-0.17-pre20000412/rshd/rshd.c	Mon Jan 22 02:55:48 2001
@@ -86,7 +86,6 @@
 
 #ifdef USE_PAM
 #include <security/pam_appl.h>
-#include <security/pam_misc.h>
 static pam_handle_t *pamh;
 #endif /* USE_PAM */
 
@@ -216,30 +215,39 @@
      *
      * No, it's not the child process, the code is just confusing.
      */
+    pam_setcred(pamh, PAM_DELETE_CRED);
     pam_close_session(pamh, 0);
     pam_end(pamh, PAM_SUCCESS);
 #endif
     exit(0);
 }
 
+#ifdef USE_PAM
+/* The rcmd/rsh protocol doesn't support requesting a password.
+   Use auth modules like pam_rhosts that don't use one.  */
+
+int null_conv(int num_msg, const struct pam_message **msg,
+              struct pam_response **resp, void *appdata_ptr)
+{
+    syslog(LOG_ERR, "PAM error: conversation is not supported\n");
+    return PAM_CONV_ERR;
+}
+#endif
 
 static struct passwd *doauth(const char *remuser, 
 			     const char *hostname, 
 			     const char *locuser)
 {
+    struct passwd *pwd;
 #ifdef USE_PAM
-    static struct pam_conv conv = { misc_conv, NULL };
+    static struct pam_conv conv = { null_conv, NULL };
     int retcode;
-#endif
-    struct passwd *pwd = getpwnam(locuser);
-    if (pwd == NULL) return NULL;
-    if (pwd->pw_uid==0) paranoid = 1;
+    char *real_user;
 
-#ifdef USE_PAM
     retcode = pam_start("rsh", locuser, &conv, &pamh);
     if (retcode != PAM_SUCCESS) {
 	syslog(LOG_ERR, "pam_start: %s\n", pam_strerror(pamh, retcode));
-	exit (1);
+	return NULL;
     }
     pam_set_item (pamh, PAM_RUSER, remuser);
     pam_set_item (pamh, PAM_RHOST, hostname);
@@ -247,12 +255,19 @@
     
     retcode = pam_authenticate(pamh, 0);
     if (retcode == PAM_SUCCESS) {
+	if (pam_get_item(pamh, PAM_USER, (const void **) &real_user)
+	    == PAM_SUCCESS && real_user && *real_user)
+	    locuser = real_user;
 	retcode = pam_acct_mgmt(pamh, 0);
     }
     if (retcode == PAM_SUCCESS) {
+	pwd = getpwnam(locuser);
+	if (pwd == NULL) return NULL;
+	if (pwd->pw_uid==0) paranoid = 1;
 	/*
-	 * Why do we need to set groups here?
-	 * Also, this stuff should be moved down near where the setuid() is.
+	 * Initialize the supplementary group list.
+	 * This should be done before pam_setcred because
+	 * the PAM modules might add groups during pam_setcred.
 	 */
 	if (setgid(pwd->pw_gid) != 0) {
 	    pam_end(pamh, PAM_SYSTEM_ERR);
@@ -262,24 +277,25 @@
 	    pam_end(pamh, PAM_SYSTEM_ERR);
 	    return NULL;
 	}
-	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+	retcode = pam_open_session(pamh,0);
     }
-    
     if (retcode == PAM_SUCCESS) {
-	retcode = pam_open_session(pamh,0);
+	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
     }
     if (retcode != PAM_SUCCESS) {
 	pam_end(pamh, retcode);
 	return NULL;
     }
-    return pwd;
 #else
+    pwd = getpwnam(locuser);
+    if (pwd == NULL) return NULL;
+    if (pwd->pw_uid==0) paranoid = 1;
     if (pwd->pw_uid==0 && !allow_root_rhosts) return NULL;
     if (ruserok(hostname, pwd->pw_uid==0, remuser, locuser) < 0) {
 	return NULL;
     }
-    return pwd;
 #endif
+    return pwd;
 }
 
 static const char *findhostname(struct sockaddr_in *fromp,
@@ -387,6 +403,10 @@
 		fail("Permission denied.\n", 
 		     remuser, hostname, locuser, cmdbuf);
 	}
+#ifdef USE_PAM
+	strncpy(locuser, pwd->pw_name, sizeof(locuser));
+	locuser[sizeof(locuser) - 1] = '\0';
+#endif
 
 	if (chdir(pwd->pw_dir) < 0) {
 		chdir("/");

==============================================================================
patch for pam 0.72 release 12mdk
==============================================================================

--- ./libpam/pam_handlers.c	2001/01/22 19:33:04	1.1
+++ ./libpam/pam_handlers.c	2001/01/22 20:02:53
@@ -495,6 +495,8 @@
 #endif
     char *mod_full_path=NULL;
     servicefn func, func2;
+    int actions2buf[_PAM_RETURN_VALUES];
+    int *actions2 = actions;
     int success;
 
     D(("called."));
@@ -649,6 +651,19 @@
 	_sym = "_pam_sm_authenticate";
 	_sym2 = "_pam_sm_setcred";
 #endif
+	actions2 = actions2buf;
+	/* Always run the pam_sm_setcred for all listed auth modules.
+	   Otherwise, we can end up not running the pam_sm_setcred
+	   for auth module(s) that authenticated successfully,
+	   e.g. if an earlier auth module is "sufficient" and
+	   its authenticate fails but its setcred succeeds.
+	   This is also apparently what Solaris PAM does.  */
+	{
+                int i;
+                for (i = 0; i < _PAM_RETURN_VALUES; i++)
+                     actions2[i] = _PAM_ACTION_IGNORE;
+		actions2[PAM_SUCCESS] = _PAM_ACTION_OK;
+	}
 	break;
     case PAM_T_SESS:
 	handler_p = &the_handlers->open_session;
@@ -774,7 +789,7 @@
 
 	(*handler_p2)->must_fail = must_fail;        /* failure forced? */
 	(*handler_p2)->func = func2;
-	memcpy((*handler_p2)->actions,actions,sizeof((*handler_p2)->actions));
+	memcpy((*handler_p2)->actions,actions2,sizeof((*handler_p2)->actions));
 	(*handler_p2)->argc = argc;
 	if (argv) {
 	    if (((*handler_p2)->argv = malloc(argvlen)) == NULL) {

--- ./modules/pam_stack/pam_stack.c~	Wed Jul 26 13:43:39 2000
+++ ./modules/pam_stack/pam_stack.c	Tue Jan 23 00:43:27 2001
@@ -263,7 +263,9 @@
 	for(i = 0; i < sizeof(defined_items) / sizeof(defined_items[0]); i++) {
 		const void *ignored;
 		pam_get_item(pamh, defined_items[i].num, &ignored);
-		if(ignored != NULL) {
+		/* What's the point of not overwriting item values?
+		   (Needed for PAM_USER to support template users.) */
+		if(ignored != NULL && defined_items[i].num != PAM_USER) {
 			if(debug) {
 				openlog("pam_stack", LOG_PID, LOG_AUTHPRIV);
 				syslog(LOG_DEBUG, "not passing %s back up to "

==============================================================================
patches for pam_krb5 1.23 release 3
(the files get patched multiple times;
 I put the various enhancements into separate patches)
==============================================================================

--- README.acct	Wed Oct 18 23:44:37 2000
+++ README	Wed Jan 17 19:53:34 2001
@@ -3,8 +3,9 @@
 token-grabbing.  The pam_krb5 module is always built, and when compiled
 on a system with libkrbafs installed, pam_krb5afs.so will also be built.
 
-It implements authentication, session management, and password-changing
-functions.  Sample configuration files for many services are included.
+It implements authentication, account management, session management,
+and password-changing functions.  Sample configuration files for many
+services are included.
 
 The pam_sm_authenticate() function checks the user name and password in
 the user's realm.  It takes the standard parameters required by the PAM
@@ -15,6 +16,12 @@
 the pam_sm_setcred() function, but the TGT is NOT stored on disk.  The
 new TGT is verified using a copy of the key for the local workstation's
 host service if it is found in the local keytab file.
+
+The pam_sm_acct_mgmt() function checks whether the user is authorized
+for access to the target user principal, using krb5_kuserok().  It
+uses the principal saved in memory by pam_sm_authenticate().  If
+the user did not use krb5 for authentication, this function simply
+succeeds, so you can "require" it in the PAM configuration file.
 
 The pam_sm_setcred() function creates a Kerberos 5 ticket file and, if
 libkrb524 was found at compile-time, can obtain and create a Kerberos 4
--- pam_krb5afs.c.acct	Wed Jan 17 19:53:34 2001
+++ pam_krb5afs.c	Wed Jan 17 20:21:07 2001
@@ -115,6 +115,7 @@
 #define MODULE_RET_NAME MODULE_NAME "_ret_stash"
 
 #define PAM_SM_AUTH
+#define SM_ACCOUNT
 #define PAM_SM_SESSION
 #define PAM_SM_PASSWORD
 
@@ -158,6 +159,10 @@
 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
 				   const char **argv);
 
+/* Account management. */
+PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
+				   const char **argv);
+
 /* Credential management. */
 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
 			      const char **argv);
@@ -278,6 +283,8 @@
 	j = i;
 	while((s[j] != '\0') && !isspace(s[j])) j++;
 	ret = malloc(j - i + 1);
+	if (ret == NULL)
+		return NULL;
 	memcpy(ret, &s[i], j - i);
 	ret[j - i] = '\0';
 	return ret;
@@ -310,9 +317,8 @@
 
 	/* Defaults: try everything (try_first_pass, use a PAG, no debug). */
 	ret = malloc(sizeof(struct config));
-	if(ret == NULL) {
+	if(ret == NULL)
 		return NULL;
-	}
 	config = ret;
 
 	memset(ret, 0, sizeof(struct config));
@@ -361,12 +367,16 @@
 	krb5_os_localaddr(context, &hostlist);
 	for(j = 0; hostlist[j] != NULL; j++) ;
 	addresses = malloc(sizeof(krb5_address) * (num_words(hosts) + 1 + j));
+	if (addresses == NULL)
+		return NULL;
 	memset(addresses, 0, sizeof(krb5_address) * (num_words(hosts) + 1 + j));
 	for(j = 0; hostlist[j] != NULL; j++) {
 		addresses[j] = hostlist[j];
 	}
 	for(i = 0; i < num_words(hosts); i++) {
 		foo = word_copy(nth_word(hosts, i));
+		if (foo == NULL)
+			return NULL;
 		krb5_os_hostaddr(context, foo, &hostlist);
 		DEBUG("also getting ticket for host %s", foo);
 		addresses[i + j] = hostlist[0];
@@ -399,9 +409,13 @@
 	profile_get_string(profile, PROFILE_NAME, "afs_cells", NULL,
 			   DEFAULT_CELLS, &cells);
 	ret->cell_list = malloc(sizeof(char*) * (num_words(cells) + 1));
+	if (ret->cell_list == NULL)
+		return NULL;
 	memset(ret->cell_list, 0, sizeof(char*) * (num_words(cells) + 1));
 	for(i = 0; i < num_words(cells); i++) {
 		ret->cell_list[i] = word_copy(nth_word(cells, i));
+		if (ret->cell_list[i] == NULL)
+			return NULL;
 		DEBUG("will afslog to cell %s", ret->cell_list[i]);
 		if(ret->krb4_convert != TRUE) {
 			ret->krb4_convert = TRUE;
@@ -507,6 +521,8 @@
 			}
 			if(responses && responses[0].resp) {
 				*out = strdup(responses[0].resp);
+				if (*out == NULL)
+					return PAM_SYSTEM_ERR;
 			}
 		} else {
 			INFO("%s in conversation function getting "
@@ -523,10 +539,13 @@
 {
 	int i = 0, ret = PAM_SUCCESS;
 	const char *p = NULL;
+	char *pp;
 	for(i = 0; i < num_prompts; i++) {
 		char *q = NULL;
 		int l = strlen(prompts[i].prompt) + strlen(": ") + 1;
 		q = malloc(l);
+		if (q == NULL)
+			return PAM_SYSTEM_ERR;
 		snprintf(q, l, "%s: ", prompts[i].prompt);
 		ret = pam_prompt_for(data,
 				     prompts[i].hidden ?
@@ -536,8 +555,14 @@
 		if(ret == PAM_SUCCESS) {
 			prompts[i].reply->length = strlen(p);
 			prompts[i].reply->data = strdup(p);
-			if(prompts[i].hidden)
-				pam_set_item(data, PAM_AUTHTOK, strdup(p));
+			if (prompts[i].reply->data == NULL)
+				return PAM_SYSTEM_ERR;
+			if(prompts[i].hidden) {
+				pp = strdup(p);
+				if (pp == NULL)
+					return PAM_SYSTEM_ERR;
+				pam_set_item(data, PAM_AUTHTOK, pp);
+			}
 		} else {
 			ret = KRB5_LIBOS_CANTREADPWD;
 			break;
@@ -672,6 +697,8 @@
 	krb5_principal principal;
 	struct config *config;
 	const char *user = NULL;
+	uid_t source_uid;
+	char *source_princ = NULL;
 	const char *password = NULL;
 	char *realm;
 	int ret = KRB5_SUCCESS, *pret = NULL;
@@ -754,7 +781,22 @@
 
 	/* Build the user's principal. */
 	if(ret == KRB5_SUCCESS) {
-		ret = krb5_parse_name(context, user, &principal);
+		/* This case is for use mainly by su.
+		   If non-root is authenticating as "root", use "source_user/root".  */
+		if (!strcmp(user, "root") && (source_uid = getuid()) != 0) {
+			pwd = getpwuid(source_uid);
+			if (pwd != NULL)
+				source_princ = (char *)malloc(strlen(pwd->pw_name) + 6);
+			if (source_princ)
+				sprintf(source_princ, "%s/root", pwd->pw_name);
+		} else {
+			source_princ = strdup(user);
+		}
+
+		if (source_princ)
+			ret = krb5_parse_name(context, source_princ, &principal);
+		else
+			ret = PAM_SYSTEM_ERR;
 		if(ret != KRB5_SUCCESS) {
 			CRIT("%s building user principal for %s",
 			     error_message(ret), user);
@@ -774,7 +816,7 @@
 	if(ret == KRB5_SUCCESS) {
 		int done = 0;
 
-		DEBUG("attempting to authenticate %s", user);
+		DEBUG("attempting to authenticate %s", source_princ);
 		/* Set up the creds structure. */
 		memset(&stash->v5_creds, 0, sizeof(stash->v5_creds));
 		/* Who we're representing. */
@@ -805,12 +847,19 @@
 
 		/* Try to converse if the password failed. */
 		if(config->try_second_pass && !done) {
+			char *p;
 			password = NULL;
 			ret = pam_prompt_for(pamh, PAM_PROMPT_ECHO_OFF,
 					     "Password: ", &password);
+			if (ret != PAM_SUCCESS)
+				goto logauth;
 			if(password) {
-				pam_set_item(pamh, PAM_AUTHTOK,
-					     strdup(password));
+				p = strdup(password);
+				if (p == NULL) {
+					ret = PAM_SYSTEM_ERR;
+					goto logauth;
+				}
+				pam_set_item(pamh, PAM_AUTHTOK, p);
 			}
 			ret = krb5_get_init_creds_password(context,
 							   &stash->v5_creds,
@@ -838,12 +887,13 @@
 	/* Verify that the TGT is good (i.e., that the reply wasn't spoofed). */
 	if(ret == KRB5_SUCCESS) {
 		if(config->validate) {
-			if(verify_tgt(user, context, config, stash) == 0) {
+			if(verify_tgt(source_princ, context, config, stash) == 0) {
 				ret = PAM_AUTH_ERR;
 			}
 		}
 	}
 
+ logauth:
 	/* Log something. */
 	if(ret == KRB5_SUCCESS) {
 		INFO("authentication succeeds for %s", user);
@@ -1015,6 +1065,7 @@
 	/* Catch any Kerberos error codes that fall through cracks and
 	   convert them to appropriate PAM error codes. */
 	switch(ret) {
+		case PAM_SYSTEM_ERR:
 		case KRB5_SUCCESS:
 		case KRB5KDC_ERR_NONE: {
 			break;
@@ -1051,6 +1102,62 @@
 	DEBUG("pam_sm_authenticate returning %d (%s)", ret,
 	      ret ? pam_strerror(pamh, ret) : "Success");
 
+	if (source_princ)
+		free(source_princ);
+	return ret;
+}
+
+/******************************************************************************/
+
+/* Check whether user is authorized to become the target principal. */
+int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
+		     const char **argv)
+{
+	struct config *config;
+	krb5_context context;
+	struct stash *stash;
+	char *user = NULL;
+	int ret = KRB5_SUCCESS;
+
+	/* First parse the arguments; if there are problems, bail. */
+	ret = krb5_init_context(&context);
+	if(ret == KRB5_SUCCESS) {
+		if((config = get_config(context, argc, argv))) {
+			ret = PAM_SUCCESS;
+		} else {
+			ret = PAM_BUF_ERR;
+			krb5_free_context(context);
+		}
+	} else {
+		ret = PAM_SYSTEM_ERR;
+	}
+
+	if(ret == PAM_SUCCESS) {
+		DEBUG("pam_sm_acct_mgmt() called");
+		ret = pam_get_user(pamh, &user, "login:");
+		if(ret != PAM_SUCCESS) {
+			CRIT("cannot determine user's login");
+			ret = PAM_USER_UNKNOWN;
+			krb5_free_context(context);
+		}
+	}
+	if(ret == PAM_SUCCESS) {
+		DEBUG("user is \"%s\"", user);
+		ret = pam_get_data(pamh, MODULE_STASH_NAME, (void*)&stash);
+		if(ret == PAM_SUCCESS) {
+			DEBUG("credentials retrieved");
+			if (!krb5_kuserok(context, stash->v5_creds.client, user))
+				ret = PAM_PERM_DENIED;
+		} else {
+			/* The user didn't authenticate using krb5. */
+			ret = PAM_SUCCESS;
+		}
+		krb5_free_context(context);
+	}
+
+	DEBUG("pam_sm_acct_mgmt returning %d (%s)", ret,
+	      ret ? pam_strerror(pamh, ret) : "Success");
+
 	return ret;
 }
 
@@ -1624,6 +1731,7 @@
 int main(int argc, char **argv)
 {
 	pam_sm_authenticate(NULL, 0, 0, NULL);
+	pam_sm_acct_mgmt(NULL, 0, 0, NULL);
 	pam_sm_setcred(NULL, 0, 0, NULL);
 	pam_sm_open_session(NULL, 0, 0, NULL);
 	pam_sm_chauthtok(NULL, 0, 0, NULL);

--- pam_krb5afs.c~	Wed Jan 17 20:25:52 2001
+++ pam_krb5afs.c	Fri Jan 19 00:39:24 2001
@@ -1228,6 +1228,11 @@
 			}
 			strncpy(stash->v5_path,v5_path, sizeof(stash->v5_path));
 			close(tmpfd);
+		} else {
+			DEBUG("Kerberos 5 credential retrieval failed for "
+			      "%s, user is probably local", user);
+			ret = PAM_SUCCESS; /* Applications consider anything else an error. */
+			goto cleanup;
 		}
 		if(ret == PAM_SUCCESS) {
 			/* Create the v5 ticket cache. */
@@ -1267,11 +1272,6 @@
 				}
 			}
 			DEBUG("%s", v5_path);
-		} else {
-			DEBUG("Kerberos 5 credential retrieval failed for "
-			      "%s, user is probably local", user);
-			stash = NULL;
-			ret = PAM_CRED_UNAVAIL;
 		}
 
 #ifdef HAVE_LIBKRB524
@@ -1411,16 +1411,23 @@
 
 	if((flags & PAM_DELETE_CRED) && (ret == KRB5_SUCCESS)) {
 		ret = pam_get_data(pamh,MODULE_STASH_NAME,(const void**)&stash);
-		if((ret == PAM_SUCCESS) && (strlen(stash->v5_path) > 0)) {
+		if(ret == PAM_SUCCESS) {
 			DEBUG("credentials retrieved");
-			/* Delete the v5 ticket cache. */
-			DEBUG("removing %s", stash->v5_path);
-			if(remove(stash->v5_path) == -1) {
-				CRIT("error removing file %s: %s",
-				     stash->v5_path, strerror(errno));
-			} else {
-				strcpy(stash->v5_path, "");
+			if (strlen(stash->v5_path) > 0) {
+				/* Delete the v5 ticket cache. */
+				DEBUG("removing %s", stash->v5_path);
+				if(remove(stash->v5_path) == -1) {
+					CRIT("error removing file %s: %s",
+					     stash->v5_path, strerror(errno));
+				} else {
+					strcpy(stash->v5_path, "");
+				}
 			}
+		} else {
+			DEBUG("Kerberos 5 credential retrieval failed for "
+			      "%s, user is probably local", user);
+			ret = PAM_SUCCESS; /* Applications consider anything else an error. */
+			goto cleanup;
 		}
 #ifdef HAVE_LIBKRB4
 		if((ret == PAM_SUCCESS) && (strlen(stash->v4_path) > 0)) {
@@ -1443,15 +1450,21 @@
 #endif
 	}
 
+ cleanup:
 	/* Done with Kerberos. */
 	krb5_free_context(context);
 
+#if 0 /* Tying these two calls together seems bogus.
+	 If authenticate succeeds but setcred fails,
+	 why would we pretend that setcred succeeded too?  */
+
 	pam_get_data(pamh, MODULE_RET_NAME, (const void**) &pret);
 	if(pret) {
 		DEBUG("recovered return code %d from prior call to "
 		      "pam_sm_authenticate()", *pret);
 		ret = *pret;
 	}
+#endif
 
 	DEBUG("pam_sm_setcred returning %d (%s)", ret,
 	      ret ? pam_strerror(pamh, ret) : "Success");

--- pam_krb5afs.c~	Sun Jan 21 02:20:49 2001
+++ pam_krb5afs.c	Sun Jan 21 02:21:42 2001
@@ -1488,7 +1488,7 @@
 	DEBUG("pam_sm_open_session() called");
 	if(context) krb5_free_context(context);
 
-	return pam_sm_setcred(pamh, flags | PAM_ESTABLISH_CRED, argc, argv);
+	return PAM_SUCCESS;
 }
 
 int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
@@ -1505,7 +1505,7 @@
 	DEBUG("pam_sm_close_session() called");
 	if(context) krb5_free_context(context);
 
-	return pam_sm_setcred(pamh, flags | PAM_DELETE_CRED, argc, argv);
+	return PAM_SUCCESS;
 }
 
 int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
--- README~	Sun Jan 21 02:20:49 2001
+++ README	Sun Jan 21 02:23:00 2001
@@ -30,9 +30,7 @@
 and get tokens for AFS cells specified in the configuration file.
 
 The session management functions (pam_sm_open_session() and
-pam_sm_close_session()) merely wrap calls to pam_setcred with the
-PAM_ESTABLISH_CREDS and PAM_DELETE_CREDS flags, respectively, which is
-handy because on my test box some things just don't work right.
+pam_sm_close_session()) simply return success.
 
 Because session-specific ticket files require that the KRBTKFILE and
 KRB5CCNAME environment variables are set correctly, certain programs that

--- pam_krb5afs.c.localname	Mon Jan 22 22:50:09 2001
+++ pam_krb5afs.c	Mon Jan 22 23:43:56 2001
@@ -67,6 +67,11 @@
 #include <sys/syslog.h>
 #endif
 
+#include <utmp.h>
+#ifndef UT_NAMESIZE
+#define UT_NAMESIZE 16
+#endif
+
 /******************************************************************************/
 
 #ifdef HAVE_COM_ERR_H
@@ -699,6 +704,7 @@
 	const char *user = NULL;
 	uid_t source_uid;
 	char *source_princ = NULL;
+	char localname[UT_NAMESIZE];
 	const char *password = NULL;
 	char *realm;
 	int ret = KRB5_SUCCESS, *pret = NULL;
@@ -761,24 +767,6 @@
 	}
 	DEBUG("user is \"%s\"", user);
 
-	/* Try to get and save the user's UID. */
-	if(config->no_user_check) {
-		stash->uid = getuid();
-		stash->gid = getgid();
-		DEBUG("using current uid %d, gid %d", stash->uid, stash->gid);
-	} else {
-		pwd = getpwnam(user);
-		if(pwd != NULL) {
-			stash->uid = pwd->pw_uid;
-			stash->gid = pwd->pw_gid;
-			DEBUG("%s has uid %d, gid %d", user,
-			      stash->uid, stash->gid);
-		} else {
-			CRIT("getpwnam(\"%s\") failed", user);
-			ret = PAM_USER_UNKNOWN;
-		}
-	}
-
 	/* Build the user's principal. */
 	if(ret == KRB5_SUCCESS) {
 		/* This case is for use mainly by su.
@@ -801,6 +789,45 @@
 			CRIT("%s building user principal for %s",
 			     error_message(ret), user);
 			ret = PAM_SYSTEM_ERR;
+		}
+	}
+
+	if (ret == KRB5_SUCCESS) {
+		/* Get the local user name for this principal. */
+		DEBUG("running krb5_aname_to_localname");
+		ret = krb5_aname_to_localname(context, principal,
+					      sizeof(localname), localname);
+		if (ret == KRB5_SUCCESS) {
+			DEBUG("changing user to \"%s\"", localname);
+			ret = pam_set_item(pamh, PAM_USER, (const void *)localname);
+			if (ret != PAM_SUCCESS) {
+				CRIT("cannot set user: %s", pam_strerror(pamh, ret));
+				ret = PAM_SERVICE_ERR;
+			} else {
+				ret = pam_get_item(pamh, PAM_USER, (const void **) &user);
+				if (ret != PAM_SUCCESS) {
+					CRIT("cannot get back user: %s", pam_strerror(pamh, ret));
+					ret = PAM_SERVICE_ERR;
+				}
+			}
+		}
+	}
+
+	/* Try to get and save the user's UID. */
+	if(config->no_user_check) {
+		stash->uid = getuid();
+		stash->gid = getgid();
+		DEBUG("using current uid %d, gid %d", stash->uid, stash->gid);
+	} else {
+		pwd = getpwnam(user);
+		if(pwd != NULL) {
+			stash->uid = pwd->pw_uid;
+			stash->gid = pwd->pw_gid;
+			DEBUG("%s has uid %d, gid %d", user,
+			      stash->uid, stash->gid);
+		} else {
+			CRIT("getpwnam(\"%s\") failed", user);
+			ret = PAM_USER_UNKNOWN;
 		}
 	}
 

==============================================================================





[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index] []