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