1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include <libintl.h>
28#include <security/pam_appl.h>
29#include <security/pam_modules.h>
30#include <string.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <sys/types.h>
34#include <pwd.h>
35#include <syslog.h>
36#include <libintl.h>
37#include <k5-int.h>
38#include <netdb.h>
39#include <unistd.h>
40#include <sys/stat.h>
41#include <fcntl.h>
42#include <errno.h>
43#include <com_err.h>
44
45#include "utils.h"
46#include "krb5_repository.h"
47
48#define	PAMTXD			"SUNW_OST_SYSOSPAM"
49#define	KRB5_DEFAULT_LIFE	60*60*10  /* 10 hours */
50
51extern void krb5_cleanup(pam_handle_t *, void *, int);
52
53static int attempt_refresh_cred(krb5_module_data_t *, char *, int);
54static int attempt_delete_initcred(krb5_module_data_t *);
55static krb5_error_code krb5_renew_tgt(krb5_module_data_t *, krb5_principal,
56		krb5_principal, int);
57
58extern uint_t kwarn_add_warning(char *, int);
59extern uint_t kwarn_del_warning(char *);
60
61/*
62 * pam_sm_setcred
63 */
64int
65pam_sm_setcred(
66	pam_handle_t *pamh,
67	int	flags,
68	int	argc,
69	const char **argv)
70{
71	int	i;
72	int	err = 0;
73	int	debug = 0;
74	krb5_module_data_t	*kmd = NULL;
75	char			*user = NULL;
76	krb5_repository_data_t	*krb5_data = NULL;
77	pam_repository_t	*rep_data = NULL;
78
79	for (i = 0; i < argc; i++) {
80		if (strcasecmp(argv[i], "debug") == 0)
81			debug = 1;
82		else if (strcasecmp(argv[i], "nowarn") == 0)
83			flags = flags | PAM_SILENT;
84	}
85
86	if (debug)
87		__pam_log(LOG_AUTH | LOG_DEBUG,
88		    "PAM-KRB5 (setcred): start: nowarn = %d, flags = 0x%x",
89		    flags & PAM_SILENT ? 1 : 0, flags);
90
91	/* make sure flags are valid */
92	if (flags &&
93	    !(flags & PAM_ESTABLISH_CRED) &&
94	    !(flags & PAM_REINITIALIZE_CRED) &&
95	    !(flags & PAM_REFRESH_CRED) &&
96	    !(flags & PAM_DELETE_CRED) &&
97	    !(flags & PAM_SILENT)) {
98		__pam_log(LOG_AUTH | LOG_ERR,
99		    "PAM-KRB5 (setcred): illegal flag %d", flags);
100		err = PAM_SYSTEM_ERR;
101		goto out;
102	}
103
104	(void) pam_get_item(pamh, PAM_USER, (void**) &user);
105
106	if (user == NULL || *user == '\0')
107		return (PAM_USER_UNKNOWN);
108
109	if (pam_get_data(pamh, KRB5_DATA, (const void**)&kmd) != PAM_SUCCESS) {
110		if (debug) {
111			__pam_log(LOG_AUTH | LOG_DEBUG,
112			    "PAM-KRB5 (setcred): kmd get failed, kmd=0x%p",
113			    kmd);
114		}
115
116		/*
117		 * User  doesn't need to authenticate for PAM_REFRESH_CRED
118		 * or for PAM_DELETE_CRED
119		 */
120		if (flags & (PAM_REFRESH_CRED|PAM_DELETE_CRED)) {
121			__pam_log(LOG_AUTH | LOG_DEBUG,
122			    "PAM-KRB5 (setcred): inst kmd structure");
123
124			kmd = calloc(1, sizeof (krb5_module_data_t));
125
126			if (kmd == NULL)
127				return (PAM_BUF_ERR);
128
129
130			/*
131			 * Need to initialize auth_status here to
132			 * PAM_AUTHINFO_UNAVAIL else there is a false positive
133			 * of PAM_SUCCESS.
134			 */
135			kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
136
137			if ((err = pam_set_data(pamh, KRB5_DATA,
138			    kmd, &krb5_cleanup)) != PAM_SUCCESS) {
139				free(kmd);
140				return (PAM_SYSTEM_ERR);
141			}
142		} else {
143			/*
144			 * This could mean that we are not the account authority
145			 * for the authenticated user.  Therefore we should
146			 * return PAM_IGNORE in order to not affect the
147			 * login process of said user.
148			 */
149			err = PAM_IGNORE;
150			goto out;
151		}
152
153	} else {  /* pam_get_data success */
154		if (kmd == NULL) {
155			if (debug) {
156				__pam_log(LOG_AUTH | LOG_DEBUG,
157				    "PAM-KRB5 (setcred): kmd structure"
158				    " gotten but is NULL for user %s", user);
159			}
160			err = PAM_SYSTEM_ERR;
161			goto out;
162		}
163
164		if (debug)
165			__pam_log(LOG_AUTH | LOG_DEBUG,
166			    "PAM-KRB5 (setcred): kmd auth_status: %s",
167			    pam_strerror(pamh, kmd->auth_status));
168
169		/*
170		 * pam_auth has set status to ignore, so we also return ignore
171		 */
172		if (kmd->auth_status == PAM_IGNORE) {
173			err = PAM_IGNORE;
174			goto out;
175		}
176	}
177
178	kmd->debug = debug;
179
180	/*
181	 * User must have passed pam_authenticate()
182	 * in order to use PAM_ESTABLISH_CRED or PAM_REINITIALIZE_CRED
183	 */
184	if ((flags & (PAM_ESTABLISH_CRED|PAM_REINITIALIZE_CRED)) &&
185	    (kmd->auth_status != PAM_SUCCESS)) {
186		if (kmd->debug)
187			__pam_log(LOG_AUTH | LOG_DEBUG,
188			    "PAM-KRB5 (setcred): unable to "
189			    "setcreds, not authenticated!");
190		return (PAM_CRED_UNAVAIL);
191	}
192
193	/*
194	 * We cannot assume that kmd->kcontext being non-NULL
195	 * means it is valid.  Other pam_krb5 mods may have
196	 * freed it but not reset it to NULL.
197	 * Log a message when debugging to track down memory
198	 * leaks.
199	 */
200	if (kmd->kcontext != NULL && kmd->debug)
201		__pam_log(LOG_AUTH | LOG_DEBUG,
202		    "PAM-KRB5 (setcred): kcontext != NULL, "
203		    "possible memory leak.");
204
205	/*
206	 * Use the authenticated and validated user, if applicable.
207	 */
208	if (kmd->user != NULL)
209		user = kmd->user;
210
211	/*
212	 * If auth was short-circuited we will not have anything to
213	 * renew, so just return here.
214	 */
215	(void) pam_get_item(pamh, PAM_REPOSITORY, (void **)&rep_data);
216
217	if (rep_data != NULL) {
218		if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
219			if (debug)
220				__pam_log(LOG_AUTH | LOG_DEBUG,
221				    "PAM-KRB5 (setcred): wrong"
222				    "repository found (%s), returning "
223				    "PAM_IGNORE", rep_data->type);
224			return (PAM_IGNORE);
225		}
226		if (rep_data->scope_len == sizeof (krb5_repository_data_t)) {
227			krb5_data = (krb5_repository_data_t *)rep_data->scope;
228
229			if (krb5_data->flags ==
230			    SUNW_PAM_KRB5_ALREADY_AUTHENTICATED &&
231			    krb5_data->principal != NULL &&
232			    strlen(krb5_data->principal)) {
233				if (debug)
234					__pam_log(LOG_AUTH | LOG_DEBUG,
235					    "PAM-KRB5 (setcred): "
236					    "Principal %s already "
237					    "authenticated, "
238					    "cannot setcred",
239					    krb5_data->principal);
240				return (PAM_SUCCESS);
241			}
242		}
243	}
244
245	if (flags & PAM_REINITIALIZE_CRED)
246		err = attempt_refresh_cred(kmd, user, PAM_REINITIALIZE_CRED);
247	else if (flags & PAM_REFRESH_CRED)
248		err = attempt_refresh_cred(kmd, user, PAM_REFRESH_CRED);
249	else if (flags & PAM_DELETE_CRED)
250		err = attempt_delete_initcred(kmd);
251	else {
252		/*
253		 * Default case:  PAM_ESTABLISH_CRED
254		 */
255		err = attempt_refresh_cred(kmd, user, PAM_ESTABLISH_CRED);
256	}
257
258	if (err != PAM_SUCCESS)
259		__pam_log(LOG_AUTH | LOG_ERR,
260		    "PAM-KRB5 (setcred): pam_setcred failed "
261		    "for %s (%s).", user, pam_strerror(pamh, err));
262
263out:
264	if (kmd && kmd->kcontext) {
265		/*
266		 * free 'kcontext' field if it is allocated,
267		 * kcontext is local to the operation being performed
268		 * not considered global to the entire pam module.
269		 */
270		krb5_free_context(kmd->kcontext);
271		kmd->kcontext = NULL;
272	}
273
274	/*
275	 * 'kmd' is not freed here, it is handled in krb5_cleanup
276	 */
277	if (debug)
278		__pam_log(LOG_AUTH | LOG_DEBUG,
279		    "PAM-KRB5 (setcred): end: %s",
280		    pam_strerror(pamh, err));
281	return (err);
282}
283
284static int
285attempt_refresh_cred(
286	krb5_module_data_t	*kmd,
287	char		*user,
288	int	flag)
289{
290	krb5_principal	me;
291	krb5_principal	server;
292	krb5_error_code	code;
293	char		kuser[2*MAXHOSTNAMELEN];
294	krb5_data tgtname = {
295		0,
296		KRB5_TGS_NAME_SIZE,
297		KRB5_TGS_NAME
298	};
299
300	/* Create a new context here. */
301	if (krb5_init_secure_context(&kmd->kcontext) != 0) {
302		if (kmd->debug)
303			__pam_log(LOG_AUTH | LOG_DEBUG,
304			    "PAM-KRB5 (setcred): unable to "
305			    "initialize krb5 context");
306		return (PAM_SYSTEM_ERR);
307	}
308
309	if (krb5_cc_default(kmd->kcontext, &kmd->ccache) != 0) {
310		return (PAM_SYSTEM_ERR);
311	}
312
313	if ((code = get_kmd_kuser(kmd->kcontext, (const char *)user, kuser,
314	    2*MAXHOSTNAMELEN)) != 0) {
315		return (code);
316	}
317
318	if (krb5_parse_name(kmd->kcontext, kuser, &me) != 0) {
319		return (PAM_SYSTEM_ERR);
320	}
321
322	if (code = krb5_build_principal_ext(kmd->kcontext, &server,
323	    krb5_princ_realm(kmd->kcontext, me)->length,
324	    krb5_princ_realm(kmd->kcontext, me)->data,
325	    tgtname.length, tgtname.data,
326	    krb5_princ_realm(kmd->kcontext, me)->length,
327	    krb5_princ_realm(kmd->kcontext, me)->data, 0)) {
328		krb5_free_principal(kmd->kcontext, me);
329		return (PAM_SYSTEM_ERR);
330	}
331
332	code = krb5_renew_tgt(kmd, me, server, flag);
333
334	krb5_free_principal(kmd->kcontext, server);
335	krb5_free_principal(kmd->kcontext, me);
336
337	if (code) {
338		if (kmd->debug)
339			__pam_log(LOG_AUTH | LOG_DEBUG,
340			    "PAM-KRB5(setcred): krb5_renew_tgt() "
341			    "failed: %s", error_message((errcode_t)code));
342		return (PAM_CRED_ERR);
343	} else {
344		return (PAM_SUCCESS);
345	}
346}
347
348/*
349 * This code will update the credential matching "server" in the user's
350 * credential cache.  The flag may be set to one of:
351 * PAM_REINITIALIZE_CRED/PAM_ESTABLISH_CRED - If we have new credentials then
352 *     create a new cred cache with these credentials else return failure.
353 * PAM_REFRESH_CRED - If we have new credentials then create a new cred cache
354 *  with these credentials else attempt to renew the credentials.
355 *
356 * Note for any of the flags that if a new credential does exist from the
357 * previous auth pass then this will overwrite any existing credentials in the
358 * credential cache.
359 */
360static krb5_error_code
361krb5_renew_tgt(
362	krb5_module_data_t *kmd,
363	krb5_principal	me,
364	krb5_principal	server,
365	int	flag)
366{
367	krb5_error_code	retval;
368	krb5_creds	creds;
369	krb5_creds	*renewed_cred = NULL;
370	char		*client_name = NULL;
371	char		*username = NULL;
372
373#define	my_creds	(kmd->initcreds)
374
375	if ((flag != PAM_REFRESH_CRED) &&
376	    (flag != PAM_REINITIALIZE_CRED) &&
377	    (flag != PAM_ESTABLISH_CRED))
378		return (KRB5KRB_ERR_GENERIC);
379
380	/* this is needed only for the ktkt_warnd */
381	if ((retval = krb5_unparse_name(kmd->kcontext, me, &client_name)) != 0)
382		return (retval);
383
384	(void) memset(&creds, 0, sizeof (krb5_creds));
385	if ((retval = krb5_copy_principal(kmd->kcontext,
386	    server, &creds.server))) {
387		if (kmd->debug)
388			__pam_log(LOG_AUTH | LOG_DEBUG,
389			    "PAM-KRB5 (setcred): krb5_copy_principal "
390			    "failed: %s",
391			    error_message((errcode_t)retval));
392		goto cleanup_creds;
393	}
394
395	/* obtain ticket & session key */
396	retval = krb5_cc_get_principal(kmd->kcontext,
397	    kmd->ccache, &creds.client);
398	if (retval && (kmd->debug))
399		__pam_log(LOG_AUTH | LOG_DEBUG,
400		    "PAM-KRB5 (setcred): User not in cred "
401		    "cache (%s)", error_message((errcode_t)retval));
402
403	/*
404	 * We got here either with the ESTABLISH | REINIT | REFRESH flag and
405	 * auth_status returns SUCCESS or REFRESH and auth_status failure.
406	 *
407	 * Rules:
408	 * - If the prior auth pass was successful then store the new
409	 * credentials in the cache, regardless of which flag.
410	 *
411	 * - Else if REFRESH flag is used and there are no new
412	 * credentials then attempt to refresh the existing credentials.
413	 *
414	 * - Note, refresh will not work if "R" flag is not set in
415	 * original credential.  We don't want to 2nd guess the
416	 * intention of the person who created the existing credential.
417	 */
418	if (kmd->auth_status == PAM_SUCCESS) {
419		/*
420		 * Create a fresh ccache, and store the credentials
421		 * we got from pam_authenticate()
422		 */
423		if ((retval = krb5_cc_initialize(kmd->kcontext,
424		    kmd->ccache, me)) != 0) {
425			__pam_log(LOG_AUTH | LOG_DEBUG,
426			    "PAM-KRB5 (setcred): krb5_cc_initialize "
427			    "failed: %s",
428			    error_message((errcode_t)retval));
429		} else if ((retval = krb5_cc_store_cred(kmd->kcontext,
430		    kmd->ccache, &my_creds)) != 0) {
431			__pam_log(LOG_AUTH | LOG_DEBUG,
432			    "PAM-KRB5 (setcred): krb5_cc_store_cred "
433			    "failed: %s",
434			    error_message((errcode_t)retval));
435		}
436	} else if ((retval == 0) && (flag & PAM_REFRESH_CRED)) {
437		/*
438		 * If we only wanted to refresh the creds but failed
439		 * due to expiration, lack of "R" flag, or other
440		 * problems, return an error.
441		 */
442		if (retval = krb5_get_credentials_renew(kmd->kcontext,
443		    0, kmd->ccache, &creds, &renewed_cred)) {
444			if (kmd->debug) {
445				__pam_log(LOG_AUTH | LOG_DEBUG,
446				    "PAM-KRB5 (setcred): "
447				    "krb5_get_credentials"
448				    "_renew(update) failed: %s",
449				    error_message((errcode_t)retval));
450			}
451		}
452	} else {
453		/*
454		 * We failed to get the user's credentials.
455		 * This might be due to permission error on the cache,
456		 * or maybe we are looking in the wrong cache file!
457		 */
458		__pam_log(LOG_AUTH | LOG_ERR,
459		    "PAM-KRB5 (setcred): Cannot find creds"
460		    " for %s (%s)",
461		    client_name ? client_name : "(unknown)",
462		    error_message((errcode_t)retval));
463	}
464
465cleanup_creds:
466
467	if ((retval == 0) && (client_name != NULL)) {
468		/*
469		 * Credential update was successful!
470		 *
471		 * We now chown the ccache to the appropriate uid/gid
472		 * combination, if its a FILE based ccache.
473		 */
474		if (!kmd->env || strstr(kmd->env, "FILE:")) {
475			uid_t uuid;
476			gid_t ugid;
477			char *tmpname = NULL;
478			char *filepath = NULL;
479
480			username = strdup(client_name);
481			if (username == NULL) {
482				__pam_log(LOG_AUTH | LOG_ERR,
483				    "PAM-KRB5 (setcred): Out of memory");
484				retval = KRB5KRB_ERR_GENERIC;
485				goto error;
486			}
487			if ((tmpname = strchr(username, '@')))
488				*tmpname = '\0';
489
490			if (get_pw_uid(username, &uuid) == 0 ||
491			    get_pw_gid(username, &ugid) == 0) {
492				__pam_log(LOG_AUTH | LOG_ERR,
493				    "PAM-KRB5 (setcred): Unable to "
494				    "find matching uid/gid pair for user `%s'",
495				    username);
496				retval = KRB5KRB_ERR_GENERIC;
497				goto error;
498			}
499
500			if (!kmd->env) {
501				char buffer[512];
502
503				if (snprintf(buffer, sizeof (buffer),
504				    "%s=FILE:/tmp/krb5cc_%d", KRB5_ENV_CCNAME,
505				    (int)uuid) >= sizeof (buffer)) {
506					retval = KRB5KRB_ERR_GENERIC;
507					goto error;
508				}
509
510				/*
511				 * We MUST copy this to the heap for the putenv
512				 * to work!
513				 */
514				kmd->env = strdup(buffer);
515				if (!kmd->env) {
516					retval = ENOMEM;
517					goto error;
518				} else {
519					if (putenv(kmd->env)) {
520						retval = ENOMEM;
521						goto error;
522					}
523				}
524			}
525
526			/*
527			 * We know at this point that kmd->env must start
528			 * with the literal string "FILE:".  Set filepath
529			 * character string to point to ":"
530			 */
531
532			filepath = strchr(kmd->env, ':');
533
534			/*
535			 * Now check if first char after ":" is null char
536			 */
537			if (filepath[1] == '\0') {
538				__pam_log(LOG_AUTH | LOG_ERR,
539				    "PAM-KRB5 (setcred): Invalid pathname "
540				    "for credential cache of user `%s'",
541				    username);
542				retval = KRB5KRB_ERR_GENERIC;
543				goto error;
544			}
545			if (chown(filepath+1, uuid, ugid)) {
546				if (kmd->debug)
547					__pam_log(LOG_AUTH | LOG_DEBUG,
548					    "PAM-KRB5 (setcred): chown to user "
549					    "`%s' failed for FILE=%s",
550					    username, filepath);
551			}
552		}
553	}
554
555error:
556	if (retval == 0) {
557		krb5_timestamp endtime;
558
559		if (renewed_cred && renewed_cred->times.endtime != 0)
560			endtime = renewed_cred->times.endtime;
561		else
562			endtime = my_creds.times.endtime;
563
564		if (kmd->debug)
565			__pam_log(LOG_AUTH | LOG_DEBUG,
566			    "PAM-KRB5 (setcred): delete/add warning");
567
568		if (kwarn_del_warning(client_name) != 0) {
569			__pam_log(LOG_AUTH | LOG_NOTICE,
570			    "PAM-KRB5 (setcred): kwarn_del_warning"
571			    " failed: ktkt_warnd(1M) down?");
572		}
573
574		if (kwarn_add_warning(client_name, endtime) != 0) {
575			__pam_log(LOG_AUTH | LOG_NOTICE,
576			    "PAM-KRB5 (setcred): kwarn_add_warning"
577			    " failed: ktkt_warnd(1M) down?");
578		}
579	}
580
581	if (renewed_cred != NULL)
582		krb5_free_creds(kmd->kcontext, renewed_cred);
583
584	if (client_name != NULL)
585		free(client_name);
586
587	if (username)
588		free(username);
589
590	krb5_free_cred_contents(kmd->kcontext, &creds);
591
592	return (retval);
593}
594
595/*
596 * Delete the user's credentials for this session
597 */
598static int
599attempt_delete_initcred(krb5_module_data_t *kmd)
600{
601	if (kmd == NULL)
602		return (PAM_SUCCESS);
603
604	if (kmd->debug) {
605		__pam_log(LOG_AUTH | LOG_DEBUG,
606		    "PAM-KRB5 (setcred): deleting user's "
607		    "credentials (initcreds)");
608	}
609	krb5_free_cred_contents(kmd->kcontext, &kmd->initcreds);
610	(void) memset((char *)&kmd->initcreds, 0, sizeof (krb5_creds));
611	kmd->auth_status = PAM_AUTHINFO_UNAVAIL;
612	return (PAM_SUCCESS);
613}
614