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