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