1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * lib/krb5/os/kuserok.c
10  *
11  * Copyright 1990,1993 by the Massachusetts Institute of Technology.
12  * All Rights Reserved.
13  *
14  * Export of this software from the United States of America may
15  *   require a specific license from the United States Government.
16  *   It is the responsibility of any person or organization contemplating
17  *   export to obtain such a license before exporting.
18  *
19  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20  * distribute this software and its documentation for any purpose and
21  * without fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright notice and
23  * this permission notice appear in supporting documentation, and that
24  * the name of M.I.T. not be used in advertising or publicity pertaining
25  * to distribution of the software without specific, written prior
26  * permission.  M.I.T. makes no representations about the suitability of
27  * this software for any purpose.  It is provided "as is" without express
28  * or implied warranty.
29  *
30  *
31  * krb5_kuserok()
32  */
33 
34 #include "k5-int.h"
35 /* #if !defined(_WIN32)            Not yet for Windows */
36 #include <stdio.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <pwd.h>
40 #include <libintl.h>
41 #include <gssapi/gssapi.h>
42 #include <gssapi/gssapi_ext.h>
43 #include <gssapi_krb5.h>
44 #include <gssapiP_krb5.h>
45 #include <syslog.h>
46 
47 extern void
48 gsscred_set_options();
49 
50 extern OM_uint32
51 gsscred_name_to_unix_cred_ext();
52 
53 extern int
54 safechown(const char *src, uid_t uid, gid_t gid, int mode);
55 
56 extern const char *error_message(long);
57 
58 #define	MAX_USERNAME 65
59 #define	CACHE_FILENAME_LEN 35
60 
61 krb5_data tgtname = {
62 	0,
63 	KRB5_TGS_NAME_SIZE,
64 	KRB5_TGS_NAME
65 };
66 
67 static krb5_error_code
68 krb5_move_ccache(krb5_context kcontext, krb5_principal client,
69 		struct passwd *pwd)
70 {
71 	char *name = 0;
72 	static char ccache_name_buf[CACHE_FILENAME_LEN];
73 	krb5_ccache ccache = NULL;
74 	krb5_error_code retval;
75 
76 	name = getenv(KRB5_ENV_CCNAME);
77 	if (name == 0)
78 		/*
79 		 * This means that there was no forwarding
80 		 * of creds
81 		 */
82 		return (0);
83 	else {
84 		/*
85 		 * creds have been forwarded and stored in
86 		 * KRB5_ENV_CCNAME and now we need to store it
87 		 * under uid
88 		 */
89 
90 		krb5_creds mcreds, save_v5creds;
91 
92 		memset(&mcreds, 0, sizeof (mcreds));
93 		memset(&save_v5creds, 0, sizeof (save_v5creds));
94 
95 		mcreds.client =  client;
96 		retval = krb5_build_principal_ext(kcontext, &mcreds.server,
97 				krb5_princ_realm(kcontext,  client)->length,
98 				krb5_princ_realm(kcontext,  client)->data,
99 				tgtname.length, tgtname.data,
100 				krb5_princ_realm(kcontext,  client)->length,
101 				krb5_princ_realm(kcontext,  client)->data,
102 				0);
103 		if (retval) {
104 			syslog(LOG_ERR,
105 				gettext("KRB5: %s while creating"
106 					"V5 krbtgt principal "),
107 				error_message(retval));
108 			return (retval);
109 		}
110 
111 		mcreds.ticket_flags = 0;
112 		retval = krb5_cc_default(kcontext, &ccache);
113 		if (retval) {
114 			syslog(LOG_ERR,
115 				gettext("KRB5: %s while getting "
116 					"default cache "),
117 				error_message(retval));
118 			return (retval);
119 		}
120 
121 		retval = krb5_cc_retrieve_cred(kcontext, ccache,
122 						0,
123 						&mcreds, &save_v5creds);
124 		if (retval) {
125 			syslog(LOG_ERR,
126 				gettext("KRB5: %s while retrieving "
127 					"cerdentials "),
128 				error_message(retval));
129 			return (retval);
130 		}
131 		/*
132 		 * reset the env variable and recreate the
133 		 * cache using the default cache name
134 		 */
135 		retval = krb5_cc_destroy(kcontext, ccache);
136 		if (retval) {
137 			syslog(LOG_ERR,
138 				gettext("KRB5: %s while destroying cache "),
139 				error_message(retval));
140 			return (retval);
141 		}
142 		krb5_unsetenv(KRB5_ENV_CCNAME);
143 		snprintf(ccache_name_buf,
144 			CACHE_FILENAME_LEN,
145 			"FILE:/tmp/krb5cc_%d", pwd->pw_uid);
146 		krb5_setenv(KRB5_ENV_CCNAME, ccache_name_buf, 1);
147 		retval =  krb5_cc_resolve(kcontext, ccache_name_buf, &ccache);
148 		if (retval) {
149 			syslog(LOG_ERR,
150 				gettext("KRB5: %s while resolving cache "),
151 				error_message(retval));
152 			return (retval);
153 		}
154 		retval = krb5_cc_initialize(kcontext, ccache, client);
155 		if (retval) {
156 			syslog(LOG_ERR,
157 				gettext("KRB5: %s while initializing cache "),
158 				error_message(retval));
159 			return (retval);
160 		}
161 		retval =  krb5_cc_store_cred(kcontext, ccache, &save_v5creds);
162 		if (retval) {
163 			syslog(LOG_ERR,
164 				gettext("KRB5: %s while storing creds "),
165 				error_message(retval));
166 			return (retval);
167 		}
168 		snprintf(ccache_name_buf,
169 			CACHE_FILENAME_LEN,
170 			"/tmp/krb5cc_%d", pwd->pw_uid);
171 		if (safechown(ccache_name_buf, pwd->pw_uid,
172 			pwd->pw_gid, -1) == -1) {
173 			syslog(LOG_ERR,
174 				gettext("KRB5: Can not change "
175 					"ownership of cache file, "
176 					"possible security breach\n"));
177 		}
178 	}
179 
180 	return (0);
181 }
182 
183 
184 /*
185  * krb5_gsscred: Given a kerberos principal try to find the corresponding
186  * local uid via the gss cred table. Return TRUE if the uid was found in the
187  * cred table, otherwise return FALSE.
188  */
189 static krb5_boolean
190 krb5_gsscred(krb5_principal principal, uid_t *uid)
191 {
192 	OM_uint32 minor, major;
193 	gss_name_t name;
194 	gss_buffer_desc name_buf;
195 
196 	name_buf.value = &principal;
197 	name_buf.length = sizeof (principal);
198 
199 	/*
200 	 * Convert the kerb principal in to a gss name
201 	 */
202 	major = gss_import_name(&minor, &name_buf,
203 				(gss_OID)gss_nt_krb5_principal, &name);
204 
205 	if (major != GSS_S_COMPLETE)
206 		return (FALSE);
207 
208 	gsscred_set_options();
209 
210 	/*
211 	 * Get the uid mapping from the gsscred table.
212 	 * (but set flag to not call back into this mech as we do krb5
213 	 * auth_to_local name mapping from this module).
214 	 */
215 	major = gsscred_name_to_unix_cred_ext(name, (gss_OID)gss_mech_krb5,
216 					  uid, 0, 0, 0, 0);
217 
218 	(void) gss_release_name(&minor, &name);
219 
220 	if (major != GSS_S_COMPLETE)
221 		return (FALSE);
222 
223 	return (TRUE);
224 }
225 
226 /*
227  * Given a Kerberos principal "principal", and a local username "luser",
228  * determine whether user is authorized to login according to the
229  * authorization file ("~luser/.k5login" by default).  Returns TRUE
230  * if authorized, FALSE if not authorized.
231  *
232  * If there is no account for "luser" on the local machine, returns
233  * FALSE.  If there is no authorization file, and the given Kerberos
234  * name "server" translates to the same name as "luser" (using
235  * krb5_aname_to_lname()), returns TRUE.  Otherwise, if the authorization file
236  * can't be accessed, returns FALSE.  Otherwise, the file is read for
237  * a matching principal name, instance, and realm.  If one is found,
238  * returns TRUE, if none is found, returns FALSE.
239  *
240  * The file entries are in the format produced by krb5_unparse_name(),
241  * one entry per line.
242  *
243  */
244 
245 krb5_boolean KRB5_CALLCONV
246 krb5_kuserok(krb5_context context, krb5_principal principal, const char *luser)
247 {
248     struct stat sbuf;
249     struct passwd *pwd;
250     char pbuf[MAXPATHLEN];
251     krb5_boolean isok = FALSE;
252     FILE *fp;
253     char kuser[MAX_USERNAME];
254     char *princname;
255     char linebuf[BUFSIZ];
256     char *newline;
257     uid_t uid;
258     int gobble;
259 
260     /* no account => no access */
261 #ifdef HAVE_GETPWNAM_R
262     char pwbuf[BUFSIZ];
263     struct passwd pwx;
264 #if !defined(GETPWNAM_R_4_ARGS)
265     /* POSIX */
266     if (getpwnam_r(luser, &pwx, pwbuf, sizeof(pwbuf), &pwd) != 0)
267 	pwd = NULL;
268 #else
269     /* draft POSIX */
270     pwd = getpwnam_r(luser, &pwx, pwbuf, sizeof(pwbuf));
271 #endif
272 #else
273     pwd = getpwnam(luser);
274 #endif
275     if (pwd == NULL)
276 	return(FALSE);
277 
278     (void) strncpy(pbuf, pwd->pw_dir, sizeof(pbuf) - 1);
279     pbuf[sizeof(pbuf) - 1] = '\0';
280     (void) strncat(pbuf, "/.k5login", sizeof(pbuf) - 1 - strlen(pbuf));
281 
282     if (access(pbuf, F_OK)) {	 /* not accessible */
283 
284 	/*
285 	 * if he's trying to log in as himself, and there is no .k5login file,
286 	 * let him.  First, have krb5 check it's rules.  If no success,
287 	 * search the gsscred table (the sequence here should be consistent
288 	 * with the uid mappings done for gssd).
289 	 */
290 	if (!(krb5_aname_to_localname(context, principal,
291 				      sizeof(kuser), kuser))
292 	    && (strcmp(kuser, luser) == 0)) {
293 		if (krb5_move_ccache(context, principal, pwd))
294 			return (FALSE);
295 	    	return(TRUE);
296 	}
297 
298 	if (krb5_gsscred(principal, &uid)) {
299 #ifdef DEBUG
300 	    char *princname;
301 
302 	    (void)krb5_unparse_name(context, principal, &princname);
303 	    syslog(LOG_DEBUG, "gsscred mapped %s to %d expecting %d (%s)\n",
304 		   princname, uid, pwd->pw_uid, luser);
305 	    free(princname);
306 #endif
307 	    if (uid == pwd->pw_uid) {
308 		if (krb5_move_ccache(context, principal, pwd))
309 			return (FALSE);
310 		return (TRUE);
311 	    }
312 	}
313 
314     }
315     if (krb5_unparse_name(context, principal, &princname))
316 	return(FALSE);			/* no hope of matching */
317 
318     /* open ~/.k5login */
319     if ((fp = fopen(pbuf, "rF")) == NULL) {
320 	free(princname);
321 	return(FALSE);
322     }
323     /*
324      * For security reasons, the .k5login file must be owned either by
325      * the user himself, or by root.  Otherwise, don't grant access.
326      */
327     if (fstat(fileno(fp), &sbuf)) {
328 	fclose(fp);
329 	free(princname);
330 	return(FALSE);
331     }
332     if ((sbuf.st_uid != pwd->pw_uid) && sbuf.st_uid) {
333 	fclose(fp);
334 	free(princname);
335 	return(FALSE);
336     }
337 
338     /* check each line */
339     while (!isok && (fgets(linebuf, BUFSIZ, fp) != NULL)) {
340 	/* null-terminate the input string */
341 	linebuf[BUFSIZ-1] = '\0';
342 	newline = NULL;
343 	/* nuke the newline if it exists */
344 	if ((newline = strchr(linebuf, '\n')))
345 	    *newline = '\0';
346 	if (!strcmp(linebuf, princname)) {
347 	    isok = TRUE;
348 	    if (krb5_move_ccache(context, principal, pwd))
349 		return (FALSE);
350 	    continue;
351 	}
352 	/* clean up the rest of the line if necessary */
353 	if (!newline)
354 	    while (((gobble = getc(fp)) != EOF) && gobble != '\n');
355     }
356     free(princname);
357     fclose(fp);
358     return(isok);
359 }
360 
361 OM_uint32
362 krb5_gss_userok(void *ctxt,
363 		OM_uint32 *minor,
364 		const gss_name_t pname,
365 		const char *user,
366 		int *user_ok)
367 {
368 	if (pname == NULL || user == NULL)
369 		return (GSS_S_CALL_INACCESSIBLE_READ);
370 
371 	if (minor == NULL || user_ok == NULL)
372 		return (GSS_S_CALL_INACCESSIBLE_WRITE);
373 
374 	*user_ok = 0;
375 
376 	if (! kg_validate_name(pname)) {
377                  *minor = (OM_uint32) G_VALIDATE_FAILED;
378                  return (GSS_S_CALL_BAD_STRUCTURE|GSS_S_BAD_NAME);
379 	}
380 
381 	if (krb5_kuserok(ctxt, (krb5_principal) pname, user)) {
382 		*user_ok = 1;
383 	}
384 	return (GSS_S_COMPLETE);
385 }
386