From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / driver / passwd-pam.c
index 448cd9cbf8da80d7a06fcdaf1fe3005d8b18e83b..d463bc2de068da1e0129e4f012ed53ce2b08ba60 100644 (file)
@@ -1,7 +1,7 @@
 /* passwd-pam.c --- verifying typed passwords with PAM
  * (Pluggable Authentication Modules.)
  * written by Bill Nottingham <notting@redhat.com> (and jwz) for
- * xscreensaver, Copyright (c) 1993-2003 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
@@ -77,9 +77,12 @@ extern void unblock_sigchld (void);
 /* Some time between Red Hat 4.2 and 7.0, the words were transposed 
    in the various PAM_x_CRED macro names.  Yay!
  */
-#ifndef  PAM_REFRESH_CRED
+#if !defined(PAM_REFRESH_CRED) && defined(PAM_CRED_REFRESH)
 # define PAM_REFRESH_CRED PAM_CRED_REFRESH
 #endif
+#if !defined(PAM_REINITIALIZE_CRED) && defined(PAM_CRED_REINITIALIZE)
+# define PAM_REINITIALIZE_CRED PAM_CRED_REINITIALIZE
+#endif
 
 static int pam_conversation (int nmsgs,
                              const struct pam_message **msg,
@@ -181,8 +184,10 @@ pam_try_unlock(saver_info *si, Bool verbose_p,
   pam_handle_t *pamh = 0;
   int status = -1;
   struct pam_conv pc;
+# ifdef HAVE_SIGTIMEDWAIT
   sigset_t set;
   struct timespec timeout;
+# endif /* HAVE_SIGTIMEDWAIT */
 
   pc.conv = &pam_conversation;
   pc.appdata_ptr = (void *) si;
@@ -237,11 +242,20 @@ pam_try_unlock(saver_info *si, Bool verbose_p,
 
   PAM_NO_DELAY(pamh);
 
+  if (verbose_p)
+    fprintf (stderr, "%s:   pam_authenticate (...) ...\n", blurb());
+
+# ifdef HAVE_SIGTIMEDWAIT
   timeout.tv_sec = 0;
   timeout.tv_nsec = 1;
-  set = block_sigchld();
+  set =
+# endif /* HAVE_SIGTIMEDWAIT */
+    block_sigchld();
   status = pam_authenticate (pamh, 0);
+# ifdef HAVE_SIGTIMEDWAIT
   sigtimedwait (&set, NULL, &timeout);
+  /* #### What is the portable thing to do if we don't have it? */
+# endif /* HAVE_SIGTIMEDWAIT */
   unblock_sigchld();
 
   if (verbose_p)
@@ -252,9 +266,22 @@ pam_try_unlock(saver_info *si, Bool verbose_p,
     {
       int status2;
 
-      /* We don't actually care if the account modules fail or succeed,
-       * but we need to run them anyway because certain pam modules
-       * depend on side effects of the account modules getting run.
+      /* On most systems, it doesn't matter whether the account modules
+         are run, or whether they fail or succeed.
+
+         On some systems, the account modules fail, because they were
+         never configured properly, but it's necessary to run them anyway
+         because certain PAM modules depend on side effects of the account
+         modules having been run.
+
+         And on still other systems, the account modules are actually
+         used, and failures in them should be considered to be true!
+
+         So:
+         - We run the account modules on all systems.
+         - Whether we ignore them is a configure option.
+
+         It's all kind of a mess.
        */
       status2 = pam_acct_mgmt (pamh, 0);
 
@@ -276,16 +303,28 @@ pam_try_unlock(saver_info *si, Bool verbose_p,
                      blurb(), status2, PAM_STRERROR(pamh, status2));
         }
 
+      /* If 'configure' requested that we believe the results of PAM
+         account module failures, then obey that status code.
+         Otherwise ignore it.
+       */
+#ifdef PAM_CHECK_ACCOUNT_TYPE
+       status = status2;
+#endif
+
       /* Each time we successfully authenticate, refresh credentials,
          for Kerberos/AFS/DCE/etc.  If this fails, just ignore that
          failure and blunder along; it shouldn't matter.
-
-         Note: this used to be PAM_REFRESH_CRED instead of
-         PAM_REINITIALIZE_CRED, but Jason Heiss <jheiss@ee.washington.edu>
-         says that the Linux PAM library ignores that one, and only refreshes
-         credentials when using PAM_REINITIALIZE_CRED.
        */
+
+#if defined(__linux__)
+      /* Apparently the Linux PAM library ignores PAM_REFRESH_CRED and only
+         refreshes credentials when using PAM_REINITIALIZE_CRED. */
       status2 = pam_setcred (pamh, PAM_REINITIALIZE_CRED);
+#else
+      /* But Solaris requires PAM_REFRESH_CRED or extra prompts appear. */
+      status2 = pam_setcred (pamh, PAM_REFRESH_CRED);
+#endif
+
       if (verbose_p)
         fprintf (stderr, "%s:   pam_setcred (...) ==> %d (%s)\n",
                  blurb(), status2, PAM_STRERROR(pamh, status2));
@@ -302,7 +341,13 @@ pam_try_unlock(saver_info *si, Bool verbose_p,
                  (status2 == PAM_SUCCESS ? "Success" : "Failure"));
     }
 
-  si->unlock_state = (status == PAM_SUCCESS) ? ul_success : ul_fail;
+  if (status == PAM_SUCCESS)
+    si->unlock_state = ul_success;          /* yay */
+  else if (si->unlock_state == ul_cancel ||
+           si->unlock_state == ul_time)
+    ;                                       /* more specific failures ok */
+  else
+    si->unlock_state = ul_fail;                     /* generic failure */
 }
 
 
@@ -382,10 +427,13 @@ pam_conversation (int nmsgs,
   struct auth_response *authresp = 0;
   struct pam_response *pam_responses;
   saver_info *si = (saver_info *) vsaver_info;
+  Bool verbose_p;
 
   /* On SunOS 5.6, the `closure' argument always comes in as random garbage. */
   si = (saver_info *) suns_pam_implementation_blows;
 
+  verbose_p = si->prefs.verbose_p;
+
   /* Converting the PAM prompts into the XScreenSaver native format.
    * It was a design goal to collapse (INFO,PROMPT) pairs from PAM
    * into a single call to the unlock_cb function. The unlock_cb function
@@ -401,25 +449,50 @@ pam_conversation (int nmsgs,
   if (!pam_responses || !messages)
     goto end;
 
+  if (verbose_p)
+    fprintf (stderr, "%s:     pam_conversation (", blurb());
+
   for (i = 0; i < nmsgs; ++i)
     {
+      if (verbose_p && i > 0) fprintf (stderr, ", ");
+
       messages[i].msg = msg[i]->msg;
 
-      /* Default fallback of PROMPT_ECHO */
-      messages[i].type = 
-       msg[i]->msg_style == PAM_PROMPT_ECHO_OFF
-       ? AUTH_MSGTYPE_PROMPT_NOECHO
-       : msg[i]->msg_style == PAM_PROMPT_ECHO_ON
-         ? AUTH_MSGTYPE_PROMPT_ECHO
-         : msg[i]->msg_style == PAM_ERROR_MSG
-           ? AUTH_MSGTYPE_ERROR
-           : msg[i]->msg_style == PAM_TEXT_INFO
-             ? AUTH_MSGTYPE_INFO
-             : AUTH_MSGTYPE_PROMPT_ECHO;
+      switch (msg[i]->msg_style) {
+      case PAM_PROMPT_ECHO_OFF: messages[i].type = AUTH_MSGTYPE_PROMPT_NOECHO;
+        if (verbose_p) fprintf (stderr, "ECHO_OFF");
+        break;
+      case PAM_PROMPT_ECHO_ON:  messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO;
+        if (verbose_p) fprintf (stderr, "ECHO_ON");
+        break;
+      case PAM_ERROR_MSG:       messages[i].type = AUTH_MSGTYPE_ERROR;
+        if (verbose_p) fprintf (stderr, "ERROR_MSG");
+        break;
+      case PAM_TEXT_INFO:       messages[i].type = AUTH_MSGTYPE_INFO;
+        if (verbose_p) fprintf (stderr, "TEXT_INFO");
+        break;
+      default:                  messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO;
+        if (verbose_p) fprintf (stderr, "PROMPT_ECHO");
+        break;
+      }
+
+      if (verbose_p) 
+        fprintf (stderr, "=\"%s\"", msg[i]->msg ? msg[i]->msg : "(null)");
     }
 
+  if (verbose_p)
+    fprintf (stderr, ") ...\n");
+
   ret = si->unlock_cb(nmsgs, messages, &authresp, si);
 
+  /* #### If the user times out, or hits ESC or Cancel, we return PAM_CONV_ERR,
+          and PAM logs this as an authentication failure.  It would be nice if
+          there was some way to indicate that this was a "cancel" rather than
+          a "fail", so that it wouldn't show up in syslog, but I think the
+          only options are PAM_SUCCESS and PAM_CONV_ERR.  (I think that
+          PAM_ABORT means "internal error", not "cancel".)  Bleh.
+   */
+
   if (ret == 0)
     {
       for (i = 0; i < nmsgs; ++i)
@@ -433,6 +506,10 @@ end:
   if (authresp)
     free(authresp);
 
+  if (verbose_p)
+    fprintf (stderr, "%s:     pam_conversation (...) ==> %s\n", blurb(),
+             (ret == 0 ? "PAM_SUCCESS" : "PAM_CONV_ERR"));
+
   if (ret == 0)
     {
       *resp = pam_responses;