1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 #include <libintl.h>
28 #include <security/pam_appl.h>
29 #include <security/pam_modules.h>
30 #include <string.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <pwd.h>
35 #include <syslog.h>
36 #include <libintl.h>
37 #include <k5-int.h>
38 #include <netdb.h>
39 #include <unistd.h>
40 #include <sys/stat.h>
41 #include <fcntl.h>
42 #include <errno.h>
43 #include <com_err.h>
44
45 #include "utils.h"
46 #include "krb5_repository.h"
47
48 #define PAMTXD "SUNW_OST_SYSOSPAM"
49 #define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */
50
51 extern void krb5_cleanup(pam_handle_t *, void *, int);
52
53 static int attempt_refresh_cred(krb5_module_data_t *, char *, int);
54 static int attempt_delete_initcred(krb5_module_data_t *);
55 static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal,
56 krb5_principal, int);
57
58 extern uint_t kwarn_add_warning(char *, int);
59 extern uint_t kwarn_del_warning(char *);
60
61 /*
62 * pam_sm_setcred
63 */
64 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)65 pam_sm_setcred(
66 pam_handle_t *pamh,
67 int flags,
68 int argc,
69 const char **argv)
70 {
71 int i;
72 int err = 0;
73 int debug = 0;
74 krb5_module_data_t *kmd = NULL;
75 char *user = NULL;
76 krb5_repository_data_t *krb5_data = NULL;
77 pam_repository_t *rep_data = NULL;
78
79 for (i = 0; i < argc; i++) {
80 if (strcasecmp(argv[i], "debug") == 0)
81 debug = 1;
82 else if (strcasecmp(argv[i], "nowarn") == 0)
83 flags = flags | PAM_SILENT;
84 }
85
86 if (debug)
87 __pam_log(LOG_AUTH | LOG_DEBUG,
88 "PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x",
89 flags & PAM_SILENT ? 1 : 0, flags);
90
91 /* make sure flags are valid */
92 if (flags &&
93 !(flags & PAM_ESTABLISH_CRED) &&
94 !(flags & PAM_REINITIALIZE_CRED) &&
95 !(flags & PAM_REFRESH_CRED) &&
96 !(flags & PAM_DELETE_CRED) &&
97 !(flags & PAM_SILENT)) {
98 __pam_log(LOG_AUTH | LOG_ERR,
99 "PAM-KRB5 (setcred): illegal flag %d", flags);
100 err = PAM_SYSTEM_ERR;
101 goto out;
102 }
103
104 (void) pam_get_item(pamh, PAM_USER, (void**) &user);
105
106 if (user == NULL || *user == '\0')
107 return (PAM_USER_UNKNOWN);
108
109 if (pam_get_data(pamh, KRB5_DATA, (const void**)&kmd) != PAM_SUCCESS) {
110 if (debug) {
111 __pam_log(LOG_AUTH | LOG_DEBUG,
112 "PAM-KRB5 (setcred): kmd get failed, kmd=0x%p",
113 kmd);
114 }
115
116 /*
117 * User doesn't need to authenticate for PAM_REFRESH_CRED
118 * or for PAM_DELETE_CRED
119 */
120 if (flags & (PAM_REFRESH_CRED|PAM_DELETE_CRED)) {
121 __pam_log(LOG_AUTH | LOG_DEBUG,
122 "PAM-KRB5 (setcred): inst kmd structure");
123
124 kmd = calloc(1, sizeof (krb5_module_data_t));
125
126 if (kmd == NULL)
127 return (PAM_BUF_ERR);
128
129
130 /*
131 * Need to initialize auth_status here to
132 * PAM_AUTHINFO_UNAVAIL else there is a false positive
133 * of PAM_SUCCESS.
134 */
135 kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
136
137 if ((err = pam_set_data(pamh, KRB5_DATA,
138 kmd, &krb5_cleanup)) != PAM_SUCCESS) {
139 free(kmd);
140 return (PAM_SYSTEM_ERR);
141 }
142 } else {
143 /*
144 * This could mean that we are not the account authority
145 * for the authenticated user. Therefore we should
146 * return PAM_IGNORE in order to not affect the
147 * login process of said user.
148 */
149 err = PAM_IGNORE;
150 goto out;
151 }
152
153 } else { /* pam_get_data success */
154 if (kmd == NULL) {
155 if (debug) {
156 __pam_log(LOG_AUTH | LOG_DEBUG,
157 "PAM-KRB5 (setcred): kmd structure"
158 " gotten but is NULL for user %s", user);
159 }
160 err = PAM_SYSTEM_ERR;
161 goto out;
162 }
163
164 if (debug)
165 __pam_log(LOG_AUTH | LOG_DEBUG,
166 "PAM-KRB5 (setcred): kmd auth_status: %s",
167 pam_strerror(pamh, kmd->auth_status));
168
169 /*
170 * pam_auth has set status to ignore, so we also return ignore
171 */
172 if (kmd->auth_status == PAM_IGNORE) {
173 err = PAM_IGNORE;
174 goto out;
175 }
176 }
177
178 kmd->debug = debug;
179
180 /*
181 * User must have passed pam_authenticate()
182 * in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED
183 */
184 if ((flags & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED)) &&
185 (kmd->auth_status != PAM_SUCCESS)) {
186 if (kmd->debug)
187 __pam_log(LOG_AUTH | LOG_DEBUG,
188 "PAM-KRB5 (setcred): unable to "
189 "setcreds, not authenticated!");
190 return (PAM_CRED_UNAVAIL);
191 }
192
193 /*
194 * We cannot assume that kmd->kcontext being non-NULL
195 * means it is valid. Other pam_krb5 mods may have
196 * freed it but not reset it to NULL.
197 * Log a message when debugging to track down memory
198 * leaks.
199 */
200 if (kmd->kcontext != NULL && kmd->debug)
201 __pam_log(LOG_AUTH | LOG_DEBUG,
202 "PAM-KRB5 (setcred): kcontext != NULL, "
203 "possible memory leak.");
204
205 /*
206 * Use the authenticated and validated user, if applicable.
207 */
208 if (kmd->user != NULL)
209 user = kmd->user;
210
211 /*
212 * If auth was short-circuited we will not have anything to
213 * renew, so just return here.
214 */
215 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
216
217 if (rep_data != NULL) {
218 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
219 if (debug)
220 __pam_log(LOG_AUTH | LOG_DEBUG,
221 "PAM-KRB5 (setcred): wrong"
222 "repository found (%s), returning "
223 "PAM_IGNORE", rep_data->type);
224 return (PAM_IGNORE);
225 }
226 if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
227 krb5_data = (krb5_repository_data_t *)rep_data->scope;
228
229 if (krb5_data->flags ==
230 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
231 krb5_data->principal != NULL &&
232 strlen(krb5_data->principal)) {
233 if (debug)
234 __pam_log(LOG_AUTH | LOG_DEBUG,
235 "PAM-KRB5 (setcred): "
236 "Principal %s already "
237 "authenticated, "
238 "cannot setcred",
239 krb5_data->principal);
240 return (PAM_SUCCESS);
241 }
242 }
243 }
244
245 if (flags & PAM_REINITIALIZE_CRED)
246 err = attempt_refresh_cred(kmd, user, PAM_REINITIALIZE_CRED);
247 else if (flags & PAM_REFRESH_CRED)
248 err = attempt_refresh_cred(kmd, user, PAM_REFRESH_CRED);
249 else if (flags & PAM_DELETE_CRED)
250 err = attempt_delete_initcred(kmd);
251 else {
252 /*
253 * Default case: PAM_ESTABLISH_CRED
254 */
255 err = attempt_refresh_cred(kmd, user, PAM_ESTABLISH_CRED);
256 }
257
258 if (err != PAM_SUCCESS)
259 __pam_log(LOG_AUTH | LOG_ERR,
260 "PAM-KRB5 (setcred): pam_setcred failed "
261 "for %s (%s).", user, pam_strerror(pamh, err));
262
263 out:
264 if (kmd && kmd->kcontext) {
265 /*
266 * free 'kcontext' field if it is allocated,
267 * kcontext is local to the operation being performed
268 * not considered global to the entire pam module.
269 */
270 krb5_free_context(kmd->kcontext);
271 kmd->kcontext = NULL;
272 }
273
274 /*
275 * 'kmd' is not freed here, it is handled in krb5_cleanup
276 */
277 if (debug)
278 __pam_log(LOG_AUTH | LOG_DEBUG,
279 "PAM-KRB5 (setcred): end: %s",
280 pam_strerror(pamh, err));
281 return (err);
282 }
283
284 static int
attempt_refresh_cred(krb5_module_data_t * kmd,char * user,int flag)285 attempt_refresh_cred(
286 krb5_module_data_t *kmd,
287 char *user,
288 int flag)
289 {
290 krb5_principal me;
291 krb5_principal server;
292 krb5_error_code code;
293 char kuser[2*MAXHOSTNAMELEN];
294 krb5_data tgtname = {
295 0,
296 KRB5_TGS_NAME_SIZE,
297 KRB5_TGS_NAME
298 };
299
300 /* Create a new context here. */
301 if (krb5_init_secure_context(&kmd->kcontext) != 0) {
302 if (kmd->debug)
303 __pam_log(LOG_AUTH | LOG_DEBUG,
304 "PAM-KRB5 (setcred): unable to "
305 "initialize krb5 context");
306 return (PAM_SYSTEM_ERR);
307 }
308
309 if (krb5_cc_default(kmd->kcontext, &kmd->ccache) != 0) {
310 return (PAM_SYSTEM_ERR);
311 }
312
313 if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
314 2*MAXHOSTNAMELEN)) != 0) {
315 return (code);
316 }
317
318 if (krb5_parse_name(kmd->kcontext, kuser, &me) != 0) {
319 return (PAM_SYSTEM_ERR);
320 }
321
322 if (code = krb5_build_principal_ext(kmd->kcontext, &server,
323 krb5_princ_realm(kmd->kcontext, me)->length,
324 krb5_princ_realm(kmd->kcontext, me)->data,
325 tgtname.length, tgtname.data,
326 krb5_princ_realm(kmd->kcontext, me)->length,
327 krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
328 krb5_free_principal(kmd->kcontext, me);
329 return (PAM_SYSTEM_ERR);
330 }
331
332 code = krb5_renew_tgt(kmd, me, server, flag);
333
334 krb5_free_principal(kmd->kcontext, server);
335 krb5_free_principal(kmd->kcontext, me);
336
337 if (code) {
338 if (kmd->debug)
339 __pam_log(LOG_AUTH | LOG_DEBUG,
340 "PAM-KRB5(setcred): krb5_renew_tgt() "
341 "failed: %s", error_message((errcode_t)code));
342 return (PAM_CRED_ERR);
343 } else {
344 return (PAM_SUCCESS);
345 }
346 }
347
348 /*
349 * This code will update the credential matching "server" in the user's
350 * credential cache. The flag may be set to one of:
351 * PAM_REINITIALIZE_CRED/PAM_ESTABLISH_CRED - If we have new credentials then
352 * create a new cred cache with these credentials else return failure.
353 * PAM_REFRESH_CRED - If we have new credentials then create a new cred cache
354 * with these credentials else attempt to renew the credentials.
355 *
356 * Note for any of the flags that if a new credential does exist from the
357 * previous auth pass then this will overwrite any existing credentials in the
358 * credential cache.
359 */
360 static krb5_error_code
krb5_renew_tgt(krb5_module_data_t * kmd,krb5_principal me,krb5_principal server,int flag)361 krb5_renew_tgt(
362 krb5_module_data_t *kmd,
363 krb5_principal me,
364 krb5_principal server,
365 int flag)
366 {
367 krb5_error_code retval;
368 krb5_creds creds;
369 krb5_creds *renewed_cred = NULL;
370 char *client_name = NULL;
371 char *username = NULL;
372
373 #define my_creds (kmd->initcreds)
374
375 if ((flag != PAM_REFRESH_CRED) &&
376 (flag != PAM_REINITIALIZE_CRED) &&
377 (flag != PAM_ESTABLISH_CRED))
378 return (KRB5KRB_ERR_GENERIC);
379
380 /* this is needed only for the ktkt_warnd */
381 if ((retval = krb5_unparse_name(kmd->kcontext, me, &client_name)) != 0)
382 return (retval);
383
384 (void) memset(&creds, 0, sizeof (krb5_creds));
385 if ((retval = krb5_copy_principal(kmd->kcontext,
386 server, &creds.server))) {
387 if (kmd->debug)
388 __pam_log(LOG_AUTH | LOG_DEBUG,
389 "PAM-KRB5 (setcred): krb5_copy_principal "
390 "failed: %s",
391 error_message((errcode_t)retval));
392 goto cleanup_creds;
393 }
394
395 /* obtain ticket & session key */
396 retval = krb5_cc_get_principal(kmd->kcontext,
397 kmd->ccache, &creds.client);
398 if (retval && (kmd->debug))
399 __pam_log(LOG_AUTH | LOG_DEBUG,
400 "PAM-KRB5 (setcred): User not in cred "
401 "cache (%s)", error_message((errcode_t)retval));
402
403 /*
404 * We got here either with the ESTABLISH | REINIT | REFRESH flag and
405 * auth_status returns SUCCESS or REFRESH and auth_status failure.
406 *
407 * Rules:
408 * - If the prior auth pass was successful then store the new
409 * credentials in the cache, regardless of which flag.
410 *
411 * - Else if REFRESH flag is used and there are no new
412 * credentials then attempt to refresh the existing credentials.
413 *
414 * - Note, refresh will not work if "R" flag is not set in
415 * original credential. We don't want to 2nd guess the
416 * intention of the person who created the existing credential.
417 */
418 if (kmd->auth_status == PAM_SUCCESS) {
419 /*
420 * Create a fresh ccache, and store the credentials
421 * we got from pam_authenticate()
422 */
423 if ((retval = krb5_cc_initialize(kmd->kcontext,
424 kmd->ccache, me)) != 0) {
425 __pam_log(LOG_AUTH | LOG_DEBUG,
426 "PAM-KRB5 (setcred): krb5_cc_initialize "
427 "failed: %s",
428 error_message((errcode_t)retval));
429 } else if ((retval = krb5_cc_store_cred(kmd->kcontext,
430 kmd->ccache, &my_creds)) != 0) {
431 __pam_log(LOG_AUTH | LOG_DEBUG,
432 "PAM-KRB5 (setcred): krb5_cc_store_cred "
433 "failed: %s",
434 error_message((errcode_t)retval));
435 }
436 } else if ((retval == 0) && (flag & PAM_REFRESH_CRED)) {
437 /*
438 * If we only wanted to refresh the creds but failed
439 * due to expiration, lack of "R" flag, or other
440 * problems, return an error.
441 */
442 if (retval = krb5_get_credentials_renew(kmd->kcontext,
443 0, kmd->ccache, &creds, &renewed_cred)) {
444 if (kmd->debug) {
445 __pam_log(LOG_AUTH | LOG_DEBUG,
446 "PAM-KRB5 (setcred): "
447 "krb5_get_credentials"
448 "_renew(update) failed: %s",
449 error_message((errcode_t)retval));
450 }
451 }
452 } else {
453 /*
454 * We failed to get the user's credentials.
455 * This might be due to permission error on the cache,
456 * or maybe we are looking in the wrong cache file!
457 */
458 __pam_log(LOG_AUTH | LOG_ERR,
459 "PAM-KRB5 (setcred): Cannot find creds"
460 " for %s (%s)",
461 client_name ? client_name : "(unknown)",
462 error_message((errcode_t)retval));
463 }
464
465 cleanup_creds:
466
467 if ((retval == 0) && (client_name != NULL)) {
468 /*
469 * Credential update was successful!
470 *
471 * We now chown the ccache to the appropriate uid/gid
472 * combination, if its a FILE based ccache.
473 */
474 if (!kmd->env || strstr(kmd->env, "FILE:")) {
475 uid_t uuid;
476 gid_t ugid;
477 char *tmpname = NULL;
478 char *filepath = NULL;
479
480 username = strdup(client_name);
481 if (username == NULL) {
482 __pam_log(LOG_AUTH | LOG_ERR,
483 "PAM-KRB5 (setcred): Out of memory");
484 retval = KRB5KRB_ERR_GENERIC;
485 goto error;
486 }
487 if ((tmpname = strchr(username, '@')))
488 *tmpname = '\0';
489
490 if (get_pw_uid(username, &uuid) == 0 ||
491 get_pw_gid(username, &ugid) == 0) {
492 __pam_log(LOG_AUTH | LOG_ERR,
493 "PAM-KRB5 (setcred): Unable to "
494 "find matching uid/gid pair for user `%s'",
495 username);
496 retval = KRB5KRB_ERR_GENERIC;
497 goto error;
498 }
499
500 if (!kmd->env) {
501 char buffer[512];
502
503 if (snprintf(buffer, sizeof (buffer),
504 "%s=FILE:/tmp/krb5cc_%d", KRB5_ENV_CCNAME,
505 (int)uuid) >= sizeof (buffer)) {
506 retval = KRB5KRB_ERR_GENERIC;
507 goto error;
508 }
509
510 /*
511 * We MUST copy this to the heap for the putenv
512 * to work!
513 */
514 kmd->env = strdup(buffer);
515 if (!kmd->env) {
516 retval = ENOMEM;
517 goto error;
518 } else {
519 if (putenv(kmd->env)) {
520 retval = ENOMEM;
521 goto error;
522 }
523 }
524 }
525
526 /*
527 * We know at this point that kmd->env must start
528 * with the literal string "FILE:". Set filepath
529 * character string to point to ":"
530 */
531
532 filepath = strchr(kmd->env, ':');
533
534 /*
535 * Now check if first char after ":" is null char
536 */
537 if (filepath[1] == '\0') {
538 __pam_log(LOG_AUTH | LOG_ERR,
539 "PAM-KRB5 (setcred): Invalid pathname "
540 "for credential cache of user `%s'",
541 username);
542 retval = KRB5KRB_ERR_GENERIC;
543 goto error;
544 }
545 if (chown(filepath+1, uuid, ugid)) {
546 if (kmd->debug)
547 __pam_log(LOG_AUTH | LOG_DEBUG,
548 "PAM-KRB5 (setcred): chown to user "
549 "`%s' failed for FILE=%s",
550 username, filepath);
551 }
552 }
553 }
554
555 error:
556 if (retval == 0) {
557 krb5_timestamp endtime;
558
559 if (renewed_cred && renewed_cred->times.endtime != 0)
560 endtime = renewed_cred->times.endtime;
561 else
562 endtime = my_creds.times.endtime;
563
564 if (kmd->debug)
565 __pam_log(LOG_AUTH | LOG_DEBUG,
566 "PAM-KRB5 (setcred): delete/add warning");
567
568 if (kwarn_del_warning(client_name) != 0) {
569 __pam_log(LOG_AUTH | LOG_NOTICE,
570 "PAM-KRB5 (setcred): kwarn_del_warning"
571 " failed: ktkt_warnd(1M) down?");
572 }
573
574 if (kwarn_add_warning(client_name, endtime) != 0) {
575 __pam_log(LOG_AUTH | LOG_NOTICE,
576 "PAM-KRB5 (setcred): kwarn_add_warning"
577 " failed: ktkt_warnd(1M) down?");
578 }
579 }
580
581 if (renewed_cred != NULL)
582 krb5_free_creds(kmd->kcontext, renewed_cred);
583
584 if (client_name != NULL)
585 free(client_name);
586
587 if (username)
588 free(username);
589
590 krb5_free_cred_contents(kmd->kcontext, &creds);
591
592 return (retval);
593 }
594
595 /*
596 * Delete the user's credentials for this session
597 */
598 static int
attempt_delete_initcred(krb5_module_data_t * kmd)599 attempt_delete_initcred(krb5_module_data_t *kmd)
600 {
601 if (kmd == NULL)
602 return (PAM_SUCCESS);
603
604 if (kmd->debug) {
605 __pam_log(LOG_AUTH | LOG_DEBUG,
606 "PAM-KRB5 (setcred): deleting user's "
607 "credentials (initcreds)");
608 }
609 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
610 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
611 kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
612 return (PAM_SUCCESS);
613 }
614