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 #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 <errno.h>
65 #include <sys/synch.h>
66 #include <gssapi/gssapi.h>
67 
68 #include <smbsrv/libsmbns.h>
69 
70 #include <smbns_krb.h>
71 
72 static int krb5_acquire_cred_kinit_main();
73 
74 typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type;
75 
76 struct k_opts {
77 	/* in seconds */
78 	krb5_deltat starttime;
79 	krb5_deltat lifetime;
80 	krb5_deltat rlife;
81 
82 	int forwardable;
83 	int proxiable;
84 	int addresses;
85 
86 	int not_forwardable;
87 	int not_proxiable;
88 	int no_addresses;
89 
90 	int verbose;
91 
92 	char *principal_name;
93 	char *principal_passwd;
94 	char *service_name;
95 	char *keytab_name;
96 	char *k5_cache_name;
97 	char *k4_cache_name;
98 
99 	action_type action;
100 };
101 
102 struct k5_data {
103 	krb5_context ctx;
104 	krb5_ccache cc;
105 	krb5_principal me;
106 	char *name;
107 };
108 
109 static int
110 k5_begin(struct k_opts *opts, struct k5_data *k5)
111 {
112 	int code;
113 	code = krb5_init_context(&k5->ctx);
114 	if (code) {
115 		return (code);
116 	}
117 
118 	if ((code = krb5_cc_default(k5->ctx, &k5->cc))) {
119 		return (code);
120 	}
121 
122 	/* Use specified name */
123 	if ((code = krb5_parse_name(k5->ctx, opts->principal_name, &k5->me))) {
124 		return (code);
125 	}
126 
127 	code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
128 	if (code) {
129 		return (code);
130 	}
131 	opts->principal_name = k5->name;
132 
133 	return (0);
134 }
135 
136 static void
137 k5_end(struct k5_data *k5)
138 {
139 	if (k5->name)
140 		krb5_free_unparsed_name(k5->ctx, k5->name);
141 	if (k5->me)
142 		krb5_free_principal(k5->ctx, k5->me);
143 	if (k5->cc)
144 		krb5_cc_close(k5->ctx, k5->cc);
145 	if (k5->ctx)
146 		krb5_free_context(k5->ctx);
147 	(void) memset(k5, 0, sizeof (*k5));
148 }
149 
150 static int
151 k5_kinit(struct k_opts *opts, struct k5_data *k5)
152 {
153 	int notix = 1;
154 	krb5_keytab keytab = 0;
155 	krb5_creds my_creds;
156 	krb5_error_code code = 0;
157 	krb5_get_init_creds_opt options;
158 	const char *errmsg;
159 
160 	krb5_get_init_creds_opt_init(&options);
161 	(void) memset(&my_creds, 0, sizeof (my_creds));
162 
163 	/*
164 	 * From this point on, we can goto cleanup because my_creds is
165 	 * initialized.
166 	 */
167 	if (opts->lifetime)
168 		krb5_get_init_creds_opt_set_tkt_life(&options, opts->lifetime);
169 	if (opts->rlife)
170 		krb5_get_init_creds_opt_set_renew_life(&options, opts->rlife);
171 	if (opts->forwardable)
172 		krb5_get_init_creds_opt_set_forwardable(&options, 1);
173 	if (opts->not_forwardable)
174 		krb5_get_init_creds_opt_set_forwardable(&options, 0);
175 	if (opts->proxiable)
176 		krb5_get_init_creds_opt_set_proxiable(&options, 1);
177 	if (opts->not_proxiable)
178 		krb5_get_init_creds_opt_set_proxiable(&options, 0);
179 	if (opts->addresses) {
180 		krb5_address **addresses = NULL;
181 		code = krb5_os_localaddr(k5->ctx, &addresses);
182 		if (code != 0) {
183 			errmsg = error_message(code);
184 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: "
185 			    "getting local addresses (%s)"), errmsg);
186 			goto cleanup;
187 		}
188 		krb5_get_init_creds_opt_set_address_list(&options, addresses);
189 	}
190 	if (opts->no_addresses)
191 		krb5_get_init_creds_opt_set_address_list(&options, NULL);
192 
193 	if ((opts->action == INIT_KT) && opts->keytab_name) {
194 		code = krb5_kt_resolve(k5->ctx, opts->keytab_name, &keytab);
195 		if (code != 0) {
196 			errmsg = error_message(code);
197 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: "
198 			    "resolving keytab %s (%s)"), errmsg,
199 			    opts->keytab_name);
200 			goto cleanup;
201 		}
202 	}
203 
204 	switch (opts->action) {
205 	case INIT_PW:
206 		code = krb5_get_init_creds_password(k5->ctx, &my_creds, k5->me,
207 		    opts->principal_passwd, NULL, 0, opts->starttime,
208 		    opts->service_name, &options);
209 		break;
210 	case INIT_KT:
211 		code = krb5_get_init_creds_keytab(k5->ctx, &my_creds, k5->me,
212 		    keytab, opts->starttime, opts->service_name, &options);
213 		break;
214 	case VALIDATE:
215 		code = krb5_get_validated_creds(k5->ctx, &my_creds, k5->me,
216 		    k5->cc, opts->service_name);
217 		break;
218 	case RENEW:
219 		code = krb5_get_renewed_creds(k5->ctx, &my_creds, k5->me,
220 		    k5->cc, opts->service_name);
221 		break;
222 	}
223 
224 	if (code) {
225 		char *doing = 0;
226 		switch (opts->action) {
227 		case INIT_PW:
228 		case INIT_KT:
229 			doing = dgettext(TEXT_DOMAIN, "k5_kinit: "
230 			    "getting initial credentials");
231 			break;
232 		case VALIDATE:
233 			doing = dgettext(TEXT_DOMAIN, "k5_kinit: "
234 			    "validating credentials");
235 			break;
236 		case RENEW:
237 			doing = dgettext(TEXT_DOMAIN, "k5_kinit: "
238 			    "renewing credentials");
239 			break;
240 		}
241 
242 		/*
243 		 * If got code == KRB5_AP_ERR_V4_REPLY && got_k4, we should
244 		 * let the user know that maybe he/she wants -4.
245 		 */
246 		if (code == KRB5KRB_AP_ERR_V4_REPLY) {
247 			syslog(LOG_ERR, "%s\n"
248 			    "The KDC doesn't support v5.  "
249 			    "You may want the -4 option in the future", doing);
250 			return (1);
251 		} else if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
252 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s "
253 			    "(Password incorrect)"), doing);
254 		} else {
255 			errmsg = error_message(code);
256 			syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "%s (%s)"),
257 			    doing, errmsg);
258 		}
259 		goto cleanup;
260 	}
261 
262 	if (!opts->lifetime) {
263 		/* We need to figure out what lifetime to use for Kerberos 4. */
264 		opts->lifetime = my_creds.times.endtime -
265 		    my_creds.times.authtime;
266 	}
267 
268 	code = krb5_cc_initialize(k5->ctx, k5->cc, k5->me);
269 	if (code) {
270 		errmsg = error_message(code);
271 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: "
272 		    "initializing cache %s (%s)"),
273 		    opts->k5_cache_name?opts->k5_cache_name:"", errmsg);
274 		goto cleanup;
275 	}
276 
277 	code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds);
278 	if (code) {
279 		errmsg = error_message(code);
280 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "k5_kinit: "
281 		    "storing credentials (%s)"), errmsg);
282 		goto cleanup;
283 	}
284 
285 	notix = 0;
286 
287 	cleanup:
288 		if (my_creds.client == k5->me) {
289 			my_creds.client = 0;
290 		}
291 		krb5_free_cred_contents(k5->ctx, &my_creds);
292 		if (keytab)
293 			krb5_kt_close(k5->ctx, keytab);
294 		return (notix?0:1);
295 }
296 
297 int
298 smb_kinit(char *user, char *passwd)
299 {
300 	struct k_opts opts;
301 	struct k5_data k5;
302 	int authed_k5 = 0;
303 
304 	assert(user);
305 	assert(passwd);
306 
307 	(void) memset(&opts, 0, sizeof (opts));
308 	opts.action = INIT_PW;
309 	opts.principal_name = user;
310 	opts.principal_passwd = passwd;
311 
312 	(void) memset(&k5, 0, sizeof (k5));
313 
314 	if (k5_begin(&opts, &k5) != 0) {
315 		syslog(LOG_ERR, dgettext(TEXT_DOMAIN, "smb_kinit: "
316 		    "NOT Authenticated to Kerberos v5  k5_begin failed\n"));
317 		return (0);
318 	}
319 
320 	authed_k5 = k5_kinit(&opts, &k5);
321 	if (authed_k5) {
322 		syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: "
323 		    "Authenticated to Kerberos v5\n"));
324 	} else {
325 		syslog(LOG_DEBUG, dgettext(TEXT_DOMAIN, "smb_kinit: "
326 		    "NOT Authenticated to Kerberos v5\n"));
327 	}
328 
329 	k5_end(&k5);
330 
331 	return (authed_k5);
332 }
333 
334 /*
335  * krb5_display_stat
336  * Display error message for GSS-API routines.
337  * Parameters:
338  *   maj       :  GSS major status
339  *   min       :  GSS minor status
340  *   caller_mod:  module name that calls this routine so that the module name
341  *                can be displayed with the error messages
342  * Returns:
343  *   None
344  */
345 static void
346 krb5_display_stat(OM_uint32 maj, OM_uint32 min, char *caller_mod)
347 {
348 	gss_buffer_desc msg;
349 	OM_uint32 msg_ctx = 0;
350 	OM_uint32 min2;
351 	(void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID,
352 	    &msg_ctx, &msg);
353 	syslog(LOG_ERR, "%s: major status error: %s\n",
354 	    caller_mod, (char *)msg.value);
355 	(void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID,
356 	    &msg_ctx, &msg);
357 	syslog(LOG_ERR, "%s: minor status error: %s\n",
358 	    caller_mod, (char *)msg.value);
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