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