summaryrefslogtreecommitdiffstats
path: root/driver/passwd-pam.c
diff options
context:
space:
mode:
authorSimon Rettberg2021-04-06 14:43:39 +0200
committerSimon Rettberg2021-04-07 13:38:37 +0200
commit38886de0c3e9ea5729ef23e4c653fa2822f52e8f (patch)
tree9b799c8c968a92cc77746a95e0e8bdd90b6b13c3 /driver/passwd-pam.c
parentMaybe not remove, but ... (diff)
downloadxscreensaver-openslx.tar.gz
xscreensaver-openslx.tar.xz
xscreensaver-openslx.zip
xscreensaver 6.00v28r1openslx
Diffstat (limited to 'driver/passwd-pam.c')
-rw-r--r--driver/passwd-pam.c312
1 files changed, 103 insertions, 209 deletions
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 <notting@redhat.com> (and jwz) for
- * xscreensaver, Copyright (c) 1993-2017 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
+ * By Bill Nottingham <notting@redhat.com> 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 <unistd.h>
#endif
-extern char *blurb(void);
-
-
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
+#include <sys/stat.h>
#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>
+#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 */