/* 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-2001 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-2008 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
* http://www.us.kernel.org/pub/linux/libs/pam/FAQ
*
* PAM Application Developers' Guide:
- * http://www.us.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_appl.html
+ * http://www.us.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_ADG.html
*
* PAM Mailing list archives:
* http://www.linuxhq.com/lnxlists/linux-pam/
#include <pwd.h>
#include <grp.h>
#include <security/pam_appl.h>
+#include <signal.h>
+#include <errno.h>
+#include <X11/Intrinsic.h>
#include <sys/stat.h>
-extern void block_sigchld (void);
+#include "auth.h"
+
+extern sigset_t block_sigchld (void);
extern void unblock_sigchld (void);
/* blargh */
#undef countof
#define countof(x) (sizeof((x))/sizeof(*(x)))
+/* 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
+# define PAM_REFRESH_CRED PAM_CRED_REFRESH
+#endif
+
static int pam_conversation (int nmsgs,
const struct pam_message **msg,
struct pam_response **resp,
void *closure);
-struct pam_closure {
- const char *user;
- const char *typed_passwd;
- Bool verbose_p;
-};
+void pam_try_unlock(saver_info *si, Bool verbose_p,
+ Bool (*valid_p)(const char *typed_passwd, Bool verbose_p));
+Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
#ifdef HAVE_PAM_FAIL_DELAY
/* We handle delays ourself.*/
static void *suns_pam_implementation_blows = 0;
-/* This can be called at any time, and says whether the typed password
- belongs to either the logged in user (real uid, not effective); or
- to root.
+/**
+ * This function is the PAM conversation driver. It conducts a full
+ * authentication round by invoking the GUI with various prompts.
*/
-Bool
-pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
+void
+pam_try_unlock(saver_info *si, Bool verbose_p,
+ Bool (*valid_p)(const char *typed_passwd, Bool verbose_p))
{
const char *service = PAM_SERVICE_NAME;
pam_handle_t *pamh = 0;
int status = -1;
struct pam_conv pc;
- struct pam_closure c;
- char *user = 0;
-
- struct passwd *p = getpwuid (getuid ());
- if (!p) return False;
-
- user = strdup (p->pw_name);
-
- c.user = user;
- c.typed_passwd = typed_passwd;
- c.verbose_p = verbose_p;
+ sigset_t set;
+ struct timespec timeout;
pc.conv = &pam_conversation;
- pc.appdata_ptr = (void *) &c;
+ pc.appdata_ptr = (void *) si;
/* On SunOS 5.6, the `appdata_ptr' slot seems to be ignored, and the
`closure' argument to pc.conv always comes in as random garbage. */
- suns_pam_implementation_blows = (void *) &c;
+ suns_pam_implementation_blows = (void *) si;
/* Initialize PAM.
*/
- status = pam_start (service, c.user, &pc, &pamh);
+ status = pam_start (service, si->user, &pc, &pamh);
if (verbose_p)
fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
- blurb(), service, c.user,
+ blurb(), service, si->user,
status, PAM_STRERROR (pamh, status));
if (status != PAM_SUCCESS) goto DONE;
far as PAM is concerned...)
*/
{
- const char *tty = ":0.0";
- status = pam_set_item (pamh, PAM_TTY, strdup(tty));
+ char *tty = strdup (":0.0");
+ status = pam_set_item (pamh, PAM_TTY, tty);
if (verbose_p)
fprintf (stderr, "%s: pam_set_item (p, PAM_TTY, \"%s\") ==> %d (%s)\n",
blurb(), tty, status, PAM_STRERROR(pamh, status));
+ free (tty);
}
/* Try to authenticate as the current user.
PAM_NO_DELAY(pamh);
- block_sigchld();
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_authenticate (...) ...\n", blurb());
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ set = 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)
fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n",
blurb(), status, PAM_STRERROR(pamh, status));
+
if (status == PAM_SUCCESS) /* Win! */
{
+ 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.
+ */
+ status2 = pam_acct_mgmt (pamh, 0);
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_acct_mgmt (...) ==> %d (%s)\n",
+ blurb(), status2, PAM_STRERROR(pamh, status2));
+
+ /* HPUX for some reason likes to make PAM defines different from
+ * everyone else's. */
+#ifdef PAM_AUTHTOKEN_REQD
+ if (status2 == PAM_AUTHTOKEN_REQD)
+#else
+ if (status2 == PAM_NEW_AUTHTOK_REQD)
+#endif
+ {
+ status2 = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_chauthtok (...) ==> %d (%s)\n",
+ blurb(), status2, PAM_STRERROR(pamh, status2));
+ }
+
/* 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.
*/
- int status2 = pam_setcred (pamh, PAM_REFRESH_CRED);
+ status2 = pam_setcred (pamh, PAM_REINITIALIZE_CRED);
if (verbose_p)
fprintf (stderr, "%s: pam_setcred (...) ==> %d (%s)\n",
blurb(), status2, PAM_STRERROR(pamh, status2));
- goto DONE;
}
- /* If that didn't work, set the user to root, and try to authenticate again.
- */
- c.user = "root";
- status = pam_set_item (pamh, PAM_USER, strdup(c.user));
- if (verbose_p)
- fprintf (stderr, "%s: pam_set_item(p, PAM_USER, \"%s\") ==> %d (%s)\n",
- blurb(), c.user, status, PAM_STRERROR(pamh, status));
- if (status != PAM_SUCCESS) goto DONE;
-
- PAM_NO_DELAY(pamh);
- status = pam_authenticate (pamh, 0);
- if (verbose_p)
- fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n",
- blurb(), status, PAM_STRERROR(pamh, status));
-
DONE:
- if (user) free (user);
if (pamh)
{
int status2 = pam_end (pamh, status);
blurb(), status2,
(status2 == PAM_SUCCESS ? "Success" : "Failure"));
}
- return (status == PAM_SUCCESS ? True : False);
+
+ 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 */
}
const char file2[] = "/etc/pam.conf";
struct stat st;
- if (stat (dir, &st) == 0 && st.st_mode & S_IFDIR)
+# ifndef S_ISDIR
+# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+# endif
+
+ if (stat (dir, &st) == 0 && S_ISDIR(st.st_mode))
{
if (stat (file, &st) != 0)
fprintf (stderr,
}
-/* This is the function PAM calls to have a conversation with the user.
- Really, this function should be the thing that pops up dialog boxes
- as needed, and prompts for various strings.
-
- But, for now, xscreensaver uses its normal password-prompting dialog
- first, and then this function simply returns the result that has been
- typed.
-
- This means that if PAM was using a retina scanner for auth, xscreensaver
- would prompt for a password; then pam_conversation() would be called
- with a string like "Please look into the retina scanner". The user
- would never see this string, and the prompted-for password would be
- ignored.
- */
static int
pam_conversation (int nmsgs,
- const struct pam_message **msg,
- struct pam_response **resp,
- void *closure)
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *vsaver_info)
{
- int replies = 0;
- struct pam_response *reply = 0;
- struct pam_closure *c = (struct pam_closure *) closure;
+ int i, ret = -1;
+ struct auth_message *messages = 0;
+ 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. */
- c = (struct pam_closure *) suns_pam_implementation_blows;
+ 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
+ * does that, but only if it is passed several prompts at a time. Most PAM
+ * modules only send a single prompt at a time, but because there is no way
+ * of telling whether there will be more prompts to follow, we can only ever
+ * pass along whatever was passed in here.
+ */
+
+ messages = calloc(nmsgs, sizeof(struct auth_message));
+ pam_responses = calloc(nmsgs, sizeof(*pam_responses));
+
+ if (!pam_responses || !messages)
+ goto end;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: pam_conversation (", blurb());
- reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply));
- if (!reply) return PAM_CONV_ERR;
-
- for (replies = 0; replies < nmsgs; replies++)
+ for (i = 0; i < nmsgs; ++i)
{
- switch (msg[replies]->msg_style)
- {
- case PAM_PROMPT_ECHO_ON:
- reply[replies].resp_retcode = PAM_SUCCESS;
- reply[replies].resp = strdup (c->user); /* freed by PAM */
- if (c->verbose_p)
- fprintf (stderr, "%s: PAM ECHO_ON(\"%s\") ==> \"%s\"\n",
- blurb(), msg[replies]->msg,
- reply[replies].resp);
- break;
- case PAM_PROMPT_ECHO_OFF:
- reply[replies].resp_retcode = PAM_SUCCESS;
- reply[replies].resp = strdup (c->typed_passwd); /* freed by PAM */
- if (c->verbose_p)
- fprintf (stderr, "%s: PAM ECHO_OFF(\"%s\") ==> password\n",
- blurb(), msg[replies]->msg);
- break;
- case PAM_TEXT_INFO:
- /* ignore it... */
- reply[replies].resp_retcode = PAM_SUCCESS;
- reply[replies].resp = 0;
- if (c->verbose_p)
- fprintf (stderr, "%s: PAM TEXT_INFO(\"%s\") ==> ignored\n",
- blurb(), msg[replies]->msg);
- break;
- case PAM_ERROR_MSG:
- /* ignore it... */
- reply[replies].resp_retcode = PAM_SUCCESS;
- reply[replies].resp = 0;
- if (c->verbose_p)
- fprintf (stderr, "%s: PAM ERROR_MSG(\"%s\") ==> ignored\n",
- blurb(), msg[replies]->msg);
- break;
- default:
- /* Must be an error of some sort... */
- free (reply);
- if (c->verbose_p)
- fprintf (stderr, "%s: PAM unknown %d(\"%s\") ==> ignored\n",
- blurb(), msg[replies]->msg_style, msg[replies]->msg);
- return PAM_CONV_ERR;
- }
+ if (verbose_p && i > 0) fprintf (stderr, ", ");
+
+ messages[i].msg = msg[i]->msg;
+
+ 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)");
}
- *resp = reply;
- return PAM_SUCCESS;
+
+ 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)
+ pam_responses[i].resp = authresp[i].response;
+ }
+
+end:
+ if (messages)
+ free(messages);
+
+ 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;
+ return PAM_SUCCESS;
+ }
+
+ /* Failure only */
+ if (pam_responses)
+ free(pam_responses);
+
+ return PAM_CONV_ERR;
}
#endif /* NO_LOCKING -- whole file */