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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <strings.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <syslog.h>
35 #include <netdb.h>
36 #include <sys/param.h>
37 #include <kerberosv5/krb5.h>
38 #include <kerberosv5/com_err.h>
39 
40 #include <smbsrv/libsmb.h>
41 #include <smbns_krb.h>
42 
43 /*
44  * Kerberized services available on the system.
45  */
46 static smb_krb5_pn_t smb_krb5_pn_tab[] = {
47 	/*
48 	 * Service keys are salted with the SMB_KRB_PN_ID_ID_SALT prinipal
49 	 * name.
50 	 */
51 	{SMB_KRB5_PN_ID_SALT,		SMB_PN_SVC_HOST,	SMB_PN_SALT},
52 
53 	/* CIFS SPNs. (HOST, CIFS, ...) */
54 	{SMB_KRB5_PN_ID_HOST_FQHN,	SMB_PN_SVC_HOST,
55 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR | SMB_PN_UPN_ATTR},
56 	{SMB_KRB5_PN_ID_HOST_SHORT,	SMB_PN_SVC_HOST,
57 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR},
58 	{SMB_KRB5_PN_ID_CIFS_FQHN,	SMB_PN_SVC_CIFS,
59 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR},
60 	{SMB_KRB5_PN_ID_CIFS_SHORT,	SMB_PN_SVC_CIFS,
61 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR},
62 	{SMB_KRB5_PN_ID_MACHINE,	NULL,
63 	    SMB_PN_KEYTAB_ENTRY},
64 
65 	/* NFS */
66 	{SMB_KRB5_PN_ID_NFS_FQHN,	SMB_PN_SVC_NFS,
67 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR},
68 
69 	/* HTTP */
70 	{SMB_KRB5_PN_ID_HTTP_FQHN,	SMB_PN_SVC_HTTP,
71 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR},
72 
73 	/* ROOT */
74 	{SMB_KRB5_PN_ID_ROOT_FQHN,	SMB_PN_SVC_ROOT,
75 	    SMB_PN_KEYTAB_ENTRY | SMB_PN_SPN_ATTR},
76 };
77 
78 #define	SMB_KRB5_SPN_TAB_SZ \
79 	(sizeof (smb_krb5_pn_tab) / sizeof (smb_krb5_pn_tab[0]))
80 
81 #define	SMB_KRB5_MAX_BUFLEN	128
82 
83 static int smb_krb5_kt_open(krb5_context, char *, krb5_keytab *);
84 static int smb_krb5_kt_addkey(krb5_context, krb5_keytab, const krb5_principal,
85     krb5_enctype, krb5_kvno, const krb5_data *, const char *);
86 static int smb_krb5_spn_count(uint32_t);
87 static smb_krb5_pn_t *smb_krb5_lookup_pn(smb_krb5_pn_id_t);
88 static char *smb_krb5_get_pn_by_id(smb_krb5_pn_id_t, uint32_t,
89     const char *);
90 static int smb_krb5_get_kprinc(krb5_context, smb_krb5_pn_id_t, uint32_t,
91     const char *, krb5_principal *);
92 
93 
94 /*
95  * Generates a null-terminated array of principal names that
96  * represents the list of the available Kerberized services
97  * of the specified type (SPN attribute, UPN attribute, or
98  * keytab entry).
99  *
100  * Returns the number of principal names returned via the 1st
101  * output parameter (i.e. vals).
102  *
103  * Caller must invoke smb_krb5_free_spns to free the allocated
104  * memory when finished.
105  */
106 uint32_t
smb_krb5_get_pn_set(smb_krb5_pn_set_t * set,uint32_t type,char * fqdn)107 smb_krb5_get_pn_set(smb_krb5_pn_set_t *set, uint32_t type, char *fqdn)
108 {
109 	int cnt, i;
110 	smb_krb5_pn_t *tabent;
111 
112 	if (!set || !fqdn)
113 		return (0);
114 
115 	bzero(set, sizeof (smb_krb5_pn_set_t));
116 	cnt = smb_krb5_spn_count(type);
117 	set->s_pns = (char **)calloc(cnt + 1, sizeof (char *));
118 
119 	if (set->s_pns == NULL)
120 		return (0);
121 
122 	for (i = 0, set->s_cnt = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) {
123 		tabent = &smb_krb5_pn_tab[i];
124 
125 		if (set->s_cnt == cnt)
126 			break;
127 
128 		if ((tabent->p_flags & type) != type)
129 			continue;
130 
131 		set->s_pns[set->s_cnt] = smb_krb5_get_pn_by_id(tabent->p_id,
132 		    type, fqdn);
133 		if (set->s_pns[set->s_cnt] == NULL) {
134 			syslog(LOG_ERR, "smbns_ksetpwd: failed to obtain "
135 			    "principal names: possible transient memory "
136 			    "shortage");
137 			smb_krb5_free_pn_set(set);
138 			return (0);
139 		}
140 
141 		set->s_cnt++;
142 	}
143 
144 	if (set->s_cnt == 0)
145 		smb_krb5_free_pn_set(set);
146 
147 	return (set->s_cnt);
148 }
149 
150 void
smb_krb5_free_pn_set(smb_krb5_pn_set_t * set)151 smb_krb5_free_pn_set(smb_krb5_pn_set_t *set)
152 {
153 	int i;
154 
155 	if (set == NULL || set->s_pns == NULL)
156 		return;
157 
158 	for (i = 0; i < set->s_cnt; i++)
159 		free(set->s_pns[i]);
160 
161 	free(set->s_pns);
162 	set->s_pns = NULL;
163 }
164 
165 /*
166  * Initialize the kerberos context.
167  * Return 0 on success. Otherwise, return -1.
168  */
169 int
smb_krb5_ctx_init(krb5_context * ctx)170 smb_krb5_ctx_init(krb5_context *ctx)
171 {
172 	if (krb5_init_context(ctx) != 0)
173 		return (-1);
174 
175 	return (0);
176 }
177 
178 /*
179  * Free the kerberos context.
180  */
181 void
smb_krb5_ctx_fini(krb5_context ctx)182 smb_krb5_ctx_fini(krb5_context ctx)
183 {
184 	krb5_free_context(ctx);
185 }
186 
187 /*
188  * Create an array of Kerberos Princiapls given an array of principal names.
189  * Caller must free the allocated memory using smb_krb5_free_kprincs()
190  * upon success.
191  *
192  * Returns 0 on success. Otherwise, returns -1.
193  */
194 int
smb_krb5_get_kprincs(krb5_context ctx,char ** names,size_t num,krb5_principal ** krb5princs)195 smb_krb5_get_kprincs(krb5_context ctx, char **names, size_t num,
196     krb5_principal **krb5princs)
197 {
198 	int i;
199 
200 	if ((*krb5princs = calloc(num, sizeof (krb5_principal *))) == NULL) {
201 		return (-1);
202 	}
203 
204 	for (i = 0; i < num; i++) {
205 		if (krb5_parse_name(ctx, names[i], &(*krb5princs)[i]) != 0) {
206 			smb_krb5_free_kprincs(ctx, *krb5princs, i);
207 			return (-1);
208 		}
209 	}
210 
211 	return (0);
212 }
213 
214 void
smb_krb5_free_kprincs(krb5_context ctx,krb5_principal * krb5princs,size_t num)215 smb_krb5_free_kprincs(krb5_context ctx, krb5_principal *krb5princs,
216     size_t num)
217 {
218 	int i;
219 
220 	for (i = 0; i < num; i++)
221 		krb5_free_principal(ctx, krb5princs[i]);
222 
223 	free(krb5princs);
224 }
225 
226 /*
227  * Set the workstation trust account password.
228  * Returns 0 on success.  Otherwise, returns non-zero value.
229  */
230 int
smb_krb5_setpwd(krb5_context ctx,const char * fqdn,char * passwd)231 smb_krb5_setpwd(krb5_context ctx, const char *fqdn, char *passwd)
232 {
233 	krb5_error_code code;
234 	krb5_ccache cc = NULL;
235 	int result_code = 0;
236 	krb5_data result_code_string, result_string;
237 	krb5_principal princ;
238 	char msg[SMB_KRB5_MAX_BUFLEN];
239 
240 	if (smb_krb5_get_kprinc(ctx, SMB_KRB5_PN_ID_HOST_FQHN,
241 	    SMB_PN_UPN_ATTR, fqdn, &princ) != 0)
242 		return (-1);
243 
244 	(void) memset(&result_code_string, 0, sizeof (result_code_string));
245 	(void) memset(&result_string, 0, sizeof (result_string));
246 
247 	if ((code = krb5_cc_default(ctx, &cc)) != 0) {
248 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to "
249 		    "find %s", SMB_CCACHE_PATH);
250 		smb_krb5_log_errmsg(ctx, msg, code);
251 		krb5_free_principal(ctx, princ);
252 		return (-1);
253 	}
254 
255 	code = krb5_set_password_using_ccache(ctx, cc, passwd, princ,
256 	    &result_code, &result_code_string, &result_string);
257 
258 	(void) krb5_cc_close(ctx, cc);
259 
260 	if (code != 0)
261 		smb_krb5_log_errmsg(ctx, "smbns_ksetpwd: KPASSWD protocol "
262 		    "exchange failed", code);
263 
264 	if (result_code != 0) {
265 		syslog(LOG_ERR, "smbns_ksetpwd: KPASSWD failed: rc=%d %.*s",
266 		    result_code,
267 		    result_code_string.length,
268 		    result_code_string.data);
269 		if (code == 0)
270 			code = EACCES;
271 	}
272 
273 	krb5_free_principal(ctx, princ);
274 	free(result_code_string.data);
275 	free(result_string.data);
276 	return (code);
277 }
278 
279 /*
280  * Open the keytab file for writing.
281  * The keytab should be closed by calling krb5_kt_close().
282  */
283 static int
smb_krb5_kt_open(krb5_context ctx,char * fname,krb5_keytab * kt)284 smb_krb5_kt_open(krb5_context ctx, char *fname, krb5_keytab *kt)
285 {
286 	char *ktname;
287 	krb5_error_code code;
288 	int len;
289 	char msg[SMB_KRB5_MAX_BUFLEN];
290 
291 	*kt = NULL;
292 	len = snprintf(NULL, 0, "WRFILE:%s", fname) + 1;
293 	if ((ktname = malloc(len)) == NULL) {
294 		syslog(LOG_ERR, "smbns_ksetpwd: unable to open keytab %s: "
295 		    "possible transient memory shortage", fname);
296 		return (-1);
297 	}
298 
299 	(void) snprintf(ktname, len, "WRFILE:%s", fname);
300 
301 	if ((code = krb5_kt_resolve(ctx, ktname, kt)) != 0) {
302 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: %s", fname);
303 		smb_krb5_log_errmsg(ctx, msg, code);
304 		free(ktname);
305 		return (-1);
306 	}
307 
308 	free(ktname);
309 	return (0);
310 }
311 
312 /*
313  * Populate the keytab with keys of the specified key version for the
314  * specified set of krb5 principals.  All service keys will be salted by:
315  * host/<truncated@15_lower_case_hostname>.<fqdn>@<REALM>
316  */
317 int
smb_krb5_kt_populate(krb5_context ctx,const char * fqdn,krb5_principal * princs,int count,char * fname,krb5_kvno kvno,char * passwd,krb5_enctype * enctypes,int enctype_count)318 smb_krb5_kt_populate(krb5_context ctx, const char *fqdn,
319     krb5_principal *princs, int count, char *fname, krb5_kvno kvno,
320     char *passwd, krb5_enctype *enctypes, int enctype_count)
321 {
322 	krb5_keytab kt = NULL;
323 	krb5_data salt;
324 	krb5_error_code code;
325 	krb5_principal salt_princ;
326 	int i, j;
327 
328 	if (smb_krb5_kt_open(ctx, fname, &kt) != 0)
329 		return (-1);
330 
331 	if (smb_krb5_get_kprinc(ctx, SMB_KRB5_PN_ID_SALT, SMB_PN_SALT,
332 	    fqdn, &salt_princ) != 0) {
333 		(void) krb5_kt_close(ctx, kt);
334 		return (-1);
335 	}
336 
337 	code = krb5_principal2salt(ctx, salt_princ, &salt);
338 	if (code != 0) {
339 		smb_krb5_log_errmsg(ctx, "smbns_ksetpwd: salt computation "
340 		    "failed", code);
341 		krb5_free_principal(ctx, salt_princ);
342 		(void) krb5_kt_close(ctx, kt);
343 		return (-1);
344 	}
345 
346 	for (j = 0; j < count; j++) {
347 		for (i = 0; i < enctype_count; i++) {
348 			if (smb_krb5_kt_addkey(ctx, kt, princs[j], enctypes[i],
349 			    kvno, &salt, passwd) != 0) {
350 				krb5_free_principal(ctx, salt_princ);
351 				krb5_xfree(salt.data);
352 				(void) krb5_kt_close(ctx, kt);
353 				return (-1);
354 			}
355 		}
356 
357 	}
358 	krb5_free_principal(ctx, salt_princ);
359 	krb5_xfree(salt.data);
360 	(void) krb5_kt_close(ctx, kt);
361 	return (0);
362 }
363 
364 boolean_t
smb_krb5_kt_find(smb_krb5_pn_id_t id,const char * fqdn,char * fname)365 smb_krb5_kt_find(smb_krb5_pn_id_t id, const char *fqdn, char *fname)
366 {
367 	krb5_context ctx;
368 	krb5_keytab kt;
369 	krb5_keytab_entry entry;
370 	krb5_principal princ;
371 	char ktname[MAXPATHLEN];
372 	boolean_t found = B_FALSE;
373 
374 	if (!fqdn || !fname)
375 		return (found);
376 
377 	if (smb_krb5_ctx_init(&ctx) != 0)
378 		return (found);
379 
380 	if (smb_krb5_get_kprinc(ctx, id, SMB_PN_KEYTAB_ENTRY, fqdn,
381 	    &princ) != 0) {
382 		smb_krb5_ctx_fini(ctx);
383 		return (found);
384 	}
385 
386 	(void) snprintf(ktname, MAXPATHLEN, "FILE:%s", fname);
387 	if (krb5_kt_resolve(ctx, ktname, &kt) == 0) {
388 		if (krb5_kt_get_entry(ctx, kt, princ, 0, 0, &entry) == 0) {
389 			found = B_TRUE;
390 			(void) krb5_kt_free_entry(ctx, &entry);
391 		}
392 
393 		(void) krb5_kt_close(ctx, kt);
394 	}
395 
396 	krb5_free_principal(ctx, princ);
397 	smb_krb5_ctx_fini(ctx);
398 	return (found);
399 }
400 
401 /*
402  * Add a key of the specified encryption type for the specified principal
403  * to the keytab file.
404  * Returns 0 on success. Otherwise, returns -1.
405  */
406 static int
smb_krb5_kt_addkey(krb5_context ctx,krb5_keytab kt,const krb5_principal princ,krb5_enctype enctype,krb5_kvno kvno,const krb5_data * salt,const char * pw)407 smb_krb5_kt_addkey(krb5_context ctx, krb5_keytab kt, const krb5_principal princ,
408     krb5_enctype enctype, krb5_kvno kvno, const krb5_data *salt,
409     const char *pw)
410 {
411 	krb5_keytab_entry *entry;
412 	krb5_data password;
413 	krb5_keyblock key;
414 	krb5_error_code code;
415 	char buf[SMB_KRB5_MAX_BUFLEN], msg[SMB_KRB5_MAX_BUFLEN];
416 	int rc = 0;
417 
418 	if ((code = krb5_enctype_to_string(enctype, buf, sizeof (buf)))) {
419 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: unknown "
420 		    "encryption type (%d)", enctype);
421 		smb_krb5_log_errmsg(ctx, msg, code);
422 		return (-1);
423 	}
424 
425 	if ((entry = (krb5_keytab_entry *) malloc(sizeof (*entry))) == NULL) {
426 		syslog(LOG_ERR, "smbns_ksetpwd: possible transient "
427 		    "memory shortage");
428 		return (-1);
429 	}
430 
431 	(void) memset((char *)entry, 0, sizeof (*entry));
432 
433 	password.length = strlen(pw);
434 	password.data = (char *)pw;
435 
436 	code = krb5_c_string_to_key(ctx, enctype, &password, salt, &key);
437 	if (code != 0) {
438 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to "
439 		    "generate key (%d)", enctype);
440 		smb_krb5_log_errmsg(ctx, msg, code);
441 		free(entry);
442 		return (-1);
443 	}
444 
445 	(void) memcpy(&entry->key, &key, sizeof (krb5_keyblock));
446 	entry->vno = kvno;
447 	entry->principal = princ;
448 
449 	if ((code = krb5_kt_add_entry(ctx, kt, entry)) != 0) {
450 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to "
451 		    "add key (%d)", enctype);
452 		smb_krb5_log_errmsg(ctx, msg, code);
453 		rc = -1;
454 	}
455 
456 	free(entry);
457 	if (key.length)
458 		krb5_free_keyblock_contents(ctx, &key);
459 	return (rc);
460 }
461 
462 static int
smb_krb5_spn_count(uint32_t type)463 smb_krb5_spn_count(uint32_t type)
464 {
465 	int i, cnt;
466 
467 	for (i = 0, cnt = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) {
468 		if (smb_krb5_pn_tab[i].p_flags & type)
469 			cnt++;
470 	}
471 
472 	return (cnt);
473 }
474 
475 /*
476  * Generate the Kerberos Principal given a principal name format and the
477  * fully qualified domain name. On success, caller must free the allocated
478  * memory by calling krb5_free_principal().
479  */
480 static int
smb_krb5_get_kprinc(krb5_context ctx,smb_krb5_pn_id_t id,uint32_t type,const char * fqdn,krb5_principal * princ)481 smb_krb5_get_kprinc(krb5_context ctx, smb_krb5_pn_id_t id, uint32_t type,
482     const char *fqdn, krb5_principal *princ)
483 {
484 	char *buf;
485 
486 	if ((buf = smb_krb5_get_pn_by_id(id, type, fqdn)) == NULL)
487 		return (-1);
488 
489 	if (krb5_parse_name(ctx, buf, princ) != 0) {
490 		free(buf);
491 		return (-1);
492 	}
493 
494 	free(buf);
495 	return (0);
496 }
497 
498 /*
499  * Looks up an entry in the principal name table given the ID.
500  */
501 static smb_krb5_pn_t *
smb_krb5_lookup_pn(smb_krb5_pn_id_t id)502 smb_krb5_lookup_pn(smb_krb5_pn_id_t id)
503 {
504 	int i;
505 	smb_krb5_pn_t *tabent;
506 
507 	for (i = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) {
508 		tabent = &smb_krb5_pn_tab[i];
509 		if (id == tabent->p_id)
510 			return (tabent);
511 	}
512 
513 	return (NULL);
514 }
515 
516 /*
517  * Construct the principal name given an ID, the requested type, and the
518  * fully-qualified name of the domain of which the principal is a member.
519  */
520 static char *
smb_krb5_get_pn_by_id(smb_krb5_pn_id_t id,uint32_t type,const char * fqdn)521 smb_krb5_get_pn_by_id(smb_krb5_pn_id_t id, uint32_t type,
522     const char *fqdn)
523 {
524 	char nbname[NETBIOS_NAME_SZ];
525 	char hostname[MAXHOSTNAMELEN];
526 	char *realm = NULL;
527 	smb_krb5_pn_t *pn;
528 	char *buf;
529 
530 	(void) smb_getnetbiosname(nbname, NETBIOS_NAME_SZ);
531 	(void) smb_gethostname(hostname, MAXHOSTNAMELEN, SMB_CASE_LOWER);
532 
533 	pn = smb_krb5_lookup_pn(id);
534 
535 	/* detect inconsistent requested format and type */
536 	if ((type & pn->p_flags) != type)
537 		return (NULL);
538 
539 	switch (id) {
540 	case SMB_KRB5_PN_ID_SALT:
541 		(void) asprintf(&buf, "%s/%s.%s",
542 		    pn->p_svc, smb_strlwr(nbname), fqdn);
543 		break;
544 
545 	case SMB_KRB5_PN_ID_HOST_FQHN:
546 	case SMB_KRB5_PN_ID_CIFS_FQHN:
547 	case SMB_KRB5_PN_ID_NFS_FQHN:
548 	case SMB_KRB5_PN_ID_HTTP_FQHN:
549 	case SMB_KRB5_PN_ID_ROOT_FQHN:
550 		(void) asprintf(&buf, "%s/%s.%s",
551 		    pn->p_svc, hostname, fqdn);
552 		break;
553 
554 	case SMB_KRB5_PN_ID_HOST_SHORT:
555 	case SMB_KRB5_PN_ID_CIFS_SHORT:
556 		(void) asprintf(&buf, "%s/%s",
557 		    pn->p_svc, nbname);
558 		break;
559 
560 	/*
561 	 * SPN for the machine account, which is simply the
562 	 * (short) machine name with a dollar sign appended.
563 	 */
564 	case SMB_KRB5_PN_ID_MACHINE:
565 		(void) asprintf(&buf, "%s$", nbname);
566 		break;
567 
568 	default:
569 		return (NULL);
570 	}
571 
572 	/*
573 	 * If the requested principal is either added to keytab / the machine
574 	 * account as the UPN attribute or used for key salt generation,
575 	 * the principal name must have the @<REALM> portion.
576 	 */
577 	if (type & (SMB_PN_KEYTAB_ENTRY | SMB_PN_UPN_ATTR | SMB_PN_SALT)) {
578 		if ((realm = strdup(fqdn)) == NULL) {
579 			free(buf);
580 			return (NULL);
581 		}
582 
583 		(void) smb_strupr(realm);
584 		if (buf != NULL) {
585 			char *tmp;
586 
587 			(void) asprintf(&tmp, "%s@%s", buf,
588 			    realm);
589 			free(buf);
590 			buf = tmp;
591 		}
592 
593 		free(realm);
594 	}
595 
596 	return (buf);
597 }
598