X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=driver%2Fpasswd-pam.c;h=334d12b913c83d8d42d7822099d47b727853a86c;hp=e29ff9c71fa6745f793f79e61cdd43739933edd0;hb=6b1c86cf395f59389e4ece4ea8f4bea2c332745b;hpb=8e0f39b4a12b9a908af2b3b175ebe87c14b4a6ab diff --git a/driver/passwd-pam.c b/driver/passwd-pam.c index e29ff9c7..334d12b9 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-2008 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/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 */ #ifdef HAVE_CONFIG_H @@ -32,9 +52,16 @@ extern char *blurb(void); #include #include #include +#include +#include +#include #include +#include "auth.h" + +extern sigset_t block_sigchld (void); +extern void unblock_sigchld (void); /* blargh */ #undef Bool @@ -47,25 +74,44 @@ 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, 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.*/ /* Don't set this to 0 (Linux bug workaround.) */ # define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1) -# else /* !HAVE_PAM_FAIL_DELAY */ +#else /* !HAVE_PAM_FAIL_DELAY */ # define PAM_NO_DELAY(pamh) /* */ -# endif /* !HAVE_PAM_FAIL_DELAY */ +#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 @@ -113,40 +159,46 @@ struct pam_closure { */ -/* 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. +/* 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.) */ -Bool -pam_passwd_valid_p (const char *typed_passwd, 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. + */ +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 *) 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, - status, pam_strerror (pamh, status)); + blurb(), service, si->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 @@ -155,40 +207,97 @@ pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p) 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); - status = pam_authenticate (pamh, 0); - if (verbose_p) - fprintf (stderr, "%s: pam_authenticate (...) ==> %d (%s)\n", - blurb(), status, pam_strerror(pamh, status)); - if (status == PAM_SUCCESS) /* Win! */ - 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; + fprintf (stderr, "%s: pam_authenticate (...) ...\n", blurb()); - PAM_NO_DELAY(pamh); + 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)); + 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 + 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)); + } DONE: - if (user) free (user); if (pamh) { int status2 = pam_end (pamh, status); @@ -198,106 +307,179 @@ pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p) 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 */ } 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; } -/* 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; - - reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply)); - if (!reply) return PAM_CONV_ERR; - - for (replies = 0; replies < nmsgs; replies++) + 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. */ + 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()); + + 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 (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 */