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	krb5_free_context(pam_context);
738	PAM_LOG("Done cleanup3");
739
740	seteuid(euid);
741	setegid(egid);
742
743	PAM_LOG("Done seteuid() & setegid()");
744
745	if (cache_name_buf != NULL)
746		free(cache_name_buf);
747cleanup4:
748	pam_unsetenv(pamh, user_key);
749	pam_unsetenv(pamh, password_key);
750	PAM_LOG("Done cleanup4");
751
752	return (retval);
753#endif
754}
755
756/*
757 * account management
758 */
759PAM_EXTERN int
760pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
761    int argc __unused, const char *argv[] __unused)
762{
763	krb5_error_code krbret;
764	krb5_context pam_context;
765	krb5_ccache ccache;
766	krb5_principal princ;
767	int retval;
768	const void *user;
769	const void *ccache_name;
770
771	if (openpam_get_option(pamh, PAM_OPT_USE_KCMINIT))
772		return (PAM_SUCCESS);
773
774	retval = pam_get_item(pamh, PAM_USER, &user);
775	if (retval != PAM_SUCCESS)
776		return (retval);
777
778	PAM_LOG("Got user: %s", (const char *)user);
779
780	retval = pam_get_data(pamh, "ccache", &ccache_name);
781	if (retval != PAM_SUCCESS)
782		return (PAM_SUCCESS);
783
784	PAM_LOG("Got credentials");
785
786	krbret = krb5_init_context(&pam_context);
787	if (krbret != 0) {
788		PAM_LOG("Error krb5_init_secure_context() failed");
789		return (PAM_PERM_DENIED);
790	}
791
792	PAM_LOG("Context initialised");
793
794	krbret = krb5_cc_resolve(pam_context, (const char *)ccache_name, &ccache);
795	if (krbret != 0) {
796		PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)ccache_name,
797		    krb5_get_err_text(pam_context, krbret));
798		krb5_free_context(pam_context);
799		return (PAM_PERM_DENIED);
800	}
801
802	PAM_LOG("Got ccache %s", (const char *)ccache_name);
803
804
805	krbret = krb5_cc_get_principal(pam_context, ccache, &princ);
806	if (krbret != 0) {
807		PAM_LOG("Error krb5_cc_get_principal(): %s",
808		    krb5_get_err_text(pam_context, krbret));
809		retval = PAM_PERM_DENIED;;
810		goto cleanup;
811	}
812
813	PAM_LOG("Got principal");
814
815	if (krb5_kuserok(pam_context, princ, (const char *)user))
816		retval = PAM_SUCCESS;
817	else
818		retval = PAM_PERM_DENIED;
819	krb5_free_principal(pam_context, princ);
820
821	PAM_LOG("Done kuserok()");
822
823cleanup:
824	krb5_free_context(pam_context);
825	PAM_LOG("Done cleanup");
826
827	return (retval);
828
829}
830
831/*
832 * password management
833 */
834PAM_EXTERN int
835pam_sm_chauthtok(pam_handle_t *pamh, int flags,
836    int argc __unused, const char *argv[] __unused)
837{
838	krb5_error_code krbret;
839	krb5_context pam_context;
840	krb5_creds creds;
841	krb5_principal princ;
842	krb5_get_init_creds_opt *opts = NULL;
843	krb5_data result_code_string, result_string;
844	int result_code, retval;
845	char *pass;
846	const void *user;
847	char *princ_name = NULL, *passdup;
848
849	if (!(flags & PAM_UPDATE_AUTHTOK))
850		return (PAM_AUTHTOK_ERR);
851
852	retval = pam_get_item(pamh, PAM_USER, &user);
853	if (retval != PAM_SUCCESS)
854		return (retval);
855
856	PAM_LOG("Got user: %s", (const char *)user);
857
858	krbret = krb5_init_context(&pam_context);
859	if (krbret != 0) {
860		PAM_LOG("Error krb5_init_secure_context() failed");
861		return (PAM_SERVICE_ERR);
862	}
863
864	PAM_LOG("Context initialised");
865
866	krb5_get_init_creds_opt_alloc(pam_context, &opts);
867
868	PAM_LOG("Credentials options initialised");
869
870	/* Get principal name */
871	krbret = krb5_parse_name(pam_context, (const char *)user, &princ);
872	if (krbret != 0) {
873		PAM_LOG("Error krb5_parse_name(): %s",
874		    krb5_get_err_text(pam_context, krbret));
875		retval = PAM_USER_UNKNOWN;
876		goto cleanup3;
877	}
878
879	/* Now convert the principal name into something human readable */
880	princ_name = NULL;
881	krbret = krb5_unparse_name(pam_context, princ, &princ_name);
882	if (krbret != 0) {
883		PAM_LOG("Error krb5_unparse_name(): %s",
884		    krb5_get_err_text(pam_context, krbret));
885		retval = PAM_SERVICE_ERR;
886		goto cleanup2;
887	}
888
889	PAM_LOG("Got principal: %s", princ_name);
890
891	/* Get password */
892	retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, (const char **)&pass, PASSWORD_PROMPT);
893	if (retval != PAM_SUCCESS)
894		goto cleanup2;
895
896	PAM_LOG("Got password");
897
898	memset(&creds, 0, sizeof(krb5_creds));
899	krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
900	    pass, NULL, pamh, 0, "kadmin/changepw", opts);
901	if (krbret != 0) {
902		PAM_LOG("Error krb5_get_init_creds_password(): %s",
903		    krb5_get_err_text(pam_context, krbret));
904		retval = PAM_AUTH_ERR;
905		goto cleanup2;
906	}
907
908	PAM_LOG("Credentials established");
909
910	/* Now get the new password */
911	for (;;) {
912		retval = pam_get_authtok(pamh,
913		    PAM_AUTHTOK, (const char **)&pass, NEW_PASSWORD_PROMPT);
914		if (retval != PAM_TRY_AGAIN)
915			break;
916		pam_error(pamh, "Mismatch; try again, EOF to quit.");
917	}
918	if (retval != PAM_SUCCESS)
919		goto cleanup;
920
921	PAM_LOG("Got new password");
922
923	/* Change it */
924	if ((passdup = strdup(pass)) == NULL) {
925		retval = PAM_BUF_ERR;
926		goto cleanup;
927	}
928	krbret = krb5_set_password(pam_context, &creds, passdup, NULL,
929	    &result_code, &result_code_string, &result_string);
930	free(passdup);
931	if (krbret != 0) {
932		PAM_LOG("Error krb5_change_password(): %s",
933		    krb5_get_err_text(pam_context, krbret));
934		retval = PAM_AUTHTOK_ERR;
935		goto cleanup;
936	}
937	if (result_code) {
938		PAM_LOG("Error krb5_change_password(): (result_code)");
939		retval = PAM_AUTHTOK_ERR;
940		goto cleanup;
941	}
942
943	PAM_LOG("Password changed");
944
945	if (result_string.data)
946		free(result_string.data);
947	if (result_code_string.data)
948		free(result_code_string.data);
949
950cleanup:
951	krb5_free_cred_contents(pam_context, &creds);
952	PAM_LOG("Done cleanup");
953cleanup2:
954	krb5_free_principal(pam_context, princ);
955	PAM_LOG("Done cleanup2");
956cleanup3:
957	if (princ_name)
958		free(princ_name);
959
960	if (opts)
961		krb5_get_init_creds_opt_free(pam_context, opts);
962
963	krb5_free_context(pam_context);
964
965	PAM_LOG("Done cleanup3");
966
967	return (retval);
968}
969
970PAM_MODULE_ENTRY("pam_krb5");
971
972/*
973 * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
974 * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services
975 * for Debian.
976 *
977 * Verify the Kerberos ticket-granting ticket just retrieved for the
978 * user.  If the Kerberos server doesn't respond, assume the user is
979 * trying to fake us out (since we DID just get a TGT from what is
980 * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
981 * the local keytab doesn't have it), and we cannot find another
982 * service we do have, let her in.
983 *
984 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
985 */
986/* ARGSUSED */
987static int
988verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
989    char *pam_service, int debug)
990{
991	krb5_error_code retval;
992	krb5_principal princ;
993	krb5_keyblock *keyblock;
994	krb5_data packet;
995	krb5_auth_context auth_context;
996	char phost[BUFSIZ];
997	const char *services[3], **service;
998
999	packet.data = 0;
1000
1001	/* If possible we want to try and verify the ticket we have
1002	 * received against a keytab.  We will try multiple service
1003	 * principals, including at least the host principal and the PAM
1004	 * service principal.  The host principal is preferred because access
1005	 * to that key is generally sufficient to compromise root, while the
1006	 * service key for this PAM service may be less carefully guarded.
1007	 * It is important to check the keytab first before the KDC so we do
1008	 * not get spoofed by a fake KDC.
1009	 */
1010	services[0] = "host";
1011	services[1] = pam_service;
1012	services[2] = NULL;
1013	keyblock = 0;
1014	retval = -1;
1015	for (service = &services[0]; *service != NULL; service++) {
1016		retval = krb5_sname_to_principal(context, NULL, *service,
1017		    KRB5_NT_SRV_HST, &princ);
1018		if (retval != 0) {
1019			if (debug)
1020				openpam_log(PAM_LOG_DEBUG,
1021				    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
1022				    "krb5_sname_to_principal()",
1023				    krb5_get_err_text(context, retval));
1024			return -1;
1025		}
1026
1027		/* Extract the name directly. */
1028		strncpy(phost, compat_princ_component(context, princ, 1),
1029		    BUFSIZ);
1030		phost[BUFSIZ - 1] = '\0';
1031
1032		/*
1033		 * Do we have service/<host> keys?
1034		 * (use default/configured keytab, kvno IGNORE_VNO to get the
1035		 * first match, and ignore enctype.)
1036		 */
1037		retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0,
1038		    &keyblock);
1039		if (retval != 0) {
1040			krb5_free_principal(context, princ);
1041			princ = NULL;
1042			continue;
1043		}
1044		break;
1045	}
1046	if (retval != 0) {	/* failed to find key */
1047		/* Keytab or service key does not exist */
1048		if (debug)
1049			openpam_log(PAM_LOG_DEBUG,
1050			    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
1051			    "krb5_kt_read_service_key()",
1052			    krb5_get_err_text(context, retval));
1053		retval = 0;
1054		goto cleanup;
1055	}
1056	if (keyblock)
1057		krb5_free_keyblock(context, keyblock);
1058
1059	/* Talk to the kdc and construct the ticket. */
1060	auth_context = NULL;
1061	retval = krb5_mk_req(context, &auth_context, 0, (char *)*service, phost,
1062		NULL, ccache, &packet);
1063	if (auth_context) {
1064		krb5_auth_con_free(context, auth_context);
1065		auth_context = NULL;	/* setup for rd_req */
1066	}
1067	if (retval) {
1068		if (debug)
1069			openpam_log(PAM_LOG_DEBUG,
1070			    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
1071			    "krb5_mk_req()",
1072			    krb5_get_err_text(context, retval));
1073		retval = -1;
1074		goto cleanup;
1075	}
1076
1077	/* Try to use the ticket. */
1078	retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL,
1079	    NULL, NULL);
1080	if (retval) {
1081		if (debug)
1082			openpam_log(PAM_LOG_DEBUG,
1083			    "pam_krb5: verify_krb_v5_tgt(): %s: %s",
1084			    "krb5_rd_req()",
1085			    krb5_get_err_text(context, retval));
1086		retval = -1;
1087	}
1088	else
1089		retval = 1;
1090
1091cleanup:
1092	if (packet.data)
1093		compat_free_data_contents(context, &packet);
1094	krb5_free_principal(context, princ);
1095	return retval;
1096}
1097
1098#ifdef COMPAT_HEIMDAL
1099#ifdef COMPAT_MIT
1100#error This cannot be MIT and Heimdal compatible!
1101#endif
1102#endif
1103
1104#ifndef COMPAT_HEIMDAL
1105#ifndef COMPAT_MIT
1106#error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified!
1107#endif
1108#endif
1109
1110#ifdef COMPAT_HEIMDAL
1111/* ARGSUSED */
1112static const char *
1113compat_princ_component(krb5_context context __unused, krb5_principal princ, int n)
1114{
1115	return princ->name.name_string.val[n];
1116}
1117
1118/* ARGSUSED */
1119static void
1120compat_free_data_contents(krb5_context context __unused, krb5_data * data)
1121{
1122	krb5_xfree(data->data);
1123}
1124#endif
1125
1126#ifdef COMPAT_MIT
1127static const char *
1128compat_princ_component(krb5_context context, krb5_principal princ, int n)
1129{
1130	return krb5_princ_component(context, princ, n)->data;
1131}
1132
1133static void
1134compat_free_data_contents(krb5_context context, krb5_data * data)
1135{
1136	krb5_free_data_contents(context, data);
1137}
1138#endif
1139