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