pam_krb5.c revision 93984
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: head/lib/libpam/modules/pam_krb5/pam_krb5.c 93984 2002-04-06 19:30:04Z des $");
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_SESSION
220#define	PAM_SM_PASSWORD
221
222#include <security/pam_appl.h>
223#include <security/pam_modules.h>
224#include <security/pam_mod_misc.h>
225
226#define	COMPAT_HEIMDAL
227/* #define	COMPAT_MIT */
228
229extern	krb5_cc_ops	krb5_mcc_ops;
230
231static int	verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int);
232static void	cleanup_cache(pam_handle_t *, void *, int);
233static const	char *compat_princ_component(krb5_context, krb5_principal, int);
234static void	compat_free_data_contents(krb5_context, krb5_data *);
235
236#define USER_PROMPT		"Username: "
237#define PASSWORD_PROMPT		"Password:"
238#define NEW_PASSWORD_PROMPT	"New Password:"
239
240enum { PAM_OPT_AUTH_AS_SELF=PAM_OPT_STD_MAX, PAM_OPT_CCACHE, PAM_OPT_FORWARDABLE, PAM_OPT_NO_CCACHE, PAM_OPT_REUSE_CCACHE };
241
242static struct opttab other_options[] = {
243	{ "auth_as_self",	PAM_OPT_AUTH_AS_SELF },
244	{ "ccache",		PAM_OPT_CCACHE },
245	{ "forwardable",	PAM_OPT_FORWARDABLE },
246	{ "no_ccache",		PAM_OPT_NO_CCACHE },
247	{ "reuse_ccache",	PAM_OPT_REUSE_CCACHE },
248	{ NULL, 0 }
249};
250
251/*
252 * authentication management
253 */
254PAM_EXTERN int
255pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
256{
257	krb5_error_code krbret;
258	krb5_context pam_context;
259	krb5_creds creds;
260	krb5_principal princ;
261	krb5_ccache ccache, ccache_check;
262	krb5_get_init_creds_opt opts;
263	struct options options;
264	struct passwd *pwd;
265	int retval;
266	const char *sourceuser, *user, *pass, *service;
267	char *principal, *princ_name, *cache_name, luser[32], *srvdup;
268
269	pam_std_option(&options, other_options, argc, argv);
270
271	PAM_LOG("Options processed");
272
273	retval = pam_get_user(pamh, &user, USER_PROMPT);
274	if (retval != PAM_SUCCESS)
275		PAM_RETURN(retval);
276
277	PAM_LOG("Got user: %s", user);
278
279	retval = pam_get_item(pamh, PAM_RUSER, (const void **)&sourceuser);
280	if (retval != PAM_SUCCESS)
281		PAM_RETURN(retval);
282
283	PAM_LOG("Got ruser: %s", sourceuser);
284
285	service = NULL;
286	pam_get_item(pamh, PAM_SERVICE, (const void **)&service);
287	if (service == NULL)
288		service = "unknown";
289
290	PAM_LOG("Got service: %s", service);
291
292	krbret = krb5_init_context(&pam_context);
293	if (krbret != 0) {
294		PAM_VERBOSE_ERROR("Kerberos 5 error");
295		PAM_RETURN(PAM_SERVICE_ERR);
296	}
297
298	PAM_LOG("Context initialised");
299
300	krb5_get_init_creds_opt_init(&opts);
301
302	if (pam_test_option(&options, PAM_OPT_FORWARDABLE, NULL))
303		krb5_get_init_creds_opt_set_forwardable(&opts, 1);
304
305	PAM_LOG("Credentials initialised");
306
307	krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE);
308	if (krbret != 0 && krbret != KRB5_CC_TYPE_EXISTS) {
309		PAM_VERBOSE_ERROR("Kerberos 5 error");
310		retval = PAM_SERVICE_ERR;
311		goto cleanup3;
312	}
313
314	PAM_LOG("Done krb5_cc_register()");
315
316	/* Get principal name */
317	if (pam_test_option(&options, PAM_OPT_AUTH_AS_SELF, NULL))
318		asprintf(&principal, "%s/%s", sourceuser, user);
319	else
320		principal = strdup(user);
321
322	PAM_LOG("Created principal: %s", principal);
323
324	krbret = krb5_parse_name(pam_context, principal, &princ);
325	free(principal);
326	if (krbret != 0) {
327		PAM_LOG("Error krb5_parse_name(): %s", error_message(krbret));
328		PAM_VERBOSE_ERROR("Kerberos 5 error");
329		retval = PAM_SERVICE_ERR;
330		goto cleanup3;
331	}
332
333	PAM_LOG("Done krb5_parse_name()");
334
335	/* Now convert the principal name into something human readable */
336	princ_name = NULL;
337	krbret = krb5_unparse_name(pam_context, princ, &princ_name);
338	if (krbret != 0) {
339		PAM_LOG("Error krb5_unparse_name(): %s", error_message(krbret));
340		PAM_VERBOSE_ERROR("Kerberos 5 error");
341		retval = PAM_SERVICE_ERR;
342		goto cleanup2;
343	}
344
345	PAM_LOG("Got principal: %s", princ_name);
346
347	/* Get password */
348	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT);
349	if (retval != PAM_SUCCESS)
350		goto cleanup2;
351
352	PAM_LOG("Got password");
353
354	/* Verify the local user exists (AFTER getting the password) */
355	if (strchr(user, '@')) {
356		/* get a local account name for this principal */
357		krbret = krb5_aname_to_localname(pam_context, princ,
358		    sizeof(luser), luser);
359		if (krbret != 0) {
360			PAM_VERBOSE_ERROR("Kerberos 5 error");
361			PAM_LOG("Error krb5_aname_to_localname(): %s",
362			    error_message(krbret));
363			retval = PAM_USER_UNKNOWN;
364			goto cleanup2;
365		}
366
367		retval = pam_set_item(pamh, PAM_USER, luser);
368		if (retval != PAM_SUCCESS)
369			goto cleanup2;
370
371		retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
372		if (retval != PAM_SUCCESS)
373			goto cleanup2;
374
375		PAM_LOG("PAM_USER Redone");
376	}
377
378	pwd = getpwnam(user);
379	if (pwd == NULL) {
380		retval = PAM_USER_UNKNOWN;
381		goto cleanup2;
382	}
383
384	PAM_LOG("Done getpwnam()");
385
386	/* Get a TGT */
387	memset(&creds, 0, sizeof(krb5_creds));
388	krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
389	    pass, NULL, pamh, 0, NULL, &opts);
390	if (krbret != 0) {
391		PAM_VERBOSE_ERROR("Kerberos 5 error");
392		PAM_LOG("Error krb5_get_init_creds_password(): %s",
393		    error_message(krbret));
394		retval = PAM_AUTH_ERR;
395		goto cleanup2;
396	}
397
398	PAM_LOG("Got TGT");
399
400	/* Generate a unique cache_name */
401	asprintf(&cache_name, "MEMORY:/tmp/%s.%d", service, getpid());
402	krbret = krb5_cc_resolve(pam_context, cache_name, &ccache);
403	free(cache_name);
404	if (krbret != 0) {
405		PAM_VERBOSE_ERROR("Kerberos 5 error");
406		PAM_LOG("Error krb5_cc_resolve(): %s", error_message(krbret));
407		retval = PAM_SERVICE_ERR;
408		goto cleanup;
409	}
410	krbret = krb5_cc_initialize(pam_context, ccache, princ);
411	if (krbret != 0) {
412		PAM_VERBOSE_ERROR("Kerberos 5 error");
413		PAM_LOG("Error krb5_cc_initialize(): %s", error_message(krbret));
414		retval = PAM_SERVICE_ERR;
415		goto cleanup;
416	}
417	krbret = krb5_cc_store_cred(pam_context, ccache, &creds);
418	if (krbret != 0) {
419		PAM_VERBOSE_ERROR("Kerberos 5 error");
420		PAM_LOG("Error krb5_cc_store_cred(): %s", error_message(krbret));
421		krb5_cc_destroy(pam_context, ccache);
422		retval = PAM_SERVICE_ERR;
423		goto cleanup;
424	}
425
426	PAM_LOG("Credentials stashed");
427
428	/* Verify them */
429	if ((srvdup = strdup(service)) == NULL) {
430		retval = PAM_BUF_ERR;
431		goto cleanup;
432	}
433	krbret = verify_krb_v5_tgt(pam_context, ccache, srvdup,
434	    pam_test_option(&options, PAM_OPT_FORWARDABLE, NULL));
435	free(srvdup);
436	if (krbret == -1) {
437		PAM_VERBOSE_ERROR("Kerberos 5 error");
438		krb5_cc_destroy(pam_context, ccache);
439		retval = PAM_AUTH_ERR;
440		goto cleanup;
441	}
442
443	PAM_LOG("Credentials stash verified");
444
445	retval = pam_get_data(pamh, "ccache", (const void **)&ccache_check);
446	if (retval == PAM_SUCCESS) {
447		krb5_cc_destroy(pam_context, ccache);
448		PAM_VERBOSE_ERROR("Kerberos 5 error");
449		retval = PAM_AUTH_ERR;
450		goto cleanup;
451	}
452
453	PAM_LOG("Credentials stash not pre-existing");
454
455	retval = pam_set_data(pamh, "ccache", ccache, cleanup_cache);
456	if (retval != 0) {
457		krb5_cc_destroy(pam_context, ccache);
458		PAM_VERBOSE_ERROR("Kerberos 5 error");
459		retval = PAM_SERVICE_ERR;
460		goto cleanup;
461	}
462
463	PAM_LOG("Credentials stash saved");
464
465cleanup:
466	krb5_free_cred_contents(pam_context, &creds);
467	PAM_LOG("Done cleanup");
468cleanup2:
469	krb5_free_principal(pam_context, princ);
470	PAM_LOG("Done cleanup2");
471cleanup3:
472	if (princ_name)
473		free(princ_name);
474
475	krb5_free_context(pam_context);
476
477	PAM_LOG("Done cleanup3");
478
479	if (retval != PAM_SUCCESS)
480		PAM_VERBOSE_ERROR("Kerberos 5 refuses you");
481
482	PAM_RETURN(retval);
483}
484
485PAM_EXTERN int
486pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
487{
488
489	krb5_error_code krbret;
490	krb5_context pam_context;
491	krb5_principal princ;
492	krb5_creds creds;
493	krb5_ccache ccache_temp, ccache_perm;
494	krb5_cc_cursor cursor;
495	struct options options;
496	struct passwd *pwd = NULL;
497	int retval;
498	char *user;
499	char *cache_name, *cache_env_name, *p, *q;
500
501	uid_t euid;
502	gid_t egid;
503
504	pam_std_option(&options, other_options, argc, argv);
505
506	PAM_LOG("Options processed");
507
508	if (flags & PAM_DELETE_CRED)
509		PAM_RETURN(PAM_SUCCESS);
510
511	if (flags & PAM_REFRESH_CRED)
512		PAM_RETURN(PAM_SUCCESS);
513
514	if (flags & PAM_REINITIALIZE_CRED)
515		PAM_RETURN(PAM_SUCCESS);
516
517	if (!(flags & PAM_ESTABLISH_CRED))
518		PAM_RETURN(PAM_SERVICE_ERR);
519
520	PAM_LOG("Establishing credentials");
521
522	/* Get username */
523	retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
524	if (retval != PAM_SUCCESS)
525		PAM_RETURN(retval);
526
527	PAM_LOG("Got user: %s", user);
528
529	krbret = krb5_init_context(&pam_context);
530	if (krbret != 0) {
531		PAM_LOG("Error krb5_init_context(): %s", error_message(krbret));
532		PAM_RETURN(PAM_SERVICE_ERR);
533	}
534
535	PAM_LOG("Context initialised");
536
537	euid = geteuid();	/* Usually 0 */
538	egid = getegid();
539
540	PAM_LOG("Got euid, egid: %d %d", euid, egid);
541
542	/* Retrieve the cache name */
543	retval = pam_get_data(pamh, "ccache", (const void **)&ccache_temp);
544	if (retval != PAM_SUCCESS)
545		goto cleanup3;
546
547	/* Get the uid. This should exist. */
548	pwd = getpwnam(user);
549	if (pwd == NULL) {
550		retval = PAM_USER_UNKNOWN;
551		goto cleanup3;
552	}
553
554	PAM_LOG("Done getpwnam()");
555
556	/* Avoid following a symlink as root */
557	if (setegid(pwd->pw_gid)) {
558		retval = PAM_SERVICE_ERR;
559		goto cleanup3;
560	}
561	if (seteuid(pwd->pw_uid)) {
562		retval = PAM_SERVICE_ERR;
563		goto cleanup3;
564	}
565
566	PAM_LOG("Done setegid() & seteuid()");
567
568	/* Get the cache name */
569	cache_name = NULL;
570	pam_test_option(&options, PAM_OPT_CCACHE, &cache_name);
571	if (cache_name == NULL)
572		asprintf(&cache_name, "FILE:/tmp/krb5cc_%d", pwd->pw_uid);
573
574	p = calloc(PATH_MAX + 16, sizeof(char));
575	q = cache_name;
576
577	if (p == NULL) {
578		PAM_LOG("Error malloc(): failure");
579		retval = PAM_BUF_ERR;
580		goto cleanup3;
581	}
582	cache_name = p;
583
584	/* convert %u and %p */
585	while (*q) {
586		if (*q == '%') {
587			q++;
588			if (*q == 'u') {
589				sprintf(p, "%d", pwd->pw_uid);
590				p += strlen(p);
591			}
592			else if (*q == 'p') {
593				sprintf(p, "%d", getpid());
594				p += strlen(p);
595			}
596			else {
597				/* Not a special token */
598				*p++ = '%';
599				q--;
600			}
601			q++;
602		}
603		else {
604			*p++ = *q++;
605		}
606	}
607
608	PAM_LOG("Got cache_name: %s", cache_name);
609
610	/* Initialize the new ccache */
611	krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ);
612	if (krbret != 0) {
613		PAM_LOG("Error krb5_cc_get_principal(): %s",
614		    error_message(krbret));
615		retval = PAM_SERVICE_ERR;
616		goto cleanup3;
617	}
618	krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm);
619	if (krbret != 0) {
620		PAM_LOG("Error krb5_cc_resolve(): %s", error_message(krbret));
621		retval = PAM_SERVICE_ERR;
622		goto cleanup2;
623	}
624	krbret = krb5_cc_initialize(pam_context, ccache_perm, princ);
625	if (krbret != 0) {
626		PAM_LOG("Error krb5_cc_initialize(): %s", error_message(krbret));
627		retval = PAM_SERVICE_ERR;
628		goto cleanup2;
629	}
630
631	PAM_LOG("Cache initialised");
632
633	/* Prepare for iteration over creds */
634	krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor);
635	if (krbret != 0) {
636		PAM_LOG("Error krb5_cc_start_seq_get(): %s", error_message(krbret));
637		krb5_cc_destroy(pam_context, ccache_perm);
638		retval = PAM_SERVICE_ERR;
639		goto cleanup2;
640	}
641
642	PAM_LOG("Prepared for iteration");
643
644	/* Copy the creds (should be two of them) */
645	while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp,
646				&cursor, &creds) == 0)) {
647		krbret = krb5_cc_store_cred(pam_context, ccache_perm, &creds);
648		if (krbret != 0) {
649			PAM_LOG("Error krb5_cc_store_cred(): %s",
650			    error_message(krbret));
651			krb5_cc_destroy(pam_context, ccache_perm);
652			krb5_free_cred_contents(pam_context, &creds);
653			retval = PAM_SERVICE_ERR;
654			goto cleanup2;
655		}
656		krb5_free_cred_contents(pam_context, &creds);
657		PAM_LOG("Iteration");
658	}
659	krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
660
661	PAM_LOG("Done iterating");
662
663	if (strstr(cache_name, "FILE:") == cache_name) {
664		if (chown(&cache_name[5], pwd->pw_uid, pwd->pw_gid) == -1) {
665			PAM_LOG("Error chown(): %s", strerror(errno));
666			krb5_cc_destroy(pam_context, ccache_perm);
667			retval = PAM_SERVICE_ERR;
668			goto cleanup2;
669		}
670		PAM_LOG("Done chown()");
671
672		if (chmod(&cache_name[5], (S_IRUSR | S_IWUSR)) == -1) {
673			PAM_LOG("Error chmod(): %s", strerror(errno));
674			krb5_cc_destroy(pam_context, ccache_perm);
675			retval = PAM_SERVICE_ERR;
676			goto cleanup2;
677		}
678		PAM_LOG("Done chmod()");
679	}
680
681	krb5_cc_close(pam_context, ccache_perm);
682
683	PAM_LOG("Cache closed");
684
685	cache_env_name = malloc(strlen(cache_name) + 12);
686	if (!cache_env_name) {
687		PAM_LOG("Error malloc(): failure");
688		krb5_cc_destroy(pam_context, ccache_perm);
689		retval = PAM_BUF_ERR;
690		goto cleanup2;
691	}
692
693	sprintf(cache_env_name, "KRB5CCNAME=%s", cache_name);
694	if ((retval = pam_putenv(pamh, cache_env_name)) != 0) {
695		PAM_LOG("Error pam_putenv(): %s", pam_strerror(pamh, retval));
696		krb5_cc_destroy(pam_context, ccache_perm);
697		retval = PAM_SERVICE_ERR;
698		goto cleanup2;
699	}
700
701	PAM_LOG("Environment done: KRB5CCNAME=%s", cache_name);
702
703cleanup2:
704	krb5_free_principal(pam_context, princ);
705	PAM_LOG("Done cleanup2");
706cleanup3:
707	krb5_free_context(pam_context);
708	PAM_LOG("Done cleanup3");
709
710	seteuid(euid);
711	setegid(egid);
712
713	PAM_LOG("Done seteuid() & setegid()");
714
715	PAM_RETURN(retval);
716}
717
718/*
719 * account management
720 */
721PAM_EXTERN int
722pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
723{
724	krb5_error_code krbret;
725	krb5_context pam_context;
726	krb5_ccache ccache;
727	krb5_principal princ;
728	struct options options;
729	int retval;
730	const char *user;
731
732	pam_std_option(&options, other_options, argc, argv);
733
734	PAM_LOG("Options processed");
735
736	retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
737	if (retval != PAM_SUCCESS)
738		PAM_RETURN(retval);
739
740	PAM_LOG("Got user: %s", user);
741
742	retval = pam_get_data(pamh, "ccache", (const void **)&ccache);
743	if (retval != PAM_SUCCESS)
744		PAM_RETURN(PAM_SUCCESS);
745
746	PAM_LOG("Got ccache");
747
748	krbret = krb5_init_context(&pam_context);
749	if (krbret != 0) {
750		PAM_LOG("Error krb5_init_context(): %s", error_message(krbret));
751		PAM_RETURN(PAM_PERM_DENIED);
752	}
753
754	PAM_LOG("Context initialised");
755
756	krbret = krb5_cc_get_principal(pam_context, ccache, &princ);
757	if (krbret != 0) {
758		PAM_LOG("Error krb5_cc_get_principal(): %s", error_message(krbret));
759		retval = PAM_PERM_DENIED;;
760		goto cleanup;
761	}
762
763	PAM_LOG("Got principal");
764
765	if (krb5_kuserok(pam_context, princ, user))
766		retval = PAM_SUCCESS;
767	else
768		retval = PAM_PERM_DENIED;
769	krb5_free_principal(pam_context, princ);
770
771	PAM_LOG("Done kuserok()");
772
773cleanup:
774	krb5_free_context(pam_context);
775	PAM_LOG("Done cleanup");
776
777	PAM_RETURN(retval);
778
779}
780
781/*
782 * session management
783 *
784 * logging only
785 */
786PAM_EXTERN int
787pam_sm_open_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
798PAM_EXTERN int
799pam_sm_close_session(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
800{
801	struct options options;
802
803	pam_std_option(&options, NULL, argc, argv);
804
805	PAM_LOG("Options processed");
806
807	PAM_RETURN(PAM_SUCCESS);
808}
809
810/*
811 * password management
812 */
813PAM_EXTERN int
814pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
815{
816	krb5_error_code krbret;
817	krb5_context pam_context;
818	krb5_creds creds;
819	krb5_principal princ;
820	krb5_get_init_creds_opt opts;
821	krb5_data result_code_string, result_string;
822	struct options options;
823	int result_code, retval;
824	const char *user, *pass;
825	char *princ_name, *passdup;
826
827	pam_std_option(&options, other_options, argc, argv);
828
829	PAM_LOG("Options processed");
830
831	if (!(flags & PAM_UPDATE_AUTHTOK))
832		PAM_RETURN(PAM_AUTHTOK_ERR);
833
834	retval = pam_get_item(pamh, PAM_USER, (const void **)&user);
835	if (retval != PAM_SUCCESS)
836		PAM_RETURN(retval);
837
838	PAM_LOG("Got user: %s", user);
839
840	krbret = krb5_init_context(&pam_context);
841	if (krbret != 0) {
842		PAM_LOG("Error krb5_init_context(): %s", error_message(krbret));
843		PAM_RETURN(PAM_SERVICE_ERR);
844	}
845
846	PAM_LOG("Context initialised");
847
848	krb5_get_init_creds_opt_init(&opts);
849
850	PAM_LOG("Credentials options initialised");
851
852	/* Get principal name */
853	krbret = krb5_parse_name(pam_context, user, &princ);
854	if (krbret != 0) {
855		PAM_LOG("Error krb5_parse_name(): %s", error_message(krbret));
856		retval = PAM_USER_UNKNOWN;
857		goto cleanup3;
858	}
859
860	/* Now convert the principal name into something human readable */
861	princ_name = NULL;
862	krbret = krb5_unparse_name(pam_context, princ, &princ_name);
863	if (krbret != 0) {
864		PAM_LOG("Error krb5_unparse_name(): %s", error_message(krbret));
865		retval = PAM_SERVICE_ERR;
866		goto cleanup2;
867	}
868
869	PAM_LOG("Got principal: %s", princ_name);
870
871	/* Get password */
872	retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, PASSWORD_PROMPT);
873	if (retval != PAM_SUCCESS)
874		goto cleanup2;
875
876	PAM_LOG("Got password");
877
878	memset(&creds, 0, sizeof(krb5_creds));
879	krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
880	    pass, NULL, pamh, 0, "kadmin/changepw", &opts);
881	if (krbret != 0) {
882		PAM_LOG("Error krb5_get_init_creds_password()",
883		    error_message(krbret));
884		retval = PAM_AUTH_ERR;
885		goto cleanup2;
886	}
887
888	PAM_LOG("Credentials established");
889
890	/* Now get the new password */
891	for (;;) {
892		retval = pam_get_authtok(pamh,
893		    PAM_AUTHTOK, &pass, NEW_PASSWORD_PROMPT);
894		if (retval != PAM_TRY_AGAIN)
895			break;
896		pam_error(pamh, "Mismatch; try again, EOF to quit.");
897	}
898	if (retval != PAM_SUCCESS)
899		goto cleanup;
900
901	PAM_LOG("Got new password");
902
903	/* Change it */
904	if ((passdup = strdup(pass)) == NULL) {
905		retval = PAM_BUF_ERR;
906		goto cleanup;
907	}
908	krbret = krb5_change_password(pam_context, &creds, passdup,
909	    &result_code, &result_code_string, &result_string);
910	free(passdup);
911	if (krbret != 0) {
912		PAM_LOG("Error krb5_change_password(): %s",
913		    error_message(krbret));
914		retval = PAM_AUTHTOK_ERR;
915		goto cleanup;
916	}
917	if (result_code) {
918		PAM_LOG("Error krb5_change_password(): (result_code)");
919		retval = PAM_AUTHTOK_ERR;
920		goto cleanup;
921	}
922
923	PAM_LOG("Password changed");
924
925	if (result_string.data)
926		free(result_string.data);
927	if (result_code_string.data)
928		free(result_code_string.data);
929
930cleanup:
931	krb5_free_cred_contents(pam_context, &creds);
932	PAM_LOG("Done cleanup");
933cleanup2:
934	krb5_free_principal(pam_context, princ);
935	PAM_LOG("Done cleanup2");
936cleanup3:
937	if (princ_name)
938		free(princ_name);
939
940	krb5_free_context(pam_context);
941
942	PAM_LOG("Done cleanup3");
943
944	PAM_RETURN(retval);
945}
946
947PAM_MODULE_ENTRY("pam_krb5");
948
949/*
950 * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
951 * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services
952 * for Debian.
953 *
954 * Verify the Kerberos ticket-granting ticket just retrieved for the
955 * user.  If the Kerberos server doesn't respond, assume the user is
956 * trying to fake us out (since we DID just get a TGT from what is
957 * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
958 * the local keytab doesn't have it), and we cannot find another
959 * service we do have, let her in.
960 *
961 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
962 */
963static int
964verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
965    char *pam_service, int debug)
966{
967	krb5_error_code retval;
968	krb5_principal princ;
969	krb5_keyblock *keyblock;
970	krb5_data packet;
971	krb5_auth_context auth_context;
972	char phost[BUFSIZ];
973	const char *services[3], **service;
974
975	packet.data = 0;
976
977	/* If possible we want to try and verify the ticket we have
978	 * received against a keytab.  We will try multiple service
979	 * principals, including at least the host principal and the PAM
980	 * service principal.  The host principal is preferred because access
981	 * to that key is generally sufficient to compromise root, while the
982	 * service key for this PAM service may be less carefully guarded.
983	 * It is important to check the keytab first before the KDC so we do
984	 * not get spoofed by a fake KDC.
985	 */
986	services[0] = "host";
987	services[1] = pam_service;
988	services[2] = NULL;
989	keyblock = 0;
990	retval = -1;
991	for (service = &services[0]; *service != NULL; service++) {
992		retval = krb5_sname_to_principal(context, NULL, *service,
993		    KRB5_NT_SRV_HST, &princ);
994		if (retval != 0) {
995			if (debug)
996				syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s", "krb5_sname_to_principal()", error_message(retval));
997			return -1;
998		}
999
1000		/* Extract the name directly. */
1001		strncpy(phost, compat_princ_component(context, princ, 1),
1002		    BUFSIZ);
1003		phost[BUFSIZ - 1] = '\0';
1004
1005		/*
1006	         * Do we have service/<host> keys?
1007	         * (use default/configured keytab, kvno IGNORE_VNO to get the
1008	         * first match, and ignore enctype.)
1009	         */
1010		retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0,
1011		    &keyblock);
1012		if (retval != 0)
1013			continue;
1014		break;
1015	}
1016	if (retval != 0) {	/* failed to find key */
1017		/* Keytab or service key does not exist */
1018		if (debug)
1019			syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s", "krb5_kt_read_service_key()", error_message(retval));
1020		retval = 0;
1021		goto cleanup;
1022	}
1023	if (keyblock)
1024		krb5_free_keyblock(context, keyblock);
1025
1026	/* Talk to the kdc and construct the ticket. */
1027	auth_context = NULL;
1028	retval = krb5_mk_req(context, &auth_context, 0, *service, phost,
1029		NULL, ccache, &packet);
1030	if (auth_context) {
1031		krb5_auth_con_free(context, auth_context);
1032		auth_context = NULL;	/* setup for rd_req */
1033	}
1034	if (retval) {
1035		if (debug)
1036			syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s", "krb5_mk_req()", error_message(retval));
1037		retval = -1;
1038		goto cleanup;
1039	}
1040
1041	/* Try to use the ticket. */
1042	retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL,
1043	    NULL, NULL);
1044	if (retval) {
1045		if (debug)
1046			syslog(LOG_DEBUG, "pam_krb5: verify_krb_v5_tgt(): %s: %s", "krb5_rd_req()", error_message(retval));
1047		retval = -1;
1048	}
1049	else
1050		retval = 1;
1051
1052cleanup:
1053	if (packet.data)
1054		compat_free_data_contents(context, &packet);
1055	krb5_free_principal(context, princ);
1056	return retval;
1057}
1058
1059/* Free the memory for cache_name. Called by pam_end() */
1060static void
1061cleanup_cache(pam_handle_t *pamh __unused, void *data, int pam_end_status __unused)
1062{
1063	krb5_context pam_context;
1064	krb5_ccache ccache;
1065
1066	if (krb5_init_context(&pam_context))
1067		return;
1068
1069	ccache = (krb5_ccache)data;
1070	krb5_cc_destroy(pam_context, ccache);
1071	krb5_free_context(pam_context);
1072}
1073
1074#ifdef COMPAT_HEIMDAL
1075#ifdef COMPAT_MIT
1076#error This cannot be MIT and Heimdal compatible!
1077#endif
1078#endif
1079
1080#ifndef COMPAT_HEIMDAL
1081#ifndef COMPAT_MIT
1082#error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified!
1083#endif
1084#endif
1085
1086#ifdef COMPAT_HEIMDAL
1087static const char *
1088compat_princ_component(krb5_context context __unused, krb5_principal princ, int n)
1089{
1090	return princ->name.name_string.val[n];
1091}
1092
1093static void
1094compat_free_data_contents(krb5_context context __unused, krb5_data * data)
1095{
1096	krb5_xfree(data->data);
1097}
1098#endif
1099
1100#ifdef COMPAT_MIT
1101static const char *
1102compat_princ_component(krb5_context context, krb5_principal princ, int n)
1103{
1104	return krb5_princ_component(context, princ, n)->data;
1105}
1106
1107static void
1108compat_free_data_contents(krb5_context context, krb5_data * data)
1109{
1110	krb5_free_data_contents(context, data);
1111}
1112#endif
1113