http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / driver / passwd-pam.c
1 /* passwd-pam.c --- verifying typed passwords with PAM
2  * (Pluggable Authentication Modules.)
3  * written by Bill Nottingham <notting@redhat.com> (and jwz) for
4  * xscreensaver, Copyright (c) 1993-2008 Jamie Zawinski <jwz@jwz.org>
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation.  No representations are made about the suitability of this
11  * software for any purpose.  It is provided "as is" without express or 
12  * implied warranty.
13  *
14  * Some PAM resources:
15  *
16  *    PAM home page:
17  *    http://www.us.kernel.org/pub/linux/libs/pam/
18  *
19  *    PAM FAQ:
20  *    http://www.us.kernel.org/pub/linux/libs/pam/FAQ
21  *
22  *    PAM Application Developers' Guide:
23  *    http://www.us.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_ADG.html
24  *
25  *    PAM Mailing list archives:
26  *    http://www.linuxhq.com/lnxlists/linux-pam/
27  *
28  *    Compatibility notes, especially between Linux and Solaris:
29  *    http://www.contrib.andrew.cmu.edu/u/shadow/pam.html
30  *
31  *    The Open Group's PAM API documentation:
32  *    http://www.opengroup.org/onlinepubs/8329799/pam_start.htm
33  */
34
35 #ifdef HAVE_CONFIG_H
36 # include "config.h"
37 #endif
38
39 #ifndef NO_LOCKING  /* whole file */
40
41 #include <stdlib.h>
42 #ifdef HAVE_UNISTD_H
43 # include <unistd.h>
44 #endif
45
46 extern char *blurb(void);
47
48
49 #include <stdio.h>
50 #include <string.h>
51 #include <sys/types.h>
52 #include <pwd.h>
53 #include <grp.h>
54 #include <security/pam_appl.h>
55 #include <signal.h>
56 #include <errno.h>
57 #include <X11/Intrinsic.h>
58
59 #include <sys/stat.h>
60
61 #include "auth.h"
62
63 extern sigset_t block_sigchld (void);
64 extern void unblock_sigchld (void);
65
66 /* blargh */
67 #undef  Bool
68 #undef  True
69 #undef  False
70 #define Bool  int
71 #define True  1
72 #define False 0
73
74 #undef countof
75 #define countof(x) (sizeof((x))/sizeof(*(x)))
76
77 /* Some time between Red Hat 4.2 and 7.0, the words were transposed 
78    in the various PAM_x_CRED macro names.  Yay!
79  */
80 #ifndef  PAM_REFRESH_CRED
81 # define PAM_REFRESH_CRED PAM_CRED_REFRESH
82 #endif
83
84 static int pam_conversation (int nmsgs,
85                              const struct pam_message **msg,
86                              struct pam_response **resp,
87                              void *closure);
88
89 void pam_try_unlock(saver_info *si, Bool verbose_p,
90                Bool (*valid_p)(const char *typed_passwd, Bool verbose_p));
91
92 Bool pam_priv_init (int argc, char **argv, Bool verbose_p);
93
94 #ifdef HAVE_PAM_FAIL_DELAY
95    /* We handle delays ourself.*/
96    /* Don't set this to 0 (Linux bug workaround.) */
97 # define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1)
98 #else  /* !HAVE_PAM_FAIL_DELAY */
99 # define PAM_NO_DELAY(pamh) /* */
100 #endif /* !HAVE_PAM_FAIL_DELAY */
101
102
103 /* On SunOS 5.6, and on Linux with PAM 0.64, pam_strerror() takes two args.
104    On some other Linux systems with some other version of PAM (e.g.,
105    whichever Debian release comes with a 2.2.5 kernel) it takes one arg.
106    I can't tell which is more "recent" or "correct" behavior, so configure
107    figures out which is in use for us.  Shoot me!
108  */
109 #ifdef PAM_STRERROR_TWO_ARGS
110 # define PAM_STRERROR(pamh, status) pam_strerror((pamh), (status))
111 #else  /* !PAM_STRERROR_TWO_ARGS */
112 # define PAM_STRERROR(pamh, status) pam_strerror((status))
113 #endif /* !PAM_STRERROR_TWO_ARGS */
114
115
116 /* PAM sucks in that there is no way to tell whether a particular service
117    is configured at all.  That is, there is no way to tell the difference
118    between "authentication of the FOO service is not allowed" and "the
119    user typed the wrong password."
120
121    On RedHat 5.1 systems, if a service name is not known, it defaults to
122    being not allowed (because the fallback service, /etc/pam.d/other, is
123    set to `pam_deny'.)
124
125    On Solaris 2.6 systems, unknown services default to authenticating normally.
126
127    So, we could simply require that the person who installs xscreensaver
128    set up an "xscreensaver" PAM service.  However, if we went that route,
129    it would have a really awful failure mode: the failure mode would be that
130    xscreensaver was willing to *lock* the screen, but would be unwilling to
131    *unlock* the screen.  (With the non-PAM password code, the analagous
132    situation -- security not being configured properly, for example do to the
133    executable not being installed as setuid root -- the failure mode is much
134    more palettable, in that xscreensaver will refuse to *lock* the screen,
135    because it can know up front that there is no password that will work.)
136
137    Another route would be to have the service name to consult be computed at
138    compile-time (perhaps with a configure option.)  However, that doesn't
139    really solve the problem, because it means that the same executable might
140    work fine on one machine, but refuse to unlock when run on another
141    machine.
142
143    Another alternative would be to look in /etc/pam.conf or /etc/pam.d/ at
144    runtime to see what services actually exist.  But I think that's no good,
145    because who is to say that the PAM info is actually specified in those
146    files?  Opening and reading those files is not a part of the PAM client
147    API, so it's not guarenteed to work on any given system.
148
149    An alternative I tried was to specify a list of services to try, and to
150    try them all in turn ("xscreensaver", "xlock", "xdm", and "login").
151    This worked, but it was slow (and I also had to do some contortions to
152    work around bugs in Linux PAM 0.64-3.)
153
154    So what we do today is, try PAM once, and if that fails, try the usual
155    getpwent() method.  So if PAM doesn't work, it will at least make an
156    attempt at looking up passwords in /etc/passwd or /etc/shadow instead.
157
158    This all kind of blows.  I'm not sure what else to do.
159  */
160
161
162 /* On SunOS 5.6, the `pam_conv.appdata_ptr' slot seems to be ignored, and
163    the `closure' argument to pc.conv always comes in as random garbage.
164    So we get around this by using a global variable instead.  Shoot me!
165
166    (I've been told this is bug 4092227, and is fixed in Solaris 7.)
167    (I've also been told that it's fixed in Solaris 2.6 by patch 106257-05.)
168  */
169 static void *suns_pam_implementation_blows = 0;
170
171
172 /**
173  * This function is the PAM conversation driver. It conducts a full
174  * authentication round by invoking the GUI with various prompts.
175  */
176 void
177 pam_try_unlock(saver_info *si, Bool verbose_p,
178                Bool (*valid_p)(const char *typed_passwd, Bool verbose_p))
179 {
180   const char *service = PAM_SERVICE_NAME;
181   pam_handle_t *pamh = 0;
182   int status = -1;
183   struct pam_conv pc;
184   sigset_t set;
185   struct timespec timeout;
186
187   pc.conv = &pam_conversation;
188   pc.appdata_ptr = (void *) si;
189
190   /* On SunOS 5.6, the `appdata_ptr' slot seems to be ignored, and the
191      `closure' argument to pc.conv always comes in as random garbage. */
192   suns_pam_implementation_blows = (void *) si;
193
194
195   /* Initialize PAM.
196    */
197   status = pam_start (service, si->user, &pc, &pamh);
198   if (verbose_p)
199     fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
200              blurb(), service, si->user,
201              status, PAM_STRERROR (pamh, status));
202   if (status != PAM_SUCCESS) goto DONE;
203
204   /* #### We should set PAM_TTY to the display we're using, but we
205      don't have that handy from here.  So set it to :0.0, which is a
206      good guess (and has the bonus of counting as a "secure tty" as
207      far as PAM is concerned...)
208    */
209   {
210     char *tty = strdup (":0.0");
211     status = pam_set_item (pamh, PAM_TTY, tty);
212     if (verbose_p)
213       fprintf (stderr, "%s:   pam_set_item (p, PAM_TTY, \"%s\") ==> %d (%s)\n",
214                blurb(), tty, status, PAM_STRERROR(pamh, status));
215     free (tty);
216   }
217
218   /* Try to authenticate as the current user.
219      We must turn off our SIGCHLD handler for the duration of the call to
220      pam_authenticate(), because in some cases, the underlying PAM code
221      will do this:
222
223         1: fork a setuid subprocess to do some dirty work;
224         2: read a response from that subprocess;
225         3: waitpid(pid, ...) on that subprocess.
226
227     If we (the ignorant parent process) have a SIGCHLD handler, then there's
228     a race condition between steps 2 and 3: if the subprocess exits before
229     waitpid() was called, then our SIGCHLD handler fires, and gets notified
230     of the subprocess death; then PAM's call to waitpid() fails, because the
231     process has already been reaped.
232
233     I consider this a bug in PAM, since the caller should be able to have
234     whatever signal handlers it wants -- the PAM documentation doesn't say
235     "oh by the way, if you use PAM, you can't use SIGCHLD."
236    */
237
238   PAM_NO_DELAY(pamh);
239
240   if (verbose_p)
241     fprintf (stderr, "%s:   pam_authenticate (...) ...\n", blurb());
242
243   timeout.tv_sec = 0;
244   timeout.tv_nsec = 1;
245   set = block_sigchld();
246   status = pam_authenticate (pamh, 0);
247 # ifdef HAVE_SIGTIMEDWAIT
248   sigtimedwait (&set, NULL, &timeout);
249   /* #### What is the portable thing to do if we don't have it? */
250 # endif /* HAVE_SIGTIMEDWAIT */
251   unblock_sigchld();
252
253   if (verbose_p)
254     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
255              blurb(), status, PAM_STRERROR(pamh, status));
256
257   if (status == PAM_SUCCESS)  /* Win! */
258     {
259       int status2;
260
261       /* We don't actually care if the account modules fail or succeed,
262        * but we need to run them anyway because certain pam modules
263        * depend on side effects of the account modules getting run.
264        */
265       status2 = pam_acct_mgmt (pamh, 0);
266
267       if (verbose_p)
268         fprintf (stderr, "%s:   pam_acct_mgmt (...) ==> %d (%s)\n",
269                  blurb(), status2, PAM_STRERROR(pamh, status2));
270
271       /* HPUX for some reason likes to make PAM defines different from
272        * everyone else's. */
273 #ifdef PAM_AUTHTOKEN_REQD
274       if (status2 == PAM_AUTHTOKEN_REQD)
275 #else
276       if (status2 == PAM_NEW_AUTHTOK_REQD)
277 #endif
278         {
279           status2 = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
280           if (verbose_p)
281             fprintf (stderr, "%s: pam_chauthtok (...) ==> %d (%s)\n",
282                      blurb(), status2, PAM_STRERROR(pamh, status2));
283         }
284
285       /* Each time we successfully authenticate, refresh credentials,
286          for Kerberos/AFS/DCE/etc.  If this fails, just ignore that
287          failure and blunder along; it shouldn't matter.
288
289          Note: this used to be PAM_REFRESH_CRED instead of
290          PAM_REINITIALIZE_CRED, but Jason Heiss <jheiss@ee.washington.edu>
291          says that the Linux PAM library ignores that one, and only refreshes
292          credentials when using PAM_REINITIALIZE_CRED.
293        */
294       status2 = pam_setcred (pamh, PAM_REINITIALIZE_CRED);
295       if (verbose_p)
296         fprintf (stderr, "%s:   pam_setcred (...) ==> %d (%s)\n",
297                  blurb(), status2, PAM_STRERROR(pamh, status2));
298     }
299
300  DONE:
301   if (pamh)
302     {
303       int status2 = pam_end (pamh, status);
304       pamh = 0;
305       if (verbose_p)
306         fprintf (stderr, "%s: pam_end (...) ==> %d (%s)\n",
307                  blurb(), status2,
308                  (status2 == PAM_SUCCESS ? "Success" : "Failure"));
309     }
310
311   if (status == PAM_SUCCESS)
312     si->unlock_state = ul_success;           /* yay */
313   else if (si->unlock_state == ul_cancel ||
314            si->unlock_state == ul_time)
315     ;                                        /* more specific failures ok */
316   else
317     si->unlock_state = ul_fail;              /* generic failure */
318 }
319
320
321 Bool 
322 pam_priv_init (int argc, char **argv, Bool verbose_p)
323 {
324   /* We have nothing to do at init-time.
325      However, we might as well do some error checking.
326      If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock"
327      does not exist, warn that PAM probably isn't going to work.
328
329      This is a priv-init instead of a non-priv init in case the directory
330      is unreadable or something (don't know if that actually happens.)
331    */
332   const char   dir[] = "/etc/pam.d";
333   const char  file[] = "/etc/pam.d/" PAM_SERVICE_NAME;
334   const char file2[] = "/etc/pam.conf";
335   struct stat st;
336
337 # ifndef S_ISDIR
338 #  define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
339 # endif
340
341   if (stat (dir, &st) == 0 && S_ISDIR(st.st_mode))
342     {
343       if (stat (file, &st) != 0)
344         fprintf (stderr,
345                  "%s: warning: %s does not exist.\n"
346                  "%s: password authentication via PAM is unlikely to work.\n",
347                  blurb(), file, blurb());
348     }
349   else if (stat (file2, &st) == 0)
350     {
351       FILE *f = fopen (file2, "r");
352       if (f)
353         {
354           Bool ok = False;
355           char buf[255];
356           while (fgets (buf, sizeof(buf), f))
357             if (strstr (buf, PAM_SERVICE_NAME))
358               {
359                 ok = True;
360                 break;
361               }
362           fclose (f);
363           if (!ok)
364             {
365               fprintf (stderr,
366                   "%s: warning: %s does not list the `%s' service.\n"
367                   "%s: password authentication via PAM is unlikely to work.\n",
368                        blurb(), file2, PAM_SERVICE_NAME, blurb());
369             }
370         }
371       /* else warn about file2 existing but being unreadable? */
372     }
373   else
374     {
375       fprintf (stderr,
376                "%s: warning: neither %s nor %s exist.\n"
377                "%s: password authentication via PAM is unlikely to work.\n",
378                blurb(), file2, file, blurb());
379     }
380
381   /* Return true anyway, just in case. */
382   return True;
383 }
384
385
386 static int
387 pam_conversation (int nmsgs,
388                   const struct pam_message **msg,
389                   struct pam_response **resp,
390                   void *vsaver_info)
391 {
392   int i, ret = -1;
393   struct auth_message *messages = 0;
394   struct auth_response *authresp = 0;
395   struct pam_response *pam_responses;
396   saver_info *si = (saver_info *) vsaver_info;
397   Bool verbose_p;
398
399   /* On SunOS 5.6, the `closure' argument always comes in as random garbage. */
400   si = (saver_info *) suns_pam_implementation_blows;
401
402   verbose_p = si->prefs.verbose_p;
403
404   /* Converting the PAM prompts into the XScreenSaver native format.
405    * It was a design goal to collapse (INFO,PROMPT) pairs from PAM
406    * into a single call to the unlock_cb function. The unlock_cb function
407    * does that, but only if it is passed several prompts at a time. Most PAM
408    * modules only send a single prompt at a time, but because there is no way
409    * of telling whether there will be more prompts to follow, we can only ever
410    * pass along whatever was passed in here.
411    */
412
413   messages = calloc(nmsgs, sizeof(struct auth_message));
414   pam_responses = calloc(nmsgs, sizeof(*pam_responses));
415   
416   if (!pam_responses || !messages)
417     goto end;
418
419   if (verbose_p)
420     fprintf (stderr, "%s:     pam_conversation (", blurb());
421
422   for (i = 0; i < nmsgs; ++i)
423     {
424       if (verbose_p && i > 0) fprintf (stderr, ", ");
425
426       messages[i].msg = msg[i]->msg;
427
428       switch (msg[i]->msg_style) {
429       case PAM_PROMPT_ECHO_OFF: messages[i].type = AUTH_MSGTYPE_PROMPT_NOECHO;
430         if (verbose_p) fprintf (stderr, "ECHO_OFF");
431         break;
432       case PAM_PROMPT_ECHO_ON:  messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO;
433         if (verbose_p) fprintf (stderr, "ECHO_ON");
434         break;
435       case PAM_ERROR_MSG:       messages[i].type = AUTH_MSGTYPE_ERROR;
436         if (verbose_p) fprintf (stderr, "ERROR_MSG");
437         break;
438       case PAM_TEXT_INFO:       messages[i].type = AUTH_MSGTYPE_INFO;
439         if (verbose_p) fprintf (stderr, "TEXT_INFO");
440         break;
441       default:                  messages[i].type = AUTH_MSGTYPE_PROMPT_ECHO;
442         if (verbose_p) fprintf (stderr, "PROMPT_ECHO");
443         break;
444       }
445
446       if (verbose_p) 
447         fprintf (stderr, "=\"%s\"", msg[i]->msg ? msg[i]->msg : "(null)");
448     }
449
450   if (verbose_p)
451     fprintf (stderr, ") ...\n");
452
453   ret = si->unlock_cb(nmsgs, messages, &authresp, si);
454
455   if (ret == 0)
456     {
457       for (i = 0; i < nmsgs; ++i)
458         pam_responses[i].resp = authresp[i].response;
459     }
460
461 end:
462   if (messages)
463     free(messages);
464
465   if (authresp)
466     free(authresp);
467
468   if (verbose_p)
469     fprintf (stderr, "%s:     pam_conversation (...) ==> %s\n", blurb(),
470              (ret == 0 ? "PAM_SUCCESS" : "PAM_CONV_ERR"));
471
472   if (ret == 0)
473     {
474       *resp = pam_responses;
475       return PAM_SUCCESS;
476     }
477
478   /* Failure only */
479     if (pam_responses)
480       free(pam_responses);
481
482     return PAM_CONV_ERR;
483 }
484
485 #endif /* NO_LOCKING -- whole file */