http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.02.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-2002 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     char *tty = strdup (":0.0");
216     status = pam_set_item (pamh, PAM_TTY, 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     free (tty);
221   }
222
223   /* Try to authenticate as the current user.
224      We must turn off our SIGCHLD handler for the duration of the call to
225      pam_authenticate(), because in some cases, the underlying PAM code
226      will do this:
227
228         1: fork a setuid subprocess to do some dirty work;
229         2: read a response from that subprocess;
230         3: waitpid(pid, ...) on that subprocess.
231
232     If we (the ignorant parent process) have a SIGCHLD handler, then there's
233     a race condition between steps 2 and 3: if the subprocess exits before
234     waitpid() was called, then our SIGCHLD handler fires, and gets notified
235     of the subprocess death; then PAM's call to waitpid() fails, because the
236     process has already been reaped.
237
238     I consider this a bug in PAM, since the caller should be able to have
239     whatever signal handlers it wants -- the PAM documentation doesn't say
240     "oh by the way, if you use PAM, you can't use SIGCHLD."
241    */
242
243   PAM_NO_DELAY(pamh);
244
245   block_sigchld();
246   status = pam_authenticate (pamh, 0);
247   unblock_sigchld();
248
249   if (verbose_p)
250     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
251              blurb(), status, PAM_STRERROR(pamh, status));
252   if (status == PAM_SUCCESS)  /* Win! */
253     {
254       /* Each time we successfully authenticate, refresh credentials,
255          for Kerberos/AFS/DCE/etc.  If this fails, just ignore that
256          failure and blunder along; it shouldn't matter.
257        */
258       int status2 = pam_setcred (pamh, PAM_REFRESH_CRED);
259       if (verbose_p)
260         fprintf (stderr, "%s:   pam_setcred (...) ==> %d (%s)\n",
261                  blurb(), status2, PAM_STRERROR(pamh, status2));
262       goto DONE;
263     }
264
265   /* If that didn't work, set the user to root, and try to authenticate again.
266    */
267   if (user) free (user);
268   user = strdup ("root");
269   c.user = user;
270   status = pam_set_item (pamh, PAM_USER, c.user);
271   if (verbose_p)
272     fprintf (stderr, "%s:   pam_set_item(p, PAM_USER, \"%s\") ==> %d (%s)\n",
273              blurb(), c.user, status, PAM_STRERROR(pamh, status));
274   if (status != PAM_SUCCESS) goto DONE;
275
276   PAM_NO_DELAY(pamh);
277   status = pam_authenticate (pamh, 0);
278   if (verbose_p)
279     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
280              blurb(), status, PAM_STRERROR(pamh, status));
281
282  DONE:
283   if (user) free (user);
284   if (pamh)
285     {
286       int status2 = pam_end (pamh, status);
287       pamh = 0;
288       if (verbose_p)
289         fprintf (stderr, "%s: pam_end (...) ==> %d (%s)\n",
290                  blurb(), status2,
291                  (status2 == PAM_SUCCESS ? "Success" : "Failure"));
292     }
293   return (status == PAM_SUCCESS ? True : False);
294 }
295
296
297 Bool 
298 pam_priv_init (int argc, char **argv, Bool verbose_p)
299 {
300   /* We have nothing to do at init-time.
301      However, we might as well do some error checking.
302      If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock"
303      does not exist, warn that PAM probably isn't going to work.
304
305      This is a priv-init instead of a non-priv init in case the directory
306      is unreadable or something (don't know if that actually happens.)
307    */
308   const char   dir[] = "/etc/pam.d";
309   const char  file[] = "/etc/pam.d/" PAM_SERVICE_NAME;
310   const char file2[] = "/etc/pam.conf";
311   struct stat st;
312
313   if (stat (dir, &st) == 0 && st.st_mode & S_IFDIR)
314     {
315       if (stat (file, &st) != 0)
316         fprintf (stderr,
317                  "%s: warning: %s does not exist.\n"
318                  "%s: password authentication via PAM is unlikely to work.\n",
319                  blurb(), file, blurb());
320     }
321   else if (stat (file2, &st) == 0)
322     {
323       FILE *f = fopen (file2, "r");
324       if (f)
325         {
326           Bool ok = False;
327           char buf[255];
328           while (fgets (buf, sizeof(buf), f))
329             if (strstr (buf, PAM_SERVICE_NAME))
330               {
331                 ok = True;
332                 break;
333               }
334           fclose (f);
335           if (!ok)
336             {
337               fprintf (stderr,
338                   "%s: warning: %s does not list the `%s' service.\n"
339                   "%s: password authentication via PAM is unlikely to work.\n",
340                        blurb(), file2, PAM_SERVICE_NAME, blurb());
341             }
342         }
343       /* else warn about file2 existing but being unreadable? */
344     }
345   else
346     {
347       fprintf (stderr,
348                "%s: warning: neither %s nor %s exist.\n"
349                "%s: password authentication via PAM is unlikely to work.\n",
350                blurb(), file2, file, blurb());
351     }
352
353   /* Return true anyway, just in case. */
354   return True;
355 }
356
357
358 /* This is the function PAM calls to have a conversation with the user.
359    Really, this function should be the thing that pops up dialog boxes
360    as needed, and prompts for various strings.
361
362    But, for now, xscreensaver uses its normal password-prompting dialog
363    first, and then this function simply returns the result that has been
364    typed.
365
366    This means that if PAM was using a retina scanner for auth, xscreensaver
367    would prompt for a password; then pam_conversation() would be called
368    with a string like "Please look into the retina scanner".  The user
369    would never see this string, and the prompted-for password would be
370    ignored.
371  */
372 static int
373 pam_conversation (int nmsgs,
374                   const struct pam_message **msg,
375                   struct pam_response **resp,
376                   void *closure)
377 {
378   int replies = 0;
379   struct pam_response *reply = 0;
380   struct pam_closure *c = (struct pam_closure *) closure;
381
382   /* On SunOS 5.6, the `closure' argument always comes in as random garbage. */
383   c = (struct pam_closure *) suns_pam_implementation_blows;
384
385
386   reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply));
387   if (!reply) return PAM_CONV_ERR;
388         
389   for (replies = 0; replies < nmsgs; replies++)
390     {
391       switch (msg[replies]->msg_style)
392         {
393         case PAM_PROMPT_ECHO_ON:
394           reply[replies].resp_retcode = PAM_SUCCESS;
395           reply[replies].resp = strdup (c->user);          /* freed by PAM */
396           if (c->verbose_p)
397             fprintf (stderr, "%s:     PAM ECHO_ON(\"%s\") ==> \"%s\"\n",
398                      blurb(), msg[replies]->msg,
399                      reply[replies].resp);
400           break;
401         case PAM_PROMPT_ECHO_OFF:
402           reply[replies].resp_retcode = PAM_SUCCESS;
403           reply[replies].resp = strdup (c->typed_passwd);   /* freed by PAM */
404           if (c->verbose_p)
405             fprintf (stderr, "%s:     PAM ECHO_OFF(\"%s\") ==> password\n",
406                      blurb(), msg[replies]->msg);
407           break;
408         case PAM_TEXT_INFO:
409           /* ignore it... */
410           reply[replies].resp_retcode = PAM_SUCCESS;
411           reply[replies].resp = 0;
412           if (c->verbose_p)
413             fprintf (stderr, "%s:     PAM TEXT_INFO(\"%s\") ==> ignored\n",
414                      blurb(), msg[replies]->msg);
415           break;
416         case PAM_ERROR_MSG:
417           /* ignore it... */
418           reply[replies].resp_retcode = PAM_SUCCESS;
419           reply[replies].resp = 0;
420           if (c->verbose_p)
421             fprintf (stderr, "%s:     PAM ERROR_MSG(\"%s\") ==> ignored\n",
422                      blurb(), msg[replies]->msg);
423           break;
424         default:
425           /* Must be an error of some sort... */
426           free (reply);
427           if (c->verbose_p)
428             fprintf (stderr, "%s:     PAM unknown %d(\"%s\") ==> ignored\n",
429                      blurb(), msg[replies]->msg_style, msg[replies]->msg);
430           return PAM_CONV_ERR;
431         }
432     }
433   *resp = reply;
434   return PAM_SUCCESS;
435 }
436
437 #endif /* NO_LOCKING -- whole file */