pull-prototypes-to-head-20020821
[openafs.git] / src / pam / afs_auth.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13 RCSID("$Header$");
14
15 #include <security/pam_appl.h>
16 #include <security/pam_modules.h>
17 #include <syslog.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <pwd.h>
21 #include <unistd.h>
22 #include <sys/param.h>
23 #include <afs/kautils.h>
24 #include "afs_message.h"
25 #include "afs_util.h"
26 #include <signal.h>
27 #include <sys/wait.h>
28 #include <errno.h>
29
30 #define RET(x) { retcode = (x); goto out; }
31
32 extern int
33 pam_sm_authenticate(
34         pam_handle_t    *pamh,
35         int             flags,
36         int             argc,
37         const char      **argv)
38 {
39     int retcode = PAM_SUCCESS;
40     int errcode = PAM_SUCCESS;
41     int code;
42     int origmask;
43     int logmask = LOG_UPTO(LOG_INFO);
44     int nowarn = 0;
45     int use_first_pass = 0;
46     int try_first_pass = 0;
47     int ignore_uid  = 0;
48     uid_t ignore_uid_id = 0;
49     char my_password_buf[256];
50     char *cell_ptr=NULL;
51     /*
52      * these options are added to handle stupid apps, which won't call
53      * pam_set_cred()
54      */
55     int refresh_token = 0;
56     int set_token = 0;
57     int dont_fork = 0;
58     /* satisfy kdm 2.x
59      */
60     int use_klog = 0;
61     int set_expires = 0;  /* This option is only used in pam_set_cred() */
62     int got_authtok = 0;        /* got PAM_AUTHTOK upon entry */
63     char *user = NULL, *password = NULL;
64     long password_expires = -1;
65     int torch_password = 1;
66     int i;
67     struct pam_conv *pam_convp = NULL;
68     int auth_ok;
69     struct passwd unix_pwd, *upwd = NULL;
70     char upwd_buf[2048];        /* size is a guess. */
71     char*       reason = NULL;
72     pid_t cpid, rcpid;
73     int   status;
74     struct sigaction newAction, origAction;
75
76
77 #ifndef AFS_SUN56_ENV
78     openlog(pam_afs_ident, LOG_CONS|LOG_PID, LOG_AUTH);
79 #endif
80     origmask = setlogmask(logmask);
81
82     /*
83      * Parse the user options.  Log an error for any unknown options.
84      */
85     for (i = 0; i < argc; i++) {
86         if (       strcasecmp(argv[i], "debug"         ) == 0) {
87             logmask |= LOG_MASK(LOG_DEBUG);
88             (void) setlogmask(logmask);
89         } else if (strcasecmp(argv[i], "nowarn"        ) == 0) {
90             nowarn = 1;
91         } else if (strcasecmp(argv[i], "use_first_pass") == 0) {
92             use_first_pass = 1;
93         } else if (strcasecmp(argv[i], "try_first_pass") == 0) {
94             try_first_pass = 1;
95         } else if (strcasecmp(argv[i], "ignore_root"   ) == 0) {
96             ignore_uid = 1;
97             ignore_uid_id = 0;
98         } else if (strcasecmp(argv[i], "ignore_uid"    ) == 0) {
99             i++;
100             if (i == argc) {
101                 pam_afs_syslog(LOG_ERR, PAMAFS_IGNOREUID, "ignore_uid missing argument");
102                 ignore_uid = 0;
103             } else {
104                 ignore_uid = 1;
105                 ignore_uid_id = (uid_t) strtol(argv[i], (char**)NULL, 10);
106                 if ( (ignore_uid_id  < 0) || (ignore_uid_id > IGNORE_MAX)) {
107                         ignore_uid = 0;
108                         pam_afs_syslog(LOG_ERR, PAMAFS_IGNOREUID, argv[i]);
109                 }
110             }
111         } else if (strcasecmp(argv[i], "cell") == 0) {
112             i++;
113             if (i == argc) {
114                 pam_afs_syslog(LOG_ERR, PAMAFS_OTHERCELL, "cell missing argument");
115             } else {
116                 cell_ptr=argv[i];
117                 pam_afs_syslog(LOG_INFO, PAMAFS_OTHERCELL, cell_ptr);
118             }       
119         } else if (strcasecmp(argv[i], "refresh_token" ) == 0) {
120             refresh_token = 1;
121         } else if (strcasecmp(argv[i], "set_token" ) == 0) {
122             set_token = 1;
123         } else if (strcasecmp(argv[i], "dont_fork" ) == 0) {
124             if (!use_klog) dont_fork = 1;
125             else pam_afs_syslog(LOG_ERR, PAMAFS_CONFLICTOPT, "dont_fork");
126         } else if (strcasecmp(argv[i], "use_klog" ) == 0) {
127             if (!dont_fork) use_klog = 1;
128             else pam_afs_syslog(LOG_ERR, PAMAFS_CONFLICTOPT, "use_klog");
129         } else if (strcasecmp(argv[i], "setenv_password_expires") == 0) {
130             set_expires = 1;
131         } else {
132             pam_afs_syslog(LOG_ERR, PAMAFS_UNKNOWNOPT, argv[i]);
133         }
134     }
135
136     /* Later we use try_first_pass to see if we can try again.    */
137     /* If use_first_pass is true we don't want to ever try again, */
138     /* so turn that flag off right now.                           */
139     if (use_first_pass) try_first_pass = 0;
140
141     if (logmask && LOG_MASK(LOG_DEBUG))
142             pam_afs_syslog(LOG_DEBUG, PAMAFS_OPTIONS, nowarn, use_first_pass, try_first_pass, ignore_uid, ignore_uid_id, refresh_token, set_token, dont_fork, use_klog);
143
144     /* Try to get the user-interaction info, if available. */
145     errcode = pam_get_item(pamh, PAM_CONV, (const void **) &pam_convp);
146     if (errcode != PAM_SUCCESS) {
147         pam_afs_syslog(LOG_WARNING, PAMAFS_NO_USER_INT);
148         pam_convp = NULL;
149     }
150
151     /* Who are we trying to authenticate here? */
152     if ((errcode = pam_get_user(pamh, (const char **)&user, "login: ")) != PAM_SUCCESS) {
153         pam_afs_syslog(LOG_ERR, PAMAFS_NOUSER, errcode);
154         RET(PAM_USER_UNKNOWN);
155     }
156
157     if (logmask && LOG_MASK(LOG_DEBUG))
158             pam_afs_syslog(LOG_DEBUG, PAMAFS_USERNAMEDEBUG, user);
159
160     /*
161      * If the user has a "local" (or via nss, possibly nss_dce) pwent,
162      * and its uid==0, and "ignore_root" was given in pam.conf,
163      * ignore the user.
164      */
165     /* enhanced: use "ignore_uid <number>" to specify the largest uid
166      * which should be ignored by this module
167      */
168 #if     defined(AFS_HPUX_ENV)
169 #if     defined(AFS_HPUX110_ENV)
170     i = getpwnam_r(user, &unix_pwd, upwd_buf, sizeof(upwd_buf), &upwd);
171 #else   /* AFS_HPUX110_ENV */
172     i = getpwnam_r(user, &unix_pwd, upwd_buf, sizeof(upwd_buf));
173     if ( i == 0 )                       /* getpwnam_r success */
174         upwd = &unix_pwd; 
175 #endif  /* else AFS_HPUX110_ENV */
176     if (ignore_uid && i == 0  && upwd->pw_uid <= ignore_uid_id) {
177         pam_afs_syslog(LOG_INFO, PAMAFS_IGNORINGROOT, user);
178         RET(PAM_AUTH_ERR);
179     }
180 #else
181 #if     defined(AFS_LINUX20_ENV) || defined(AFS_FBSD_ENV)
182     upwd = getpwnam(user);
183 #else
184     upwd = getpwnam_r(user, &unix_pwd, upwd_buf, sizeof(upwd_buf));
185 #endif
186     if (ignore_uid && upwd != NULL && upwd->pw_uid <= ignore_uid_id) {
187         pam_afs_syslog(LOG_INFO, PAMAFS_IGNORINGROOT, user);
188         RET(PAM_AUTH_ERR);
189     }
190 #endif
191     errcode = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &password);
192     if (errcode != PAM_SUCCESS || password == NULL) {
193         if (use_first_pass) {
194             pam_afs_syslog(LOG_ERR, PAMAFS_PASSWD_REQ, user);
195             RET(PAM_AUTH_ERR);
196         }
197         password = NULL;        /* In case it isn't already NULL */
198         if (logmask && LOG_MASK(LOG_DEBUG))
199             pam_afs_syslog(LOG_DEBUG, PAMAFS_NOFIRSTPASS, user);
200     } else if (password[0] == '\0') {
201         /* Actually we *did* get one but it was empty. */
202         torch_password = 0;
203         pam_afs_syslog(LOG_INFO, PAMAFS_NILPASSWORD, user);
204         RET(PAM_NEW_AUTHTOK_REQD);
205     } else {
206         if (logmask && LOG_MASK(LOG_DEBUG))
207             pam_afs_syslog(LOG_DEBUG, PAMAFS_GOTPASS, user);
208         torch_password = 0;
209         got_authtok = 1;
210     }
211     if (!(use_first_pass || try_first_pass)) {
212         password = NULL;
213     }
214
215 try_auth:
216     if (password == NULL) {
217
218         torch_password = 1;
219
220         if (use_first_pass)
221             RET(PAM_AUTH_ERR);  /* shouldn't happen */
222         if (try_first_pass)
223             try_first_pass = 0; /* we come back if try_first_pass==1 below */
224
225         if (pam_convp == NULL || pam_convp->conv == NULL) {
226             pam_afs_syslog(LOG_ERR, PAMAFS_CANNOT_PROMPT);
227             RET(PAM_AUTH_ERR);
228         }
229
230         errcode = pam_afs_prompt(pam_convp, &password, 0, PAMAFS_PWD_PROMPT);
231         if (errcode != PAM_SUCCESS || password == NULL) {
232             pam_afs_syslog(LOG_ERR, PAMAFS_GETPASS_FAILED);
233             RET(PAM_AUTH_ERR);
234         }
235         if (password[0] == '\0') {
236             pam_afs_syslog(LOG_INFO, PAMAFS_NILPASSWORD, user);
237             RET(PAM_NEW_AUTHTOK_REQD);
238         }
239
240         /*
241          * We aren't going to free the password later (we will wipe it,
242          * though), because the storage for it if we get it from other
243          * paths may belong to someone else.  Since we do need to free
244          * this storage, copy it to a buffer that won't need to be freed
245          * later, and free this storage now.
246          */
247
248         strncpy(my_password_buf, password, sizeof(my_password_buf));
249         my_password_buf[sizeof(my_password_buf)-1] = '\0';
250         memset(password, 0, strlen(password));
251         free(password);
252         password = my_password_buf;
253
254     }
255
256     /* Be sure to allocate a PAG here if we should set a token,
257      * All of the remaining stuff to authenticate the user and to
258      * get a token is done in a child process - if not suppressed by the config,
259      * see below
260      * But dont get a PAG if the refresh_token option was set
261      * We have to do this in such a way because some
262      * apps (such as screensavers) wont call setcred but authenticate :-(
263      */
264     if (!refresh_token) {
265        setpag();
266 #ifdef AFS_KERBEROS_ENV
267        ktc_newpag();
268 #endif
269        if (logmask && LOG_MASK(LOG_DEBUG))
270          syslog(LOG_DEBUG, "New PAG created in pam_authenticate()");
271     }
272
273     if (!dont_fork) {
274     /* Prepare for fork(): set SIGCHLD signal handler to default */
275     sigemptyset(&newAction.sa_mask);
276     newAction.sa_handler   = SIG_DFL;
277     newAction.sa_flags     = 0;
278     code = sigaction(SIGCHLD, &newAction, &origAction);
279     if (code) {
280        pam_afs_syslog(LOG_ERR, PAMAFS_PAMERROR, errno);
281        RET(PAM_AUTH_ERR);
282     }
283
284     /* Fork a process and let it verify authentication. So that any
285      * memory/sockets allocated will get cleaned up when the child
286      * exits: defect 11686.
287      */
288         if (use_klog) { /* used by kdm 2.x */
289            if (refresh_token || set_token) {
290               i = do_klog(user, password, NULL, cell_ptr);
291            } else {
292               i = do_klog(user, password, "00:00:01", cell_ptr);
293               ktc_ForgetAllTokens();
294            }
295            if (logmask && LOG_MASK(LOG_DEBUG))
296              syslog(LOG_DEBUG, "do_klog returned %d", i);
297            auth_ok = i ? 0 : 1;
298         } else {
299           if (logmask && LOG_MASK(LOG_DEBUG))
300             syslog(LOG_DEBUG, "forking ...");
301     cpid = fork();
302     if (cpid <= 0) {     /* The child process */
303       if (logmask && LOG_MASK(LOG_DEBUG))
304         syslog(LOG_DEBUG, "in child");
305               if (refresh_token || set_token)
306                  code = ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION,
307                                     user, /* kerberos name */
308                                     NULL, /* instance */
309                                     cell_ptr, /* realm */
310                                     password, /* password */
311                                     0, /* default lifetime */
312                                     &password_expires,
313                                     0, /* spare 2 */
314                                     &reason /* error string */ );
315               else
316                  code = ka_VerifyUserPassword(KA_USERAUTH_VERSION,
317                                     user, /* kerberos name */
318                                     NULL, /* instance */
319                                     cell_ptr, /* realm */
320                                     password, /* password */
321                                     0, /* spare 2 */
322                                     &reason /* error string */ );
323        if (code) {
324           pam_afs_syslog(LOG_ERR, PAMAFS_LOGIN_FAILED, user, reason);
325           auth_ok = 0;
326        } else {
327           auth_ok = 1;
328        }
329        if (logmask && LOG_MASK(LOG_DEBUG))
330          syslog(LOG_DEBUG, "child: auth_ok=%d", auth_ok);
331        if (cpid == 0) exit(auth_ok);
332     } else {
333        do {
334          if (logmask && LOG_MASK(LOG_DEBUG))
335            syslog(LOG_DEBUG, "in parent, waiting ...");
336           rcpid = waitpid(cpid, &status, 0);
337        } while ((rcpid == -1) && (errno == EINTR));
338         
339        if ((rcpid == cpid) && WIFEXITED(status)) {
340           auth_ok = WEXITSTATUS(status);
341        } else {
342           auth_ok = 0;
343        }
344        if (logmask && LOG_MASK(LOG_DEBUG))
345          syslog(LOG_DEBUG, "parent: auth_ok=%d", auth_ok);
346            }
347     }
348     /* Restore old signal handler */
349     code = sigaction(SIGCHLD, &origAction, NULL);
350     if (code) {
351        pam_afs_syslog(LOG_ERR, PAMAFS_PAMERROR, errno);
352     }
353     } else { /* dont_fork, used by httpd */
354       if (logmask && LOG_MASK(LOG_DEBUG))
355         syslog(LOG_DEBUG, "dont_fork");
356         if (refresh_token || set_token)
357             code = ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION,
358                                     user, /* kerberos name */
359                                     NULL, /* instance */
360                                     cell_ptr, /* realm */
361                                     password, /* password */
362                                     0, /* default lifetime */
363                                     &password_expires,
364                                     0, /* spare 2 */
365                                     &reason /* error string */ );
366         else
367             code = ka_VerifyUserPassword(KA_USERAUTH_VERSION,
368                                     user, /* kerberos name */
369                                     NULL, /* instance */
370                                     cell_ptr, /* realm */
371                                     password, /* password */
372                                     0, /* spare 2 */
373                                     &reason /* error string */ );
374         if (logmask && LOG_MASK(LOG_DEBUG))
375           syslog(LOG_DEBUG, "dont_fork, code = %d",code);
376         if (code) {
377             pam_afs_syslog(LOG_ERR, PAMAFS_LOGIN_FAILED, user, reason);
378             auth_ok = 0;
379         } else {
380             auth_ok = 1;
381         }
382         if (logmask && LOG_MASK(LOG_DEBUG))
383           syslog(LOG_DEBUG, "dont_fork: auth_ok=%d", auth_ok);
384     }
385
386     if (!auth_ok && try_first_pass) {
387         password = NULL;
388         goto try_auth;
389     }
390
391     /* We don't care if this fails; all we can do is try. */
392     /* It is not reasonable to store the password only if it was correct
393      * because it could satisfy another module that is called in the chain
394      * after pam_afs
395      */
396     if (!got_authtok) {
397         torch_password = 0;
398         (void) pam_set_item(pamh, PAM_AUTHTOK, password);
399     }
400
401     if (logmask && LOG_MASK(LOG_DEBUG))
402       syslog(LOG_DEBUG, "leaving auth: auth_ok=%d", auth_ok);
403     if (code == KANOENT) RET(PAM_USER_UNKNOWN);
404     RET(auth_ok ? PAM_SUCCESS : PAM_AUTH_ERR);
405         
406  out:
407     if ( password  )
408     {
409         /* we store the password in the data portion */
410         char*   tmp = strdup(password);
411         (void) pam_set_data(pamh, pam_afs_lh, tmp, lc_cleanup);
412         if ( torch_password) memset(password, 0, strlen(password));
413     }
414     (void) setlogmask(origmask);
415 #ifndef AFS_SUN56_ENV
416     closelog();
417 #endif
418     return retcode;
419 }