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