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