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