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-2005.
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/types.h>
51 #include <sys/stat.h>
52 #include <errno.h>
53 #include <limits.h>
54 #include <pwd.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <syslog.h>
59 #include <unistd.h>
60
61 #include <krb5.h>
62 #include <com_err.h>
63
64 #define PAM_SM_AUTH
65 #define PAM_SM_ACCOUNT
66 #define PAM_SM_PASSWORD
67
68 #include <security/pam_appl.h>
69 #include <security/pam_modules.h>
70 #include <security/pam_mod_misc.h>
71 #include <security/openpam.h>
72
73 #define COMPAT_HEIMDAL
74 /* #define COMPAT_MIT */
75
76 static int verify_krb_v5_tgt_begin(krb5_context, char *, int,
77 const char **, krb5_principal *, char[static BUFSIZ]);
78 static int verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int,
79 const char *, krb5_principal, char[static BUFSIZ]);
80 static void verify_krb_v5_tgt_cleanup(krb5_context, int,
81 const char *, krb5_principal, char[static BUFSIZ]);
82 static void cleanup_cache(pam_handle_t *, void *, int);
83 static const char *compat_princ_component(krb5_context, krb5_principal, int);
84 static void compat_free_data_contents(krb5_context, krb5_data *);
85
86 #define USER_PROMPT "Username: "
87 #define PASSWORD_PROMPT "Password:"
88 #define NEW_PASSWORD_PROMPT "New Password:"
89
90 #define PAM_OPT_CCACHE "ccache"
91 #define PAM_OPT_DEBUG "debug"
92 #define PAM_OPT_FORWARDABLE "forwardable"
93 #define PAM_OPT_NO_CCACHE "no_ccache"
94 #define PAM_OPT_NO_USER_CHECK "no_user_check"
95 #define PAM_OPT_REUSE_CCACHE "reuse_ccache"
96 #define PAM_OPT_NO_USER_CHECK "no_user_check"
97 #define PAM_OPT_ALLOW_KDC_SPOOF "allow_kdc_spoof"
98
99 #define PAM_LOG_KRB5_ERR(ctx, rv, fmt, ...) \
100 do { \
101 const char *krb5msg = krb5_get_error_message(ctx, rv); \
102 PAM_LOG(fmt ": %s", ##__VA_ARGS__, krb5msg); \
103 krb5_free_error_message(ctx, krb5msg); \
104 } while (0)
105
106 /*
107 * authentication management
108 */
109 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)110 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
111 int argc __unused, const char *argv[] __unused)
112 {
113 krb5_error_code krbret;
114 krb5_context krbctx;
115 int debug;
116 const char *auth_service;
117 krb5_principal auth_princ;
118 char auth_phost[BUFSIZ];
119 krb5_creds creds;
120 krb5_principal princ;
121 krb5_ccache ccache;
122 krb5_get_init_creds_opt *opts;
123 struct passwd *pwd;
124 int retval;
125 const void *ccache_data;
126 const char *user, *pass;
127 const void *sourceuser, *service;
128 char *principal, *princ_name, *ccache_name, luser[32], *srvdup;
129
130 retval = pam_get_user(pamh, &user, USER_PROMPT);
131 if (retval != PAM_SUCCESS)
132 return (retval);
133
134 PAM_LOG("Got user: %s", user);
135
136 retval = pam_get_item(pamh, PAM_RUSER, &sourceuser);
137 if (retval != PAM_SUCCESS)
138 return (retval);
139
140 PAM_LOG("Got ruser: %s", (const char *)sourceuser);
141
142 service = NULL;
143 pam_get_item(pamh, PAM_SERVICE, &service);
144 if (service == NULL)
145 service = "unknown";
146
147 PAM_LOG("Got service: %s", (const char *)service);
148
149 if ((srvdup = strdup(service)) == NULL) {
150 retval = PAM_BUF_ERR;
151 goto cleanup6;
152 }
153
154 krbret = krb5_init_context(&krbctx);
155 if (krbret != 0) {
156 PAM_VERBOSE_ERROR("Kerberos 5 error");
157 retval = PAM_SERVICE_ERR;
158 goto cleanup5;
159 }
160
161 PAM_LOG("Context initialised");
162
163 debug = openpam_get_option(pamh, PAM_OPT_DEBUG) ? 1 : 0;
164 krbret = verify_krb_v5_tgt_begin(krbctx, srvdup, debug,
165 &auth_service, &auth_princ, auth_phost);
166 if (krbret != 0) { /* failed to find key */
167 /* Keytab or service key does not exist */
168 /*
169 * Give up now because we can't authenticate the KDC
170 * with a keytab, unless the administrator asked to
171 * have the traditional behaviour of being vulnerable
172 * to spoofed KDCs.
173 */
174 if (!openpam_get_option(pamh, PAM_OPT_ALLOW_KDC_SPOOF)) {
175 retval = PAM_SERVICE_ERR;
176 goto cleanup4;
177 }
178 }
179
180 krbret = krb5_cc_register(krbctx, &krb5_mcc_ops, FALSE);
181 if (krbret != 0 && krbret != KRB5_CC_TYPE_EXISTS) {
182 PAM_VERBOSE_ERROR("Kerberos 5 error");
183 retval = PAM_SERVICE_ERR;
184 goto cleanup3;
185 }
186
187 PAM_LOG("Done krb5_cc_register()");
188
189 /* Get principal name */
190 if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
191 asprintf(&principal, "%s/%s", (const char *)sourceuser, user);
192 else
193 principal = strdup(user);
194
195 PAM_LOG("Created principal: %s", principal);
196
197 krbret = krb5_parse_name(krbctx, principal, &princ);
198 free(principal);
199 if (krbret != 0) {
200 PAM_LOG_KRB5_ERR(krbctx, krbret, "Error krb5_parse_name()");
201 PAM_VERBOSE_ERROR("Kerberos 5 error");
202 retval = PAM_SERVICE_ERR;
203 goto cleanup3;
204 }
205
206 PAM_LOG("Done krb5_parse_name()");
207
208 /* Now convert the principal name into something human readable */
209 princ_name = NULL;
210 krbret = krb5_unparse_name(krbctx, princ, &princ_name);
211 if (krbret != 0) {
212 PAM_LOG_KRB5_ERR(krbctx, krbret,
213 "Error krb5_unparse_name()");
214 PAM_VERBOSE_ERROR("Kerberos 5 error");
215 retval = PAM_SERVICE_ERR;
216 goto cleanup2;
217 }
218
219 PAM_LOG("Got principal: %s", princ_name);
220
221 /* Get password */
222 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT);
223 if (retval != PAM_SUCCESS)
224 goto cleanup2;
225
226 PAM_LOG("Got password");
227
228 if (openpam_get_option(pamh, PAM_OPT_NO_USER_CHECK))
229 PAM_LOG("Skipping local user check");
230 else {
231
232 /* Verify the local user exists (AFTER getting the password) */
233 if (strchr(user, '@')) {
234 /* get a local account name for this principal */
235 krbret = krb5_aname_to_localname(krbctx, princ,
236 sizeof(luser), luser);
237 if (krbret != 0) {
238 PAM_VERBOSE_ERROR("Kerberos 5 error");
239 PAM_LOG_KRB5_ERR(krbctx, krbret,
240 "Error krb5_aname_to_localname()");
241 retval = PAM_USER_UNKNOWN;
242 goto cleanup2;
243 }
244
245 retval = pam_set_item(pamh, PAM_USER, luser);
246 if (retval != PAM_SUCCESS)
247 goto cleanup2;
248
249 PAM_LOG("PAM_USER Redone");
250 }
251
252 if (!openpam_get_option(pamh, PAM_OPT_NO_USER_CHECK)) {
253 pwd = getpwnam(user);
254 if (pwd == NULL) {
255 retval = PAM_USER_UNKNOWN;
256 goto cleanup2;
257 }
258 }
259
260 PAM_LOG("Done getpwnam()");
261 }
262
263 /* Initialize credentials request options. */
264 krbret = krb5_get_init_creds_opt_alloc(krbctx, &opts);
265 if (krbret != 0) {
266 PAM_LOG_KRB5_ERR(krbctx, krbret,
267 "Error krb5_get_init_creds_opt_alloc()");
268 PAM_VERBOSE_ERROR("Kerberos 5 error");
269 retval = PAM_SERVICE_ERR;
270 goto cleanup2;
271 }
272 krb5_get_init_creds_opt_set_default_flags(krbctx,
273 service, NULL, opts);
274
275 if (openpam_get_option(pamh, PAM_OPT_FORWARDABLE))
276 krb5_get_init_creds_opt_set_forwardable(opts, 1);
277
278 PAM_LOG("Credential options initialised");
279
280 /* Get a TGT */
281 memset(&creds, 0, sizeof(krb5_creds));
282 krbret = krb5_get_init_creds_password(krbctx, &creds, princ,
283 pass, NULL, pamh, 0, NULL, opts);
284 krb5_get_init_creds_opt_free(krbctx, opts);
285 if (krbret != 0) {
286 PAM_VERBOSE_ERROR("Kerberos 5 error");
287 PAM_LOG_KRB5_ERR(krbctx, krbret,
288 "Error krb5_get_init_creds_password()");
289 retval = PAM_AUTH_ERR;
290 goto cleanup2;
291 }
292
293 PAM_LOG("Got TGT");
294
295 /* Generate a temporary cache */
296 krbret = krb5_cc_new_unique(krbctx, krb5_cc_type_memory, NULL, &ccache);
297 if (krbret != 0) {
298 PAM_VERBOSE_ERROR("Kerberos 5 error");
299 PAM_LOG_KRB5_ERR(krbctx, krbret,
300 "Error krb5_cc_new_unique()");
301 retval = PAM_SERVICE_ERR;
302 goto cleanup;
303 }
304 krbret = krb5_cc_initialize(krbctx, ccache, princ);
305 if (krbret != 0) {
306 PAM_VERBOSE_ERROR("Kerberos 5 error");
307 PAM_LOG_KRB5_ERR(krbctx, krbret,
308 "Error krb5_cc_initialize()");
309 retval = PAM_SERVICE_ERR;
310 goto cleanup;
311 }
312 krbret = krb5_cc_store_cred(krbctx, ccache, &creds);
313 if (krbret != 0) {
314 PAM_VERBOSE_ERROR("Kerberos 5 error");
315 PAM_LOG_KRB5_ERR(krbctx, krbret,
316 "Error krb5_cc_store_cred()");
317 krb5_cc_destroy(krbctx, ccache);
318 retval = PAM_SERVICE_ERR;
319 goto cleanup;
320 }
321
322 PAM_LOG("Credentials stashed");
323
324 /* Verify them */
325 krbret = verify_krb_v5_tgt(krbctx, ccache, srvdup,
326 debug,
327 auth_service, auth_princ, auth_phost);
328 free(srvdup);
329 srvdup = NULL;
330 if (krbret == -1) {
331 PAM_VERBOSE_ERROR("Kerberos 5 error");
332 krb5_cc_destroy(krbctx, ccache);
333 retval = PAM_AUTH_ERR;
334 goto cleanup;
335 }
336
337 PAM_LOG("Credentials stash verified");
338
339 retval = pam_get_data(pamh, "ccache", &ccache_data);
340 if (retval == PAM_SUCCESS) {
341 krb5_cc_destroy(krbctx, ccache);
342 PAM_VERBOSE_ERROR("Kerberos 5 error");
343 retval = PAM_AUTH_ERR;
344 goto cleanup;
345 }
346
347 PAM_LOG("Credentials stash not pre-existing");
348
349 asprintf(&ccache_name, "%s:%s", krb5_cc_get_type(krbctx,
350 ccache), krb5_cc_get_name(krbctx, ccache));
351 if (ccache_name == NULL) {
352 PAM_VERBOSE_ERROR("Kerberos 5 error");
353 retval = PAM_BUF_ERR;
354 goto cleanup;
355 }
356 retval = pam_set_data(pamh, "ccache", ccache_name, cleanup_cache);
357 if (retval != 0) {
358 krb5_cc_destroy(krbctx, ccache);
359 PAM_VERBOSE_ERROR("Kerberos 5 error");
360 retval = PAM_SERVICE_ERR;
361 goto cleanup;
362 }
363
364 PAM_LOG("Credentials stash saved");
365
366 cleanup:
367 krb5_free_cred_contents(krbctx, &creds);
368 PAM_LOG("Done cleanup");
369 cleanup2:
370 krb5_free_principal(krbctx, princ);
371 if (princ_name)
372 free(princ_name);
373 PAM_LOG("Done cleanup2");
374
375 cleanup3:
376 krb5_free_context(krbctx);
377
378 PAM_LOG("Done cleanup3");
379
380 cleanup4:
381 verify_krb_v5_tgt_cleanup(krbctx, debug,
382 auth_service, auth_princ, auth_phost);
383 PAM_LOG("Done cleanup4");
384
385 cleanup5:
386 if (srvdup != NULL)
387 free(srvdup);
388 PAM_LOG("Done cleanup5");
389
390 cleanup6:
391 if (retval != PAM_SUCCESS)
392 PAM_VERBOSE_ERROR("Kerberos 5 refuses you");
393 PAM_LOG("Done cleanup6");
394
395 return (retval);
396 }
397
398 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc __unused,const char * argv[]__unused)399 pam_sm_setcred(pam_handle_t *pamh, int flags,
400 int argc __unused, const char *argv[] __unused)
401 {
402 #ifdef _FREEFALL_CONFIG
403 return (PAM_SUCCESS);
404 #else
405
406 krb5_error_code krbret;
407 krb5_context krbctx;
408 krb5_principal princ;
409 krb5_creds creds;
410 krb5_ccache ccache_temp, ccache_perm;
411 krb5_cc_cursor cursor;
412 struct passwd *pwd = NULL;
413 int retval;
414 const char *cache_name, *q;
415 const void *user;
416 const void *cache_data;
417 char *cache_name_buf = NULL, *p;
418
419 uid_t euid;
420 gid_t egid;
421
422 if (flags & PAM_DELETE_CRED)
423 return (PAM_SUCCESS);
424
425 if (flags & PAM_REFRESH_CRED)
426 return (PAM_SUCCESS);
427
428 if (flags & PAM_REINITIALIZE_CRED)
429 return (PAM_SUCCESS);
430
431 if (!(flags & PAM_ESTABLISH_CRED))
432 return (PAM_SERVICE_ERR);
433
434 /* If a persistent cache isn't desired, stop now. */
435 if (openpam_get_option(pamh, PAM_OPT_NO_CCACHE) ||
436 openpam_get_option(pamh, PAM_OPT_NO_USER_CHECK))
437 return (PAM_SUCCESS);
438
439 PAM_LOG("Establishing credentials");
440
441 /* Get username */
442 retval = pam_get_item(pamh, PAM_USER, &user);
443 if (retval != PAM_SUCCESS)
444 return (retval);
445
446 PAM_LOG("Got user: %s", (const char *)user);
447
448 krbret = krb5_init_context(&krbctx);
449 if (krbret != 0) {
450 PAM_LOG("Error krb5_init_context() failed");
451 return (PAM_SERVICE_ERR);
452 }
453
454 PAM_LOG("Context initialised");
455
456 euid = geteuid(); /* Usually 0 */
457 egid = getegid();
458
459 PAM_LOG("Got euid, egid: %d %d", euid, egid);
460
461 /* Retrieve the temporary cache */
462 retval = pam_get_data(pamh, "ccache", &cache_data);
463 if (retval != PAM_SUCCESS) {
464 retval = PAM_CRED_UNAVAIL;
465 goto cleanup3;
466 }
467 krbret = krb5_cc_resolve(krbctx, cache_data, &ccache_temp);
468 if (krbret != 0) {
469 PAM_LOG_KRB5_ERR(krbctx, krbret,
470 "Error krb5_cc_resolve(\"%s\")", (const char *)cache_data);
471 retval = PAM_SERVICE_ERR;
472 goto cleanup3;
473 }
474
475 /* Get the uid. This should exist. */
476 pwd = getpwnam(user);
477 if (pwd == NULL) {
478 retval = PAM_USER_UNKNOWN;
479 goto cleanup3;
480 }
481
482 PAM_LOG("Done getpwnam()");
483
484 /* Avoid following a symlink as root */
485 if (setegid(pwd->pw_gid)) {
486 retval = PAM_SERVICE_ERR;
487 goto cleanup3;
488 }
489 if (seteuid(pwd->pw_uid)) {
490 retval = PAM_SERVICE_ERR;
491 goto cleanup3;
492 }
493
494 PAM_LOG("Done setegid() & seteuid()");
495
496 /* Get the cache name */
497 cache_name = openpam_get_option(pamh, PAM_OPT_CCACHE);
498 if (cache_name == NULL) {
499 asprintf(&cache_name_buf, "FILE:/tmp/krb5cc_%d", pwd->pw_uid);
500 cache_name = cache_name_buf;
501 }
502
503 p = calloc(PATH_MAX + 16, sizeof(char));
504 q = cache_name;
505
506 if (p == NULL) {
507 PAM_LOG("Error malloc(): failure");
508 retval = PAM_BUF_ERR;
509 goto cleanup3;
510 }
511 cache_name = p;
512
513 /* convert %u and %p */
514 while (*q) {
515 if (*q == '%') {
516 q++;
517 if (*q == 'u') {
518 sprintf(p, "%d", pwd->pw_uid);
519 p += strlen(p);
520 }
521 else if (*q == 'p') {
522 sprintf(p, "%d", getpid());
523 p += strlen(p);
524 }
525 else {
526 /* Not a special token */
527 *p++ = '%';
528 q--;
529 }
530 q++;
531 }
532 else {
533 *p++ = *q++;
534 }
535 }
536
537 PAM_LOG("Got cache_name: %s", cache_name);
538
539 /* Initialize the new ccache */
540 krbret = krb5_cc_get_principal(krbctx, ccache_temp, &princ);
541 if (krbret != 0) {
542 PAM_LOG_KRB5_ERR(krbctx, krbret,
543 "Error krb5_cc_get_principal()");
544 retval = PAM_SERVICE_ERR;
545 goto cleanup3;
546 }
547 krbret = krb5_cc_resolve(krbctx, cache_name, &ccache_perm);
548 if (krbret != 0) {
549 PAM_LOG_KRB5_ERR(krbctx, krbret, "Error krb5_cc_resolve()");
550 retval = PAM_SERVICE_ERR;
551 goto cleanup2;
552 }
553 krbret = krb5_cc_initialize(krbctx, ccache_perm, princ);
554 if (krbret != 0) {
555 PAM_LOG_KRB5_ERR(krbctx, krbret,
556 "Error krb5_cc_initialize()");
557 retval = PAM_SERVICE_ERR;
558 goto cleanup2;
559 }
560
561 PAM_LOG("Cache initialised");
562
563 /* Prepare for iteration over creds */
564 krbret = krb5_cc_start_seq_get(krbctx, ccache_temp, &cursor);
565 if (krbret != 0) {
566 PAM_LOG_KRB5_ERR(krbctx, krbret,
567 "Error krb5_cc_start_seq_get()");
568 krb5_cc_destroy(krbctx, ccache_perm);
569 retval = PAM_SERVICE_ERR;
570 goto cleanup2;
571 }
572
573 PAM_LOG("Prepared for iteration");
574
575 /* Copy the creds (should be two of them) */
576 while (krb5_cc_next_cred(krbctx, ccache_temp, &cursor, &creds) == 0) {
577 krbret = krb5_cc_store_cred(krbctx, ccache_perm, &creds);
578 if (krbret != 0) {
579 PAM_LOG_KRB5_ERR(krbctx, krbret,
580 "Error krb5_cc_store_cred()");
581 krb5_cc_destroy(krbctx, ccache_perm);
582 krb5_free_cred_contents(krbctx, &creds);
583 retval = PAM_SERVICE_ERR;
584 goto cleanup2;
585 }
586 krb5_free_cred_contents(krbctx, &creds);
587 PAM_LOG("Iteration");
588 }
589 krb5_cc_end_seq_get(krbctx, ccache_temp, &cursor);
590
591 PAM_LOG("Done iterating");
592
593 if (strstr(cache_name, "FILE:") == cache_name) {
594 if (chown(&cache_name[5], pwd->pw_uid, pwd->pw_gid) == -1) {
595 PAM_LOG("Error chown(): %s", strerror(errno));
596 krb5_cc_destroy(krbctx, ccache_perm);
597 retval = PAM_SERVICE_ERR;
598 goto cleanup2;
599 }
600 PAM_LOG("Done chown()");
601
602 if (chmod(&cache_name[5], (S_IRUSR | S_IWUSR)) == -1) {
603 PAM_LOG("Error chmod(): %s", strerror(errno));
604 krb5_cc_destroy(krbctx, ccache_perm);
605 retval = PAM_SERVICE_ERR;
606 goto cleanup2;
607 }
608 PAM_LOG("Done chmod()");
609 }
610
611 krb5_cc_close(krbctx, ccache_perm);
612
613 PAM_LOG("Cache closed");
614
615 retval = pam_setenv(pamh, "KRB5CCNAME", cache_name, 1);
616 if (retval != PAM_SUCCESS) {
617 PAM_LOG("Error pam_setenv(): %s", pam_strerror(pamh, retval));
618 krb5_cc_destroy(krbctx, ccache_perm);
619 retval = PAM_SERVICE_ERR;
620 goto cleanup2;
621 }
622
623 PAM_LOG("Environment done: KRB5CCNAME=%s", cache_name);
624
625 cleanup2:
626 krb5_free_principal(krbctx, princ);
627 PAM_LOG("Done cleanup2");
628 cleanup3:
629 krb5_free_context(krbctx);
630 PAM_LOG("Done cleanup3");
631
632 seteuid(euid);
633 setegid(egid);
634
635 PAM_LOG("Done seteuid() & setegid()");
636
637 if (cache_name_buf != NULL)
638 free(cache_name_buf);
639
640 return (retval);
641 #endif
642 }
643
644 /*
645 * account management
646 */
647 PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)648 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
649 int argc __unused, const char *argv[] __unused)
650 {
651 krb5_error_code krbret;
652 krb5_context krbctx;
653 krb5_ccache ccache;
654 krb5_principal princ;
655 int retval;
656 const void *user;
657 const void *ccache_name;
658
659 retval = pam_get_item(pamh, PAM_USER, &user);
660 if (retval != PAM_SUCCESS)
661 return (retval);
662
663 PAM_LOG("Got user: %s", (const char *)user);
664
665 retval = pam_get_data(pamh, "ccache", &ccache_name);
666 if (retval != PAM_SUCCESS)
667 return (PAM_SUCCESS);
668
669 PAM_LOG("Got credentials");
670
671 krbret = krb5_init_context(&krbctx);
672 if (krbret != 0) {
673 PAM_LOG("Error krb5_init_context() failed");
674 return (PAM_PERM_DENIED);
675 }
676
677 PAM_LOG("Context initialised");
678
679 krbret = krb5_cc_resolve(krbctx, (const char *)ccache_name, &ccache);
680 if (krbret != 0) {
681 PAM_LOG_KRB5_ERR(krbctx, krbret,
682 "Error krb5_cc_resolve(\"%s\")", (const char *)ccache_name);
683 krb5_free_context(krbctx);
684 return (PAM_PERM_DENIED);
685 }
686
687 PAM_LOG("Got ccache %s", (const char *)ccache_name);
688
689
690 krbret = krb5_cc_get_principal(krbctx, ccache, &princ);
691 if (krbret != 0) {
692 PAM_LOG_KRB5_ERR(krbctx, krbret,
693 "Error krb5_cc_get_principal()");
694 retval = PAM_PERM_DENIED;
695 goto cleanup;
696 }
697
698 PAM_LOG("Got principal");
699
700 if (krb5_kuserok(krbctx, princ, (const char *)user))
701 retval = PAM_SUCCESS;
702 else
703 retval = PAM_PERM_DENIED;
704 krb5_free_principal(krbctx, princ);
705
706 PAM_LOG("Done kuserok()");
707
708 cleanup:
709 krb5_free_context(krbctx);
710 PAM_LOG("Done cleanup");
711
712 return (retval);
713
714 }
715
716 /*
717 * password management
718 */
719 PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc __unused,const char * argv[]__unused)720 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
721 int argc __unused, const char *argv[] __unused)
722 {
723 krb5_error_code krbret;
724 krb5_context krbctx;
725 krb5_creds creds;
726 krb5_principal princ;
727 krb5_get_init_creds_opt *opts;
728 krb5_data result_code_string, result_string;
729 int result_code, retval;
730 const char *pass;
731 const void *user;
732 char *princ_name, *passdup;
733
734 if (!(flags & PAM_UPDATE_AUTHTOK))
735 return (PAM_AUTHTOK_ERR);
736
737 retval = pam_get_item(pamh, PAM_USER, &user);
738 if (retval != PAM_SUCCESS)
739 return (retval);
740
741 PAM_LOG("Got user: %s", (const char *)user);
742
743 krbret = krb5_init_context(&krbctx);
744 if (krbret != 0) {
745 PAM_LOG("Error krb5_init_context() failed");
746 return (PAM_SERVICE_ERR);
747 }
748
749 PAM_LOG("Context initialised");
750
751 /* Get principal name */
752 krbret = krb5_parse_name(krbctx, (const char *)user, &princ);
753 if (krbret != 0) {
754 PAM_LOG_KRB5_ERR(krbctx, krbret,
755 "Error krb5_parse_name()");
756 retval = PAM_USER_UNKNOWN;
757 goto cleanup3;
758 }
759
760 /* Now convert the principal name into something human readable */
761 princ_name = NULL;
762 krbret = krb5_unparse_name(krbctx, princ, &princ_name);
763 if (krbret != 0) {
764 PAM_LOG_KRB5_ERR(krbctx, krbret,
765 "Error krb5_unparse_name()");
766 retval = PAM_SERVICE_ERR;
767 goto cleanup2;
768 }
769
770 PAM_LOG("Got principal: %s", princ_name);
771
772 /* Get password */
773 retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, PASSWORD_PROMPT);
774 if (retval != PAM_SUCCESS)
775 goto cleanup2;
776
777 PAM_LOG("Got password");
778
779 /* Initialize credentials request options. */
780 krbret = krb5_get_init_creds_opt_alloc(krbctx, &opts);
781 if (krbret != 0) {
782 PAM_LOG_KRB5_ERR(krbctx, krbret,
783 "Error krb5_get_init_creds_opt_alloc()");
784 PAM_VERBOSE_ERROR("Kerberos 5 error");
785 retval = PAM_SERVICE_ERR;
786 goto cleanup2;
787 }
788
789 PAM_LOG("Credentials options initialised");
790
791 memset(&creds, 0, sizeof(krb5_creds));
792 krbret = krb5_get_init_creds_password(krbctx, &creds, princ,
793 pass, NULL, pamh, 0, "kadmin/changepw", opts);
794 krb5_get_init_creds_opt_free(krbctx, opts);
795 if (krbret != 0) {
796 PAM_LOG_KRB5_ERR(krbctx, krbret,
797 "Error krb5_get_init_creds_password()");
798 retval = PAM_AUTH_ERR;
799 goto cleanup2;
800 }
801
802 PAM_LOG("Credentials established");
803
804 /* Now get the new password */
805 for (;;) {
806 retval = pam_get_authtok(pamh,
807 PAM_AUTHTOK, &pass, NEW_PASSWORD_PROMPT);
808 if (retval != PAM_TRY_AGAIN)
809 break;
810 pam_error(pamh, "Mismatch; try again, EOF to quit.");
811 }
812 if (retval != PAM_SUCCESS)
813 goto cleanup;
814
815 PAM_LOG("Got new password");
816
817 /* Change it */
818 if ((passdup = strdup(pass)) == NULL) {
819 retval = PAM_BUF_ERR;
820 goto cleanup;
821 }
822 krbret = krb5_set_password(krbctx, &creds, passdup, NULL,
823 &result_code, &result_code_string, &result_string);
824 free(passdup);
825 if (krbret != 0) {
826 PAM_LOG_KRB5_ERR(krbctx, krbret,
827 "Error krb5_change_password()");
828 retval = PAM_AUTHTOK_ERR;
829 goto cleanup;
830 }
831 if (result_code) {
832 PAM_LOG("Error krb5_change_password(): (result_code)");
833 retval = PAM_AUTHTOK_ERR;
834 goto cleanup;
835 }
836
837 PAM_LOG("Password changed");
838
839 if (result_string.data)
840 free(result_string.data);
841 if (result_code_string.data)
842 free(result_code_string.data);
843
844 cleanup:
845 krb5_free_cred_contents(krbctx, &creds);
846 PAM_LOG("Done cleanup");
847 cleanup2:
848 krb5_free_principal(krbctx, princ);
849 if (princ_name)
850 free(princ_name);
851 PAM_LOG("Done cleanup2");
852
853 cleanup3:
854 krb5_free_context(krbctx);
855
856 PAM_LOG("Done cleanup3");
857
858 return (retval);
859 }
860
861 PAM_MODULE_ENTRY("pam_krb5");
862
863 /*
864 * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
865 * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services
866 * for Debian.
867 *
868 * Verify the Kerberos ticket-granting ticket just retrieved for the
869 * user. If the Kerberos server doesn't respond, assume the user is
870 * trying to fake us out (since we DID just get a TGT from what is
871 * supposedly our KDC). If the host/<host> service is unknown (i.e.,
872 * the local keytab doesn't have it), and we cannot find another
873 * service we do have, let her in.
874 *
875 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
876 */
877 /* ARGSUSED */
878 static int
verify_krb_v5_tgt_begin(krb5_context context,char * pam_service,int debug,const char ** servicep,krb5_principal * princp __unused,char phost[static BUFSIZ])879 verify_krb_v5_tgt_begin(krb5_context context, char *pam_service, int debug,
880 const char **servicep, krb5_principal *princp __unused, char phost[static BUFSIZ])
881 {
882 krb5_error_code retval;
883 krb5_principal princ;
884 krb5_keyblock *keyblock;
885 const char *services[3], **service;
886
887 *servicep = NULL;
888
889 if (debug)
890 openlog("pam_krb5", LOG_PID, LOG_AUTHPRIV);
891
892 /* If possible we want to try and verify the ticket we have
893 * received against a keytab. We will try multiple service
894 * principals, including at least the host principal and the PAM
895 * service principal. The host principal is preferred because access
896 * to that key is generally sufficient to compromise root, while the
897 * service key for this PAM service may be less carefully guarded.
898 * It is important to check the keytab first before the KDC so we do
899 * not get spoofed by a fake KDC.
900 */
901 services[0] = "host";
902 services[1] = pam_service;
903 services[2] = NULL;
904 keyblock = NULL;
905 retval = -1;
906 for (service = &services[0]; *service != NULL; service++) {
907 retval = krb5_sname_to_principal(context, NULL, *service,
908 KRB5_NT_SRV_HST, &princ);
909 if (retval != 0) {
910 if (debug) {
911 const char *msg = krb5_get_error_message(
912 context, retval);
913 syslog(LOG_DEBUG,
914 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
915 "krb5_sname_to_principal()", msg);
916 krb5_free_error_message(context, msg);
917 }
918 return -1;
919 }
920
921 /* Extract the name directly. */
922 strncpy(phost, compat_princ_component(context, princ, 1),
923 BUFSIZ);
924 phost[BUFSIZ - 1] = '\0';
925
926 /*
927 * Do we have service/<host> keys?
928 * (use default/configured keytab, kvno IGNORE_VNO to get the
929 * first match, and ignore enctype.)
930 */
931 retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0,
932 &keyblock);
933 if (retval != 0)
934 continue;
935 break;
936 }
937 if (keyblock)
938 krb5_free_keyblock(context, keyblock);
939
940 return (retval);
941 }
942
943 static int
verify_krb_v5_tgt(krb5_context context,krb5_ccache ccache,char * pam_service __unused,int debug,const char * service,krb5_principal princ,char phost[static BUFSIZ])944 verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
945 char *pam_service __unused, int debug,
946 const char *service, krb5_principal princ, char phost[static BUFSIZ])
947 {
948 krb5_error_code retval;
949 krb5_auth_context auth_context = NULL;
950 krb5_data packet;
951
952 if (service == NULL)
953 return (0); /* uncertain, can't authenticate KDC */
954
955 packet.data = 0;
956
957 /* Talk to the kdc and construct the ticket. */
958 auth_context = NULL;
959 retval = krb5_mk_req(context, &auth_context, 0, service, phost,
960 NULL, ccache, &packet);
961 if (auth_context) {
962 krb5_auth_con_free(context, auth_context);
963 auth_context = NULL; /* setup for rd_req */
964 }
965 if (retval) {
966 if (debug) {
967 const char *msg = krb5_get_error_message(context,
968 retval);
969 syslog(LOG_DEBUG,
970 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
971 "krb5_mk_req()", msg);
972 krb5_free_error_message(context, msg);
973 }
974 retval = -1;
975 goto cleanup;
976 }
977
978 /* Try to use the ticket. */
979 retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL,
980 NULL, NULL);
981 if (retval) {
982 if (debug) {
983 const char *msg = krb5_get_error_message(context,
984 retval);
985 syslog(LOG_DEBUG,
986 "pam_krb5: verify_krb_v5_tgt(): %s: %s",
987 "krb5_rd_req()", msg);
988 krb5_free_error_message(context, msg);
989 }
990 retval = -1;
991 }
992 else
993 retval = 1;
994
995 cleanup:
996 if (packet.data)
997 compat_free_data_contents(context, &packet);
998 return (retval);
999 }
1000
1001 static void
verify_krb_v5_tgt_cleanup(krb5_context context,int debug,const char * service,krb5_principal princ,char phost[static BUFSIZ]__unused)1002 verify_krb_v5_tgt_cleanup(krb5_context context, int debug,
1003 const char *service, krb5_principal princ, char phost[static BUFSIZ] __unused)
1004 {
1005
1006 if (service)
1007 krb5_free_principal(context, princ);
1008 if (debug)
1009 closelog();
1010
1011 }
1012
1013 /* Free the memory for cache_name. Called by pam_end() */
1014 /* ARGSUSED */
1015 static void
cleanup_cache(pam_handle_t * pamh __unused,void * data,int pam_end_status __unused)1016 cleanup_cache(pam_handle_t *pamh __unused, void *data, int pam_end_status __unused)
1017 {
1018 krb5_context krbctx;
1019 krb5_ccache ccache;
1020 krb5_error_code krbret;
1021
1022 if (krb5_init_context(&krbctx))
1023 return;
1024
1025 krbret = krb5_cc_resolve(krbctx, data, &ccache);
1026 if (krbret == 0)
1027 krb5_cc_destroy(krbctx, ccache);
1028 krb5_free_context(krbctx);
1029 free(data);
1030 }
1031
1032 #ifdef COMPAT_HEIMDAL
1033 #ifdef COMPAT_MIT
1034 #error This cannot be MIT and Heimdal compatible!
1035 #endif
1036 #endif
1037
1038 #ifndef COMPAT_HEIMDAL
1039 #ifndef COMPAT_MIT
1040 #error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified!
1041 #endif
1042 #endif
1043
1044 #ifdef COMPAT_HEIMDAL
1045 /* ARGSUSED */
1046 static const char *
compat_princ_component(krb5_context context __unused,krb5_principal princ,int n)1047 compat_princ_component(krb5_context context __unused, krb5_principal princ, int n)
1048 {
1049 return princ->name.name_string.val[n];
1050 }
1051
1052 /* ARGSUSED */
1053 static void
compat_free_data_contents(krb5_context context __unused,krb5_data * data)1054 compat_free_data_contents(krb5_context context __unused, krb5_data * data)
1055 {
1056 krb5_xfree(data->data);
1057 }
1058 #endif
1059
1060 #ifdef COMPAT_MIT
1061 static const char *
compat_princ_component(krb5_context context,krb5_principal princ,int n)1062 compat_princ_component(krb5_context context, krb5_principal princ, int n)
1063 {
1064 return krb5_princ_component(context, princ, n)->data;
1065 }
1066
1067 static void
compat_free_data_contents(krb5_context context,krb5_data * data)1068 compat_free_data_contents(krb5_context context, krb5_data * data)
1069 {
1070 krb5_free_data_contents(context, data);
1071 }
1072 #endif
1073