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