xref: /illumos-gate/usr/src/lib/smbsrv/libsmbns/common/smbns_krb.c (revision c9eab9d4e096bb9b983e9b007577edfa73c32eff)
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 2008 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, dgettext(TEXT_DOMAIN, "k5_kinit: "
182 			    "getting local addresses (%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, dgettext(TEXT_DOMAIN, "k5_kinit: "
195 			    "resolving keytab %s (%s)"), errmsg,
196 			    opts->keytab_name);
197 			goto cleanup;
198 		}
199 	}
200 
201 	switch (opts->action) {
202 	case INIT_PW:
203 		code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me,
204 		    opts->principal_passwd, NULL, 0, opts->starttime,
205 		    opts->service_name, &options);
206 		break;
207 	case INIT_KT:
208 		code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me,
209 		    keytab, opts->starttime, opts->service_name, &options);
210 		break;
211 	case VALIDATE:
212 		code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me,
213 		    k5->cc, opts->service_name);
214 		break;
215 	case RENEW:
216 		code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me,
217 		    k5->cc, opts->service_name);
218 		break;
219 	}
220 
221 	if (code) {
222 		char *doing = 0;
223 		switch (opts->action) {
224 		case INIT_PW:
225 		case INIT_KT:
226 			doing = dgettext(TEXT_DOMAIN, "k5_kinit: "
227 			    "getting initial credentials");
228 			break;
229 		case VALIDATE:
230 			doing = dgettext(TEXT_DOMAIN, "k5_kinit: "
231 			    "validating credentials");
232 			break;
233 		case RENEW:
234 			doing = dgettext(TEXT_DOMAIN, "k5_kinit: "
235 			    "renewing credentials");
236 			break;
237 		}
238 
239 		/*
240 		 * If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should
241 		 * let the user know that maybe he/she wants -4.
242 		 */
243 		if (code == KRB5KRB_AP_ERR_V4_REPLY) {
244 			syslog(LOG_ERR, "%s\n"
245 			    "The KDC doesn't support v5.  "
246 			    "You may want the -4 option in the future", doing);
247 			return (1);
248 		} else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
249 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s "
250 			    "(Password incorrect)"), doing);
251 		} else {
252 			errmsg = error_message(code);
253 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s (%s)"),
254 			    doing, errmsg);
255 		}
256 		goto cleanup;
257 	}
258 
259 	if (!opts->lifetime) {
260 		/* We need to figure out what lifetime to use for Kerberos 4. */
261 		opts->lifetime = my_creds.times.endtime -
262 		    my_creds.times.authtime;
263 	}
264 
265 	code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me);
266 	if (code) {
267 		errmsg = error_message(code);
268 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: "
269 		    "initializing cache %s (%s)"),
270 		    opts->k5_cache_name?opts->k5_cache_name:"", errmsg);
271 		goto cleanup;
272 	}
273 
274 	code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds);
275 	if (code) {
276 		errmsg = error_message(code);
277 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: "
278 		    "storing credentials (%s)"), errmsg);
279 		goto cleanup;
280 	}
281 
282 	notix = 0;
283 
284 	cleanup:
285 		if (my_creds.client == k5->me) {
286 			my_creds.client = 0;
287 		}
288 		krb5_free_cred_contents(k5->ctx, &my_creds);
289 		if (keytab)
290 			krb5_kt_close(k5->ctx, keytab);
291 		return (notix?0:1);
292 }
293 
294 int
295 smb_kinit(char *user, char *passwd)
296 {
297 	struct k_opts opts;
298 	struct k5_data k5;
299 	int authed_k5 = 0;
300 
301 	assert(user);
302 	assert(passwd);
303 
304 	(void) memset(&opts, 0, sizeof (opts));
305 	opts.action = INIT_PW;
306 	opts.principal_name = user;
307 	opts.principal_passwd = passwd;
308 
309 	(void) memset(&k5, 0, sizeof (k5));
310 
311 	if (k5_begin(&opts, &k5) != 0) {
312 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "smb_kinit: "
313 		    "NOT Authenticated to Kerberos v5  k5_begin failed\n"));
314 		return (0);
315 	}
316 
317 	authed_k5 = k5_kinit(&opts, &k5);
318 	if (authed_k5) {
319 		syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: "
320 		    "Authenticated to Kerberos v5\n"));
321 	} else {
322 		syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: "
323 		    "NOT Authenticated to Kerberos v5\n"));
324 	}
325 
326 	k5_end(&k5);
327 
328 	return (authed_k5);
329 }
330 
331 /*
332  * krb5_display_stat
333  * Display error message for GSS-API routines.
334  * Parameters:
335  *   maj       :  GSS major status
336  *   min       :  GSS minor status
337  *   caller_mod:  module name that calls this routine so that the module name
338  *                can be displayed with the error messages
339  * Returns:
340  *   None
341  */
342 static void
343 krb5_display_stat(OM_uint32 maj, OM_uint32 min, char *caller_mod)
344 {
345 	gss_buffer_desc msg;
346 	OM_uint32 msg_ctx = 0;
347 	OM_uint32 min2;
348 	(void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID,
349 	    &msg_ctx, &msg);
350 	syslog(LOG_ERR, "%s: major status error: %s\n",
351 	    caller_mod, (char *)msg.value);
352 	(void) gss_release_buffer(&min2, &msg);
353 
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 	(void) gss_release_buffer(&min2, &msg);
359 }
360 
361 /*
362  * krb5_acquire_cred_kinit
363  *
364  * Wrapper for krb5_acquire_cred_kinit_main with mutex to protect credential
365  * cache file when calling krb5_acquire_cred or kinit.
366  */
367 
368 int
369 krb5_acquire_cred_kinit(char *user, char *pwd, gss_cred_id_t *cred_handle,
370 	gss_OID *oid, int *kinit_retry, char *caller_mod)
371 {
372 	int ret;
373 
374 	ret = krb5_acquire_cred_kinit_main(user, pwd,
375 	    cred_handle, oid, kinit_retry, caller_mod);
376 	return (ret);
377 }
378 
379 /*
380  * krb5_acquire_cred_kinit_main
381  *
382  * This routine is called by ADS module to get a handle to administrative
383  * user's credential stored locally on the system.  The credential is the TGT.
384  * If the attempt at getting handle fails then a second attempt will be made
385  * after getting a new TGT.
386  *
387  * If there's no username then we must be using host credentials and we don't
388  * bother trying to acquire a credential for GSS_C_NO_NAME (which should be
389  * equivalent to using GSS_C_NO_CREDENTIAL, but it isn't in a very subtle way
390  * because mech_krb5 isn't so smart).  Specifically mech_krb5 will try hard
391  * to get a non-expired TGT using the keytab if we're running as root (or fake
392  * it, using the special app_krb5_user_uid() function), but only when we use
393  * the default credential, as opposed to a credential for the default principal.
394  *
395  * Paramters:
396  *   user       : username to retrieve a handle to its credential
397  *   pwd        : password of username in case obtaining a new TGT is needed
398  *   kinit_retry: if 0 then a second attempt will be made to get handle to the
399  *                credential if the first attempt fails
400  *   caller_mod : name of module that call this routine so that the module name
401  *                can be included with error messages
402  * Returns:
403  *   cred_handle: handle to the administrative user's credential (TGT)
404  *   oid        : contains Kerberos 5 object identifier
405  *   kinit_retry: A 1 indicates that a second attempt has been made to get
406  *                handle to the credential and no further attempts can be made
407  *   -1         : error
408  *    0         : success
409  */
410 static int
411 krb5_acquire_cred_kinit_main(char *user, char *pwd, gss_cred_id_t *cred_handle,
412 	gss_OID *oid, int *kinit_retry, char *caller_mod)
413 {
414 	OM_uint32 maj, min;
415 	gss_name_t desired_name = GSS_C_NO_NAME;
416 	gss_OID_set desired_mechs;
417 	gss_buffer_desc oidstr, name_buf;
418 	char str[50], user_name[50];
419 
420 	*cred_handle = GSS_C_NO_CREDENTIAL;
421 	*oid = GSS_C_NO_OID;
422 	if (user == NULL || *user == '\0')
423 		return (0);
424 
425 	/* Object Identifier for Kerberos 5 */
426 	(void) strcpy(str, "{ 1 2 840 113554 1 2 2 }");
427 	oidstr.value = str;
428 	oidstr.length = strlen(str);
429 	if ((maj = gss_str_to_oid(&min, &oidstr, oid)) != GSS_S_COMPLETE) {
430 		krb5_display_stat(maj, min, caller_mod);
431 		return (-1);
432 	}
433 	if ((maj = gss_create_empty_oid_set(&min, &desired_mechs))
434 	    != GSS_S_COMPLETE) {
435 		krb5_display_stat(maj, min, caller_mod);
436 		(void) gss_release_oid(&min, oid);
437 		return (-1);
438 	}
439 	if ((maj = gss_add_oid_set_member(&min, *oid, &desired_mechs))
440 	    != 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 	(void) strcpy(user_name, user);
448 	name_buf.value = user_name;
449 	name_buf.length = strlen(user_name)+1;
450 	if ((maj = gss_import_name(&min, &name_buf, GSS_C_NT_USER_NAME,
451 	    &desired_name)) != GSS_S_COMPLETE) {
452 		krb5_display_stat(maj, min, caller_mod);
453 		(void) gss_release_oid(&min, oid);
454 		(void) gss_release_oid_set(&min, &desired_mechs);
455 		return (-1);
456 	}
457 
458 acquire_cred:
459 	if ((maj = gss_acquire_cred(&min, desired_name, 0, desired_mechs,
460 	    GSS_C_INITIATE, cred_handle, NULL, NULL)) != GSS_S_COMPLETE) {
461 		if (!*kinit_retry && pwd != NULL && *pwd != '\0') {
462 			syslog(LOG_ERR, "%s: Retry kinit to "
463 			    "acquire credential.\n", caller_mod);
464 			(void) smb_kinit(user, pwd);
465 			*kinit_retry = 1;
466 			goto acquire_cred;
467 		} else {
468 			krb5_display_stat(maj, min, caller_mod);
469 			(void) gss_release_oid(&min, oid);
470 			(void) gss_release_oid_set(&min, &desired_mechs);
471 			(void) gss_release_name(&min, &desired_name);
472 			if (pwd == NULL || *pwd == '\0') {
473 				/* See above */
474 				*cred_handle = GSS_C_NO_CREDENTIAL;
475 				return (0);
476 			}
477 			return (-1);
478 		}
479 	}
480 
481 	(void) gss_release_oid_set(&min, &desired_mechs);
482 	(void) gss_release_name(&min, &desired_name);
483 
484 	return (0);
485 }
486 
487 /*
488  * krb5_establish_sec_ctx_kinit
489  *
490  * This routine is called by the ADS module to establish a security
491  * context before ADS updates are allowed.  If establishing a security context
492  * fails for any reason, a second attempt will be made after a new TGT is
493  * obtained.  This routine is called many time as needed until a security
494  * context is established.
495  *
496  * The resources use for the security context must be released if security
497  * context establishment process fails.
498  * Parameters:
499  *   user       : user used in establishing a security context for.  Is used for
500  *                obtaining a new TGT for a second attempt at establishing
501  *                security context
502  *   pwd        : password of above user
503  *   cred_handle: a handle to the user credential (TGT) stored locally
504  *   gss_context: initially set to GSS_C_NO_CONTEXT but will contain a handle
505  *                to a security context
506  *   target_name: contains service name to establish a security context with,
507  *                ie ldap or dns
508  *   gss_flags  : flags used in establishing security context
509  *   inputptr   : initially set to GSS_C_NO_BUFFER but will be token data
510  *                received from service's server to be processed to generate
511  *                further token to be sent back to service's server during
512  *                security context establishment
513  *   kinit_retry: if 0 then a second attempt will be made to get handle to the
514  *                credential if the first attempt fails
515  *   caller_mod : name of module that call this routine so that the module name
516  *                can be included with error messages
517  * Returns:
518  *   gss_context    : a handle to a security context
519  *   out_tok        : token data to be sent to service's server to establish
520  *                    security context
521  *   ret_flags      : return flags
522  *   time_rec       : valid time for security context, not currently used
523  *   kinit_retry    : A 1 indicates that a second attempt has been made to get
524  *                    handle to the credential and no further attempts can be
525  *                    made
526  *   do_acquire_cred: A 1 indicates that a new handle to the local credential
527  *                    is needed for second attempt at security context
528  *                    establishment
529  *   maj            : major status code used if determining is security context
530  *                    establishment is successful
531  */
532 int
533 krb5_establish_sec_ctx_kinit(char *user, char *pwd,
534     gss_cred_id_t cred_handle, gss_ctx_id_t *gss_context,
535     gss_name_t target_name, gss_OID oid, int gss_flags,
536     gss_buffer_desc *inputptr, gss_buffer_desc* out_tok,
537     OM_uint32 *ret_flags, OM_uint32 *time_rec,
538     int *kinit_retry, int *do_acquire_cred,
539     OM_uint32 *maj, char *caller_mod)
540 {
541 	OM_uint32 min;
542 
543 	*maj = gss_init_sec_context(&min, cred_handle, gss_context,
544 	    target_name, oid, gss_flags, 0, NULL, inputptr, NULL,
545 	    out_tok, ret_flags, time_rec);
546 	if (*maj != GSS_S_COMPLETE && *maj != GSS_S_CONTINUE_NEEDED) {
547 		if (*gss_context != NULL)
548 			(void) gss_delete_sec_context(&min, gss_context, NULL);
549 
550 		if ((user != NULL) && (pwd != NULL) && !*kinit_retry) {
551 			syslog(LOG_ERR, "%s: Retry kinit to establish "
552 			    "security context.\n", caller_mod);
553 			(void) smb_kinit(user, pwd);
554 			*kinit_retry = 1;
555 			*do_acquire_cred = 1;
556 			return (-1);
557 		} else {
558 			krb5_display_stat(*maj, min, caller_mod);
559 			return (-1);
560 		}
561 	}
562 	return (0);
563 }
564 
565 /*
566  * smb_ccache_init
567  *
568  * Creates the directory where the Kerberos ccache file is located
569  * and set KRB5CCNAME in the environment.
570  *
571  * Returns 0 upon succcess.  Otherwise, returns
572  * -1 if it fails to create the specified directory fails.
573  * -2 if it fails to set the KRB5CCNAME environment variable.
574  */
575 int
576 smb_ccache_init(char *dir, char *filename)
577 {
578 	static char buf[MAXPATHLEN];
579 
580 	if ((mkdir(dir, 0700) < 0) && (errno != EEXIST))
581 		return (-1);
582 
583 	(void) snprintf(buf, MAXPATHLEN, "KRB5CCNAME=%s/%s", dir, filename);
584 	if (putenv(buf) != 0)
585 		return (-2);
586 	return (0);
587 }
588 
589 void
590 smb_ccache_remove(char *path)
591 {
592 	if ((remove(path) < 0) && (errno != ENOENT))
593 		syslog(LOG_ERR, "failed to remove ccache (%s)", path);
594 }
595