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.
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.
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]
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
28 #include <security/pam_appl.h>
29 #include <security/pam_modules.h>
33 #include <sys/types.h>
46 #include "krb5_repository.h"
48 #define PAMTXD "SUNW_OST_SYSOSPAM"
49 #define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */
51 extern void krb5_cleanup(pam_handle_t
*, void *, int);
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
,
58 extern uint_t
kwarn_add_warning(char *, int);
59 extern uint_t
kwarn_del_warning(char *);
74 krb5_module_data_t
*kmd
= NULL
;
76 krb5_repository_data_t
*krb5_data
= NULL
;
77 pam_repository_t
*rep_data
= NULL
;
79 for (i
= 0; i
< argc
; i
++) {
80 if (strcasecmp(argv
[i
], "debug") == 0)
82 else if (strcasecmp(argv
[i
], "nowarn") == 0)
83 flags
= flags
| PAM_SILENT
;
87 __pam_log(LOG_AUTH
| LOG_DEBUG
,
88 "PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x",
89 flags
& PAM_SILENT
? 1 : 0, flags
);
91 /* make sure flags are valid */
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
;
104 (void) pam_get_item(pamh
, PAM_USER
, (void**) &user
);
106 if (user
== NULL
|| *user
== '\0')
107 return (PAM_USER_UNKNOWN
);
109 if (pam_get_data(pamh
, KRB5_DATA
, (const void**)&kmd
) != PAM_SUCCESS
) {
111 __pam_log(LOG_AUTH
| LOG_DEBUG
,
112 "PAM-KRB5 (setcred): kmd get failed, kmd=0x%p",
117 * User doesn't need to authenticate for PAM_REFRESH_CRED
118 * or for PAM_DELETE_CRED
120 if (flags
& (PAM_REFRESH_CRED
|PAM_DELETE_CRED
)) {
121 __pam_log(LOG_AUTH
| LOG_DEBUG
,
122 "PAM-KRB5 (setcred): inst kmd structure");
124 kmd
= calloc(1, sizeof (krb5_module_data_t
));
127 return (PAM_BUF_ERR
);
131 * Need to initialize auth_status here to
132 * PAM_AUTHINFO_UNAVAIL else there is a false positive
135 kmd
->auth_status
= PAM_AUTHINFO_UNAVAIL
;
137 if ((err
= pam_set_data(pamh
, KRB5_DATA
,
138 kmd
, &krb5_cleanup
)) != PAM_SUCCESS
) {
140 return (PAM_SYSTEM_ERR
);
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.
153 } else { /* pam_get_data success */
156 __pam_log(LOG_AUTH
| LOG_DEBUG
,
157 "PAM-KRB5 (setcred): kmd structure"
158 " gotten but is NULL for user %s", user
);
160 err
= PAM_SYSTEM_ERR
;
165 __pam_log(LOG_AUTH
| LOG_DEBUG
,
166 "PAM-KRB5 (setcred): kmd auth_status: %s",
167 pam_strerror(pamh
, kmd
->auth_status
));
170 * pam_auth has set status to ignore, so we also return ignore
172 if (kmd
->auth_status
== PAM_IGNORE
) {
181 * User must have passed pam_authenticate()
182 * in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED
184 if ((flags
& (PAM_ESTABLISH_CRED
|PAM_REINITIALIZE_CRED
)) &&
185 (kmd
->auth_status
!= PAM_SUCCESS
)) {
187 __pam_log(LOG_AUTH
| LOG_DEBUG
,
188 "PAM-KRB5 (setcred): unable to "
189 "setcreds, not authenticated!");
190 return (PAM_CRED_UNAVAIL
);
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
200 if (kmd
->kcontext
!= NULL
&& kmd
->debug
)
201 __pam_log(LOG_AUTH
| LOG_DEBUG
,
202 "PAM-KRB5 (setcred): kcontext != NULL, "
203 "possible memory leak.");
206 * Use the authenticated and validated user, if applicable.
208 if (kmd
->user
!= NULL
)
212 * If auth was short-circuited we will not have anything to
213 * renew, so just return here.
215 (void) pam_get_item(pamh
, PAM_REPOSITORY
, (void **)&rep_data
);
217 if (rep_data
!= NULL
) {
218 if (strcmp(rep_data
->type
, KRB5_REPOSITORY_NAME
) != 0) {
220 __pam_log(LOG_AUTH
| LOG_DEBUG
,
221 "PAM-KRB5 (setcred): wrong"
222 "repository found (%s), returning "
223 "PAM_IGNORE", rep_data
->type
);
226 if (rep_data
->scope_len
== sizeof (krb5_repository_data_t
)) {
227 krb5_data
= (krb5_repository_data_t
*)rep_data
->scope
;
229 if (krb5_data
->flags
==
230 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED
&&
231 krb5_data
->principal
!= NULL
&&
232 strlen(krb5_data
->principal
)) {
234 __pam_log(LOG_AUTH
| LOG_DEBUG
,
235 "PAM-KRB5 (setcred): "
236 "Principal %s already "
239 krb5_data
->principal
);
240 return (PAM_SUCCESS
);
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
);
253 * Default case: PAM_ESTABLISH_CRED
255 err
= attempt_refresh_cred(kmd
, user
, PAM_ESTABLISH_CRED
);
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
));
264 if (kmd
&& kmd
->kcontext
) {
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.
270 krb5_free_context(kmd
->kcontext
);
271 kmd
->kcontext
= NULL
;
275 * 'kmd' is not freed here, it is handled in krb5_cleanup
278 __pam_log(LOG_AUTH
| LOG_DEBUG
,
279 "PAM-KRB5 (setcred): end: %s",
280 pam_strerror(pamh
, err
));
285 attempt_refresh_cred(
286 krb5_module_data_t
*kmd
,
291 krb5_principal server
;
292 krb5_error_code code
;
293 char kuser
[2*MAXHOSTNAMELEN
];
294 krb5_data tgtname
= {
300 /* Create a new context here. */
301 if (krb5_init_secure_context(&kmd
->kcontext
) != 0) {
303 __pam_log(LOG_AUTH
| LOG_DEBUG
,
304 "PAM-KRB5 (setcred): unable to "
305 "initialize krb5 context");
306 return (PAM_SYSTEM_ERR
);
309 if (krb5_cc_default(kmd
->kcontext
, &kmd
->ccache
) != 0) {
310 return (PAM_SYSTEM_ERR
);
313 if ((code
= get_kmd_kuser(kmd
->kcontext
, (const char *)user
, kuser
,
314 2*MAXHOSTNAMELEN
)) != 0) {
318 if (krb5_parse_name(kmd
->kcontext
, kuser
, &me
) != 0) {
319 return (PAM_SYSTEM_ERR
);
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
);
332 code
= krb5_renew_tgt(kmd
, me
, server
, flag
);
334 krb5_free_principal(kmd
->kcontext
, server
);
335 krb5_free_principal(kmd
->kcontext
, me
);
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
);
344 return (PAM_SUCCESS
);
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.
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
360 static krb5_error_code
362 krb5_module_data_t
*kmd
,
364 krb5_principal server
,
367 krb5_error_code retval
;
369 krb5_creds
*renewed_cred
= NULL
;
370 char *client_name
= NULL
;
371 char *username
= NULL
;
373 #define my_creds (kmd->initcreds)
375 if ((flag
!= PAM_REFRESH_CRED
) &&
376 (flag
!= PAM_REINITIALIZE_CRED
) &&
377 (flag
!= PAM_ESTABLISH_CRED
))
378 return (KRB5KRB_ERR_GENERIC
);
380 /* this is needed only for the ktkt_warnd */
381 if ((retval
= krb5_unparse_name(kmd
->kcontext
, me
, &client_name
)) != 0)
384 (void) memset(&creds
, 0, sizeof (krb5_creds
));
385 if ((retval
= krb5_copy_principal(kmd
->kcontext
,
386 server
, &creds
.server
))) {
388 __pam_log(LOG_AUTH
| LOG_DEBUG
,
389 "PAM-KRB5 (setcred): krb5_copy_principal "
391 error_message((errcode_t
)retval
));
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
));
404 * We got here either with the ESTABLISH | REINIT | REFRESH flag and
405 * auth_status returns SUCCESS or REFRESH and auth_status failure.
408 * - If the prior auth pass was successful then store the new
409 * credentials in the cache, regardless of which flag.
411 * - Else if REFRESH flag is used and there are no new
412 * credentials then attempt to refresh the existing credentials.
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.
418 if (kmd
->auth_status
== PAM_SUCCESS
) {
420 * Create a fresh ccache, and store the credentials
421 * we got from pam_authenticate()
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 "
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 "
434 error_message((errcode_t
)retval
));
436 } else if ((retval
== 0) && (flag
& PAM_REFRESH_CRED
)) {
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.
442 if (retval
= krb5_get_credentials_renew(kmd
->kcontext
,
443 0, kmd
->ccache
, &creds
, &renewed_cred
)) {
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
));
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!
458 __pam_log(LOG_AUTH
| LOG_ERR
,
459 "PAM-KRB5 (setcred): Cannot find creds"
461 client_name
? client_name
: "(unknown)",
462 error_message((errcode_t
)retval
));
467 if ((retval
== 0) && (client_name
!= NULL
)) {
469 * Credential update was successful!
471 * We now chown the ccache to the appropriate uid/gid
472 * combination, if its a FILE based ccache.
474 if (!kmd
->env
|| strstr(kmd
->env
, "FILE:")) {
477 char *tmpname
= NULL
;
478 char *filepath
= NULL
;
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
;
487 if ((tmpname
= strchr(username
, '@')))
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'",
496 retval
= KRB5KRB_ERR_GENERIC
;
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
;
511 * We MUST copy this to the heap for the putenv
514 kmd
->env
= strdup(buffer
);
519 if (putenv(kmd
->env
)) {
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 ":"
532 filepath
= strchr(kmd
->env
, ':');
535 * Now check if first char after ":" is null char
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'",
542 retval
= KRB5KRB_ERR_GENERIC
;
545 if (chown(filepath
+1, uuid
, ugid
)) {
547 __pam_log(LOG_AUTH
| LOG_DEBUG
,
548 "PAM-KRB5 (setcred): chown to user "
549 "`%s' failed for FILE=%s",
557 krb5_timestamp endtime
;
559 if (renewed_cred
&& renewed_cred
->times
.endtime
!= 0)
560 endtime
= renewed_cred
->times
.endtime
;
562 endtime
= my_creds
.times
.endtime
;
565 __pam_log(LOG_AUTH
| LOG_DEBUG
,
566 "PAM-KRB5 (setcred): delete/add warning");
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?");
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?");
581 if (renewed_cred
!= NULL
)
582 krb5_free_creds(kmd
->kcontext
, renewed_cred
);
588 krb5_free_cred_contents(kmd
->kcontext
, &creds
);
594 * Delete the user's credentials for this session
597 attempt_delete_initcred(krb5_module_data_t
*kmd
)
600 return (PAM_SUCCESS
);
603 __pam_log(LOG_AUTH
| LOG_DEBUG
,
604 "PAM-KRB5 (setcred): deleting user's "
605 "credentials (initcreds)");
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
);