pam_krb5.c revision 1.1 1 /*-
2 * This pam_krb5 module contains code that is:
3 * Copyright (c) Derrick J. Brashear, 1996. All rights reserved.
4 * Copyright (c) Frank Cusack, 1999-2001. All rights reserved.
5 * Copyright (c) Jacques A. Vidrine, 2000-2001. All rights reserved.
6 * Copyright (c) Nicolas Williams, 2001. All rights reserved.
7 * Copyright (c) Perot Systems Corporation, 2001. All rights reserved.
8 * Copyright (c) Mark R V Murray, 2001. All rights reserved.
9 * Copyright (c) Networks Associates Technology, Inc., 2002-2003.
10 * All rights reserved.
11 *
12 * Portions of this software were developed for the FreeBSD Project by
13 * ThinkSec AS and NAI Labs, the Security Research Division of Network
14 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
15 * ("CBOSS"), as part of the DARPA CHATS research program.
16 *
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions
19 * are met:
20 * 1. Redistributions of source code must retain the above copyright
21 * notices, and the entire permission notice in its entirety,
22 * including the disclaimer of warranties.
23 * 2. Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
26 * 3. The name of the author may not be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * ALTERNATIVELY, this product may be distributed under the terms of
31 * the GNU Public License, in which case the provisions of the GPL are
32 * required INSTEAD OF the above restrictions. (This clause is
33 * necessary due to a potential bad interaction between the GPL and
34 * the restrictions contained in a BSD-style copyright.)
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
40 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
41 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
42 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46 * OF THE POSSIBILITY OF SUCH DAMAGE.
47 *
48 */
49
50 #include <sys/cdefs.h>
51 __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_krb5/pam_krb5.c,v 1.20 2004/02/10 10:13:20 des Exp $");
52
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <errno.h>
56 #include <limits.h>
57 #include <pwd.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <syslog.h>
62 #include <unistd.h>
63
64 #include <krb5.h>
65 #include <com_err.h>
66
67 #define PAM_SM_AUTH
68 #define PAM_SM_ACCOUNT
69 #define PAM_SM_PASSWORD
70
71 #include <security/pam_appl.h>
72 #include <security/pam_modules.h>
73 #include <security/pam_mod_misc.h>
74 #include <security/openpam.h>
75
76 #define COMPAT_HEIMDAL
77 /* #define COMPAT_MIT */
78
79 static int verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int);
80 static void cleanup_cache(pam_handle_t *, void *, int);
81 static const char *compat_princ_component(krb5_context, krb5_principal, int);
82 static void compat_free_data_contents(krb5_context, krb5_data *);
83
84 #define USER_PROMPT "Username: "
85 #define PASSWORD_PROMPT "Password:"
86 #define NEW_PASSWORD_PROMPT "New Password:"
87
88 #define PAM_OPT_CCACHE "ccache"
89 #define PAM_OPT_FORWARDABLE "forwardable"
90 #define PAM_OPT_NO_CCACHE "no_ccache"
91 #define PAM_OPT_REUSE_CCACHE "reuse_ccache"
92
93 /*
94 * authentication management
95 */
96 PAM_EXTERN int
97 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
98 int argc __unused, const char *argv[] __unused)
99 {
100 krb5_error_code krbret;
101 krb5_context pam_context;
102 krb5_creds creds;
103 krb5_principal princ;
104 krb5_ccache ccache;
105 krb5_get_init_creds_opt opts;
106 struct passwd *pwd;
107 int retval;
108 void *ccache_data;
109 const char *user, *pass;
110 const void *sourceuser, *service;
111 char *principal, *princ_name, *ccache_name, luser[32], *srvdup;
112
113 retval = pam_get_user(pamh, &user, USER_PROMPT);
114 if (retval != PAM_SUCCESS)
115 return (retval);
116
117 PAM_LOG("Got user: %s", user);
118
119 retval = pam_get_item(pamh, PAM_RUSER, &sourceuser);
120 if (retval != PAM_SUCCESS)
121 return (retval);
122
123 PAM_LOG("Got ruser: %s", (const char *)sourceuser);
124
125 service = NULL;
126 pam_get_item(pamh, PAM_SERVICE, &service);
127 if (service == NULL)
128 service = "unknown";
129
130 PAM_LOG("Got service: %s", (const char *)service);
131
132 krbret = krb5_init_context(&pam_context);
133 if (krbret != 0) {
134 PAM_VERBOSE_ERROR("Kerberos 5 error");
135 return (PAM_SERVICE_ERR);
136 }
137
138 PAM_LOG("Context initialised");
139
140 krb5_get_init_creds_opt_init(&opts);
141
142 if (openpam_get_option(pamh, PAM_OPT_FORWARDABLE))
143 krb5_get_init_creds_opt_set_forwardable(&opts, 1);
144
145 PAM_LOG("Credentials initialised");
146
147 krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE);
148 if (krbret != 0 && krbret != KRB5_CC_TYPE_EXISTS) {
149 PAM_VERBOSE_ERROR("Kerberos 5 error");
150 retval = PAM_SERVICE_ERR;
151 goto cleanup3;
152 }
153
154 PAM_LOG("Done krb5_cc_register()");
155
156 /* Get principal name */
157 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
158 asprintf(&principal, "%s/%s", (const char *)sourceuser, user);
159 else
160 principal = strdup(user);
161
162 PAM_LOG("Created principal: %s", principal);
163
164 krbret = krb5_parse_name(pam_context, principal, &princ);
165 free(principal);
166 if (krbret != 0) {
167 PAM_LOG("Error krb5_parse_name(): %s",
168 krb5_get_err_text(pam_context, krbret));
169 PAM_VERBOSE_ERROR("Kerberos 5 error");
170 retval = PAM_SERVICE_ERR;
171 goto cleanup3;
172 }
173
174 PAM_LOG("Done krb5_parse_name()");
175
176 /* Now convert the principal name into something human readable */
177 princ_name = NULL;
178 krbret = krb5_unparse_name(pam_context, princ, &princ_name);
179 if (krbret != 0) {
180 PAM_LOG("Error krb5_unparse_name(): %s",
181 krb5_get_err_text(pam_context, krbret));
182 PAM_VERBOSE_ERROR("Kerberos 5 error");
183 retval = PAM_SERVICE_ERR;
184 goto cleanup2;
185 }
186
187 PAM_LOG("Got principal: %s", princ_name);
188
189 /* Get password */
190 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT);
191 if (retval != PAM_SUCCESS)
192 goto cleanup2;
193
194 PAM_LOG("Got password");
195
196 /* Verify the local user exists (AFTER getting the password) */
197 if (strchr(user, '@')) {
198 /* get a local account name for this principal */
199 krbret = krb5_aname_to_localname(pam_context, princ,
200 sizeof(luser), luser);
201 if (krbret != 0) {
202 PAM_VERBOSE_ERROR("Kerberos 5 error");
203 PAM_LOG("Error krb5_aname_to_localname(): %s",
204 krb5_get_err_text(pam_context, krbret));
205 retval = PAM_USER_UNKNOWN;
206 goto cleanup2;
207 }
208
209 retval = pam_set_item(pamh, PAM_USER, luser);
210 if (retval != PAM_SUCCESS)
211 goto cleanup2;
212
213 PAM_LOG("PAM_USER Redone");
214 }
215
216 pwd = getpwnam(user);
217 if (pwd == NULL) {
218 retval = PAM_USER_UNKNOWN;
219 goto cleanup2;
220 }
221
222 PAM_LOG("Done getpwnam()");
223
224 /* Get a TGT */
225 memset(&creds, 0, sizeof(krb5_creds));
226 krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
227 pass, NULL, pamh, 0, NULL, &opts);
228 if (krbret != 0) {
229 PAM_VERBOSE_ERROR("Kerberos 5 error");
230 PAM_LOG("Error krb5_get_init_creds_password(): %s",
231 krb5_get_err_text(pam_context, krbret));
232 retval = PAM_AUTH_ERR;
233 goto cleanup2;
234 }
235
236 PAM_LOG("Got TGT");
237
238 /* Generate a temporary cache */
239 krbret = krb5_cc_gen_new(pam_context, &krb5_mcc_ops, &ccache);
240 if (krbret != 0) {
241 PAM_VERBOSE_ERROR("Kerberos 5 error");
242 PAM_LOG("Error krb5_cc_gen_new(): %s",
243 krb5_get_err_text(pam_context, krbret));
244 retval = PAM_SERVICE_ERR;
245 goto cleanup;
246 }
247 krbret = krb5_cc_initialize(pam_context, ccache, princ);
248 if (krbret != 0) {
249 PAM_VERBOSE_ERROR("Kerberos 5 error");
250 PAM_LOG("Error krb5_cc_initialize(): %s",
251 krb5_get_err_text(pam_context, krbret));
252 retval = PAM_SERVICE_ERR;
253 goto cleanup;
254 }
255 krbret = krb5_cc_store_cred(pam_context, ccache, &creds);
256 if (krbret != 0) {
257 PAM_VERBOSE_ERROR("Kerberos 5 error");
258 PAM_LOG("Error krb5_cc_store_cred(): %s",
259 krb5_get_err_text(pam_context, krbret));
260 krb5_cc_destroy(pam_context, ccache);
261 retval = PAM_SERVICE_ERR;
262 goto cleanup;
263 }
264
265 PAM_LOG("Credentials stashed");
266
267 /* Verify them */
268 if ((srvdup = strdup(service)) == NULL) {
269 retval = PAM_BUF_ERR;
270 goto cleanup;
271 }
272 krbret = verify_krb_v5_tgt(pam_context, ccache, srvdup,
273 openpam_get_option(pamh, PAM_OPT_FORWARDABLE) ? 1 : 0);
274 free(srvdup);
275 if (krbret == -1) {
276 PAM_VERBOSE_ERROR("Kerberos 5 error");
277 krb5_cc_destroy(pam_context, ccache);
278 retval = PAM_AUTH_ERR;
279 goto cleanup;
280 }
281
282 PAM_LOG("Credentials stash verified");
283
284 retval = pam_get_data(pamh, "ccache", &ccache_data);
285 if (retval == PAM_SUCCESS) {
286 krb5_cc_destroy(pam_context, ccache);
287 PAM_VERBOSE_ERROR("Kerberos 5 error");
288 retval = PAM_AUTH_ERR;
289 goto cleanup;
290 }
291
292 PAM_LOG("Credentials stash not pre-existing");
293
294 asprintf(&ccache_name, "%s:%s", krb5_cc_get_type(pam_context,
295 ccache), krb5_cc_get_name(pam_context, ccache));
296 if (ccache_name == NULL) {
297 PAM_VERBOSE_ERROR("Kerberos 5 error");
298 retval = PAM_BUF_ERR;
299 goto cleanup;
300 }
301 retval = pam_set_data(pamh, "ccache", ccache_name, cleanup_cache);
302 if (retval != 0) {
303 krb5_cc_destroy(pam_context, ccache);
304 PAM_VERBOSE_ERROR("Kerberos 5 error");
305 retval = PAM_SERVICE_ERR;
306 goto cleanup;
307 }
308
309 PAM_LOG("Credentials stash saved");
310
311 cleanup:
312 krb5_free_cred_contents(pam_context, &creds);
313 PAM_LOG("Done cleanup");
314 cleanup2:
315 krb5_free_principal(pam_context, princ);
316 PAM_LOG("Done cleanup2");
317 cleanup3:
318 if (princ_name)
319 free(princ_name);
320
321 krb5_free_context(pam_context);
322
323 PAM_LOG("Done cleanup3");
324
325 if (retval != PAM_SUCCESS)
326 PAM_VERBOSE_ERROR("Kerberos 5 refuses you");
327
328 return (retval);
329 }
330
331 PAM_EXTERN int
332 pam_sm_setcred(pam_handle_t *pamh, int flags,
333 int argc __unused, const char *argv[] __unused)
334 {
335
336 krb5_error_code krbret;
337 krb5_context pam_context;
338 krb5_principal princ;
339 krb5_creds creds;
340 krb5_ccache ccache_temp, ccache_perm;
341 krb5_cc_cursor cursor;
342 struct passwd *pwd = NULL;
343 int retval;
344 const char *cache_name, *q;
345 const void *user;
346 void *cache_data;
347 char *cache_name_buf = NULL, *p;
348
349 uid_t euid;
350 gid_t egid;
351
352 if (flags & PAM_DELETE_CRED)
353 return (PAM_SUCCESS);
354
355 if (flags & PAM_REFRESH_CRED)
356 return (PAM_SUCCESS);
357
358 if (flags & PAM_REINITIALIZE_CRED)
359 return (PAM_SUCCESS);
360
361 if (!(flags & PAM_ESTABLISH_CRED))
362 return (PAM_SERVICE_ERR);
363
364 PAM_LOG("Establishing credentials");
365
366 /* Get username */
367 retval = pam_get_item(pamh, PAM_USER, &user);
368 if (retval != PAM_SUCCESS)
369 return (retval);
370
371 PAM_LOG("Got user: %s", (const char *)user);
372
373 krbret = krb5_init_context(&pam_context);
374 if (krbret != 0) {
375 PAM_LOG("Error krb5_init_context() failed");
376 return (PAM_SERVICE_ERR);
377 }
378
379 PAM_LOG("Context initialised");
380
381 euid = geteuid(); /* Usually 0 */
382 egid = getegid();
383
384 PAM_LOG("Got euid, egid: %d %d", euid, egid);
385
386 /* Retrieve the temporary cache */
387 retval = pam_get_data(pamh, "ccache", &cache_data);
388 if (retval != PAM_SUCCESS) {
389 retval = PAM_CRED_UNAVAIL;
390 goto cleanup3;
391 }
392 krbret = krb5_cc_resolve(pam_context, cache_data, &ccache_temp);
393 if (krbret != 0) {
394 PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)cache_data,
395 krb5_get_err_text(pam_context, krbret));
396 retval = PAM_SERVICE_ERR;
397 goto cleanup3;
398 }
399
400 /* Get the uid. This should exist. */
401 pwd = getpwnam(user);
402 if (pwd == NULL) {
403 retval = PAM_USER_UNKNOWN;
404 goto cleanup3;
405 }
406
407 PAM_LOG("Done getpwnam()");
408
409 /* Avoid following a symlink as root */
410 if (setegid(pwd->pw_gid)) {
411 retval = PAM_SERVICE_ERR;
412 goto cleanup3;
413 }
414 if (seteuid(pwd->pw_uid)) {
415 retval = PAM_SERVICE_ERR;
416 goto cleanup3;
417 }
418
419 PAM_LOG("Done setegid() & seteuid()");
420
421 /* Get the cache name */
422 cache_name = openpam_get_option(pamh, PAM_OPT_CCACHE);
423 if (cache_name == NULL) {
424 asprintf(&cache_name_buf, "FILE:/tmp/krb5cc_%d", pwd->pw_uid);
425 cache_name = cache_name_buf;
426 }
427
428 p = calloc(PATH_MAX + 16, sizeof(char));
429 q = cache_name;
430
431 if (p == NULL) {
432 PAM_LOG("Error malloc(): failure");
433 retval = PAM_BUF_ERR;
434 goto cleanup3;
435 }
436 cache_name = p;
437
438 /* convert %u and %p */
439 while (*q) {
440 if (*q == '%') {
441 q++;
442 if (*q == 'u') {
443 sprintf(p, "%d", pwd->pw_uid);
444 p += strlen(p);
445 }
446 else if (*q == 'p') {
447 sprintf(p, "%d", getpid());
448 p += strlen(p);
449 }
450 else {
451 /* Not a special token */
452 *p++ = '%';
453 q--;
454 }
455 q++;
456 }
457 else {
458 *p++ = *q++;
459 }
460 }
461
462 PAM_LOG("Got cache_name: %s", cache_name);
463
464 /* Initialize the new ccache */
465 krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ);
466 if (krbret != 0) {
467 PAM_LOG("Error krb5_cc_get_principal(): %s",
468 krb5_get_err_text(pam_context, krbret));
469 retval = PAM_SERVICE_ERR;
470 goto cleanup3;
471 }
472 krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm);
473 if (krbret != 0) {
474 PAM_LOG("Error krb5_cc_resolve(): %s",
475 krb5_get_err_text(pam_context, krbret));
476 retval = PAM_SERVICE_ERR;
477 goto cleanup2;
478 }
479 krbret = krb5_cc_initialize(pam_context, ccache_perm, princ);
480 if (krbret != 0) {
481 PAM_LOG("Error krb5_cc_initialize(): %s",
482 krb5_get_err_text(pam_context, krbret));
483 retval = PAM_SERVICE_ERR;
484 goto cleanup2;
485 }
486
487 PAM_LOG("Cache initialised");
488
489 /* Prepare for iteration over creds */
490 krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor);
491 if (krbret != 0) {
492 PAM_LOG("Error krb5_cc_start_seq_get(): %s",
493 krb5_get_err_text(pam_context, krbret));
494 krb5_cc_destroy(pam_context, ccache_perm);
495 retval = PAM_SERVICE_ERR;
496 goto cleanup2;
497 }
498
499 PAM_LOG("Prepared for iteration");
500
501 /* Copy the creds (should be two of them) */
502 while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp,
503 &cursor, &creds) == 0)) {
504 krbret = krb5_cc_store_cred(pam_context, ccache_perm, &creds);
505 if (krbret != 0) {
506 PAM_LOG("Error krb5_cc_store_cred(): %s",
507 krb5_get_err_text(pam_context, krbret));
508 krb5_cc_destroy(pam_context, ccache_perm);
509 krb5_free_cred_contents(pam_context, &creds);
510 retval = PAM_SERVICE_ERR;
511 goto cleanup2;
512 }
513 krb5_free_cred_contents(pam_context, &creds);
514 PAM_LOG("Iteration");
515 }
516 krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
517
518 PAM_LOG("Done iterating");
519
520 if (strstr(cache_name, "FILE:") == cache_name) {
521 if (chown(&cache_name[5], pwd->pw_uid, pwd->pw_gid) == -1) {
522 PAM_LOG("Error chown(): %s", strerror(errno));
523 krb5_cc_destroy(pam_context, ccache_perm);
524 retval = PAM_SERVICE_ERR;
525 goto cleanup2;
526 }
527 PAM_LOG("Done chown()");
528
529 if (chmod(&cache_name[5], (S_IRUSR | S_IWUSR)) == -1) {
530 PAM_LOG("Error chmod(): %s", strerror(errno));
531 krb5_cc_destroy(pam_context, ccache_perm);
532 retval = PAM_SERVICE_ERR;
533 goto cleanup2;
534 }
535 PAM_LOG("Done chmod()");
536 }
537
538 krb5_cc_close(pam_context, ccache_perm);
539
540 PAM_LOG("Cache closed");
541
542 retval = pam_setenv(pamh, "KRB5CCNAME", cache_name, 1);
543 if (retval != PAM_SUCCESS) {
544 PAM_LOG("Error pam_setenv(): %s", pam_strerror(pamh, retval));
545 krb5_cc_destroy(pam_context, ccache_perm);
546 retval = PAM_SERVICE_ERR;
547 goto cleanup2;
548 }
549
550 PAM_LOG("Environment done: KRB5CCNAME=%s", cache_name);
551
552 cleanup2:
553 krb5_free_principal(pam_context, princ);
554 PAM_LOG("Done cleanup2");
555 cleanup3:
556 krb5_free_context(pam_context);
557 PAM_LOG("Done cleanup3");
558
559 seteuid(euid);
560 setegid(egid);
561
562 PAM_LOG("Done seteuid() & setegid()");
563
564 if (cache_name_buf != NULL)
565 free(cache_name_buf);
566
567 return (retval);
568 }
569
570 /*
571 * account management
572 */
573 PAM_EXTERN int
574 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
575 int argc __unused, const char *argv[] __unused)
576 {
577 krb5_error_code krbret;
578 krb5_context pam_context;
579 krb5_ccache ccache;
580 krb5_principal princ;
581 int retval;
582 const void *user;
583 void *ccache_name;
584
585 retval = pam_get_item(pamh, PAM_USER, &user);
586 if (retval != PAM_SUCCESS)
587 return (retval);
588
589 PAM_LOG("Got user: %s", (const char *)user);
590
591 retval = pam_get_data(pamh, "ccache", &ccache_name);
592 if (retval != PAM_SUCCESS)
593 return (PAM_SUCCESS);
594
595 PAM_LOG("Got credentials");
596
597 krbret = krb5_init_context(&pam_context);
598 if (krbret != 0) {
599 PAM_LOG("Error krb5_init_context() failed");
600 return (PAM_PERM_DENIED);
601 }
602
603 PAM_LOG("Context initialised");
604
605 krbret = krb5_cc_resolve(pam_context, (const char *)ccache_name, &ccache);
606 if (krbret != 0) {
607 PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)ccache_name,
608 krb5_get_err_text(pam_context, krbret));
609 krb5_free_context(pam_context);
610 return (PAM_PERM_DENIED);
611 }
612
613 PAM_LOG("Got ccache %s", (const char *)ccache_name);
614
615
616 krbret = krb5_cc_get_principal(pam_context, ccache, &princ);
617 if (krbret != 0) {
618 PAM_LOG("Error krb5_cc_get_principal(): %s",
619 krb5_get_err_text(pam_context, krbret));
620 retval = PAM_PERM_DENIED;;
621 goto cleanup;
622 }
623
624 PAM_LOG("Got principal");
625
626 if (krb5_kuserok(pam_context, princ, (const char *)user))
627 retval = PAM_SUCCESS;
628 else
629 retval = PAM_PERM_DENIED;
630 krb5_free_principal(pam_context, princ);
631
632 PAM_LOG("Done kuserok()");
633
634 cleanup:
635 krb5_free_context(pam_context);
636 PAM_LOG("Done cleanup");
637
638 return (retval);
639
640 }
641
642 /*
643 * password management
644 */
645 PAM_EXTERN int
646 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
647 int argc __unused, const char *argv[] __unused)
648 {
649 krb5_error_code krbret;
650 krb5_context pam_context;
651 krb5_creds creds;
652 krb5_principal princ;
653 krb5_get_init_creds_opt opts;
654 krb5_data result_code_string, result_string;
655 int result_code, retval;
656 const char *pass;
657 const void *user;
658 char *princ_name, *passdup;
659
660 if (!(flags & PAM_UPDATE_AUTHTOK))
661 return (PAM_AUTHTOK_ERR);
662
663 retval = pam_get_item(pamh, PAM_USER, &user);
664 if (retval != PAM_SUCCESS)
665 return (retval);
666
667 PAM_LOG("Got user: %s", (const char *)user);
668
669 krbret = krb5_init_context(&pam_context);
670 if (krbret != 0) {
671 PAM_LOG("Error krb5_init_context() failed");
672 return (PAM_SERVICE_ERR);
673 }
674
675 PAM_LOG("Context initialised");
676
677 krb5_get_init_creds_opt_init(&opts);
678
679 PAM_LOG("Credentials options initialised");
680
681 /* Get principal name */
682 krbret = krb5_parse_name(pam_context, (const char *)user, &princ);
683 if (krbret != 0) {
684 PAM_LOG("Error krb5_parse_name(): %s",
685 krb5_get_err_text(pam_context, krbret));
686 retval = PAM_USER_UNKNOWN;
687 goto cleanup3;
688 }
689
690 /* Now convert the principal name into something human readable */
691 princ_name = NULL;
692 krbret = krb5_unparse_name(pam_context, princ, &princ_name);
693 if (krbret != 0) {
694 PAM_LOG("Error krb5_unparse_name(): %s",
695 krb5_get_err_text(pam_context, krbret));
696 retval = PAM_SERVICE_ERR;
697 goto cleanup2;
698 }
699
700 PAM_LOG("Got principal: %s", princ_name);
701
702 /* Get password */
703 retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, PASSWORD_PROMPT);
704 if (retval != PAM_SUCCESS)
705 goto cleanup2;
706
707 PAM_LOG("Got password");
708
709 memset(&creds, 0, sizeof(krb5_creds));
710 krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
711 pass, NULL, pamh, 0, "kadmin/changepw", &opts);
712 if (krbret != 0) {
713 PAM_LOG("Error krb5_get_init_creds_password(): %s",
714 krb5_get_err_text(pam_context, krbret));
715 retval = PAM_AUTH_ERR;
716 goto cleanup2;
717 }
718
719 PAM_LOG("Credentials established");
720
721 /* Now get the new password */
722 for (;;) {
723 retval = pam_get_authtok(pamh,
724 PAM_AUTHTOK, &pass, NEW_PASSWORD_PROMPT);
725 if (retval != PAM_TRY_AGAIN)
726 break;
727 pam_error(pamh, "Mismatch; try again, EOF to quit.");
728 }
729 if (retval != PAM_SUCCESS)
730 goto cleanup;
731
732 PAM_LOG("Got new password");
733
734 /* Change it */
735 if ((passdup = strdup(pass)) == NULL) {
736 retval = PAM_BUF_ERR;
737 goto cleanup;
738 }
739 krbret = krb5_change_password(pam_context, &creds, passdup,
740 &result_code, &result_code_string, &result_string);
741 free(passdup);
742 if (krbret != 0) {
743 PAM_LOG("Error krb5_change_password(): %s",
744 krb5_get_err_text(pam_context, krbret));
745 retval = PAM_AUTHTOK_ERR;
746 goto cleanup;
747 }
748 if (result_code) {
749 PAM_LOG("Error krb5_change_password(): (result_code)");
750 retval = PAM_AUTHTOK_ERR;
751 goto cleanup;
752 }
753
754 PAM_LOG("Password changed");
755
756 if (result_string.data)
757 free(result_string.data);
758 if (result_code_string.data)
759 free(result_code_string.data);
760
761 cleanup:
762 krb5_free_cred_contents(pam_context, &creds);
763 PAM_LOG("Done cleanup");
764 cleanup2:
765 krb5_free_principal(pam_context, princ);
766 PAM_LOG("Done cleanup2");
767 cleanup3:
768 if (princ_name)
769 free(princ_name);
770
771 krb5_free_context(pam_context);
772
773 PAM_LOG("Done cleanup3");
774
775 return (retval);
776 }
777
778 PAM_MODULE_ENTRY("pam_krb5");
779
780 /*
781 * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
782 * Modified by Sam Hartman <hartmans (at) mit.edu> to support PAM services
783 * for Debian.
784 *
785 * Verify the Kerberos ticket-granting ticket just retrieved for the
786 * user. If the Kerberos server doesn't respond, assume the user is
787 * trying to fake us out (since we DID just get a TGT from what is
788 * supposedly our KDC). If the host/<host> service is unknown (i.e.,
789 * the local keytab doesn't have it), and we cannot find another
790 * service we do have, let her in.
791 *
792 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
793 */
794 /* ARGSUSED */
795 static int
796 verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
797 char *pam_service, int debug)
798 {
799 krb5_error_code retval;
800 krb5_principal princ;
801 krb5_keyblock *keyblock;
802 krb5_data packet;
803 krb5_auth_context auth_context;
804 char phost[BUFSIZ];
805 const char *services[3], **service;
806
807 packet.data = 0;
808
809 /* If possible we want to try and verify the ticket we have
810 * received against a keytab. We will try multiple service
811 * principals, including at least the host principal and the PAM
812 * service principal. The host principal is preferred because access
813 * to that key is generally sufficient to compromise root, while the
814 * service key for this PAM service may be less carefully guarded.
815 * It is important to check the keytab first before the KDC so we do
816 * not get spoofed by a fake KDC.
817 */
818 services[0] = "host";
819 services[1] = pam_service;
820 services[2] = NULL;
821 keyblock = 0;
822 retval = -1;
823 for (service = &services[0]; *service != NULL; service++) {
824 retval = krb5_sname_to_principal(context, NULL, *service,
825 KRB5_NT_SRV_HST, &princ);
826 if (retval != 0) {
827 if (debug)
828 syslog(LOG_DEBUG,
829 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
830 "krb5_sname_to_principal()",
831 krb5_get_err_text(context, retval));
832 return -1;
833 }
834
835 /* Extract the name directly. */
836 strncpy(phost, compat_princ_component(context, princ, 1),
837 BUFSIZ);
838 phost[BUFSIZ - 1] = '\0';
839
840 /*
841 * Do we have service/<host> keys?
842 * (use default/configured keytab, kvno IGNORE_VNO to get the
843 * first match, and ignore enctype.)
844 */
845 retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0,
846 &keyblock);
847 if (retval != 0)
848 continue;
849 break;
850 }
851 if (retval != 0) { /* failed to find key */
852 /* Keytab or service key does not exist */
853 if (debug)
854 syslog(LOG_DEBUG,
855 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
856 "krb5_kt_read_service_key()",
857 krb5_get_err_text(context, retval));
858 retval = 0;
859 goto cleanup;
860 }
861 if (keyblock)
862 krb5_free_keyblock(context, keyblock);
863
864 /* Talk to the kdc and construct the ticket. */
865 auth_context = NULL;
866 retval = krb5_mk_req(context, &auth_context, 0, *service, phost,
867 NULL, ccache, &packet);
868 if (auth_context) {
869 krb5_auth_con_free(context, auth_context);
870 auth_context = NULL; /* setup for rd_req */
871 }
872 if (retval) {
873 if (debug)
874 syslog(LOG_DEBUG,
875 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
876 "krb5_mk_req()",
877 krb5_get_err_text(context, retval));
878 retval = -1;
879 goto cleanup;
880 }
881
882 /* Try to use the ticket. */
883 retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL,
884 NULL, NULL);
885 if (retval) {
886 if (debug)
887 syslog(LOG_DEBUG,
888 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
889 "krb5_rd_req()",
890 krb5_get_err_text(context, retval));
891 retval = -1;
892 }
893 else
894 retval = 1;
895
896 cleanup:
897 if (packet.data)
898 compat_free_data_contents(context, &packet);
899 krb5_free_principal(context, princ);
900 return retval;
901 }
902
903 /* Free the memory for cache_name. Called by pam_end() */
904 /* ARGSUSED */
905 static void
906 cleanup_cache(pam_handle_t *pamh __unused, void *data, int pam_end_status __unused)
907 {
908 krb5_context pam_context;
909 krb5_ccache ccache;
910 krb5_error_code krbret;
911
912 if (krb5_init_context(&pam_context))
913 return;
914
915 krbret = krb5_cc_resolve(pam_context, data, &ccache);
916 if (krbret == 0)
917 krb5_cc_destroy(pam_context, ccache);
918 krb5_free_context(pam_context);
919 free(data);
920 }
921
922 #ifdef COMPAT_HEIMDAL
923 #ifdef COMPAT_MIT
924 #error This cannot be MIT and Heimdal compatible!
925 #endif
926 #endif
927
928 #ifndef COMPAT_HEIMDAL
929 #ifndef COMPAT_MIT
930 #error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified!
931 #endif
932 #endif
933
934 #ifdef COMPAT_HEIMDAL
935 /* ARGSUSED */
936 static const char *
937 compat_princ_component(krb5_context context __unused, krb5_principal princ, int n)
938 {
939 return princ->name.name_string.val[n];
940 }
941
942 /* ARGSUSED */
943 static void
944 compat_free_data_contents(krb5_context context __unused, krb5_data * data)
945 {
946 krb5_xfree(data->data);
947 }
948 #endif
949
950 #ifdef COMPAT_MIT
951 static const char *
952 compat_princ_component(krb5_context context, krb5_principal princ, int n)
953 {
954 return krb5_princ_component(context, princ, n)->data;
955 }
956
957 static void
958 compat_free_data_contents(krb5_context context, krb5_data * data)
959 {
960 krb5_free_data_contents(context, data);
961 }
962 #endif
963