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]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
26 #include <kadm5/admin.h>
29 #include <security/pam_appl.h>
30 #include <security/pam_modules.h>
31 #include <security/pam_impl.h>
36 #include <sys/types.h>
43 #include "krb5_repository.h"
45 #define KRB5_AUTOMIGRATE_DATA "SUNW-KRB5-AUTOMIGRATE-DATA"
47 #define min(a, b) ((a) < (b) ? (a) : (b))
50 * pam_sm_acct_mgmt main account managment routine.
55 krb5_module_data_t
*kmd
,
57 kadm5_principal_ent_rec
*prent
, /* out */
62 krb5_principal princ
= 0;
63 char admin_realm
[1024];
64 char kprinc
[2*MAXHOSTNAMELEN
];
65 char *cpw_service
, *password
;
68 kadm5_config_params params
;
70 password
= kmd
->password
;
71 context
= kmd
->kcontext
;
73 if ((code
= get_kmd_kuser(context
, (const char *)princ_str
,
74 kprinc
, 2*MAXHOSTNAMELEN
)) != 0) {
78 code
= krb5_parse_name(context
, kprinc
, &princ
);
80 return (PAM_SYSTEM_ERR
);
83 if (strlen(password
) == 0) {
84 krb5_free_principal(context
, princ
);
86 __pam_log(LOG_AUTH
| LOG_DEBUG
,
87 "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0");
88 return (PAM_AUTH_ERR
);
91 (void) strlcpy(admin_realm
,
92 krb5_princ_realm(context
, princ
)->data
,
93 sizeof (admin_realm
));
95 (void) memset((char *)¶ms
, 0, sizeof (params
));
96 params
.mask
|= KADM5_CONFIG_REALM
;
97 params
.realm
= admin_realm
;
99 if (kadm5_get_cpw_host_srv_name(context
, admin_realm
, &cpw_service
)) {
100 __pam_log(LOG_AUTH
| LOG_ERR
,
101 "PAM-KRB5 (acct): unable to get host based "
102 "service name for realm '%s'",
104 krb5_free_principal(context
, princ
);
105 return (PAM_SYSTEM_ERR
);
108 code
= kadm5_init_with_password(kprinc
, password
, cpw_service
,
109 ¶ms
, KADM5_STRUCT_VERSION
,
110 KADM5_API_VERSION_2
, NULL
,
114 __pam_log(LOG_AUTH
| LOG_DEBUG
,
115 "PAM-KRB5 (acct): fetch_princ_entry: "
116 "init_with_pw failed: code = %d", code
);
117 krb5_free_principal(context
, princ
);
118 return ((code
== KADM5_BAD_PASSWORD
) ?
119 PAM_AUTH_ERR
: PAM_SYSTEM_ERR
);
122 if (_kadm5_get_kpasswd_protocol(server_handle
) != KRB5_CHGPWD_RPCSEC
) {
124 __pam_log(LOG_AUTH
| LOG_DEBUG
,
125 "PAM-KRB5 (acct): fetch_princ_entry: "
126 "non-RPCSEC_GSS chpw server, can't get "
128 (void) kadm5_destroy(server_handle
);
129 krb5_free_principal(context
, princ
);
130 return (PAM_SYSTEM_ERR
);
133 code
= kadm5_get_principal(server_handle
, princ
, prent
,
134 KADM5_PRINCIPAL_NORMAL_MASK
);
137 (void) kadm5_destroy(server_handle
);
138 krb5_free_principal(context
, princ
);
139 return ((code
== KADM5_UNK_PRINC
) ?
140 PAM_USER_UNKNOWN
: PAM_SYSTEM_ERR
);
143 (void) kadm5_destroy(server_handle
);
144 krb5_free_principal(context
, princ
);
146 return (PAM_SUCCESS
);
152 * Warn the user if their pw is set to expire.
154 * We first check to see if the KDC had set any account or password
155 * expiration information in the key expiration field. If this was
156 * not set then we must assume that the KDC could be broken and revert
157 * to fetching pw/account expiration information from kadm. We can not
158 * determine the difference between broken KDCs that do not send key-exp
159 * vs. principals that do not have an expiration policy. The up-shot
160 * is that pam_krb5 will probably not be stacked for acct mgmt if the
161 * environment does not have an exp policy, avoiding the second exchange
162 * using the kadm protocol.
168 krb5_module_data_t
*kmd
,
173 kadm5_principal_ent_rec prent
;
174 krb5_timestamp now
, days
, expiration
;
175 char messages
[PAM_MAX_NUM_MSG
][PAM_MAX_MSG_SIZE
], *password
;
176 krb5_error_code code
;
179 __pam_log(LOG_AUTH
| LOG_DEBUG
,
180 "PAM-KRB5 (acct): exp_warn start: user = '%s'",
181 user
? user
: "<null>");
183 password
= kmd
->password
;
185 if (!pamh
|| !user
|| !password
) {
186 err
= PAM_SERVICE_ERR
;
191 * If we error out from krb5_init_secure_context, then just set error
192 * code, check to see about debug message and exit out of routine as the
193 * context could not possibly have been setup.
196 if (code
= krb5_init_secure_context(&kmd
->kcontext
)) {
197 err
= PAM_SYSTEM_ERR
;
199 __pam_log(LOG_AUTH
| LOG_ERR
, "PAM-KRB5 (acct): "
200 "krb5_init_secure_context failed: code=%d",
204 if (code
= krb5_timeofday(kmd
->kcontext
, &now
)) {
205 err
= PAM_SYSTEM_ERR
;
207 __pam_log(LOG_AUTH
| LOG_ERR
,
208 "PAM-KRB5 (acct): krb5_timeofday failed: code=%d",
213 if (kmd
->expiration
!= 0) {
214 expiration
= kmd
->expiration
;
216 (void) memset(&prent
, 0, sizeof (prent
));
217 if ((err
= fetch_princ_entry(kmd
, user
, &prent
, debug
))
220 __pam_log(LOG_AUTH
| LOG_DEBUG
,
221 "PAM-KRB5 (acct): exp_warn: fetch_pr failed %d",
225 if (prent
.princ_expire_time
!= 0 && prent
.pw_expiration
!= 0)
226 expiration
= min(prent
.princ_expire_time
,
227 prent
.pw_expiration
);
229 expiration
= prent
.princ_expire_time
?
230 prent
.princ_expire_time
: prent
.pw_expiration
;
234 __pam_log(LOG_AUTH
| LOG_DEBUG
,
235 "PAM-KRB5 (acct): exp_warn: "
236 "princ/pw_exp exp=%ld, now =%ld, days=%ld",
240 ? ((expiration
- now
) / DAY
)
243 /* warn user if principal's pw is set to expire */
244 if (expiration
> 0) {
245 days
= (expiration
- now
) / DAY
;
247 (void) snprintf(messages
[0],
248 sizeof (messages
[0]),
249 dgettext(TEXT_DOMAIN
,
250 "Your Kerberos account/password will expire "
251 "within 24 hours.\n"));
253 (void) snprintf(messages
[0],
254 sizeof (messages
[0]),
255 dgettext(TEXT_DOMAIN
,
256 "Your Kerberos account/password will expire "
259 (void) snprintf(messages
[0],
260 sizeof (messages
[0]),
261 dgettext(TEXT_DOMAIN
,
262 "Your Kerberos account/password will expire in "
266 (void) __pam_display_msg(pamh
, PAM_TEXT_INFO
, 1,
270 /* things went smooth */
276 krb5_free_context(kmd
->kcontext
);
277 kmd
->kcontext
= NULL
;
283 __pam_log(LOG_AUTH
| LOG_DEBUG
,
284 "PAM-KRB5 (acct): exp_warn end: err = %d", err
);
293 * - check if pw expired (flag set in auth)
294 * - warn user if pw is set to expire
297 * - we require the auth module to have already run (sets module data)
298 * - we don't worry about an expired princ cuz if that's the case,
299 * auth would have failed
310 char *userdata
= NULL
;
313 krb5_module_data_t
*kmd
= NULL
;
314 char messages
[PAM_MAX_NUM_MSG
][PAM_MAX_MSG_SIZE
];
315 int debug
= 0; /* pam.conf entry option */
316 int nowarn
= 0; /* pam.conf entry option, no expire warnings */
317 pam_repository_t
*rep_data
= NULL
;
319 for (i
= 0; i
< argc
; i
++) {
320 if (strcasecmp(argv
[i
], "debug") == 0)
322 else if (strcasecmp(argv
[i
], "nowarn") == 0) {
324 flags
= flags
| PAM_SILENT
;
326 __pam_log(LOG_AUTH
| LOG_ERR
,
327 "PAM-KRB5 (acct): illegal option %s",
333 __pam_log(LOG_AUTH
| LOG_DEBUG
,
334 "PAM-KRB5 (acct): debug=%d, nowarn=%d",
337 (void) pam_get_item(pamh
, PAM_REPOSITORY
, (void **)&rep_data
);
339 if (rep_data
!= NULL
) {
341 * If the repository is not ours,
344 if (strcmp(rep_data
->type
, KRB5_REPOSITORY_NAME
) != 0) {
346 __pam_log(LOG_AUTH
| LOG_DEBUG
,
347 "PAM-KRB5 (acct): wrong"
348 "repository found (%s), returning "
349 "PAM_IGNORE", rep_data
->type
);
356 (void) pam_get_item(pamh
, PAM_USER
, (void **) &user
);
358 if (user
== NULL
|| *user
== '\0') {
359 err
= PAM_USER_UNKNOWN
;
363 /* get pam_krb5_migrate specific data */
364 err
= pam_get_data(pamh
, KRB5_AUTOMIGRATE_DATA
,
365 (const void **)&userdata
);
366 if (err
!= PAM_SUCCESS
) {
368 __pam_log(LOG_AUTH
| LOG_DEBUG
, "PAM-KRB5 (acct): "
369 "no module data for KRB5_AUTOMIGRATE_DATA");
372 * We try and reauthenticate, since this user has a
373 * newly created krb5 principal via the pam_krb5_migrate
374 * auth module. That way, this new user will have fresh
375 * creds (assuming pam_sm_authenticate() succeeds).
377 if (strcmp(user
, userdata
) == 0)
378 (void) pam_sm_authenticate(pamh
, flags
, argc
,
379 (const char **)argv
);
382 __pam_log(LOG_AUTH
| LOG_DEBUG
,
383 "PAM-KRB5 (acct): PAM_USER %s"
384 "does not match user %s from pam_get_data()",
385 user
, (char *)userdata
);
388 /* get krb5 module data */
389 if ((err
= pam_get_data(pamh
, KRB5_DATA
, (const void **)&kmd
))
391 if (err
== PAM_NO_MODULE_DATA
) {
393 * pam_auth never called (possible config
394 * error; no pam_krb5 auth entry in pam.conf),
397 __pam_log(LOG_AUTH
| LOG_DEBUG
,
398 "PAM-KRB5 (acct): no module data");
403 __pam_log(LOG_AUTH
| LOG_ERR
,
404 "PAM-KRB5 (acct): get module"
405 " data failed: err=%d",
411 debug
= debug
|| kmd
->debug
;
414 * auth mod set status to ignore, most likely cuz root key is
415 * in keytab, so skip other checks and return ignore
417 if (kmd
->auth_status
== PAM_IGNORE
) {
419 __pam_log(LOG_AUTH
| LOG_DEBUG
,
420 "PAM-KRB5 (acct): kmd auth_status is IGNORE");
426 * If there is no Kerberos related user and there is authentication
427 * data, this means that while the user has successfully passed
428 * authentication, Kerberos is not the account authority because there
429 * is no valid Kerberos principal. PAM_IGNORE is returned since
430 * Kerberos is not authoritative for this user. Other modules in the
431 * account stack will need to determine the success or failure for this
434 if (kmd
->auth_status
== PAM_USER_UNKNOWN
) {
437 "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN");
443 * age_status will be set to PAM_NEW_AUTHTOK_REQD in pam_krb5's
444 * 'auth' if the user's key/pw has expired and needs to be changed
446 if (kmd
->age_status
== PAM_NEW_AUTHTOK_REQD
) {
448 (void) snprintf(messages
[0], sizeof (messages
[0]),
449 dgettext(TEXT_DOMAIN
,
450 "Your Kerberos password has expired.\n"));
451 (void) __pam_display_msg(pamh
, PAM_TEXT_INFO
,
454 err
= PAM_NEW_AUTHTOK_REQD
;
458 if (kmd
->auth_status
== PAM_SUCCESS
&& !(flags
& PAM_SILENT
) &&
459 !nowarn
&& kmd
->password
) {
460 /* if we fail, let it slide, it's only a warning brah */
461 (void) exp_warn(pamh
, user
, kmd
, debug
);
465 * If Kerberos is treated as optional in the PAM stack, it is possible
466 * that there is a KRB5_DATA item and a non-Kerberos account authority.
467 * In that case, PAM_IGNORE is returned.
469 err
= kmd
->auth_status
!= PAM_SUCCESS
? PAM_IGNORE
: kmd
->auth_status
;
473 __pam_log(LOG_AUTH
| LOG_DEBUG
,
474 "PAM-KRB5 (acct): end: %s", pam_strerror(pamh
, err
));