pam_krb5.c revision 147810
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 147810 2005-07-07 14:16:38Z kensmith $");
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#ifdef _FREEFALL_CONFIG
337	return (PAM_SUCCESS);
338#else
339
340	krb5_error_code krbret;
341	krb5_context pam_context;
342	krb5_principal princ;
343	krb5_creds creds;
344	krb5_ccache ccache_temp, ccache_perm;
345	krb5_cc_cursor cursor;
346	struct passwd *pwd = NULL;
347	int retval;
348	const char *cache_name, *q;
349	const void *user;
350	void *cache_data;
351	char *cache_name_buf = NULL, *p;
352
353	uid_t euid;
354	gid_t egid;
355
356	if (flags & PAM_DELETE_CRED)
357		return (PAM_SUCCESS);
358
359	if (flags & PAM_REFRESH_CRED)
360		return (PAM_SUCCESS);
361
362	if (flags & PAM_REINITIALIZE_CRED)
363		return (PAM_SUCCESS);
364
365	if (!(flags & PAM_ESTABLISH_CRED))
366		return (PAM_SERVICE_ERR);
367
368	/* If a persistent cache isn't desired, stop now. */
369	if (openpam_get_option(pamh, PAM_OPT_NO_CCACHE))
370		return (PAM_SUCCESS);
371
372	PAM_LOG("Establishing credentials");
373
374	/* Get username */
375	retval = pam_get_item(pamh, PAM_USER, &user);
376	if (retval != PAM_SUCCESS)
377		return (retval);
378
379	PAM_LOG("Got user: %s", (const char *)user);
380
381	krbret = krb5_init_context(&pam_context);
382	if (krbret != 0) {
383		PAM_LOG("Error krb5_init_context() failed");
384		return (PAM_SERVICE_ERR);
385	}
386
387	PAM_LOG("Context initialised");
388
389	euid = geteuid();	/* Usually 0 */
390	egid = getegid();
391
392	PAM_LOG("Got euid, egid: %d %d", euid, egid);
393
394	/* Retrieve the temporary cache */
395	retval = pam_get_data(pamh, "ccache", &cache_data);
396	if (retval != PAM_SUCCESS) {
397		retval = PAM_CRED_UNAVAIL;
398		goto cleanup3;
399	}
400	krbret = krb5_cc_resolve(pam_context, cache_data, &ccache_temp);
401	if (krbret != 0) {
402		PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)cache_data,
403		    krb5_get_err_text(pam_context, krbret));
404		retval = PAM_SERVICE_ERR;
405		goto cleanup3;
406	}
407
408	/* Get the uid. This should exist. */
409	pwd = getpwnam(user);
410	if (pwd == NULL) {
411		retval = PAM_USER_UNKNOWN;
412		goto cleanup3;
413	}
414
415	PAM_LOG("Done getpwnam()");
416
417	/* Avoid following a symlink as root */
418	if (setegid(pwd->pw_gid)) {
419		retval = PAM_SERVICE_ERR;
420		goto cleanup3;
421	}
422	if (seteuid(pwd->pw_uid)) {
423		retval = PAM_SERVICE_ERR;
424		goto cleanup3;
425	}
426
427	PAM_LOG("Done setegid() & seteuid()");
428
429	/* Get the cache name */
430	cache_name = openpam_get_option(pamh, PAM_OPT_CCACHE);
431	if (cache_name == NULL) {
432		asprintf(&cache_name_buf, "FILE:/tmp/krb5cc_%d", pwd->pw_uid);
433		cache_name = cache_name_buf;
434	}
435
436	p = calloc(PATH_MAX + 16, sizeof(char));
437	q = cache_name;
438
439	if (p == NULL) {
440		PAM_LOG("Error malloc(): failure");
441		retval = PAM_BUF_ERR;
442		goto cleanup3;
443	}
444	cache_name = p;
445
446	/* convert %u and %p */
447	while (*q) {
448		if (*q == '%') {
449			q++;
450			if (*q == 'u') {
451				sprintf(p, "%d", pwd->pw_uid);
452				p += strlen(p);
453			}
454			else if (*q == 'p') {
455				sprintf(p, "%d", getpid());
456				p += strlen(p);
457			}
458			else {
459				/* Not a special token */
460				*p++ = '%';
461				q--;
462			}
463			q++;
464		}
465		else {
466			*p++ = *q++;
467		}
468	}
469
470	PAM_LOG("Got cache_name: %s", cache_name);
471
472	/* Initialize the new ccache */
473	krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ);
474	if (krbret != 0) {
475		PAM_LOG("Error krb5_cc_get_principal(): %s",
476		    krb5_get_err_text(pam_context, krbret));
477		retval = PAM_SERVICE_ERR;
478		goto cleanup3;
479	}
480	krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm);
481	if (krbret != 0) {
482		PAM_LOG("Error krb5_cc_resolve(): %s",
483		    krb5_get_err_text(pam_context, krbret));
484		retval = PAM_SERVICE_ERR;
485		goto cleanup2;
486	}
487	krbret = krb5_cc_initialize(pam_context, ccache_perm, princ);
488	if (krbret != 0) {
489		PAM_LOG("Error krb5_cc_initialize(): %s",
490		    krb5_get_err_text(pam_context, krbret));
491		retval = PAM_SERVICE_ERR;
492		goto cleanup2;
493	}
494
495	PAM_LOG("Cache initialised");
496
497	/* Prepare for iteration over creds */
498	krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor);
499	if (krbret != 0) {
500		PAM_LOG("Error krb5_cc_start_seq_get(): %s",
501		    krb5_get_err_text(pam_context, krbret));
502		krb5_cc_destroy(pam_context, ccache_perm);
503		retval = PAM_SERVICE_ERR;
504		goto cleanup2;
505	}
506
507	PAM_LOG("Prepared for iteration");
508
509	/* Copy the creds (should be two of them) */
510	while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp,
511				&cursor, &creds) == 0)) {
512		krbret = krb5_cc_store_cred(pam_context, ccache_perm, &creds);
513		if (krbret != 0) {
514			PAM_LOG("Error krb5_cc_store_cred(): %s",
515			    krb5_get_err_text(pam_context, krbret));
516			krb5_cc_destroy(pam_context, ccache_perm);
517			krb5_free_cred_contents(pam_context, &creds);
518			retval = PAM_SERVICE_ERR;
519			goto cleanup2;
520		}
521		krb5_free_cred_contents(pam_context, &creds);
522		PAM_LOG("Iteration");
523	}
524	krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
525
526	PAM_LOG("Done iterating");
527
528	if (strstr(cache_name, "FILE:") == cache_name) {
529		if (chown(&cache_name[5], pwd->pw_uid, pwd->pw_gid) == -1) {
530			PAM_LOG("Error chown(): %s", strerror(errno));
531			krb5_cc_destroy(pam_context, ccache_perm);
532			retval = PAM_SERVICE_ERR;
533			goto cleanup2;
534		}
535		PAM_LOG("Done chown()");
536
537		if (chmod(&cache_name[5], (S_IRUSR | S_IWUSR)) == -1) {
538			PAM_LOG("Error chmod(): %s", strerror(errno));
539			krb5_cc_destroy(pam_context, ccache_perm);
540			retval = PAM_SERVICE_ERR;
541			goto cleanup2;
542		}
543		PAM_LOG("Done chmod()");
544	}
545
546	krb5_cc_close(pam_context, ccache_perm);
547
548	PAM_LOG("Cache closed");
549
550	retval = pam_setenv(pamh, "KRB5CCNAME", cache_name, 1);
551	if (retval != PAM_SUCCESS) {
552		PAM_LOG("Error pam_setenv(): %s", pam_strerror(pamh, retval));
553		krb5_cc_destroy(pam_context, ccache_perm);
554		retval = PAM_SERVICE_ERR;
555		goto cleanup2;
556	}
557
558	PAM_LOG("Environment done: KRB5CCNAME=%s", cache_name);
559
560cleanup2:
561	krb5_free_principal(pam_context, princ);
562	PAM_LOG("Done cleanup2");
563cleanup3:
564	krb5_free_context(pam_context);
565	PAM_LOG("Done cleanup3");
566
567	seteuid(euid);
568	setegid(egid);
569
570	PAM_LOG("Done seteuid() & setegid()");
571
572	if (cache_name_buf != NULL)
573		free(cache_name_buf);
574
575	return (retval);
576#endif
577}
578
579/*
580 * account management
581 */
582PAM_EXTERN int
583pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
584    int argc __unused, const char *argv[] __unused)
585{
586	krb5_error_code krbret;
587	krb5_context pam_context;
588	krb5_ccache ccache;
589	krb5_principal princ;
590	int retval;
591	const void *user;
592	void *ccache_name;
593
594	retval = pam_get_item(pamh, PAM_USER, &user);
595	if (retval != PAM_SUCCESS)
596		return (retval);
597
598	PAM_LOG("Got user: %s", (const char *)user);
599
600	retval = pam_get_data(pamh, "ccache", &ccache_name);
601	if (retval != PAM_SUCCESS)
602		return (PAM_SUCCESS);
603
604	PAM_LOG("Got credentials");
605
606	krbret = krb5_init_context(&pam_context);
607	if (krbret != 0) {
608		PAM_LOG("Error krb5_init_context() failed");
609		return (PAM_PERM_DENIED);
610	}
611
612	PAM_LOG("Context initialised");
613
614	krbret = krb5_cc_resolve(pam_context, (const char *)ccache_name, &ccache);
615	if (krbret != 0) {
616		PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)ccache_name,
617		    krb5_get_err_text(pam_context, krbret));
618		krb5_free_context(pam_context);
619		return (PAM_PERM_DENIED);
620	}
621
622	PAM_LOG("Got ccache %s", (const char *)ccache_name);
623
624
625	krbret = krb5_cc_get_principal(pam_context, ccache, &princ);
626	if (krbret != 0) {
627		PAM_LOG("Error krb5_cc_get_principal(): %s",
628		    krb5_get_err_text(pam_context, krbret));
629		retval = PAM_PERM_DENIED;;
630		goto cleanup;
631	}
632
633	PAM_LOG("Got principal");
634
635	if (krb5_kuserok(pam_context, princ, (const char *)user))
636		retval = PAM_SUCCESS;
637	else
638		retval = PAM_PERM_DENIED;
639	krb5_free_principal(pam_context, princ);
640
641	PAM_LOG("Done kuserok()");
642
643cleanup:
644	krb5_free_context(pam_context);
645	PAM_LOG("Done cleanup");
646
647	return (retval);
648
649}
650
651/*
652 * password management
653 */
654PAM_EXTERN int
655pam_sm_chauthtok(pam_handle_t *pamh, int flags,
656    int argc __unused, const char *argv[] __unused)
657{
658	krb5_error_code krbret;
659	krb5_context pam_context;
660	krb5_creds creds;
661	krb5_principal princ;
662	krb5_get_init_creds_opt opts;
663	krb5_data result_code_string, result_string;
664	int result_code, retval;
665	const char *pass;
666	const void *user;
667	char *princ_name, *passdup;
668
669	if (!(flags & PAM_UPDATE_AUTHTOK))
670		return (PAM_AUTHTOK_ERR);
671
672	retval = pam_get_item(pamh, PAM_USER, &user);
673	if (retval != PAM_SUCCESS)
674		return (retval);
675
676	PAM_LOG("Got user: %s", (const char *)user);
677
678	krbret = krb5_init_context(&pam_context);
679	if (krbret != 0) {
680		PAM_LOG("Error krb5_init_context() failed");
681		return (PAM_SERVICE_ERR);
682	}
683
684	PAM_LOG("Context initialised");
685
686	krb5_get_init_creds_opt_init(&opts);
687
688	PAM_LOG("Credentials options initialised");
689
690	/* Get principal name */
691	krbret = krb5_parse_name(pam_context, (const char *)user, &princ);
692	if (krbret != 0) {
693		PAM_LOG("Error krb5_parse_name(): %s",
694		    krb5_get_err_text(pam_context, krbret));
695		retval = PAM_USER_UNKNOWN;
696		goto cleanup3;
697	}
698
699	/* Now convert the principal name into something human readable */
700	princ_name = NULL;
701	krbret = krb5_unparse_name(pam_context, princ, &princ_name);
702	if (krbret != 0) {
703		PAM_LOG("Error krb5_unparse_name(): %s",
704		    krb5_get_err_text(pam_context, krbret));
705		retval = PAM_SERVICE_ERR;
706		goto cleanup2;
707	}
708
709	PAM_LOG("Got principal: %s", princ_name);
710
711	/* Get password */
712	retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, PASSWORD_PROMPT);
713	if (retval != PAM_SUCCESS)
714		goto cleanup2;
715
716	PAM_LOG("Got password");
717
718	memset(&creds, 0, sizeof(krb5_creds));
719	krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
720	    pass, NULL, pamh, 0, "kadmin/changepw", &opts);
721	if (krbret != 0) {
722		PAM_LOG("Error krb5_get_init_creds_password(): %s",
723		    krb5_get_err_text(pam_context, krbret));
724		retval = PAM_AUTH_ERR;
725		goto cleanup2;
726	}
727
728	PAM_LOG("Credentials established");
729
730	/* Now get the new password */
731	for (;;) {
732		retval = pam_get_authtok(pamh,
733		    PAM_AUTHTOK, &pass, NEW_PASSWORD_PROMPT);
734		if (retval != PAM_TRY_AGAIN)
735			break;
736		pam_error(pamh, "Mismatch; try again, EOF to quit.");
737	}
738	if (retval != PAM_SUCCESS)
739		goto cleanup;
740
741	PAM_LOG("Got new password");
742
743	/* Change it */
744	if ((passdup = strdup(pass)) == NULL) {
745		retval = PAM_BUF_ERR;
746		goto cleanup;
747	}
748	krbret = krb5_change_password(pam_context, &creds, passdup,
749	    &result_code, &result_code_string, &result_string);
750	free(passdup);
751	if (krbret != 0) {
752		PAM_LOG("Error krb5_change_password(): %s",
753		    krb5_get_err_text(pam_context, krbret));
754		retval = PAM_AUTHTOK_ERR;
755		goto cleanup;
756	}
757	if (result_code) {
758		PAM_LOG("Error krb5_change_password(): (result_code)");
759		retval = PAM_AUTHTOK_ERR;
760		goto cleanup;
761	}
762
763	PAM_LOG("Password changed");
764
765	if (result_string.data)
766		free(result_string.data);
767	if (result_code_string.data)
768		free(result_code_string.data);
769
770cleanup:
771	krb5_free_cred_contents(pam_context, &creds);
772	PAM_LOG("Done cleanup");
773cleanup2:
774	krb5_free_principal(pam_context, princ);
775	PAM_LOG("Done cleanup2");
776cleanup3:
777	if (princ_name)
778		free(princ_name);
779
780	krb5_free_context(pam_context);
781
782	PAM_LOG("Done cleanup3");
783
784	return (retval);
785}
786
787PAM_MODULE_ENTRY("pam_krb5");
788
789/*
790 * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
791 * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services
792 * for Debian.
793 *
794 * Verify the Kerberos ticket-granting ticket just retrieved for the
795 * user.  If the Kerberos server doesn't respond, assume the user is
796 * trying to fake us out (since we DID just get a TGT from what is
797 * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
798 * the local keytab doesn't have it), and we cannot find another
799 * service we do have, let her in.
800 *
801 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
802 */
803/* ARGSUSED */
804static int
805verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
806    char *pam_service, int debug)
807{
808	krb5_error_code retval;
809	krb5_principal princ;
810	krb5_keyblock *keyblock;
811	krb5_data packet;
812	krb5_auth_context auth_context;
813	char phost[BUFSIZ];
814	const char *services[3], **service;
815
816	packet.data = 0;
817
818	/* If possible we want to try and verify the ticket we have
819	 * received against a keytab.  We will try multiple service
820	 * principals, including at least the host principal and the PAM
821	 * service principal.  The host principal is preferred because access
822	 * to that key is generally sufficient to compromise root, while the
823	 * service key for this PAM service may be less carefully guarded.
824	 * It is important to check the keytab first before the KDC so we do
825	 * not get spoofed by a fake KDC.
826	 */
827	services[0] = "host";
828	services[1] = pam_service;
829	services[2] = NULL;
830	keyblock = 0;
831	retval = -1;
832	for (service = &services[0]; *service != NULL; service++) {
833		retval = krb5_sname_to_principal(context, NULL, *service,
834		    KRB5_NT_SRV_HST, &princ);
835		if (retval != 0) {
836			if (debug)
837				syslog(LOG_DEBUG,
838				    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
839				    "krb5_sname_to_principal()",
840				    krb5_get_err_text(context, retval));
841			return -1;
842		}
843
844		/* Extract the name directly. */
845		strncpy(phost, compat_princ_component(context, princ, 1),
846		    BUFSIZ);
847		phost[BUFSIZ - 1] = '\0';
848
849		/*
850		 * Do we have service/<host> keys?
851		 * (use default/configured keytab, kvno IGNORE_VNO to get the
852		 * first match, and ignore enctype.)
853		 */
854		retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0,
855		    &keyblock);
856		if (retval != 0)
857			continue;
858		break;
859	}
860	if (retval != 0) {	/* failed to find key */
861		/* Keytab or service key does not exist */
862		if (debug)
863			syslog(LOG_DEBUG,
864			    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
865			    "krb5_kt_read_service_key()",
866			    krb5_get_err_text(context, retval));
867		retval = 0;
868		goto cleanup;
869	}
870	if (keyblock)
871		krb5_free_keyblock(context, keyblock);
872
873	/* Talk to the kdc and construct the ticket. */
874	auth_context = NULL;
875	retval = krb5_mk_req(context, &auth_context, 0, *service, phost,
876		NULL, ccache, &packet);
877	if (auth_context) {
878		krb5_auth_con_free(context, auth_context);
879		auth_context = NULL;	/* setup for rd_req */
880	}
881	if (retval) {
882		if (debug)
883			syslog(LOG_DEBUG,
884			    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
885			    "krb5_mk_req()",
886			    krb5_get_err_text(context, retval));
887		retval = -1;
888		goto cleanup;
889	}
890
891	/* Try to use the ticket. */
892	retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL,
893	    NULL, NULL);
894	if (retval) {
895		if (debug)
896			syslog(LOG_DEBUG,
897			    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
898			    "krb5_rd_req()",
899			    krb5_get_err_text(context, retval));
900		retval = -1;
901	}
902	else
903		retval = 1;
904
905cleanup:
906	if (packet.data)
907		compat_free_data_contents(context, &packet);
908	krb5_free_principal(context, princ);
909	return retval;
910}
911
912/* Free the memory for cache_name. Called by pam_end() */
913/* ARGSUSED */
914static void
915cleanup_cache(pam_handle_t *pamh __unused, void *data, int pam_end_status __unused)
916{
917	krb5_context pam_context;
918	krb5_ccache ccache;
919	krb5_error_code krbret;
920
921	if (krb5_init_context(&pam_context))
922		return;
923
924	krbret = krb5_cc_resolve(pam_context, data, &ccache);
925	if (krbret == 0)
926		krb5_cc_destroy(pam_context, ccache);
927	krb5_free_context(pam_context);
928	free(data);
929}
930
931#ifdef COMPAT_HEIMDAL
932#ifdef COMPAT_MIT
933#error This cannot be MIT and Heimdal compatible!
934#endif
935#endif
936
937#ifndef COMPAT_HEIMDAL
938#ifndef COMPAT_MIT
939#error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified!
940#endif
941#endif
942
943#ifdef COMPAT_HEIMDAL
944/* ARGSUSED */
945static const char *
946compat_princ_component(krb5_context context __unused, krb5_principal princ, int n)
947{
948	return princ->name.name_string.val[n];
949}
950
951/* ARGSUSED */
952static void
953compat_free_data_contents(krb5_context context __unused, krb5_data * data)
954{
955	krb5_xfree(data->data);
956}
957#endif
958
959#ifdef COMPAT_MIT
960static const char *
961compat_princ_component(krb5_context context, krb5_principal princ, int n)
962{
963	return krb5_princ_component(context, princ, n)->data;
964}
965
966static void
967compat_free_data_contents(krb5_context context, krb5_data * data)
968{
969	krb5_free_data_contents(context, data);
970}
971#endif
972