http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.34.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-2001 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/pam_appl.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
56 #include <sys/stat.h>
57
58 extern void block_sigchld (void);
59 extern void unblock_sigchld (void);
60
61 /* blargh */
62 #undef  Bool
63 #undef  True
64 #undef  False
65 #define Bool  int
66 #define True  1
67 #define False 0
68
69 #undef countof
70 #define countof(x) (sizeof((x))/sizeof(*(x)))
71
72 /* Some time between Red Hat 4.2 and 7.0, the words were transposed 
73    in the various PAM_x_CRED macro names.  Yay!
74  */
75 #ifndef  PAM_REFRESH_CRED
76 # define PAM_REFRESH_CRED PAM_CRED_REFRESH
77 #endif
78
79 static int pam_conversation (int nmsgs,
80                              const struct pam_message **msg,
81                              struct pam_response **resp,
82                              void *closure);
83
84 struct pam_closure {
85   const char *user;
86   const char *typed_passwd;
87   Bool verbose_p;
88 };
89
90
91 #ifdef HAVE_PAM_FAIL_DELAY
92    /* We handle delays ourself.*/
93    /* Don't set this to 0 (Linux bug workaround.) */
94 # define PAM_NO_DELAY(pamh) pam_fail_delay ((pamh), 1)
95 #else  /* !HAVE_PAM_FAIL_DELAY */
96 # define PAM_NO_DELAY(pamh) /* */
97 #endif /* !HAVE_PAM_FAIL_DELAY */
98
99
100 /* On SunOS 5.6, and on Linux with PAM 0.64, pam_strerror() takes two args.
101    On some other Linux systems with some other version of PAM (e.g.,
102    whichever Debian release comes with a 2.2.5 kernel) it takes one arg.
103    I can't tell which is more "recent" or "correct" behavior, so configure
104    figures out which is in use for us.  Shoot me!
105  */
106 #ifdef PAM_STRERROR_TWO_ARGS
107 # define PAM_STRERROR(pamh, status) pam_strerror((pamh), (status))
108 #else  /* !PAM_STRERROR_TWO_ARGS */
109 # define PAM_STRERROR(pamh, status) pam_strerror((status))
110 #endif /* !PAM_STRERROR_TWO_ARGS */
111
112
113 /* PAM sucks in that there is no way to tell whether a particular service
114    is configured at all.  That is, there is no way to tell the difference
115    between "authentication of the FOO service is not allowed" and "the
116    user typed the wrong password."
117
118    On RedHat 5.1 systems, if a service name is not known, it defaults to
119    being not allowed (because the fallback service, /etc/pam.d/other, is
120    set to `pam_deny'.)
121
122    On Solaris 2.6 systems, unknown services default to authenticating normally.
123
124    So, we could simply require that the person who installs xscreensaver
125    set up an "xscreensaver" PAM service.  However, if we went that route,
126    it would have a really awful failure mode: the failure mode would be that
127    xscreensaver was willing to *lock* the screen, but would be unwilling to
128    *unlock* the screen.  (With the non-PAM password code, the analagous
129    situation -- security not being configured properly, for example do to the
130    executable not being installed as setuid root -- the failure mode is much
131    more palettable, in that xscreensaver will refuse to *lock* the screen,
132    because it can know up front that there is no password that will work.)
133
134    Another route would be to have the service name to consult be computed at
135    compile-time (perhaps with a configure option.)  However, that doesn't
136    really solve the problem, because it means that the same executable might
137    work fine on one machine, but refuse to unlock when run on another
138    machine.
139
140    Another alternative would be to look in /etc/pam.conf or /etc/pam.d/ at
141    runtime to see what services actually exist.  But I think that's no good,
142    because who is to say that the PAM info is actually specified in those
143    files?  Opening and reading those files is not a part of the PAM client
144    API, so it's not guarenteed to work on any given system.
145
146    An alternative I tried was to specify a list of services to try, and to
147    try them all in turn ("xscreensaver", "xlock", "xdm", and "login").
148    This worked, but it was slow (and I also had to do some contortions to
149    work around bugs in Linux PAM 0.64-3.)
150
151    So what we do today is, try PAM once, and if that fails, try the usual
152    getpwent() method.  So if PAM doesn't work, it will at least make an
153    attempt at looking up passwords in /etc/passwd or /etc/shadow instead.
154
155    This all kind of blows.  I'm not sure what else to do.
156  */
157
158
159 /* On SunOS 5.6, the `pam_conv.appdata_ptr' slot seems to be ignored, and
160    the `closure' argument to pc.conv always comes in as random garbage.
161    So we get around this by using a global variable instead.  Shoot me!
162
163    (I've been told this is bug 4092227, and is fixed in Solaris 7.)
164    (I've also been told that it's fixed in Solaris 2.6 by patch 106257-05.)
165  */
166 static void *suns_pam_implementation_blows = 0;
167
168
169 /* This can be called at any time, and says whether the typed password
170    belongs to either the logged in user (real uid, not effective); or
171    to root.
172  */
173 Bool
174 pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
175 {
176   const char *service = PAM_SERVICE_NAME;
177   pam_handle_t *pamh = 0;
178   int status = -1;
179   struct pam_conv pc;
180   struct pam_closure c;
181   char *user = 0;
182
183   struct passwd *p = getpwuid (getuid ());
184   if (!p) return False;
185
186   user = strdup (p->pw_name);
187
188   c.user = user;
189   c.typed_passwd = typed_passwd;
190   c.verbose_p = verbose_p;
191
192   pc.conv = &pam_conversation;
193   pc.appdata_ptr = (void *) &c;
194
195   /* On SunOS 5.6, the `appdata_ptr' slot seems to be ignored, and the
196      `closure' argument to pc.conv always comes in as random garbage. */
197   suns_pam_implementation_blows = (void *) &c;
198
199
200   /* Initialize PAM.
201    */
202   status = pam_start (service, c.user, &pc, &pamh);
203   if (verbose_p)
204     fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
205              blurb(), service, c.user,
206              status, PAM_STRERROR (pamh, status));
207   if (status != PAM_SUCCESS) goto DONE;
208
209   /* #### We should set PAM_TTY to the display we're using, but we
210      don't have that handy from here.  So set it to :0.0, which is a
211      good guess (and has the bonus of counting as a "secure tty" as
212      far as PAM is concerned...)
213    */
214   {
215     const char *tty = ":0.0";
216     status = pam_set_item (pamh, PAM_TTY, strdup(tty));
217     if (verbose_p)
218       fprintf (stderr, "%s:   pam_set_item (p, PAM_TTY, \"%s\") ==> %d (%s)\n",
219                blurb(), tty, status, PAM_STRERROR(pamh, status));
220   }
221
222   /* Try to authenticate as the current user.
223      We must turn off our SIGCHLD handler for the duration of the call to
224      pam_authenticate(), because in some cases, the underlying PAM code
225      will do this:
226
227         1: fork a setuid subprocess to do some dirty work;
228         2: read a response from that subprocess;
229         3: waitpid(pid, ...) on that subprocess.
230
231     If we (the ignorant parent process) have a SIGCHLD handler, then there's
232     a race condition between steps 2 and 3: if the subprocess exits before
233     waitpid() was called, then our SIGCHLD handler fires, and gets notified
234     of the subprocess death; then PAM's call to waitpid() fails, because the
235     process has already been reaped.
236
237     I consider this a bug in PAM, since the caller should be able to have
238     whatever signal handlers it wants -- the PAM documentation doesn't say
239     "oh by the way, if you use PAM, you can't use SIGCHLD."
240    */
241
242   PAM_NO_DELAY(pamh);
243
244   block_sigchld();
245   status = pam_authenticate (pamh, 0);
246   unblock_sigchld();
247
248   if (verbose_p)
249     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
250              blurb(), status, PAM_STRERROR(pamh, status));
251   if (status == PAM_SUCCESS)  /* Win! */
252     {
253       /* Each time we successfully authenticate, refresh credentials,
254          for Kerberos/AFS/DCE/etc.  If this fails, just ignore that
255          failure and blunder along; it shouldn't matter.
256        */
257       int status2 = pam_setcred (pamh, PAM_REFRESH_CRED);
258       if (verbose_p)
259         fprintf (stderr, "%s:   pam_setcred (...) ==> %d (%s)\n",
260                  blurb(), status2, PAM_STRERROR(pamh, status2));
261       goto DONE;
262     }
263
264   /* If that didn't work, set the user to root, and try to authenticate again.
265    */
266   c.user = "root";
267   status = pam_set_item (pamh, PAM_USER, strdup(c.user));
268   if (verbose_p)
269     fprintf (stderr, "%s:   pam_set_item(p, PAM_USER, \"%s\") ==> %d (%s)\n",
270              blurb(), c.user, status, PAM_STRERROR(pamh, status));
271   if (status != PAM_SUCCESS) goto DONE;
272
273   PAM_NO_DELAY(pamh);
274   status = pam_authenticate (pamh, 0);
275   if (verbose_p)
276     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
277              blurb(), status, PAM_STRERROR(pamh, status));
278
279  DONE:
280   if (user) free (user);
281   if (pamh)
282     {
283       int status2 = pam_end (pamh, status);
284       pamh = 0;
285       if (verbose_p)
286         fprintf (stderr, "%s: pam_end (...) ==> %d (%s)\n",
287                  blurb(), status2,
288                  (status2 == PAM_SUCCESS ? "Success" : "Failure"));
289     }
290   return (status == PAM_SUCCESS ? True : False);
291 }
292
293
294 Bool 
295 pam_priv_init (int argc, char **argv, Bool verbose_p)
296 {
297   /* We have nothing to do at init-time.
298      However, we might as well do some error checking.
299      If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock"
300      does not exist, warn that PAM probably isn't going to work.
301
302      This is a priv-init instead of a non-priv init in case the directory
303      is unreadable or something (don't know if that actually happens.)
304    */
305   const char   dir[] = "/etc/pam.d";
306   const char  file[] = "/etc/pam.d/" PAM_SERVICE_NAME;
307   const char file2[] = "/etc/pam.conf";
308   struct stat st;
309
310   if (stat (dir, &st) == 0 && st.st_mode & S_IFDIR)
311     {
312       if (stat (file, &st) != 0)
313         fprintf (stderr,
314                  "%s: warning: %s does not exist.\n"
315                  "%s: password authentication via PAM is unlikely to work.\n",
316                  blurb(), file, blurb());
317     }
318   else if (stat (file2, &st) == 0)
319     {
320       FILE *f = fopen (file2, "r");
321       if (f)
322         {
323           Bool ok = False;
324           char buf[255];
325           while (fgets (buf, sizeof(buf), f))
326             if (strstr (buf, PAM_SERVICE_NAME))
327               {
328                 ok = True;
329                 break;
330               }
331           fclose (f);
332           if (!ok)
333             {
334               fprintf (stderr,
335                   "%s: warning: %s does not list the `%s' service.\n"
336                   "%s: password authentication via PAM is unlikely to work.\n",
337                        blurb(), file2, PAM_SERVICE_NAME, blurb());
338             }
339         }
340       /* else warn about file2 existing but being unreadable? */
341     }
342   else
343     {
344       fprintf (stderr,
345                "%s: warning: neither %s nor %s exist.\n"
346                "%s: password authentication via PAM is unlikely to work.\n",
347                blurb(), file2, file, blurb());
348     }
349
350   /* Return true anyway, just in case. */
351   return True;
352 }
353
354
355 /* This is the function PAM calls to have a conversation with the user.
356    Really, this function should be the thing that pops up dialog boxes
357    as needed, and prompts for various strings.
358
359    But, for now, xscreensaver uses its normal password-prompting dialog
360    first, and then this function simply returns the result that has been
361    typed.
362
363    This means that if PAM was using a retina scanner for auth, xscreensaver
364    would prompt for a password; then pam_conversation() would be called
365    with a string like "Please look into the retina scanner".  The user
366    would never see this string, and the prompted-for password would be
367    ignored.
368  */
369 static int
370 pam_conversation (int nmsgs,
371                   const struct pam_message **msg,
372                   struct pam_response **resp,
373                   void *closure)
374 {
375   int replies = 0;
376   struct pam_response *reply = 0;
377   struct pam_closure *c = (struct pam_closure *) closure;
378
379   /* On SunOS 5.6, the `closure' argument always comes in as random garbage. */
380   c = (struct pam_closure *) suns_pam_implementation_blows;
381
382
383   reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply));
384   if (!reply) return PAM_CONV_ERR;
385         
386   for (replies = 0; replies < nmsgs; replies++)
387     {
388       switch (msg[replies]->msg_style)
389         {
390         case PAM_PROMPT_ECHO_ON:
391           reply[replies].resp_retcode = PAM_SUCCESS;
392           reply[replies].resp = strdup (c->user);          /* freed by PAM */
393           if (c->verbose_p)
394             fprintf (stderr, "%s:     PAM ECHO_ON(\"%s\") ==> \"%s\"\n",
395                      blurb(), msg[replies]->msg,
396                      reply[replies].resp);
397           break;
398         case PAM_PROMPT_ECHO_OFF:
399           reply[replies].resp_retcode = PAM_SUCCESS;
400           reply[replies].resp = strdup (c->typed_passwd);   /* freed by PAM */
401           if (c->verbose_p)
402             fprintf (stderr, "%s:     PAM ECHO_OFF(\"%s\") ==> password\n",
403                      blurb(), msg[replies]->msg);
404           break;
405         case PAM_TEXT_INFO:
406           /* ignore it... */
407           reply[replies].resp_retcode = PAM_SUCCESS;
408           reply[replies].resp = 0;
409           if (c->verbose_p)
410             fprintf (stderr, "%s:     PAM TEXT_INFO(\"%s\") ==> ignored\n",
411                      blurb(), msg[replies]->msg);
412           break;
413         case PAM_ERROR_MSG:
414           /* ignore it... */
415           reply[replies].resp_retcode = PAM_SUCCESS;
416           reply[replies].resp = 0;
417           if (c->verbose_p)
418             fprintf (stderr, "%s:     PAM ERROR_MSG(\"%s\") ==> ignored\n",
419                      blurb(), msg[replies]->msg);
420           break;
421         default:
422           /* Must be an error of some sort... */
423           free (reply);
424           if (c->verbose_p)
425             fprintf (stderr, "%s:     PAM unknown %d(\"%s\") ==> ignored\n",
426                      blurb(), msg[replies]->msg_style, msg[replies]->msg);
427           return PAM_CONV_ERR;
428         }
429     }
430   *resp = reply;
431   return PAM_SUCCESS;
432 }
433
434 #endif /* NO_LOCKING -- whole file */