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 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <strings.h>
33 #include <unistd.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <syslog.h>
37 #include <netdb.h>
38 #include <sys/param.h>
39 #include <kerberosv5/krb5.h>
40 #include <kerberosv5/com_err.h>
41 
42 #include <smbsrv/libsmb.h>
43 #include <smbns_krb.h>
44 
45 static char *spn_prefix[] = {"host/", "nfs/", "HTTP/", "root/"};
46 
47 static int smb_krb5_open_wrfile(krb5_context ctx, char *fname,
48     krb5_keytab *kt);
49 static int smb_krb5_ktadd(krb5_context ctx, krb5_keytab kt,
50     const krb5_principal princ, krb5_enctype enctype, krb5_kvno kvno,
51     const char *pw);
52 static krb5_error_code smb_krb5_ktremove(krb5_context ctx, krb5_keytab kt,
53     const krb5_principal princ);
54 
55 /*
56  * smb_krb5_get_spn
57  *
58  * Gets Service Principal Name.
59  * Caller must free the memory allocated for the spn.
60  */
61 char *
62 smb_krb5_get_spn(smb_krb5_spn_idx_t idx, char *fqhost)
63 {
64 	int len;
65 	char *princ;
66 	char *spn;
67 
68 	if (!fqhost)
69 		return (NULL);
70 
71 	if ((idx < 0) || (idx >= SMBKRB5_SPN_IDX_MAX))
72 		return (NULL);
73 
74 	spn = spn_prefix[idx];
75 	len = strlen(spn) + strlen(fqhost) + 1;
76 	princ = (char *)malloc(len);
77 
78 	if (!princ)
79 		return (NULL);
80 
81 	(void) snprintf(princ, len, "%s%s", spn, fqhost);
82 	return (princ);
83 }
84 
85 /*
86  * smb_krb5_get_upn
87  *
88  * Gets User Principal Name.
89  * Caller must free the memory allocated for the upn.
90  */
91 char *
92 smb_krb5_get_upn(char *spn, char *domain)
93 {
94 	int len;
95 	char *realm;
96 	char *upn;
97 
98 	if (!spn || !domain)
99 		return (NULL);
100 
101 	realm = strdup(domain);
102 	if (!realm)
103 		return (NULL);
104 
105 	(void) utf8_strupr(realm);
106 
107 	len = strlen(spn) + 1 + strlen(realm) + 1;
108 	upn = (char *)malloc(len);
109 	if (!upn) {
110 		free(realm);
111 		return (NULL);
112 	}
113 
114 	(void) snprintf(upn, len, "%s@%s", spn, realm);
115 	free(realm);
116 
117 	return (upn);
118 }
119 
120 /*
121  * smb_krb5_get_host_upn
122  *
123  * Derives UPN by the given fully-qualified hostname.
124  * Caller must free the memory allocated for the upn.
125  */
126 static char *
127 smb_krb5_get_host_upn(const char *fqhn)
128 {
129 	char *upn;
130 	char *realm;
131 	char *dom;
132 	int len;
133 
134 	if ((dom = strchr(fqhn, '.')) == NULL)
135 		return (NULL);
136 
137 	if ((realm = strdup(++dom)) == NULL)
138 		return (NULL);
139 
140 	(void) utf8_strupr(realm);
141 
142 	len = strlen(spn_prefix[SMBKRB5_SPN_IDX_HOST]) + strlen(fqhn) +
143 	    + 1 + strlen(realm) + 1;
144 	if ((upn = malloc(len)) == NULL) {
145 		free(realm);
146 		return (NULL);
147 	}
148 
149 	(void) snprintf(upn, len, "%s%s@%s", spn_prefix[SMBKRB5_SPN_IDX_HOST],
150 	    fqhn, realm);
151 
152 	free(realm);
153 	return (upn);
154 }
155 
156 /*
157  * smb_krb5_ctx_init
158  *
159  * Initialize the kerberos context.
160  * Return 0 on success. Otherwise, return -1.
161  */
162 int
163 smb_krb5_ctx_init(krb5_context *ctx)
164 {
165 	if (krb5_init_context(ctx) != 0)
166 		return (-1);
167 
168 	return (0);
169 }
170 
171 /*
172  * smb_krb5_get_principals
173  *
174  * Setup the krb5_principal array given the principals in string format.
175  * Return 0 on success. Otherwise, return -1.
176  */
177 int
178 smb_krb5_get_principals(char *domain, krb5_context ctx,
179     krb5_principal *krb5princs)
180 {
181 	char fqhn[MAXHOSTNAMELEN];
182 	int i;
183 	char *spn, *upn;
184 
185 	if (smb_gethostname(fqhn, MAXHOSTNAMELEN, 0) != 0)
186 			return (-1);
187 
188 	(void) snprintf(fqhn, MAXHOSTNAMELEN, "%s.%s", fqhn,
189 	    domain);
190 
191 	for (i = 0; i < SMBKRB5_SPN_IDX_MAX; i++) {
192 
193 		if ((spn = smb_krb5_get_spn(i, fqhn)) == NULL) {
194 			return (-1);
195 		}
196 
197 		upn = smb_krb5_get_upn(spn, domain);
198 		free(spn);
199 
200 		if (krb5_parse_name(ctx, upn, &krb5princs[i]) != 0) {
201 			smb_krb5_free_principals(ctx, krb5princs, i - 1);
202 			free(upn);
203 			return (-1);
204 		}
205 		free(upn);
206 	}
207 	return (0);
208 }
209 
210 void
211 smb_krb5_free_principals(krb5_context ctx, krb5_principal *krb5princs,
212     size_t num)
213 {
214 	int i;
215 
216 	for (i = 0; i < num; i++)
217 		krb5_free_principal(ctx, krb5princs[i]);
218 }
219 
220 /*
221  * smb_krb5_ctx_fini
222  *
223  * Free the kerberos context.
224  */
225 void
226 smb_krb5_ctx_fini(krb5_context ctx)
227 {
228 	krb5_free_context(ctx);
229 }
230 
231 /*
232  * smb_ksetpw
233  *
234  * Set the workstation trust account password.
235  * Returns 0 on success.  Otherwise, returns non-zero value.
236  */
237 int
238 smb_krb5_setpwd(krb5_context ctx, krb5_principal princ, char *passwd)
239 {
240 	krb5_error_code code;
241 	krb5_ccache cc = NULL;
242 	int result_code;
243 	krb5_data result_code_string, result_string;
244 
245 	(void) memset(&result_code_string, 0, sizeof (result_code_string));
246 	(void) memset(&result_string, 0, sizeof (result_string));
247 
248 	if ((code = krb5_cc_default(ctx, &cc)) != 0) {
249 		syslog(LOG_ERR, "smb_krb5_setpwd: failed to find a ccache\n");
250 		return (-1);
251 	}
252 
253 	code = krb5_set_password_using_ccache(ctx, cc, passwd, princ,
254 	    &result_code, &result_code_string, &result_string);
255 
256 	krb5_cc_close(ctx, cc);
257 
258 	if (code != 0)
259 		(void) syslog(LOG_ERR,
260 		    "smb_krb5_setpwd: Result: %.*s (%d) %.*s\n",
261 		    result_code == 0 ?
262 		    strlen("success") : result_code_string.length,
263 		    result_code == 0 ? "success" : result_code_string.data,
264 		    result_code, result_string.length, result_string.data);
265 
266 	free(result_code_string.data);
267 	free(result_string.data);
268 	return (code);
269 }
270 
271 /*
272  * smb_krb5_open_wrfile
273  *
274  * Open the keytab file for writing.
275  * The keytab should be closed by calling krb5_kt_close().
276  */
277 static int
278 smb_krb5_open_wrfile(krb5_context ctx, char *fname, krb5_keytab *kt)
279 {
280 	char *ktname;
281 	int len;
282 
283 	*kt = NULL;
284 	len = snprintf(NULL, 0, "WRFILE:%s", fname) + 1;
285 	if ((ktname = malloc(len)) == NULL) {
286 		syslog(LOG_ERR, "smb_krb5_write_keytab: resource shortage");
287 		return (-1);
288 	}
289 
290 	(void) snprintf(ktname, len, "WRFILE:%s", fname);
291 
292 	if (krb5_kt_resolve(ctx, ktname, kt) != 0) {
293 		syslog(LOG_ERR, "smb_krb5_write_keytab: failed to open/create "
294 		    "keytab %s\n", fname);
295 		free(ktname);
296 		return (-1);
297 	}
298 
299 	free(ktname);
300 	return (0);
301 }
302 
303 /*
304  * smb_krb5_remove_keytab_entries
305  *
306  * Remove the keys from the keytab for the specified principal.
307  */
308 int
309 smb_krb5_remove_keytab_entries(krb5_context ctx, krb5_principal *princs,
310     char *fname)
311 {
312 	krb5_keytab kt = NULL;
313 	int rc = 0, i;
314 	krb5_error_code code;
315 
316 	if (smb_krb5_open_wrfile(ctx, fname, &kt) != 0)
317 		return (-1);
318 
319 	for (i = 0; i < SMBKRB5_SPN_IDX_MAX; i++) {
320 		if ((code = smb_krb5_ktremove(ctx, kt, princs[i])) != 0) {
321 			syslog(LOG_ERR, "smb_krb5_remove_keytab_entries: %s",
322 			    error_message(code));
323 			rc = -1;
324 			break;
325 		}
326 	}
327 
328 	krb5_kt_close(ctx, kt);
329 	return (rc);
330 }
331 
332 /*
333  * smb_krb5_update_keytab_entries
334  *
335  * Update the keys for the specified principal in the keytab.
336  * Returns 0 on success.  Otherwise, returns -1.
337  */
338 int
339 smb_krb5_update_keytab_entries(krb5_context ctx, krb5_principal *princs,
340     char *fname, krb5_kvno kvno, char *passwd, krb5_enctype *enctypes,
341     int enctype_count)
342 {
343 	krb5_keytab kt = NULL;
344 	int i, j;
345 	krb5_error_code code;
346 
347 	if (smb_krb5_open_wrfile(ctx, fname, &kt) != 0)
348 		return (-1);
349 
350 	for (j = 0; j < SMBKRB5_SPN_IDX_MAX; j++) {
351 		if ((code = smb_krb5_ktremove(ctx, kt, princs[j])) != 0) {
352 			syslog(LOG_ERR, "smb_krb5_update_keytab_entries: %s",
353 			    error_message(code));
354 			krb5_kt_close(ctx, kt);
355 			return (-1);
356 		}
357 
358 		for (i = 0; i < enctype_count; i++) {
359 			if (smb_krb5_ktadd(ctx, kt, princs[j], enctypes[i],
360 			    kvno, passwd) != 0) {
361 				krb5_kt_close(ctx, kt);
362 				return (-1);
363 			}
364 		}
365 
366 	}
367 	krb5_kt_close(ctx, kt);
368 	return (0);
369 }
370 
371 boolean_t
372 smb_krb5_find_keytab_entries(const char *fqhn, char *fname)
373 {
374 	krb5_context ctx;
375 	krb5_keytab kt;
376 	krb5_keytab_entry entry;
377 	krb5_principal princ;
378 	char ktname[MAXPATHLEN];
379 	char *upn;
380 	boolean_t found = B_FALSE;
381 
382 	if (!fqhn || !fname)
383 		return (found);
384 
385 	if ((upn = smb_krb5_get_host_upn((char *)fqhn)) == NULL)
386 		return (found);
387 
388 	if (smb_krb5_ctx_init(&ctx) != 0) {
389 		free(upn);
390 		return (found);
391 	}
392 
393 	if (krb5_parse_name(ctx, upn, &princ) != 0) {
394 		free(upn);
395 		smb_krb5_ctx_fini(ctx);
396 		return (found);
397 	}
398 
399 	free(upn);
400 	(void) snprintf(ktname, MAXPATHLEN, "FILE:%s", fname);
401 	if (krb5_kt_resolve(ctx, ktname, &kt) == 0) {
402 		if (krb5_kt_get_entry(ctx, kt, princ, 0, 0, &entry) == 0) {
403 			found = B_TRUE;
404 			krb5_kt_free_entry(ctx, &entry);
405 		}
406 
407 		krb5_kt_close(ctx, kt);
408 	}
409 
410 	krb5_free_principal(ctx, princ);
411 	smb_krb5_ctx_fini(ctx);
412 	return (found);
413 }
414 
415 /*
416  * smb_krb5_ktremove
417  *
418  * Removes the old entries for the specified principal from the keytab.
419  *
420  * Returns 0 upon success. Otherwise, returns KRB5 error code.
421  */
422 static krb5_error_code
423 smb_krb5_ktremove(krb5_context ctx, krb5_keytab kt, const krb5_principal princ)
424 {
425 	krb5_keytab_entry entry;
426 	krb5_kt_cursor cursor;
427 	int code;
428 
429 	code = krb5_kt_get_entry(ctx, kt, princ, 0, 0, &entry);
430 	if (code != 0) {
431 		if (code == ENOENT || code == KRB5_KT_NOTFOUND)
432 			return (0);
433 
434 		return (code);
435 	}
436 
437 	krb5_kt_free_entry(ctx, &entry);
438 
439 	if ((code = krb5_kt_start_seq_get(ctx, kt, &cursor)) != 0)
440 		return (code);
441 
442 	while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) {
443 		if (krb5_principal_compare(ctx, princ, entry.principal)) {
444 
445 			code = krb5_kt_end_seq_get(ctx, kt, &cursor);
446 			if (code != 0) {
447 				krb5_kt_free_entry(ctx, &entry);
448 				return (code);
449 			}
450 
451 			code = krb5_kt_remove_entry(ctx, kt, &entry);
452 			if (code != 0) {
453 				krb5_kt_free_entry(ctx, &entry);
454 				return (code);
455 			}
456 
457 			code = krb5_kt_start_seq_get(ctx, kt, &cursor);
458 			if (code != 0) {
459 				krb5_kt_free_entry(ctx, &entry);
460 				return (code);
461 			}
462 
463 		}
464 		krb5_kt_free_entry(ctx, &entry);
465 	}
466 
467 	if (code && code != KRB5_KT_END) {
468 		(void) krb5_kt_end_seq_get(ctx, kt, &cursor);
469 		return (code);
470 	}
471 
472 	if ((code = krb5_kt_end_seq_get(ctx, kt, &cursor)))
473 		return (code);
474 
475 	return (0);
476 }
477 
478 /*
479  * smb_krb5_ktadd
480  *
481  * Add a Keberos key to the keytab file.
482  * Returns 0 on success. Otherwise, returns -1.
483  */
484 static int
485 smb_krb5_ktadd(krb5_context ctx, krb5_keytab kt, const krb5_principal princ,
486 	krb5_enctype enctype, krb5_kvno kvno, const char *pw)
487 {
488 	krb5_keytab_entry *entry;
489 	krb5_data password, salt;
490 	krb5_keyblock key;
491 	krb5_error_code code;
492 	char buf[100];
493 	int rc = 0;
494 
495 	if ((code = krb5_enctype_to_string(enctype, buf, sizeof (buf)))) {
496 		syslog(LOG_ERR, "smb_krb5_ktadd[%d]: unknown enctype",
497 		    enctype);
498 		return (-1);
499 	}
500 
501 	if ((entry = (krb5_keytab_entry *) malloc(sizeof (*entry))) == NULL) {
502 		syslog(LOG_ERR, "smb_krb5_ktadd[%d]: resource shortage",
503 		    enctype);
504 		return (-1);
505 	}
506 
507 	(void) memset((char *)entry, 0, sizeof (*entry));
508 
509 	password.length = strlen(pw);
510 	password.data = (char *)pw;
511 
512 	if ((code = krb5_principal2salt(ctx, princ, &salt)) != 0) {
513 		syslog(LOG_ERR, "smb_krb5_ktadd[%d]: failed to compute salt",
514 		    enctype);
515 		free(entry);
516 		return (-1);
517 	}
518 
519 	code = krb5_c_string_to_key(ctx, enctype, &password, &salt, &key);
520 	krb5_xfree(salt.data);
521 	if (code != 0) {
522 		syslog(LOG_ERR, "smb_krb5_ktadd[%d]: failed to generate key",
523 		    enctype);
524 		free(entry);
525 		return (-1);
526 	}
527 
528 	(void) memcpy(&entry->key, &key, sizeof (krb5_keyblock));
529 	entry->vno = kvno;
530 	entry->principal = princ;
531 
532 	if ((code = krb5_kt_add_entry(ctx, kt, entry)) != 0) {
533 		syslog(LOG_ERR, "smb_krb5_ktadd[%d] failed to add entry to "
534 		    "keytab (%d)", enctype, code);
535 		rc = -1;
536 	}
537 
538 	free(entry);
539 	if (key.length)
540 		krb5_free_keyblock_contents(ctx, &key);
541 	return (rc);
542 }
543