From 38886de0c3e9ea5729ef23e4c653fa2822f52e8f Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 6 Apr 2021 14:43:39 +0200 Subject: xscreensaver 6.00 --- driver/passwd-pam.c | 312 +++++++++++++++++----------------------------------- 1 file changed, 103 insertions(+), 209 deletions(-) (limited to 'driver/passwd-pam.c') diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c index d463bc2..87942ab 100644 --- a/driver/passwd-pam.c +++ b/driver/passwd-pam.c @@ -1,7 +1,6 @@ /* passwd-pam.c --- verifying typed passwords with PAM - * (Pluggable Authentication Modules.) - * written by Bill Nottingham (and jwz) for - * xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski + * xscreensaver, Copyright © 1993-2021 Jamie Zawinski + * By Bill Nottingham and jwz. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -10,26 +9,37 @@ * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. - * - * Some PAM resources: - * - * PAM home page: - * http://www.us.kernel.org/pub/linux/libs/pam/ - * - * PAM FAQ: - * 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/Linux-PAM_ADG.html - * - * PAM Mailing list archives: - * http://www.linuxhq.com/lnxlists/linux-pam/ - * - * Compatibility notes, especially between Linux and Solaris: - * http://www.contrib.andrew.cmu.edu/u/shadow/pam.html - * - * The Open Group's PAM API documentation: - * http://www.opengroup.org/onlinepubs/8329799/pam_start.htm + */ + +/* PAM sucks in that there is no way to tell whether a particular service is + configured at all. That is, there is no way to tell the difference between + "authentication of the FOO service is not allowed" and "the user typed the + wrong password." + + On RedHat 5.1 systems, if a service name is not known, it defaults to being + not allowed (because the fallback service, /etc/pam.d/other, is set to + `pam_deny'.) + + On Solaris 2.6 systems, unknown services default to authenticating normally. + + So, we require that an "xscreensaver" PAM service exist. This has a bad + failure mode, however: if that service doesn't exist, then XScreenSaver + will lock the screen, but is unable to unlock. (With the non-PAM password + code, XScreenSaver can refuse to lock, because it is able to determine up + front that it was unable to retrieve the password info.) + + At startup, we check for the existence of /etc/pam.d/xscreensaver or + /etc/pam.conf, and if those don't exist, we print a warning that PAM is + probably not configured properly. This isn't *necessarily* correct, since + those files are not a part of PAM's C API, but it's how real-world systems + actually work. + + Also note that FreeBSD's implementation of PAM requires the calling process + to be running as root during the entire interactive PAM conversation: it + can't ever disavow privileges. Linux's PAM implementation uses a setuid + helper so that a non-root process can still authenticate, as is right and + proper. Consequently, XScreenSaver does not support PAM on FreeBSD. + Dear FreeBSD, get your shit together. */ #ifdef HAVE_CONFIG_H @@ -43,36 +53,17 @@ # include #endif -extern char *blurb(void); - - #include #include #include +#include #include #include #include -#include -#include -#include - -#include +#include "blurb.h" #include "auth.h" -extern sigset_t block_sigchld (void); -extern void unblock_sigchld (void); - -/* blargh */ -#undef Bool -#undef True -#undef False -#define Bool int -#define True 1 -#define False 0 - -#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! @@ -89,10 +80,14 @@ static int pam_conversation (int nmsgs, struct pam_response **resp, void *closure); -void pam_try_unlock(saver_info *si, Bool verbose_p, - Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)); +typedef struct { + Bool (*conv_fn) (void *closure, + int nmsgs, + const auth_message *msg, + auth_response **resp); + void *conv_fn_closure; +} pam_conv_closure; -Bool pam_priv_init (int argc, char **argv, Bool verbose_p); #ifdef HAVE_PAM_FAIL_DELAY /* We handle delays ourself.*/ @@ -116,52 +111,6 @@ Bool pam_priv_init (int argc, char **argv, Bool verbose_p); #endif /* !PAM_STRERROR_TWO_ARGS */ -/* PAM sucks in that there is no way to tell whether a particular service - is configured at all. That is, there is no way to tell the difference - between "authentication of the FOO service is not allowed" and "the - user typed the wrong password." - - On RedHat 5.1 systems, if a service name is not known, it defaults to - being not allowed (because the fallback service, /etc/pam.d/other, is - set to `pam_deny'.) - - On Solaris 2.6 systems, unknown services default to authenticating normally. - - So, we could simply require that the person who installs xscreensaver - set up an "xscreensaver" PAM service. However, if we went that route, - it would have a really awful failure mode: the failure mode would be that - xscreensaver was willing to *lock* the screen, but would be unwilling to - *unlock* the screen. (With the non-PAM password code, the analagous - situation -- security not being configured properly, for example do to the - executable not being installed as setuid root -- the failure mode is much - more palettable, in that xscreensaver will refuse to *lock* the screen, - because it can know up front that there is no password that will work.) - - Another route would be to have the service name to consult be computed at - compile-time (perhaps with a configure option.) However, that doesn't - really solve the problem, because it means that the same executable might - work fine on one machine, but refuse to unlock when run on another - machine. - - Another alternative would be to look in /etc/pam.conf or /etc/pam.d/ at - runtime to see what services actually exist. But I think that's no good, - because who is to say that the PAM info is actually specified in those - files? Opening and reading those files is not a part of the PAM client - API, so it's not guarenteed to work on any given system. - - An alternative I tried was to specify a list of services to try, and to - try them all in turn ("xscreensaver", "xlock", "xdm", and "login"). - This worked, but it was slow (and I also had to do some contortions to - work around bugs in Linux PAM 0.64-3.) - - So what we do today is, try PAM once, and if that fails, try the usual - getpwent() method. So if PAM doesn't work, it will at least make an - attempt at looking up passwords in /etc/passwd or /etc/shadow instead. - - This all kind of blows. I'm not sure what else to do. - */ - - /* On SunOS 5.6, the `pam_conv.appdata_ptr' slot seems to be ignored, and the `closure' argument to pc.conv always comes in as random garbage. So we get around this by using a global variable instead. Shoot me! @@ -172,97 +121,70 @@ Bool pam_priv_init (int argc, char **argv, Bool verbose_p); static void *suns_pam_implementation_blows = 0; -/** - * This function is the PAM conversation driver. It conducts a full - * authentication round by invoking the GUI with various prompts. +/* This function invokes the PAM conversation. It conducts a full + authentication round by presenting the GUI with various prompts. */ -void -pam_try_unlock(saver_info *si, Bool verbose_p, - Bool (*valid_p)(const char *typed_passwd, Bool verbose_p)) +Bool +pam_try_unlock (void *closure, + Bool (*conv_fn) (void *closure, + int nmsgs, + const auth_message *msg, + auth_response **resp)) { const char *service = PAM_SERVICE_NAME; pam_handle_t *pamh = 0; int status = -1; struct pam_conv pc; -# ifdef HAVE_SIGTIMEDWAIT - sigset_t set; - struct timespec timeout; -# endif /* HAVE_SIGTIMEDWAIT */ + struct passwd *p = getpwuid (getuid()); + const char *user = (p && p->pw_name && *p->pw_name ? p->pw_name : 0); + pam_conv_closure conv_closure; + + if (!user) + { + fprintf (stderr, "%s: PAM: unable to get current user\n", blurb()); + return False; + } + conv_closure.conv_fn = conv_fn; + conv_closure.conv_fn_closure = closure; + pc.appdata_ptr = &conv_closure; pc.conv = &pam_conversation; - 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 *) si; + suns_pam_implementation_blows = pc.appdata_ptr; /* Initialize PAM. */ - status = pam_start (service, si->user, &pc, &pamh); + status = pam_start (service, user, &pc, &pamh); if (verbose_p) - fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n", - blurb(), service, si->user, + fprintf (stderr, "%s: PAM: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n", + blurb(), service, user, status, PAM_STRERROR (pamh, status)); if (status != PAM_SUCCESS) goto DONE; - /* #### We should set PAM_TTY to the display we're using, but we - don't have that handy from here. So set it to :0.0, which is a - good guess (and has the bonus of counting as a "secure tty" as - far as PAM is concerned...) - */ { - char *tty = strdup (":0.0"); + char *tty = getenv ("DISPLAY"); + if (!tty || !*tty) tty = ":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. - We must turn off our SIGCHLD handler for the duration of the call to - pam_authenticate(), because in some cases, the underlying PAM code - will do this: - - 1: fork a setuid subprocess to do some dirty work; - 2: read a response from that subprocess; - 3: waitpid(pid, ...) on that subprocess. - - If we (the ignorant parent process) have a SIGCHLD handler, then there's - a race condition between steps 2 and 3: if the subprocess exits before - waitpid() was called, then our SIGCHLD handler fires, and gets notified - of the subprocess death; then PAM's call to waitpid() fails, because the - process has already been reaped. - - I consider this a bug in PAM, since the caller should be able to have - whatever signal handlers it wants -- the PAM documentation doesn't say - "oh by the way, if you use PAM, you can't use SIGCHLD." - */ - 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 = -# 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) fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n", blurb(), status, PAM_STRERROR(pamh, status)); - if (status == PAM_SUCCESS) /* Win! */ + if (status == PAM_SUCCESS) /* So far so good... */ { int status2; @@ -341,22 +263,16 @@ pam_try_unlock(saver_info *si, Bool verbose_p, (status2 == PAM_SUCCESS ? "Success" : "Failure")); } - 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 */ + return (status == PAM_SUCCESS); } Bool -pam_priv_init (int argc, char **argv, Bool verbose_p) +pam_priv_init (void) { /* We have nothing to do at init-time. However, we might as well do some error checking. - If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock" + If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xscreensaver" does not exist, warn that PAM probably isn't going to work. This is a priv-init instead of a non-priv init in case the directory @@ -375,8 +291,8 @@ pam_priv_init (int argc, char **argv, Bool verbose_p) { if (stat (file, &st) != 0) fprintf (stderr, - "%s: warning: %s does not exist.\n" - "%s: password authentication via PAM is unlikely to work.\n", + "%s: PAM: warning: %s does not exist.\n" + "%s: PAM: password authentication is unlikely to work.\n", blurb(), file, blurb()); } else if (stat (file2, &st) == 0) @@ -396,8 +312,8 @@ pam_priv_init (int argc, char **argv, Bool verbose_p) if (!ok) { fprintf (stderr, - "%s: warning: %s does not list the `%s' service.\n" - "%s: password authentication via PAM is unlikely to work.\n", + "%s: PAM: warning: %s does not list the `%s' service.\n" + "%s: PAM: password authentication is unlikely to work.\n", blurb(), file2, PAM_SERVICE_NAME, blurb()); } } @@ -406,8 +322,8 @@ pam_priv_init (int argc, char **argv, Bool verbose_p) else { fprintf (stderr, - "%s: warning: neither %s nor %s exist.\n" - "%s: password authentication via PAM is unlikely to work.\n", + "%s: PAM: warning: neither %s nor %s exist.\n" + "%s: PAM: password authentication is unlikely to work.\n", blurb(), file2, file, blurb()); } @@ -416,23 +332,22 @@ pam_priv_init (int argc, char **argv, Bool verbose_p) } +/* This is pam_conv->conv */ static int pam_conversation (int nmsgs, const struct pam_message **msg, struct pam_response **resp, - void *vsaver_info) + void *closure) { - int i, ret = -1; - struct auth_message *messages = 0; - struct auth_response *authresp = 0; + int i; + auth_message *messages = 0; + auth_response *authresp = 0; struct pam_response *pam_responses; - saver_info *si = (saver_info *) vsaver_info; - Bool verbose_p; + pam_conv_closure *conv_closure; /* 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; + closure = suns_pam_implementation_blows; + conv_closure = closure; /* Converting the PAM prompts into the XScreenSaver native format. * It was a design goal to collapse (INFO,PROMPT) pairs from PAM @@ -443,11 +358,9 @@ pam_conversation (int nmsgs, * 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; + messages = calloc (nmsgs, sizeof(*messages)); + pam_responses = calloc (nmsgs, sizeof(*pam_responses)); + if (!pam_responses || !messages) abort(); if (verbose_p) fprintf (stderr, "%s: pam_conversation (", blurb()); @@ -483,44 +396,25 @@ pam_conversation (int nmsgs, 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; - } + /* This opens the dialog box and runs the X11 event loop. + It only returns if the user entered a password. + If they hit cancel, or timed out, it exited. + */ + conv_closure->conv_fn (conv_closure->conv_fn_closure, nmsgs, messages, + &authresp); -end: - if (messages) - free(messages); + for (i = 0; i < nmsgs; ++i) + pam_responses[i].resp = authresp[i].response; - if (authresp) - free(authresp); + 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); + fprintf (stderr, "%s: pam_conversation (...) ==> PAM_SUCCESS\n", + blurb()); - return PAM_CONV_ERR; + *resp = pam_responses; + return PAM_SUCCESS; } #endif /* NO_LOCKING -- whole file */ -- cgit v1.2.3-55-g7522