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