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