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