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