f7d8528fd1a8e665caa8945bf7520258383b7e9e
[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-1998 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
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18
19 #ifndef NO_LOCKING  /* whole file */
20
21 #include <stdlib.h>
22 #ifdef HAVE_UNISTD_H
23 # include <unistd.h>
24 #endif
25
26 extern char *blurb(void);
27
28
29 #include <stdio.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <pwd.h>
33 #include <grp.h>
34 #include <security/pam_appl.h>
35
36 #include <sys/stat.h>
37
38
39 /* blargh */
40 #undef  Bool
41 #undef  True
42 #undef  False
43 #define Bool  int
44 #define True  1
45 #define False 0
46
47 #undef countof
48 #define countof(x) (sizeof((x))/sizeof(*(x)))
49
50 static int pam_conversation (int nmsgs,
51                              const struct pam_message **msg,
52                              struct pam_response **resp,
53                              void *closure);
54
55 struct pam_closure {
56   const char *user;
57   const char *typed_passwd;
58   Bool verbose_p;
59 };
60
61
62 /* PAM sucks in that there is no way to tell whether a particular service
63    is configured at all.  That is, there is no way to tell the difference
64    between "authentication of the FOO service is not allowed" and "the
65    user typed the wrong password."
66
67    On RedHat 5.1 systems, if a service name is not known, it defaults to
68    being not allowed (because the fallback service, /etc/pam.d/other, is
69    set to `pam_deny'.)
70
71    On Solaris 2.6 systems, unknown services default to authenticating normally.
72
73    So, we could simply require that the person who installs xscreensaver
74    set up an "xscreensaver" PAM service.  However, if we went that route,
75    it would have a really awful failure mode: the failure mode would be that
76    xscreensaver was willing to *lock* the screen, but would be unwilling to
77    *unlock* the screen.  (With the non-PAM password code, the analagous
78    situation -- security not being configured properly, for example do to the
79    executable not being installed as setuid root -- the failure mode is much
80    more palettable, in that xscreensaver will refuse to *lock* the screen,
81    because it can know up front that there is no password that will work.)
82
83    Another route would be to have the service name to consult be computed at
84    compile-time (perhaps with a configure option.)  However, that doesn't
85    really solve the problem, because it means that the same executable might
86    work fine on one machine, but refuse to unlock when run on another
87    machine.
88
89    Another alternative would be to look in /etc/pam.conf or /etc/pam.d/ at
90    runtime to see what services actually exist.  But I think that's no good,
91    because who is to say that the PAM info is actually specified in those
92    files?  Opening and reading those files is not a part of the PAM client
93    API, so it's not guarenteed to work on any given system.
94
95    An alternative I tried was to specify a list of services to try, and to
96    try them all in turn ("xscreensaver", "xlock", "xdm", and "login").
97    This worked, but it was slow (and I also had to do some contortions to
98    work around bugs in Linux PAM 0.64-3.)
99
100    So what we do today is, try PAM once, and if that fails, try the usual
101    getpwent() method.  So if PAM doesn't work, it will at least make an
102    attempt at looking up passwords in /etc/passwd or /etc/shadow instead.
103
104    This all kind of blows.  I'm not sure what else to do.
105  */
106
107
108 /* This can be called at any time, and says whether the typed password
109    belongs to either the logged in user (real uid, not effective); or
110    to root.
111  */
112 Bool
113 pam_passwd_valid_p (const char *typed_passwd, Bool verbose_p)
114 {
115   const char *service = PAM_SERVICE_NAME;
116   pam_handle_t *pamh = 0;
117   int status = -1;
118   struct pam_conv pc;
119   struct pam_closure c;
120   char *user = 0;
121
122   struct passwd *p = getpwuid (getuid ());
123   if (!p) return False;
124
125   user = strdup (p->pw_name);
126
127   c.user = user;
128   c.typed_passwd = typed_passwd;
129   c.verbose_p = verbose_p;
130
131   pc.conv = &pam_conversation;
132   pc.appdata_ptr = (void *) &c;
133
134
135   /* Initialize PAM.
136    */
137   status = pam_start (service, c.user, &pc, &pamh);
138   if (verbose_p)
139     fprintf (stderr, "%s: pam_start (\"%s\", \"%s\", ...) ==> %d (%s)\n",
140              blurb(), service, c.user,
141              status, pam_strerror (pamh, status));
142   if (status != PAM_SUCCESS) goto DONE;
143
144 # ifdef HAVE_PAM_FAIL_DELAY
145   pam_fail_delay (pamh, 0);     /* We handle delays ourself. */
146 # endif /* HAVE_PAM_FAIL_DELAY */
147
148   /* #### We should set PAM_TTY to the display we're using, but we
149      don't have that handy from here.  So set it to :0.0, which is a
150      good guess (and has the bonus of counting as a "secure tty" as
151      far as PAM is concerned...)
152    */
153   {
154     const char *tty = ":0.0";
155     status = pam_set_item (pamh, PAM_TTY, strdup(tty));
156     if (verbose_p)
157       fprintf (stderr, "%s:   pam_set_item (p, PAM_TTY, \"%s\") ==> %d (%s)\n",
158                blurb(), tty, status, pam_strerror(pamh, status));
159   }
160
161   /* Try to authenticate as the current user.
162    */
163   status = pam_authenticate (pamh, 0);
164   if (verbose_p)
165     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
166              blurb(), status, pam_strerror(pamh, status));
167   if (status == PAM_SUCCESS)  /* Win! */
168     goto DONE;
169
170   /* If that didn't work, set the user to root, and try to authenticate again.
171    */
172   c.user = "root";
173   status = pam_set_item (pamh, PAM_USER, strdup(c.user));
174   if (verbose_p)
175     fprintf (stderr, "%s:   pam_set_item(p, PAM_USER, \"%s\") ==> %d (%s)\n",
176              blurb(), c.user, status, pam_strerror(pamh, status));
177   if (status != PAM_SUCCESS) goto DONE;
178
179   status = pam_authenticate (pamh, 0);
180   if (verbose_p)
181     fprintf (stderr, "%s:   pam_authenticate (...) ==> %d (%s)\n",
182              blurb(), status, pam_strerror(pamh, status));
183
184  DONE:
185   if (user) free (user);
186   if (pamh)
187     {
188       int status2 = pam_end (pamh, status);
189       pamh = 0;
190       if (verbose_p)
191         fprintf (stderr, "%s: pam_end (...) ==> %d (%s)\n",
192                  blurb(), status2,
193                  (status2 == PAM_SUCCESS ? "Success" : "Failure"));
194     }
195   return (status == PAM_SUCCESS ? True : False);
196 }
197
198
199 Bool 
200 pam_lock_init (int argc, char **argv, Bool verbose_p)
201 {
202   /* We have nothing to do at init-time.
203      However, we might as well do some error checking.
204      If "/etc/pam.d" exists and is a directory, but "/etc/pam.d/xlock"
205      does not exist, warn that PAM probably isn't going to work.
206    */
207   const char  dir[] = "/etc/pam.d";
208   const char file[] = "/etc/pam.d/" PAM_SERVICE_NAME;
209   struct stat st;
210   if (stat (dir, &st) == 0 && st.st_mode & S_IFDIR)
211     if (stat (file, &st) != 0)
212       fprintf (stderr,
213                "%s: warning: %s does not exist.\n"
214                "%s: password authentication via PAM is unlikely to work.\n",
215                blurb(), file, blurb());
216
217   /* Return true anyway, just in case. */
218   return True;
219 }
220
221
222 /* This is the function PAM calls to have a conversation with the user.
223    Really, this function should be the thing that pops up dialog boxes
224    as needed, and prompts for various strings.
225
226    But, for now, xscreensaver uses its normal password-prompting dialog
227    first, and then this function simply returns the result that has been
228    typed.
229
230    This means that if PAM was using a retina scanner for auth, xscreensaver
231    would prompt for a password; then pam_conversation() would be called
232    with a string like "Please look into the retina scanner".  The user
233    would never see this string, and the prompted-for password would be
234    ignored.
235  */
236 static int
237 pam_conversation (int nmsgs,
238                   const struct pam_message **msg,
239                   struct pam_response **resp,
240                   void *closure)
241 {
242   int replies = 0;
243   struct pam_response *reply = 0;
244   struct pam_closure *c = (struct pam_closure *) closure;
245
246   reply = (struct pam_response *) calloc (nmsgs, sizeof (*reply));
247   if (!reply) return PAM_CONV_ERR;
248         
249   for (replies = 0; replies < nmsgs; replies++)
250     {
251       switch (msg[replies]->msg_style)
252         {
253         case PAM_PROMPT_ECHO_ON:
254           reply[replies].resp_retcode = PAM_SUCCESS;
255           reply[replies].resp = strdup (c->user);          /* freed by PAM */
256           if (c->verbose_p)
257             fprintf (stderr, "%s:     PAM ECHO_ON(\"%s\") ==> \"%s\"\n",
258                      blurb(), msg[replies]->msg,
259                      reply[replies].resp);
260           break;
261         case PAM_PROMPT_ECHO_OFF:
262           reply[replies].resp_retcode = PAM_SUCCESS;
263           reply[replies].resp = strdup (c->typed_passwd);   /* freed by PAM */
264           if (c->verbose_p)
265             fprintf (stderr, "%s:     PAM ECHO_OFF(\"%s\") ==> password\n",
266                      blurb(), msg[replies]->msg);
267           break;
268         case PAM_TEXT_INFO:
269           /* ignore it... */
270           reply[replies].resp_retcode = PAM_SUCCESS;
271           reply[replies].resp = 0;
272           if (c->verbose_p)
273             fprintf (stderr, "%s:     PAM TEXT_INFO(\"%s\") ==> ignored\n",
274                      blurb(), msg[replies]->msg);
275           break;
276         case PAM_ERROR_MSG:
277           /* ignore it... */
278           reply[replies].resp_retcode = PAM_SUCCESS;
279           reply[replies].resp = 0;
280           if (c->verbose_p)
281             fprintf (stderr, "%s:     PAM ERROR_MSG(\"%s\") ==> ignored\n",
282                      blurb(), msg[replies]->msg);
283           break;
284         default:
285           /* Must be an error of some sort... */
286           free (reply);
287           if (c->verbose_p)
288             fprintf (stderr, "%s:     PAM unknown %d(\"%s\") ==> ignored\n",
289                      blurb(), msg[replies]->msg_style, msg[replies]->msg);
290           return PAM_CONV_ERR;
291         }
292     }
293   *resp = reply;
294   return PAM_SUCCESS;
295 }
296
297 #endif /* NO_LOCKING -- whole file */