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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #include <kadm5/admin.h>
27 #include <krb5.h>
28
29 #include <security/pam_appl.h>
30 #include <security/pam_modules.h>
31 #include <security/pam_impl.h>
32 #include <syslog.h>
33 #include <string.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <sys/types.h>
37 #include <pwd.h>
38 #include <libintl.h>
39 #include <netdb.h>
40 #include "utils.h"
41 #include <shadow.h>
42
43 #include "krb5_repository.h"
44
45 #define KRB5_AUTOMIGRATE_DATA "SUNW-KRB5-AUTOMIGRATE-DATA"
46
47 #define min(a, b) ((a) < (b) ? (a) : (b))
48
49 /*
50 * pam_sm_acct_mgmt main account managment routine.
51 */
52
53 static int
fetch_princ_entry(krb5_module_data_t * kmd,char * princ_str,kadm5_principal_ent_rec * prent,int debug)54 fetch_princ_entry(
55 krb5_module_data_t *kmd,
56 char *princ_str,
57 kadm5_principal_ent_rec *prent, /* out */
58 int debug)
59
60 {
61 kadm5_ret_t code;
62 krb5_principal princ = 0;
63 char admin_realm[1024];
64 char kprinc[2*MAXHOSTNAMELEN];
65 char *cpw_service, *password;
66 void *server_handle;
67 krb5_context context;
68 kadm5_config_params params;
69
70 password = kmd->password;
71 context = kmd->kcontext;
72
73 if ((code = get_kmd_kuser(context, (const char *)princ_str,
74 kprinc, 2*MAXHOSTNAMELEN)) != 0) {
75 return (code);
76 }
77
78 code = krb5_parse_name(context, kprinc, &princ);
79 if (code != 0) {
80 return (PAM_SYSTEM_ERR);
81 }
82
83 if (strlen(password) == 0) {
84 krb5_free_principal(context, princ);
85 if (debug)
86 __pam_log(LOG_AUTH | LOG_DEBUG,
87 "PAM-KRB5 (acct): fetch_princ_entry: pwlen=0");
88 return (PAM_AUTH_ERR);
89 }
90
91 (void) strlcpy(admin_realm,
92 krb5_princ_realm(context, princ)->data,
93 sizeof (admin_realm));
94
95 (void) memset((char *)¶ms, 0, sizeof (params));
96 params.mask |= KADM5_CONFIG_REALM;
97 params.realm = admin_realm;
98
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'",
103 admin_realm);
104 krb5_free_principal(context, princ);
105 return (PAM_SYSTEM_ERR);
106 }
107
108 code = kadm5_init_with_password(kprinc, password, cpw_service,
109 ¶ms, KADM5_STRUCT_VERSION,
110 KADM5_API_VERSION_2, NULL,
111 &server_handle);
112 if (code != 0) {
113 if (debug)
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);
120 }
121
122 if (_kadm5_get_kpasswd_protocol(server_handle) != KRB5_CHGPWD_RPCSEC) {
123 if (debug)
124 __pam_log(LOG_AUTH | LOG_DEBUG,
125 "PAM-KRB5 (acct): fetch_princ_entry: "
126 "non-RPCSEC_GSS chpw server, can't get "
127 "princ entry");
128 (void) kadm5_destroy(server_handle);
129 krb5_free_principal(context, princ);
130 return (PAM_SYSTEM_ERR);
131 }
132
133 code = kadm5_get_principal(server_handle, princ, prent,
134 KADM5_PRINCIPAL_NORMAL_MASK);
135
136 if (code != 0) {
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);
141 }
142
143 (void) kadm5_destroy(server_handle);
144 krb5_free_principal(context, princ);
145
146 return (PAM_SUCCESS);
147 }
148
149 /*
150 * exp_warn
151 *
152 * Warn the user if their pw is set to expire.
153 *
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.
163 */
164 static int
exp_warn(pam_handle_t * pamh,char * user,krb5_module_data_t * kmd,int debug)165 exp_warn(
166 pam_handle_t *pamh,
167 char *user,
168 krb5_module_data_t *kmd,
169 int debug)
170
171 {
172 int err;
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;
177
178 if (debug)
179 __pam_log(LOG_AUTH | LOG_DEBUG,
180 "PAM-KRB5 (acct): exp_warn start: user = '%s'",
181 user ? user : "<null>");
182
183 password = kmd->password;
184
185 if (!pamh || !user || !password) {
186 err = PAM_SERVICE_ERR;
187 goto exit;
188 }
189
190 /*
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.
194 */
195
196 if (code = krb5_init_secure_context(&kmd->kcontext)) {
197 err = PAM_SYSTEM_ERR;
198 if (debug)
199 __pam_log(LOG_AUTH | LOG_ERR, "PAM-KRB5 (acct): "
200 "krb5_init_secure_context failed: code=%d",
201 code);
202 goto exit;
203 }
204 if (code = krb5_timeofday(kmd->kcontext, &now)) {
205 err = PAM_SYSTEM_ERR;
206 if (debug)
207 __pam_log(LOG_AUTH | LOG_ERR,
208 "PAM-KRB5 (acct): krb5_timeofday failed: code=%d",
209 code);
210 goto out;
211 }
212
213 if (kmd->expiration != 0) {
214 expiration = kmd->expiration;
215 } else {
216 (void) memset(&prent, 0, sizeof (prent));
217 if ((err = fetch_princ_entry(kmd, user, &prent, debug))
218 != PAM_SUCCESS) {
219 if (debug)
220 __pam_log(LOG_AUTH | LOG_DEBUG,
221 "PAM-KRB5 (acct): exp_warn: fetch_pr failed %d",
222 err);
223 goto out;
224 }
225 if (prent.princ_expire_time != 0 && prent.pw_expiration != 0)
226 expiration = min(prent.princ_expire_time,
227 prent.pw_expiration);
228 else
229 expiration = prent.princ_expire_time ?
230 prent.princ_expire_time : prent.pw_expiration;
231 }
232
233 if (debug)
234 __pam_log(LOG_AUTH | LOG_DEBUG,
235 "PAM-KRB5 (acct): exp_warn: "
236 "princ/pw_exp exp=%ld, now =%ld, days=%ld",
237 expiration,
238 now,
239 expiration > 0
240 ? ((expiration - now) / DAY)
241 : 0);
242
243 /* warn user if principal's pw is set to expire */
244 if (expiration > 0) {
245 days = (expiration - now) / DAY;
246 if (days <= 0)
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"));
252 else if (days == 1)
253 (void) snprintf(messages[0],
254 sizeof (messages[0]),
255 dgettext(TEXT_DOMAIN,
256 "Your Kerberos account/password will expire "
257 "in 1 day.\n"));
258 else
259 (void) snprintf(messages[0],
260 sizeof (messages[0]),
261 dgettext(TEXT_DOMAIN,
262 "Your Kerberos account/password will expire in "
263 "%d days.\n"),
264 (int)days);
265
266 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1,
267 messages, NULL);
268 }
269
270 /* things went smooth */
271 err = PAM_SUCCESS;
272
273 out:
274
275 if (kmd->kcontext) {
276 krb5_free_context(kmd->kcontext);
277 kmd->kcontext = NULL;
278 }
279
280 exit:
281
282 if (debug)
283 __pam_log(LOG_AUTH | LOG_DEBUG,
284 "PAM-KRB5 (acct): exp_warn end: err = %d", err);
285
286 return (err);
287 }
288
289 /*
290 * pam_krb5 acct_mgmt
291 *
292 * we do
293 * - check if pw expired (flag set in auth)
294 * - warn user if pw is set to expire
295 *
296 * notes
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
300 */
301 int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char ** argv)302 pam_sm_acct_mgmt(
303 pam_handle_t *pamh,
304 int flags,
305 int argc,
306 const char **argv)
307
308 {
309 char *user = NULL;
310 char *userdata = NULL;
311 int err;
312 int i;
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;
318
319 for (i = 0; i < argc; i++) {
320 if (strcasecmp(argv[i], "debug") == 0)
321 debug = 1;
322 else if (strcasecmp(argv[i], "nowarn") == 0) {
323 nowarn = 1;
324 flags = flags | PAM_SILENT;
325 } else {
326 __pam_log(LOG_AUTH | LOG_ERR,
327 "PAM-KRB5 (acct): illegal option %s",
328 argv[i]);
329 }
330 }
331
332 if (debug)
333 __pam_log(LOG_AUTH | LOG_DEBUG,
334 "PAM-KRB5 (acct): debug=%d, nowarn=%d",
335 debug, nowarn);
336
337 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
338
339 if (rep_data != NULL) {
340 /*
341 * If the repository is not ours,
342 * return PAM_IGNORE.
343 */
344 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
345 if (debug)
346 __pam_log(LOG_AUTH | LOG_DEBUG,
347 "PAM-KRB5 (acct): wrong"
348 "repository found (%s), returning "
349 "PAM_IGNORE", rep_data->type);
350 return (PAM_IGNORE);
351 }
352 }
353
354
355 /* get user name */
356 (void) pam_get_item(pamh, PAM_USER, (void **) &user);
357
358 if (user == NULL || *user == '\0') {
359 err = PAM_USER_UNKNOWN;
360 goto out;
361 }
362
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) {
367 if (debug)
368 __pam_log(LOG_AUTH | LOG_DEBUG, "PAM-KRB5 (acct): "
369 "no module data for KRB5_AUTOMIGRATE_DATA");
370 } else {
371 /*
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).
376 */
377 if (strcmp(user, userdata) == 0)
378 (void) pam_sm_authenticate(pamh, flags, argc,
379 (const char **)argv);
380 else
381 if (debug)
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);
386 }
387
388 /* get krb5 module data */
389 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
390 != PAM_SUCCESS) {
391 if (err == PAM_NO_MODULE_DATA) {
392 /*
393 * pam_auth never called (possible config
394 * error; no pam_krb5 auth entry in pam.conf),
395 */
396 if (debug) {
397 __pam_log(LOG_AUTH | LOG_DEBUG,
398 "PAM-KRB5 (acct): no module data");
399 }
400 err = PAM_IGNORE;
401 goto out;
402 } else {
403 __pam_log(LOG_AUTH | LOG_ERR,
404 "PAM-KRB5 (acct): get module"
405 " data failed: err=%d",
406 err);
407 }
408 goto out;
409 }
410
411 debug = debug || kmd->debug;
412
413 /*
414 * auth mod set status to ignore, most likely cuz root key is
415 * in keytab, so skip other checks and return ignore
416 */
417 if (kmd->auth_status == PAM_IGNORE) {
418 if (debug)
419 __pam_log(LOG_AUTH | LOG_DEBUG,
420 "PAM-KRB5 (acct): kmd auth_status is IGNORE");
421 err = PAM_IGNORE;
422 goto out;
423 }
424
425 /*
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
432 * user.
433 */
434 if (kmd->auth_status == PAM_USER_UNKNOWN) {
435 if (debug)
436 syslog(LOG_DEBUG,
437 "PAM-KRB5 (acct): kmd auth_status is USER UNKNOWN");
438 err = PAM_IGNORE;
439 goto out;
440 }
441
442 /*
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
445 */
446 if (kmd->age_status == PAM_NEW_AUTHTOK_REQD) {
447 if (!nowarn) {
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,
452 1, messages, NULL);
453 }
454 err = PAM_NEW_AUTHTOK_REQD;
455 goto out;
456 }
457
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);
462 }
463
464 /*
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.
468 */
469 err = kmd->auth_status != PAM_SUCCESS ? PAM_IGNORE : kmd->auth_status;
470
471 out:
472 if (debug)
473 __pam_log(LOG_AUTH | LOG_DEBUG,
474 "PAM-KRB5 (acct): end: %s", pam_strerror(pamh, err));
475
476 return (err);
477 }
478