xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c (revision 56e2cc86321ec889bf83a888d902c60d6fb2ef8d)
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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 /*
26  * Copyright 1990 by the Massachusetts Institute of Technology.
27  * All Rights Reserved.
28  *
29  * Export of this software from the United States of America may
30  *   require a specific license from the United States Government.
31  *   It is the responsibility of any person or organization contemplating
32  *   export to obtain such a license before exporting.
33  *
34  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
35  * distribute this software and its documentation for any purpose and
36  * without fee is hereby granted, provided that the above copyright
37  * notice appear in all copies and that both that copyright notice and
38  * this permission notice appear in supporting documentation, and that
39  * the name of M.I.T. not be used in advertising or publicity pertaining
40  * to distribution of the software without specific, written prior
41  * permission.  Furthermore if you modify this software you must label
42  * your software as modified software and not distribute it in such a
43  * fashion that it might be confused with the original M.I.T. software.
44  * M.I.T. makes no representations about the suitability of
45  * this software for any purpose.  It is provided "as is" without express
46  * or implied warranty.
47  *
48  *
49  * Initialize a credentials cache.
50  */
51 #include <kerberosv5/krb5.h>
52 #include <kerberosv5/com_err.h>
53 #include <assert.h>
54 #include <string.h>
55 #include <stdio.h>
56 #include <time.h>
57 #include <netdb.h>
58 #include <syslog.h>
59 #include <locale.h>
60 #include <strings.h>
61 #include <errno.h>
62 #include <sys/synch.h>
63 #include <gssapi/gssapi.h>
64 
65 #include <smbsrv/libsmbns.h>
66 
67 #include <smbns_krb.h>
68 
69 static int krb5_acquire_cred_kinit_main();
70 
71 typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
72 
73 struct k_opts {
74 	/* in seconds */
75 	krb5_deltat starttime;
76 	krb5_deltat lifetime;
77 	krb5_deltat rlife;
78 
79 	int forwardable;
80 	int proxiable;
81 	int addresses;
82 
83 	int not_forwardable;
84 	int not_proxiable;
85 	int no_addresses;
86 
87 	int verbose;
88 
89 	char *principal_name;
90 	char *principal_passwd;
91 	char *service_name;
92 	char *keytab_name;
93 	char *k5_cache_name;
94 	char *k4_cache_name;
95 
96 	action_type action;
97 };
98 
99 struct k5_data {
100 	krb5_context ctx;
101 	krb5_ccache cc;
102 	krb5_principal me;
103 	char *name;
104 };
105 
106 static int
107 k5_begin(struct k_opts *opts, struct k5_data *k5)
108 {
109 	int code;
110 	code = krb5_init_context(&k5->ctx);
111 	if (code) {
112 		return (code);
113 	}
114 
115 	if ((code = krb5_cc_default(k5->ctx, &k5->cc))) {
116 		return (code);
117 	}
118 
119 	/* Use specified name */
120 	if ((code = krb5_parse_name(k5->ctx, opts->principal_name, &k5->me))) {
121 		return (code);
122 	}
123 
124 	code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
125 	if (code) {
126 		return (code);
127 	}
128 	opts->principal_name = k5->name;
129 
130 	return (0);
131 }
132 
133 static void
134 k5_end(struct k5_data *k5)
135 {
136 	if (k5->name)
137 		krb5_free_unparsed_name(k5->ctx, k5->name);
138 	if (k5->me)
139 		krb5_free_principal(k5->ctx, k5->me);
140 	if (k5->cc)
141 		krb5_cc_close(k5->ctx, k5->cc);
142 	if (k5->ctx)
143 		krb5_free_context(k5->ctx);
144 	(void) memset(k5, 0, sizeof (*k5));
145 }
146 
147 static int
148 k5_kinit(struct k_opts *opts, struct k5_data *k5)
149 {
150 	int notix = 1;
151 	krb5_keytab keytab = 0;
152 	krb5_creds my_creds;
153 	krb5_error_code code = 0;
154 	krb5_get_init_creds_opt options;
155 	const char *errmsg;
156 
157 	krb5_get_init_creds_opt_init(&options);
158 	(void) memset(&my_creds, 0, sizeof (my_creds));
159 
160 	/*
161 	 * From this point on, we can goto cleanup because my_creds is
162 	 * initialized.
163 	 */
164 	if (opts->lifetime)
165 		krb5_get_init_creds_opt_set_tkt_life(&options, opts->lifetime);
166 	if (opts->rlife)
167 		krb5_get_init_creds_opt_set_renew_life(&options, opts->rlife);
168 	if (opts->forwardable)
169 		krb5_get_init_creds_opt_set_forwardable(&options, 1);
170 	if (opts->not_forwardable)
171 		krb5_get_init_creds_opt_set_forwardable(&options, 0);
172 	if (opts->proxiable)
173 		krb5_get_init_creds_opt_set_proxiable(&options, 1);
174 	if (opts->not_proxiable)
175 		krb5_get_init_creds_opt_set_proxiable(&options, 0);
176 	if (opts->addresses) {
177 		krb5_address **addresses = NULL;
178 		code = krb5_os_localaddr(k5->ctx, &addresses);
179 		if (code != 0) {
180 			errmsg = error_message(code);
181 			syslog(LOG_ERR, "k5_kinit: getting local addresses "
182 			    "(%s)", errmsg);
183 			goto cleanup;
184 		}
185 		krb5_get_init_creds_opt_set_address_list(&options, addresses);
186 	}
187 	if (opts->no_addresses)
188 		krb5_get_init_creds_opt_set_address_list(&options, NULL);
189 
190 	if ((opts->action == INIT_KT) && opts->keytab_name) {
191 		code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab);
192 		if (code != 0) {
193 			errmsg = error_message(code);
194 			syslog(LOG_ERR, "k5_kinit: resolving keytab %s (%s)",
195 			    errmsg, opts->keytab_name);
196 			goto cleanup;
197 		}
198 	}
199 
200 	switch (opts->action) {
201 	case INIT_PW:
202 		code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me,
203 		    opts->principal_passwd, NULL, 0, opts->starttime,
204 		    opts->service_name, &options);
205 		break;
206 	case INIT_KT:
207 		code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me,
208 		    keytab, opts->starttime, opts->service_name, &options);
209 		break;
210 	case VALIDATE:
211 		code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me,
212 		    k5->cc, opts->service_name);
213 		break;
214 	case RENEW:
215 		code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me,
216 		    k5->cc, opts->service_name);
217 		break;
218 	}
219 
220 	if (code) {
221 		char *doing = 0;
222 		switch (opts->action) {
223 		case INIT_PW:
224 		case INIT_KT:
225 			doing = "k5_kinit: getting initial credentials";
226 			break;
227 		case VALIDATE:
228 			doing = "k5_kinit: validating credentials";
229 			break;
230 		case RENEW:
231 			doing = "k5_kinit: renewing credentials";
232 			break;
233 		}
234 
235 		/*
236 		 * If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should
237 		 * let the user know that maybe he/she wants -4.
238 		 */
239 		if (code == KRB5KRB_AP_ERR_V4_REPLY) {
240 			syslog(LOG_ERR, "%s\n"
241 			    "The KDC doesn't support v5.  "
242 			    "You may want the -4 option in the future", doing);
243 			return (1);
244 		} else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
245 			syslog(LOG_ERR, "%s (Password incorrect)", doing);
246 		} else {
247 			errmsg = error_message(code);
248 			syslog(LOG_ERR, "%s (%s)", doing, errmsg);
249 		}
250 		goto cleanup;
251 	}
252 
253 	if (!opts->lifetime) {
254 		/* We need to figure out what lifetime to use for Kerberos 4. */
255 		opts->lifetime = my_creds.times.endtime -
256 		    my_creds.times.authtime;
257 	}
258 
259 	code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me);
260 	if (code) {
261 		errmsg = error_message(code);
262 		syslog(LOG_ERR, "k5_kinit: initializing cache %s (%s)",
263 		    opts->k5_cache_name ? opts->k5_cache_name : "", errmsg);
264 		goto cleanup;
265 	}
266 
267 	code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds);
268 	if (code) {
269 		errmsg = error_message(code);
270 		syslog(LOG_ERR, "k5_kinit: storing credentials (%s)", errmsg);
271 		goto cleanup;
272 	}
273 
274 	notix = 0;
275 
276 	cleanup:
277 		if (my_creds.client == k5->me) {
278 			my_creds.client = 0;
279 		}
280 		krb5_free_cred_contents(k5->ctx, &my_creds);
281 		if (keytab)
282 			krb5_kt_close(k5->ctx, keytab);
283 		return (notix?0:1);
284 }
285 
286 int
287 smb_kinit(char *user, char *passwd)
288 {
289 	struct k_opts opts;
290 	struct k5_data k5;
291 	int authed_k5 = 0;
292 
293 	assert(user);
294 	assert(passwd);
295 
296 	(void) memset(&opts, 0, sizeof (opts));
297 	opts.action = INIT_PW;
298 	opts.principal_name = user;
299 	opts.principal_passwd = passwd;
300 
301 	(void) memset(&k5, 0, sizeof (k5));
302 
303 	if (k5_begin(&opts, &k5) != 0) {
304 		syslog(LOG_ERR, "NOT authenticated with Kerberos v5. "
305 		    "k5_begin failed\n");
306 		return (0);
307 	}
308 
309 	authed_k5 = k5_kinit(&opts, &k5);
310 	if (authed_k5)
311 		syslog(LOG_DEBUG, "Authenticated with Kerberos v5\n");
312 	else
313 		syslog(LOG_DEBUG, "NOT authenticated with Kerberos v5\n");
314 
315 	k5_end(&k5);
316 
317 	return (authed_k5);
318 }
319 
320 /*
321  * krb5_display_stat
322  * Display error message for GSS-API routines.
323  * Parameters:
324  *   maj       :  GSS major status
325  *   min       :  GSS minor status
326  *   caller_mod:  module name that calls this routine so that the module name
327  *                can be displayed with the error messages
328  * Returns:
329  *   None
330  */
331 static void
332 krb5_display_stat(OM_uint32 maj, OM_uint32 min, char *caller_mod)
333 {
334 	gss_buffer_desc msg;
335 	OM_uint32 msg_ctx = 0;
336 	OM_uint32 min2;
337 	(void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID,
338 	    &msg_ctx, &msg);
339 	syslog(LOG_ERR, "%s: major status error: %s\n",
340 	    caller_mod, (char *)msg.value);
341 	(void) gss_release_buffer(&min2, &msg);
342 
343 	(void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID,
344 	    &msg_ctx, &msg);
345 	syslog(LOG_ERR, "%s: minor status error: %s\n",
346 	    caller_mod, (char *)msg.value);
347 	(void) gss_release_buffer(&min2, &msg);
348 }
349 
350 /*
351  * krb5_acquire_cred_kinit
352  *
353  * Wrapper for krb5_acquire_cred_kinit_main with mutex to protect credential
354  * cache file when calling krb5_acquire_cred or kinit.
355  */
356 
357 int
358 krb5_acquire_cred_kinit(char *user, char *pwd, gss_cred_id_t *cred_handle,
359 	gss_OID *oid, int *kinit_retry, char *caller_mod)
360 {
361 	int ret;
362 
363 	ret = krb5_acquire_cred_kinit_main(user, pwd,
364 	    cred_handle, oid, kinit_retry, caller_mod);
365 	return (ret);
366 }
367 
368 /*
369  * krb5_acquire_cred_kinit_main
370  *
371  * This routine is called by ADS module to get a handle to administrative
372  * user's credential stored locally on the system.  The credential is the TGT.
373  * If the attempt at getting handle fails then a second attempt will be made
374  * after getting a new TGT.
375  *
376  * If there's no username then we must be using host credentials and we don't
377  * bother trying to acquire a credential for GSS_C_NO_NAME (which should be
378  * equivalent to using GSS_C_NO_CREDENTIAL, but it isn't in a very subtle way
379  * because mech_krb5 isn't so smart).  Specifically mech_krb5 will try hard
380  * to get a non-expired TGT using the keytab if we're running as root (or fake
381  * it, using the special app_krb5_user_uid() function), but only when we use
382  * the default credential, as opposed to a credential for the default principal.
383  *
384  * Paramters:
385  *   user       : username to retrieve a handle to its credential
386  *   pwd        : password of username in case obtaining a new TGT is needed
387  *   kinit_retry: if 0 then a second attempt will be made to get handle to the
388  *                credential if the first attempt fails
389  *   caller_mod : name of module that call this routine so that the module name
390  *                can be included with error messages
391  * Returns:
392  *   cred_handle: handle to the administrative user's credential (TGT)
393  *   oid        : contains Kerberos 5 object identifier
394  *   kinit_retry: A 1 indicates that a second attempt has been made to get
395  *                handle to the credential and no further attempts can be made
396  *   -1         : error
397  *    0         : success
398  */
399 static int
400 krb5_acquire_cred_kinit_main(char *user, char *pwd, gss_cred_id_t *cred_handle,
401 	gss_OID *oid, int *kinit_retry, char *caller_mod)
402 {
403 	OM_uint32 maj, min;
404 	gss_name_t desired_name = GSS_C_NO_NAME;
405 	gss_OID_set desired_mechs;
406 	gss_buffer_desc oidstr, name_buf;
407 	char str[50], user_name[50];
408 
409 	*cred_handle = GSS_C_NO_CREDENTIAL;
410 	*oid = GSS_C_NO_OID;
411 	if (user == NULL || *user == '\0')
412 		return (0);
413 
414 	/* Object Identifier for Kerberos 5 */
415 	(void) strcpy(str, "{ 1 2 840 113554 1 2 2 }");
416 	oidstr.value = str;
417 	oidstr.length = strlen(str);
418 	if ((maj = gss_str_to_oid(&min, &oidstr, oid)) != GSS_S_COMPLETE) {
419 		krb5_display_stat(maj, min, caller_mod);
420 		return (-1);
421 	}
422 	if ((maj = gss_create_empty_oid_set(&min, &desired_mechs))
423 	    != GSS_S_COMPLETE) {
424 		krb5_display_stat(maj, min, caller_mod);
425 		(void) gss_release_oid(&min, oid);
426 		return (-1);
427 	}
428 	if ((maj = gss_add_oid_set_member(&min, *oid, &desired_mechs))
429 	    != GSS_S_COMPLETE) {
430 		krb5_display_stat(maj, min, caller_mod);
431 		(void) gss_release_oid(&min, oid);
432 		(void) gss_release_oid_set(&min, &desired_mechs);
433 		return (-1);
434 	}
435 
436 	(void) strcpy(user_name, user);
437 	name_buf.value = user_name;
438 	name_buf.length = strlen(user_name)+1;
439 	if ((maj = gss_import_name(&min, &name_buf, GSS_C_NT_USER_NAME,
440 	    &desired_name)) != GSS_S_COMPLETE) {
441 		krb5_display_stat(maj, min, caller_mod);
442 		(void) gss_release_oid(&min, oid);
443 		(void) gss_release_oid_set(&min, &desired_mechs);
444 		return (-1);
445 	}
446 
447 acquire_cred:
448 	if ((maj = gss_acquire_cred(&min, desired_name, 0, desired_mechs,
449 	    GSS_C_INITIATE, cred_handle, NULL, NULL)) != GSS_S_COMPLETE) {
450 		if (!*kinit_retry && pwd != NULL && *pwd != '\0') {
451 			(void) smb_kinit(user, pwd);
452 			*kinit_retry = 1;
453 			goto acquire_cred;
454 		} else {
455 			krb5_display_stat(maj, min, caller_mod);
456 			(void) gss_release_oid(&min, oid);
457 			(void) gss_release_oid_set(&min, &desired_mechs);
458 			(void) gss_release_name(&min, &desired_name);
459 			if (pwd == NULL || *pwd == '\0') {
460 				/* See above */
461 				*cred_handle = GSS_C_NO_CREDENTIAL;
462 				return (0);
463 			}
464 			return (-1);
465 		}
466 	}
467 
468 	(void) gss_release_oid_set(&min, &desired_mechs);
469 	(void) gss_release_name(&min, &desired_name);
470 
471 	return (0);
472 }
473 
474 /*
475  * krb5_establish_sec_ctx_kinit
476  *
477  * This routine is called by the ADS module to establish a security
478  * context before ADS updates are allowed.  If establishing a security context
479  * fails for any reason, a second attempt will be made after a new TGT is
480  * obtained.  This routine is called many time as needed until a security
481  * context is established.
482  *
483  * The resources use for the security context must be released if security
484  * context establishment process fails.
485  * Parameters:
486  *   user       : user used in establishing a security context for.  Is used for
487  *                obtaining a new TGT for a second attempt at establishing
488  *                security context
489  *   pwd        : password of above user
490  *   cred_handle: a handle to the user credential (TGT) stored locally
491  *   gss_context: initially set to GSS_C_NO_CONTEXT but will contain a handle
492  *                to a security context
493  *   target_name: contains service name to establish a security context with,
494  *                ie ldap or dns
495  *   gss_flags  : flags used in establishing security context
496  *   inputptr   : initially set to GSS_C_NO_BUFFER but will be token data
497  *                received from service's server to be processed to generate
498  *                further token to be sent back to service's server during
499  *                security context establishment
500  *   kinit_retry: if 0 then a second attempt will be made to get handle to the
501  *                credential if the first attempt fails
502  *   caller_mod : name of module that call this routine so that the module name
503  *                can be included with error messages
504  * Returns:
505  *   gss_context    : a handle to a security context
506  *   out_tok        : token data to be sent to service's server to establish
507  *                    security context
508  *   ret_flags      : return flags
509  *   time_rec       : valid time for security context, not currently used
510  *   kinit_retry    : A 1 indicates that a second attempt has been made to get
511  *                    handle to the credential and no further attempts can be
512  *                    made
513  *   do_acquire_cred: A 1 indicates that a new handle to the local credential
514  *                    is needed for second attempt at security context
515  *                    establishment
516  *   maj            : major status code used if determining is security context
517  *                    establishment is successful
518  */
519 int
520 krb5_establish_sec_ctx_kinit(char *user, char *pwd,
521     gss_cred_id_t cred_handle, gss_ctx_id_t *gss_context,
522     gss_name_t target_name, gss_OID oid, int gss_flags,
523     gss_buffer_desc *inputptr, gss_buffer_desc* out_tok,
524     OM_uint32 *ret_flags, OM_uint32 *time_rec,
525     int *kinit_retry, int *do_acquire_cred,
526     OM_uint32 *maj, char *caller_mod)
527 {
528 	OM_uint32 min;
529 
530 	*maj = gss_init_sec_context(&min, cred_handle, gss_context,
531 	    target_name, oid, gss_flags, 0, NULL, inputptr, NULL,
532 	    out_tok, ret_flags, time_rec);
533 	if (*maj != GSS_S_COMPLETE && *maj != GSS_S_CONTINUE_NEEDED) {
534 		if (*gss_context != NULL)
535 			(void) gss_delete_sec_context(&min, gss_context, NULL);
536 
537 		if ((user != NULL) && (pwd != NULL) && !*kinit_retry) {
538 			(void) smb_kinit(user, pwd);
539 			*kinit_retry = 1;
540 			*do_acquire_cred = 1;
541 			return (-1);
542 		} else {
543 			krb5_display_stat(*maj, min, caller_mod);
544 			return (-1);
545 		}
546 	}
547 	return (0);
548 }
549 
550 /*
551  * smb_ccache_init
552  *
553  * Creates the directory where the Kerberos ccache file is located
554  * and set KRB5CCNAME in the environment.
555  *
556  * Returns 0 upon succcess.  Otherwise, returns
557  * -1 if it fails to create the specified directory fails.
558  * -2 if it fails to set the KRB5CCNAME environment variable.
559  */
560 int
561 smb_ccache_init(char *dir, char *filename)
562 {
563 	static char buf[MAXPATHLEN];
564 
565 	if ((mkdir(dir, 0700) < 0) && (errno != EEXIST))
566 		return (-1);
567 
568 	(void) snprintf(buf, MAXPATHLEN, "KRB5CCNAME=%s/%s", dir, filename);
569 	if (putenv(buf) != 0)
570 		return (-2);
571 	return (0);
572 }
573 
574 void
575 smb_ccache_remove(char *path)
576 {
577 	if ((remove(path) < 0) && (errno != ENOENT))
578 		syslog(LOG_ERR, "failed to remove ccache (%s)", path);
579 }
580