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