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