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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26 #include <security/pam_appl.h>
27 #include <security/pam_modules.h>
28 #include <security/pam_impl.h>
29 #include <string.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <pwd.h>
35 #include <syslog.h>
36 #include <libintl.h>
37 #include <k5-int.h>
38 #include "profile/prof_int.h"
39 #include <netdb.h>
40 #include <ctype.h>
41 #include "utils.h"
42 #include "krb5_repository.h"
43
44 #define KRB5_DEFAULT_OPTIONS 0
45
46 int forwardable_flag = 0;
47 int renewable_flag = 0;
48 int proxiable_flag = 0;
49 int no_address_flag = 0;
50 profile_options_boolean config_option[] = {
51 { "forwardable", &forwardable_flag, 0 },
52 { "renewable", &renewable_flag, 0 },
53 { "proxiable", &proxiable_flag, 0 },
54 { "no_addresses", &no_address_flag, 0 },
55 { NULL, NULL, 0 }
56 };
57 char *renew_timeval;
58 char *life_timeval;
59 profile_option_strings config_times[] = {
60 { "max_life", &life_timeval, 0 },
61 { "max_renewable_life", &renew_timeval, 0 },
62 { NULL, NULL, 0 }
63 };
64 char *realmdef[] = { "realms", NULL, NULL, NULL };
65 char *appdef[] = { "appdefaults", "kinit", NULL };
66
67 #define krb_realm (*(realmdef + 1))
68
69 int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, char *,
70 char **, boolean_t);
71 void krb5_cleanup(pam_handle_t *, void *, int);
72
73 extern errcode_t profile_get_options_boolean();
74 extern errcode_t profile_get_options_string();
75 extern int krb5_verifypw(char *, char *, int);
76 extern krb5_error_code krb5_verify_init_creds(krb5_context,
77 krb5_creds *, krb5_principal, krb5_keytab, krb5_ccache *,
78 krb5_verify_init_creds_opt *);
79 extern krb5_error_code __krb5_get_init_creds_password(krb5_context,
80 krb5_creds *, krb5_principal, char *, krb5_prompter_fct, void *,
81 krb5_deltat, char *, krb5_get_init_creds_opt *,
82 krb5_kdc_rep **);
83
84 /*
85 * pam_sm_authenticate - Authenticate user
86 */
87 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)88 pam_sm_authenticate(
89 pam_handle_t *pamh,
90 int flags,
91 int argc,
92 const char **argv)
93 {
94 char *user = NULL;
95 int err;
96 int result = PAM_AUTH_ERR;
97 /* pam.conf options */
98 int debug = 0;
99 int warn = 1;
100 /* return an error on password expire */
101 int err_on_exp = 0;
102 int i;
103 char *password = NULL;
104 uid_t pw_uid;
105 krb5_module_data_t *kmd = NULL;
106 krb5_repository_data_t *krb5_data = NULL;
107 pam_repository_t *rep_data = NULL;
108 boolean_t do_pkinit = FALSE;
109
110 for (i = 0; i < argc; i++) {
111 if (strcmp(argv[i], "debug") == 0) {
112 debug = 1;
113 } else if (strcmp(argv[i], "nowarn") == 0) {
114 warn = 0;
115 } else if (strcmp(argv[i], "err_on_exp") == 0) {
116 err_on_exp = 1;
117 } else if (strcmp(argv[i], "pkinit") == 0) {
118 do_pkinit = TRUE;
119 } else {
120 __pam_log(LOG_AUTH | LOG_ERR,
121 "PAM-KRB5 (auth) unrecognized option %s", argv[i]);
122 }
123 }
124 if (flags & PAM_SILENT) warn = 0;
125
126 if (debug)
127 __pam_log(LOG_AUTH | LOG_DEBUG,
128 "PAM-KRB5 (auth): pam_sm_authenticate flags=%d",
129 flags);
130
131 /*
132 * pam_get_data could fail if we are being called for the first time
133 * or if the module is not found, PAM_NO_MODULE_DATA is not an error
134 */
135 err = pam_get_data(pamh, KRB5_DATA, (const void**)&kmd);
136 if (!(err == PAM_SUCCESS || err == PAM_NO_MODULE_DATA))
137 return (PAM_SYSTEM_ERR);
138
139 /*
140 * If pam_krb5 was stacked higher in the auth stack and did PKINIT
141 * preauth sucessfully then this instance is a fallback to password
142 * based preauth and should just return PAM_IGNORE.
143 *
144 * The else clause is handled further down.
145 */
146 if (kmd != NULL) {
147 if (++(kmd->auth_calls) > 2) {
148 /*
149 * pam_krb5 has been stacked > 2 times in the auth
150 * stack. Clear out the current kmd and proceed as if
151 * this is the first time pam_krb5 auth has been called.
152 */
153 if (debug) {
154 __pam_log(LOG_AUTH | LOG_DEBUG,
155 "PAM-KRB5 (auth): stacked more than"
156 " two times, clearing kmd");
157 }
158 /* clear out/free current kmd */
159 err = pam_set_data(pamh, KRB5_DATA, NULL, NULL);
160 if (err != PAM_SUCCESS) {
161 krb5_cleanup(pamh, kmd, err);
162 result = err;
163 goto out;
164 }
165 kmd = NULL;
166 } else if (kmd->auth_calls == 2 &&
167 kmd->auth_status == PAM_SUCCESS) {
168 /*
169 * The previous instance of pam_krb5 succeeded and this
170 * instance was a fall back in case it didn't succeed so
171 * return ignore.
172 */
173 if (debug) {
174 __pam_log(LOG_AUTH | LOG_DEBUG,
175 "PAM-KRB5 (auth): PKINIT succeeded "
176 "earlier so returning PAM_IGNORE");
177 }
178 return (PAM_IGNORE);
179 }
180 }
181
182 (void) pam_get_item(pamh, PAM_USER, (void**) &user);
183
184 if (user == NULL || *user == '\0') {
185 if (do_pkinit) {
186 /*
187 * If doing PKINIT it is okay to prompt for the user
188 * name.
189 */
190 if ((err = pam_get_user(pamh, &user, NULL)) !=
191 PAM_SUCCESS) {
192 if (debug) {
193 __pam_log(LOG_AUTH | LOG_DEBUG,
194 "PAM-KRB5 (auth): get user failed: "
195 "%s", pam_strerror(pamh, err));
196 }
197 return (err);
198 }
199 } else {
200 if (debug)
201 __pam_log(LOG_AUTH | LOG_DEBUG,
202 "PAM-KRB5 (auth): user empty or null");
203 return (PAM_USER_UNKNOWN);
204 }
205 }
206
207 /* make sure a password entry exists for this user */
208 if (!get_pw_uid(user, &pw_uid))
209 return (PAM_USER_UNKNOWN);
210
211 if (kmd == NULL) {
212 kmd = calloc(1, sizeof (krb5_module_data_t));
213 if (kmd == NULL) {
214 result = PAM_BUF_ERR;
215 goto out;
216 }
217
218 err = pam_set_data(pamh, KRB5_DATA, kmd, &krb5_cleanup);
219 if (err != PAM_SUCCESS) {
220 free(kmd);
221 result = err;
222 goto out;
223 }
224 }
225
226 if (!kmd->env) {
227 char buffer[512];
228
229 if (snprintf(buffer, sizeof (buffer),
230 "%s=FILE:/tmp/krb5cc_%d",
231 KRB5_ENV_CCNAME, (int)pw_uid) >= sizeof (buffer)) {
232 result = PAM_SYSTEM_ERR;
233 goto out;
234 }
235
236 /* we MUST copy this to the heap for the putenv to work! */
237 kmd->env = strdup(buffer);
238 if (!kmd->env) {
239 result = PAM_BUF_ERR;
240 goto out;
241 } else {
242 if (putenv(kmd->env)) {
243 result = PAM_SYSTEM_ERR;
244 goto out;
245 }
246 }
247 }
248
249 if (kmd->user != NULL)
250 free(kmd->user);
251 if ((kmd->user = strdup(user)) == NULL) {
252 result = PAM_BUF_ERR;
253 goto out;
254 }
255
256 kmd->auth_status = PAM_AUTH_ERR;
257 kmd->debug = debug;
258 kmd->warn = warn;
259 kmd->err_on_exp = err_on_exp;
260 kmd->ccache = NULL;
261 kmd->kcontext = NULL;
262 kmd->password = NULL;
263 kmd->age_status = PAM_SUCCESS;
264 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
265 kmd->auth_calls = 1;
266 kmd->preauth_type = do_pkinit ? KRB_PKINIT : KRB_PASSWD;
267
268 /*
269 * For apps that already did krb5 auth exchange...
270 * Now that we've created the kmd structure, we can
271 * return SUCCESS. 'kmd' may be needed later by other
272 * PAM functions, thats why we wait until this point to
273 * return.
274 */
275 (void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
276
277 if (rep_data != NULL) {
278 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
279 if (debug)
280 __pam_log(LOG_AUTH | LOG_DEBUG,
281 "PAM-KRB5 (auth): wrong"
282 "repository found (%s), returning "
283 "PAM_IGNORE", rep_data->type);
284 return (PAM_IGNORE);
285 }
286 if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
287 krb5_data = (krb5_repository_data_t *)rep_data->scope;
288
289 if (krb5_data->flags ==
290 SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
291 krb5_data->principal != NULL &&
292 strlen(krb5_data->principal)) {
293 if (debug)
294 __pam_log(LOG_AUTH | LOG_DEBUG,
295 "PAM-KRB5 (auth): Principal "
296 "%s already authenticated",
297 krb5_data->principal);
298 kmd->auth_status = PAM_SUCCESS;
299 return (PAM_SUCCESS);
300 }
301 }
302 }
303
304 /*
305 * if root key exists in the keytab, it's a random key so no
306 * need to prompt for pw and we just return IGNORE.
307 *
308 * note we don't need to force a prompt for pw as authtok_get
309 * is required to be stacked above this module.
310 */
311 if ((strcmp(user, ROOT_UNAME) == 0) &&
312 key_in_keytab(user, debug)) {
313 if (debug)
314 __pam_log(LOG_AUTH | LOG_DEBUG,
315 "PAM-KRB5 (auth): "
316 "key for '%s' in keytab, returning IGNORE", user);
317 result = PAM_IGNORE;
318 goto out;
319 }
320
321 (void) pam_get_item(pamh, PAM_AUTHTOK, (void **)&password);
322
323 result = attempt_krb5_auth(pamh, kmd, user, &password, 1);
324
325 out:
326 if (kmd) {
327 if (debug)
328 __pam_log(LOG_AUTH | LOG_DEBUG,
329 "PAM-KRB5 (auth): pam_sm_auth finalize"
330 " ccname env, result =%d, env ='%s',"
331 " age = %d, status = %d",
332 result, kmd->env ? kmd->env : "<null>",
333 kmd->age_status, kmd->auth_status);
334
335 if (kmd->env &&
336 !(kmd->age_status == PAM_NEW_AUTHTOK_REQD &&
337 kmd->auth_status == PAM_SUCCESS)) {
338
339
340 if (result == PAM_SUCCESS) {
341 /*
342 * Put ccname into the pamh so that login
343 * apps can pick this up when they run
344 * pam_getenvlist().
345 */
346 if ((result = pam_putenv(pamh, kmd->env))
347 != PAM_SUCCESS) {
348 /* should not happen but... */
349 __pam_log(LOG_AUTH | LOG_ERR,
350 "PAM-KRB5 (auth):"
351 " pam_putenv failed: result: %d",
352 result);
353 goto cleanupccname;
354 }
355 } else {
356 cleanupccname:
357 /* for lack of a Solaris unputenv() */
358 krb5_unsetenv(KRB5_ENV_CCNAME);
359 free(kmd->env);
360 kmd->env = NULL;
361 }
362 }
363 kmd->auth_status = result;
364 }
365
366 if (debug)
367 __pam_log(LOG_AUTH | LOG_DEBUG,
368 "PAM-KRB5 (auth): end: %s", pam_strerror(pamh, result));
369
370 return (result);
371 }
372
373 static krb5_error_code
pam_krb5_prompter(krb5_context ctx,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt prompts[])374 pam_krb5_prompter(
375 krb5_context ctx,
376 void *data,
377 /* ARGSUSED1 */
378 const char *name,
379 const char *banner,
380 int num_prompts,
381 krb5_prompt prompts[])
382 {
383 krb5_error_code rc = KRB5_LIBOS_CANTREADPWD;
384 pam_handle_t *pamh = (pam_handle_t *)data;
385 struct pam_conv *pam_convp;
386 struct pam_message *msgs = NULL;
387 struct pam_response *ret_respp = NULL;
388 int i;
389 krb5_prompt_type *prompt_type = krb5_get_prompt_types(ctx);
390 char tmpbuf[PAM_MAX_MSG_SIZE];
391
392 if (prompts) {
393 assert(num_prompts > 0);
394 }
395 /*
396 * Because this function should never be used for password prompts,
397 * disallow password prompts.
398 */
399 for (i = 0; i < num_prompts; i++) {
400 switch (prompt_type[i]) {
401 case KRB5_PROMPT_TYPE_PASSWORD:
402 case KRB5_PROMPT_TYPE_NEW_PASSWORD:
403 case KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN:
404 return (rc);
405 }
406 }
407
408 if (pam_get_item(pamh, PAM_CONV, (void **)&pam_convp) != PAM_SUCCESS) {
409 return (rc);
410 }
411 if (pam_convp == NULL) {
412 return (rc);
413 }
414
415 msgs = (struct pam_message *)calloc(num_prompts,
416 sizeof (struct pam_message));
417 if (msgs == NULL) {
418 return (rc);
419 }
420 (void) memset(msgs, 0, sizeof (struct pam_message) * num_prompts);
421
422 for (i = 0; i < num_prompts; i++) {
423 /* convert krb prompt style to PAM style */
424 if (prompts[i].hidden) {
425 msgs[i].msg_style = PAM_PROMPT_ECHO_OFF;
426 } else {
427 msgs[i].msg_style = PAM_PROMPT_ECHO_ON;
428 }
429 /*
430 * krb expects the prompting function to append ": " to the
431 * prompt string.
432 */
433 if (snprintf(tmpbuf, sizeof (tmpbuf), "%s: ",
434 prompts[i].prompt) < 0) {
435 goto cleanup;
436 }
437 msgs[i].msg = strdup(tmpbuf);
438 if (msgs[i].msg == NULL) {
439 goto cleanup;
440 }
441 }
442
443 /*
444 * Call PAM conv function to display the prompt.
445 */
446
447 if ((pam_convp->conv)(num_prompts, &msgs, &ret_respp,
448 pam_convp->appdata_ptr) == PAM_SUCCESS) {
449 for (i = 0; i < num_prompts; i++) {
450 /* convert PAM response to krb prompt reply format */
451 assert(prompts[i].reply->data != NULL);
452 assert(ret_respp[i].resp != NULL);
453
454 if (strlcpy(prompts[i].reply->data,
455 ret_respp[i].resp, prompts[i].reply->length) >=
456 prompts[i].reply->length) {
457 char errmsg[1][PAM_MAX_MSG_SIZE];
458
459 (void) snprintf(errmsg[0], PAM_MAX_MSG_SIZE,
460 "%s", dgettext(TEXT_DOMAIN,
461 "Reply too long: "));
462 (void) __pam_display_msg(pamh, PAM_ERROR_MSG,
463 1, errmsg, NULL);
464 goto cleanup;
465 } else {
466 char *retp;
467
468 /*
469 * newline must be replaced with \0 terminator
470 */
471 retp = strchr(prompts[i].reply->data, '\n');
472 if (retp != NULL)
473 *retp = '\0';
474 /* NULL terminator should not be counted */
475 prompts[i].reply->length =
476 strlen(prompts[i].reply->data);
477 }
478 }
479 rc = 0;
480 }
481
482 cleanup:
483 for (i = 0; i < num_prompts; i++) {
484 if (msgs[i].msg) {
485 free(msgs[i].msg);
486 }
487 if (ret_respp[i].resp) {
488 /* 0 out sensitive data before free() */
489 (void) memset(ret_respp[i].resp, 0,
490 strlen(ret_respp[i].resp));
491 free(ret_respp[i].resp);
492 }
493 }
494 free(msgs);
495 free(ret_respp);
496 return (rc);
497 }
498
499 int
attempt_krb5_auth(pam_handle_t * pamh,krb5_module_data_t * kmd,char * user,char ** krb5_pass,boolean_t verify_tik)500 attempt_krb5_auth(
501 pam_handle_t *pamh,
502 krb5_module_data_t *kmd,
503 char *user,
504 char **krb5_pass,
505 boolean_t verify_tik)
506 {
507 krb5_principal me = NULL, clientp = NULL;
508 krb5_principal server = NULL, serverp = NULL;
509 krb5_creds *my_creds;
510 krb5_timestamp now;
511 krb5_error_code code = 0;
512 char kuser[2*MAXHOSTNAMELEN];
513 krb5_deltat lifetime;
514 krb5_deltat rlife;
515 krb5_deltat krb5_max_duration;
516 int options = KRB5_DEFAULT_OPTIONS;
517 krb5_data tgtname = {
518 0,
519 KRB5_TGS_NAME_SIZE,
520 KRB5_TGS_NAME
521 };
522 krb5_get_init_creds_opt *opts = NULL;
523 krb5_kdc_rep *as_reply = NULL;
524 /*
525 * "result" should not be assigned PAM_SUCCESS unless
526 * authentication has succeeded and there are no other errors.
527 *
528 * "code" is sometimes used for PAM codes, sometimes for krb5
529 * codes. Be careful.
530 */
531 int result = PAM_AUTH_ERR;
532
533 if (kmd->debug)
534 __pam_log(LOG_AUTH | LOG_DEBUG,
535 "PAM-KRB5 (auth): attempt_krb5_auth: start: user='%s'",
536 user ? user : "<null>");
537
538 /* need to free context with krb5_free_context */
539 if (code = krb5_init_secure_context(&kmd->kcontext)) {
540 __pam_log(LOG_AUTH | LOG_ERR,
541 "PAM-KRB5 (auth): Error initializing "
542 "krb5: %s",
543 error_message(code));
544 return (PAM_SYSTEM_ERR);
545 }
546
547 if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
548 2*MAXHOSTNAMELEN)) != 0) {
549 /* get_kmd_kuser returns proper PAM error statuses */
550 return (code);
551 }
552
553 if ((code = krb5_parse_name(kmd->kcontext, kuser, &me)) != 0) {
554 krb5_free_context(kmd->kcontext);
555 kmd->kcontext = NULL;
556 return (PAM_SYSTEM_ERR);
557 }
558
559 /* call krb5_free_cred_contents() on error */
560 my_creds = &kmd->initcreds;
561
562 if ((code =
563 krb5_copy_principal(kmd->kcontext, me, &my_creds->client))) {
564 result = PAM_SYSTEM_ERR;
565 goto out_err;
566 }
567 clientp = my_creds->client;
568
569 if (code = krb5_build_principal_ext(kmd->kcontext, &server,
570 krb5_princ_realm(kmd->kcontext, me)->length,
571 krb5_princ_realm(kmd->kcontext, me)->data,
572 tgtname.length, tgtname.data,
573 krb5_princ_realm(kmd->kcontext, me)->length,
574 krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
575 __pam_log(LOG_AUTH | LOG_ERR,
576 "PAM-KRB5 (auth): attempt_krb5_auth: "
577 "krb5_build_princ_ext failed: %s",
578 error_message(code));
579 result = PAM_SYSTEM_ERR;
580 goto out;
581 }
582
583 if (code = krb5_copy_principal(kmd->kcontext, server,
584 &my_creds->server)) {
585 result = PAM_SYSTEM_ERR;
586 goto out_err;
587 }
588 serverp = my_creds->server;
589
590 if (code = krb5_timeofday(kmd->kcontext, &now)) {
591 __pam_log(LOG_AUTH | LOG_ERR,
592 "PAM-KRB5 (auth): attempt_krb5_auth: "
593 "krb5_timeofday failed: %s",
594 error_message(code));
595 result = PAM_SYSTEM_ERR;
596 goto out;
597 }
598
599 /*
600 * set the values for lifetime and rlife to be the maximum
601 * possible
602 */
603 krb5_max_duration = KRB5_KDB_EXPIRATION - now - 60*60;
604 lifetime = krb5_max_duration;
605 rlife = krb5_max_duration;
606
607 /*
608 * Let us get the values for various options
609 * from Kerberos configuration file
610 */
611
612 krb_realm = krb5_princ_realm(kmd->kcontext, me)->data;
613 profile_get_options_boolean(kmd->kcontext->profile,
614 realmdef, config_option);
615 profile_get_options_boolean(kmd->kcontext->profile,
616 appdef, config_option);
617 profile_get_options_string(kmd->kcontext->profile,
618 realmdef, config_times);
619 profile_get_options_string(kmd->kcontext->profile,
620 appdef, config_times);
621
622 if (renew_timeval) {
623 code = krb5_string_to_deltat(renew_timeval, &rlife);
624 if (code != 0 || rlife == 0 || rlife > krb5_max_duration) {
625 __pam_log(LOG_AUTH | LOG_ERR,
626 "PAM-KRB5 (auth): Bad max_renewable_life "
627 " value '%s' in Kerberos config file",
628 renew_timeval);
629 result = PAM_SYSTEM_ERR;
630 goto out;
631 }
632 }
633 if (life_timeval) {
634 code = krb5_string_to_deltat(life_timeval, &lifetime);
635 if (code != 0 || lifetime == 0 ||
636 lifetime > krb5_max_duration) {
637 __pam_log(LOG_AUTH | LOG_ERR,
638 "lifetime value '%s' in Kerberos config file",
639 life_timeval);
640 result = PAM_SYSTEM_ERR;
641 goto out;
642 }
643 }
644 /* start timer when request gets to KDC */
645 my_creds->times.starttime = 0;
646 my_creds->times.endtime = now + lifetime;
647
648 if (options & KDC_OPT_RENEWABLE) {
649 my_creds->times.renew_till = now + rlife;
650 } else
651 my_creds->times.renew_till = 0;
652
653 code = krb5_get_init_creds_opt_alloc(kmd->kcontext, &opts);
654 if (code != 0) {
655 __pam_log(LOG_AUTH | LOG_ERR,
656 "Error allocating gic opts: %s",
657 error_message(code));
658 result = PAM_SYSTEM_ERR;
659 goto out;
660 }
661
662 krb5_get_init_creds_opt_set_tkt_life(opts, lifetime);
663
664 if (proxiable_flag) { /* Set in config file */
665 if (kmd->debug)
666 __pam_log(LOG_AUTH | LOG_DEBUG,
667 "PAM-KRB5 (auth): Proxiable tickets "
668 "requested");
669 krb5_get_init_creds_opt_set_proxiable(opts, TRUE);
670 }
671 if (forwardable_flag) {
672 if (kmd->debug)
673 __pam_log(LOG_AUTH | LOG_DEBUG,
674 "PAM-KRB5 (auth): Forwardable tickets "
675 "requested");
676 krb5_get_init_creds_opt_set_forwardable(opts, TRUE);
677 }
678 if (renewable_flag) {
679 if (kmd->debug)
680 __pam_log(LOG_AUTH | LOG_DEBUG,
681 "PAM-KRB5 (auth): Renewable tickets "
682 "requested");
683 krb5_get_init_creds_opt_set_renew_life(opts, rlife);
684 }
685 if (no_address_flag) {
686 if (kmd->debug)
687 __pam_log(LOG_AUTH | LOG_DEBUG,
688 "PAM-KRB5 (auth): Addressless tickets "
689 "requested");
690 krb5_get_init_creds_opt_set_address_list(opts, NULL);
691 }
692
693 /*
694 * mech_krb5 interprets empty passwords as NULL passwords and tries to
695 * read a password from stdin. Since we are in pam this is bad and
696 * should not be allowed.
697 *
698 * Note, the logic now is that if the preauth_type is PKINIT then
699 * provide a proper PAMcentric prompt function that the underlying
700 * PKINIT preauth plugin will use to prompt for the PIN.
701 */
702 if (kmd->preauth_type == KRB_PKINIT) {
703 /*
704 * Do PKINIT preauth
705 *
706 * Note: we want to limit preauth types to just those for PKINIT
707 * but krb5_get_init_creds() doesn't support that at this point.
708 * Instead we rely on pam_krb5_prompter() to limit prompts to
709 * non-password types. So all we can do here is set the preauth
710 * list so krb5_get_init_creds() will try that first.
711 */
712 krb5_preauthtype pk_pa_list[] = {
713 KRB5_PADATA_PK_AS_REQ,
714 KRB5_PADATA_PK_AS_REQ_OLD
715 };
716 krb5_get_init_creds_opt_set_preauth_list(opts, pk_pa_list, 2);
717
718 if (*krb5_pass == NULL || strlen(*krb5_pass) != 0) {
719 if (*krb5_pass != NULL) {
720 /* treat the krb5_pass as a PIN */
721 code = krb5_get_init_creds_opt_set_pa(
722 kmd->kcontext, opts, "PIN", *krb5_pass);
723 }
724
725 if (!code) {
726 code = __krb5_get_init_creds_password(
727 kmd->kcontext,
728 my_creds,
729 me,
730 NULL, /* clear text passwd */
731 pam_krb5_prompter, /* prompter */
732 pamh, /* prompter data */
733 0, /* start time */
734 NULL, /* defaults to krbtgt@REALM */
735 opts,
736 &as_reply);
737 }
738 } else {
739 /* invalid PIN */
740 code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
741 }
742 } else {
743 /*
744 * Do password based preauths
745 *
746 * See earlier PKINIT comment. We are doing something similar
747 * here but we do not pass in a prompter (we assume
748 * pam_authtok_get has already prompted for that).
749 */
750 if (*krb5_pass == NULL || strlen(*krb5_pass) == 0) {
751 code = KRB5KRB_AP_ERR_BAD_INTEGRITY;
752 } else {
753 krb5_preauthtype pk_pa_list[] = {
754 KRB5_PADATA_ENC_TIMESTAMP
755 };
756
757 krb5_get_init_creds_opt_set_preauth_list(opts,
758 pk_pa_list, 1);
759
760 /*
761 * We call our own private version of gic_pwd, because
762 * we need more information, such as password/account
763 * expiration, that is found in the as_reply. The
764 * "prompter" interface is not granular enough for PAM
765 * to make use of.
766 */
767 code = __krb5_get_init_creds_password(kmd->kcontext,
768 my_creds,
769 me,
770 *krb5_pass, /* clear text passwd */
771 NULL, /* prompter */
772 NULL, /* data */
773 0, /* start time */
774 NULL, /* defaults to krbtgt@REALM */
775 opts,
776 &as_reply);
777 }
778 }
779
780 if (kmd->debug)
781 __pam_log(LOG_AUTH | LOG_DEBUG,
782 "PAM-KRB5 (auth): attempt_krb5_auth: "
783 "krb5_get_init_creds_password returns: %s",
784 code == 0 ? "SUCCESS" : error_message(code));
785
786 switch (code) {
787 case 0:
788 /* got a tgt, let's verify it */
789 if (verify_tik) {
790 krb5_verify_init_creds_opt vopts;
791
792 krb5_principal sp = NULL;
793 char kt_name[MAX_KEYTAB_NAME_LEN];
794 char *fqdn;
795
796 krb5_verify_init_creds_opt_init(&vopts);
797
798 code = krb5_verify_init_creds(kmd->kcontext,
799 my_creds,
800 NULL, /* defaults to host/localhost@REALM */
801 NULL,
802 NULL,
803 &vopts);
804
805 if (code) {
806 result = PAM_SYSTEM_ERR;
807
808 /*
809 * Give a better error message when the
810 * keytable entry isn't found or the keytab
811 * file cannot be found.
812 */
813 if (krb5_sname_to_principal(kmd->kcontext, NULL,
814 NULL, KRB5_NT_SRV_HST, &sp))
815 fqdn = "<fqdn>";
816 else
817 fqdn = sp->data[1].data;
818
819 if (krb5_kt_default_name(kmd->kcontext, kt_name,
820 sizeof (kt_name)))
821 (void) strlcpy(kt_name,
822 "default keytab",
823 sizeof (kt_name));
824
825 switch (code) {
826 case KRB5_KT_NOTFOUND:
827 __pam_log(LOG_AUTH | LOG_ERR,
828 "PAM-KRB5 (auth): "
829 "krb5_verify_init_creds failed:"
830 " Key table entry \"host/%s\""
831 " not found in %s",
832 fqdn, kt_name);
833 break;
834 case ENOENT:
835 __pam_log(LOG_AUTH | LOG_ERR,
836 "PAM-KRB5 (auth): "
837 "krb5_verify_init_creds failed:"
838 " Keytab file \"%s\""
839 " does not exist.\n",
840 kt_name);
841 break;
842 default:
843 __pam_log(LOG_AUTH | LOG_ERR,
844 "PAM-KRB5 (auth): "
845 "krb5_verify_init_creds failed:"
846 " %s",
847 error_message(code));
848 break;
849 }
850
851 if (sp)
852 krb5_free_principal(kmd->kcontext, sp);
853 }
854 }
855
856 if (code == 0)
857 kmd->expiration = as_reply->enc_part2->key_exp;
858
859 break;
860
861 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
862 /*
863 * Since this principal is not part of the local
864 * Kerberos realm, we just return PAM_USER_UNKNOWN.
865 */
866 result = PAM_USER_UNKNOWN;
867
868 if (kmd->debug)
869 __pam_log(LOG_AUTH | LOG_DEBUG,
870 "PAM-KRB5 (auth): attempt_krb5_auth:"
871 " User is not part of the local Kerberos"
872 " realm: %s", error_message(code));
873 break;
874
875 case KRB5KDC_ERR_PREAUTH_FAILED:
876 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
877 /*
878 * We could be trying the password from a previous
879 * pam authentication module, but we don't want to
880 * generate an error if the unix password is different
881 * than the Kerberos password...
882 */
883 break;
884
885 case KRB5KDC_ERR_KEY_EXP:
886 if (!kmd->err_on_exp) {
887 /*
888 * Request a tik for changepw service and it will tell
889 * us if pw is good or not. If PKINIT is being done it
890 * is possible that *krb5_pass may be NULL so check for
891 * that. If that is the case this function will return
892 * an error.
893 */
894 if (*krb5_pass != NULL) {
895 code = krb5_verifypw(kuser, *krb5_pass,
896 kmd->debug);
897 if (kmd->debug) {
898 __pam_log(LOG_AUTH | LOG_DEBUG,
899 "PAM-KRB5 (auth): "
900 "attempt_krb5_auth: "
901 "verifypw %d", code);
902 }
903 if (code == 0) {
904 /*
905 * pw is good, set age status for
906 * acct_mgmt.
907 */
908 kmd->age_status = PAM_NEW_AUTHTOK_REQD;
909 }
910 }
911
912 }
913 break;
914
915 default:
916 result = PAM_SYSTEM_ERR;
917 if (kmd->debug)
918 __pam_log(LOG_AUTH | LOG_DEBUG,
919 "PAM-KRB5 (auth): error %d - %s",
920 code, error_message(code));
921 break;
922 }
923
924 if (code == 0) {
925 /*
926 * success for the entered pw or PKINIT succeeded.
927 *
928 * we can't rely on the pw in PAM_AUTHTOK
929 * to be the (correct) krb5 one so
930 * store krb5 pw in module data for
931 * use in acct_mgmt. Note that *krb5_pass may be NULL if we're
932 * doing PKINIT.
933 */
934 if (*krb5_pass != NULL &&
935 !(kmd->password = strdup(*krb5_pass))) {
936 __pam_log(LOG_AUTH | LOG_ERR,
937 "Cannot strdup password");
938 result = PAM_BUF_ERR;
939 goto out_err;
940 }
941
942 result = PAM_SUCCESS;
943 goto out;
944 }
945
946 out_err:
947 /* jump (or reach) here if error and cred cache has been init */
948
949 if (kmd->debug)
950 __pam_log(LOG_AUTH | LOG_DEBUG,
951 "PAM-KRB5 (auth): clearing initcreds in "
952 "pam_authenticate()");
953
954 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
955 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
956
957 out:
958 if (server)
959 krb5_free_principal(kmd->kcontext, server);
960 if (me)
961 krb5_free_principal(kmd->kcontext, me);
962 if (as_reply)
963 krb5_free_kdc_rep(kmd->kcontext, as_reply);
964
965 /*
966 * clientp or serverp could be NULL in certain error cases in this
967 * function. mycreds->[client|server] could also be NULL in case
968 * of error in this function, see out_err above. The pointers clientp
969 * and serverp reference the input argument in my_creds for
970 * get_init_creds and must be freed if the input argument does not
971 * match the output argument, which occurs during a successful call
972 * to get_init_creds.
973 */
974 if (clientp && my_creds->client && clientp != my_creds->client)
975 krb5_free_principal(kmd->kcontext, clientp);
976 if (serverp && my_creds->server && serverp != my_creds->server)
977 krb5_free_principal(kmd->kcontext, serverp);
978
979 if (kmd->kcontext) {
980 krb5_free_context(kmd->kcontext);
981 kmd->kcontext = NULL;
982 }
983 if (opts)
984 krb5_get_init_creds_opt_free(kmd->kcontext, opts);
985
986 if (kmd->debug)
987 __pam_log(LOG_AUTH | LOG_DEBUG,
988 "PAM-KRB5 (auth): attempt_krb5_auth returning %d",
989 result);
990
991 return (kmd->auth_status = result);
992 }
993
994 /*ARGSUSED*/
995 void
krb5_cleanup(pam_handle_t * pamh,void * data,int pam_status)996 krb5_cleanup(pam_handle_t *pamh, void *data, int pam_status)
997 {
998 krb5_module_data_t *kmd = (krb5_module_data_t *)data;
999
1000 if (kmd == NULL)
1001 return;
1002
1003 if (kmd->debug) {
1004 __pam_log(LOG_AUTH | LOG_DEBUG,
1005 "PAM-KRB5 (auth): krb5_cleanup auth_status = %d",
1006 kmd->auth_status);
1007 }
1008
1009 /*
1010 * Apps could be calling pam_end here, so we should always clean
1011 * up regardless of success or failure here.
1012 */
1013 if (kmd->ccache)
1014 (void) krb5_cc_close(kmd->kcontext, kmd->ccache);
1015
1016 if (kmd->password) {
1017 (void) memset(kmd->password, 0, strlen(kmd->password));
1018 free(kmd->password);
1019 }
1020
1021 if (kmd->user)
1022 free(kmd->user);
1023
1024 if (kmd->env)
1025 free(kmd->env);
1026
1027 krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
1028 (void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
1029
1030 free(kmd);
1031 }
1032