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