X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Fpasswd-pam.c;h=a4b132123f7c8ce63078961b80617466efef6a54;hp=f7d8528fd1a8e665caa8945bf7520258383b7e9e;hb=07faf451b99879183ed7e909e43a0e065be1ee7f;hpb=3210e7e80ee2b5a7d2049a5aaff9f17b9c93dcc9 diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c index f7d8528f..a4b13212 100644 --- a/driver/passwd-pam.c +++ b/driver/passwd-pam.c @@ -1,7 +1,7 @@ /* passwd-pam.c --- verifying typed passwords with PAM * (Pluggable Authentication Modules.) * written by Bill Nottingham (and jwz) for - * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski + * xscreensaver, Copyright (c) 1993-2003 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -10,6 +10,26 @@ * 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/pam_appl.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 */ #ifdef HAVE_CONFIG_H @@ -32,9 +52,13 @@ extern char *blurb(void); #include #include #include +#include +#include #include +extern sigset_t block_sigchld (void); +extern void unblock_sigchld (void); /* blargh */ #undef Bool @@ -47,6 +71,13 @@ extern char *blurb(void); #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, @@ -58,6 +89,30 @@ struct pam_closure { Bool verbose_p; }; +Bool pam_passwd_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.*/ + /* Don't set this to 0 (Linux bug workaround.) */ +# define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1) +#else /* !HAVE_PAM_FAIL_DELAY */ +# define PAM_NO_DELAY(pamh) /* */ +#endif /* !HAVE_PAM_FAIL_DELAY */ + + +/* On SunOS 5.6, and on Linux with PAM 0.64, pam_strerror() takes two args. + On some other Linux systems with some other version of PAM (e.g., + whichever Debian release comes with a 2.2.5 kernel) it takes one arg. + I can't tell which is more "recent" or "correct" behavior, so configure + figures out which is in use for us. Shoot me! + */ +#ifdef PAM_STRERROR_TWO_ARGS +# define PAM_STRERROR(pamh, status) pam_strerror((pamh), (status)) +#else /* !PAM_STRERROR_TWO_ARGS */ +# define PAM_STRERROR(pamh, status) pam_strerror((status)) +#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 @@ -105,6 +160,16 @@ struct pam_closure { */ +/* 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! + + (I've been told this is bug 4092227, and is fixed in Solaris 7.) + (I've also been told that it's fixed in Solaris 2.6 by patch 106257-05.) + */ +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. @@ -118,6 +183,8 @@ pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p) struct pam_conv pc; struct pam_closure c; char *user = 0; + sigset_t set; + struct timespec timeout; struct passwd *p = getpwuid (getuid ()); if (!p) return False; @@ -131,6 +198,10 @@ pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p) pc.conv = &pam_conversation; pc.appdata_ptr = (void *) &c; + /* 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; + /* Initialize PAM. */ @@ -138,48 +209,109 @@ pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p) if (verbose_p) fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n", blurb(), service, c.user, - status, pam_strerror (pamh, status)); + status, PAM_STRERROR (pamh, status)); if (status != PAM_SUCCESS) goto DONE; -# ifdef HAVE_PAM_FAIL_DELAY - pam_fail_delay (pamh, 0); /* We handle delays ourself. */ -# endif /* HAVE_PAM_FAIL_DELAY */ - /* #### 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...) */ { - 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)); + 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); + + timeout.tv_sec = 0; + timeout.tv_nsec = 1; + set = block_sigchld(); status = pam_authenticate (pamh, 0); + sigtimedwait (&set, NULL, &timeout); + unblock_sigchld(); + if (verbose_p) fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n", - blurb(), status, pam_strerror(pamh, status)); + blurb(), status, PAM_STRERROR(pamh, status)); if (status == PAM_SUCCESS) /* Win! */ - goto DONE; + { + 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)); + + /* 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 + says that the Linux PAM library ignores that one, and only refreshes + credentials when using PAM_REINITIALIZE_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; + } +#ifdef ALLOW_ROOT_PASSWD /* 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 (user) free (user); + user = strdup ("root"); + c.user = user; + status = pam_set_item (pamh, PAM_USER, 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)); + blurb(), c.user, status, PAM_STRERROR(pamh, status)); if (status != PAM_SUCCESS) goto DONE; + PAM_NO_DELAY(pamh); + + set = block_sigchld(); status = pam_authenticate (pamh, 0); + sigtimedwait(&set, NULL, &timeout); + unblock_sigchld(); + if (verbose_p) fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n", - blurb(), status, pam_strerror(pamh, status)); + blurb(), status, PAM_STRERROR(pamh, status)); + +#endif /* ALLOW_ROOT_PASSWD */ DONE: if (user) free (user); @@ -197,22 +329,64 @@ pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p) Bool -pam_lock_init (int argc, char **argv, Bool verbose_p) +pam_priv_init (int argc, char **argv, Bool verbose_p) { /* 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" 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 + is unreadable or something (don't know if that actually happens.) */ - const char dir[] = "/etc/pam.d"; - const char file[] = "/etc/pam.d/" PAM_SERVICE_NAME; + const char dir[] = "/etc/pam.d"; + const char file[] = "/etc/pam.d/" PAM_SERVICE_NAME; + const char file2[] = "/etc/pam.conf"; struct stat st; - if (stat (dir, &st) == 0 && st.st_mode & S_IFDIR) - if (stat (file, &st) != 0) + +# 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, + "%s: warning: %s does not exist.\n" + "%s: password authentication via PAM is unlikely to work.\n", + blurb(), file, blurb()); + } + else if (stat (file2, &st) == 0) + { + FILE *f = fopen (file2, "r"); + if (f) + { + Bool ok = False; + char buf[255]; + while (fgets (buf, sizeof(buf), f)) + if (strstr (buf, PAM_SERVICE_NAME)) + { + ok = True; + break; + } + fclose (f); + if (!ok) + { + fprintf (stderr, + "%s: warning: %s does not list the `%s' service.\n" + "%s: password authentication via PAM is unlikely to work.\n", + blurb(), file2, PAM_SERVICE_NAME, blurb()); + } + } + /* else warn about file2 existing but being unreadable? */ + } + else + { fprintf (stderr, - "%s: warning: %s does not exist.\n" + "%s: warning: neither %s nor %s exist.\n" "%s: password authentication via PAM is unlikely to work.\n", - blurb(), file, blurb()); + blurb(), file2, file, blurb()); + } /* Return true anyway, just in case. */ return True; @@ -243,6 +417,10 @@ pam_conversation (int nmsgs, struct pam_response *reply = 0; struct pam_closure *c = (struct pam_closure *) closure; + /* On SunOS 5.6, the `closure' argument always comes in as random garbage. */ + c = (struct pam_closure *) suns_pam_implementation_blows; + + reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply)); if (!reply) return PAM_CONV_ERR;