xref: /titanic_50/usr/src/lib/smbsrv/libsmbns/common/smbns_ksetpwd.c (revision 12b65585e720714b31036daaa2b30eb76014048e)
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
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
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
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
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
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
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
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 	if (code != 0)
259 		smb_krb5_log_errmsg(ctx, "smbns_ksetpwd: KPASSWD protocol "
260 		    "exchange failed", code);
261 
262 	(void) krb5_cc_close(ctx, cc);
263 
264 	if (result_code != 0)
265 		syslog(LOG_ERR, "smbns_ksetpwd: KPASSWD failed: %s",
266 		    result_code_string.data);
267 
268 	krb5_free_principal(ctx, princ);
269 	free(result_code_string.data);
270 	free(result_string.data);
271 	return (code);
272 }
273 
274 /*
275  * Open the keytab file for writing.
276  * The keytab should be closed by calling krb5_kt_close().
277  */
278 static int
279 smb_krb5_kt_open(krb5_context ctx, char *fname, krb5_keytab *kt)
280 {
281 	char *ktname;
282 	krb5_error_code code;
283 	int len;
284 	char msg[SMB_KRB5_MAX_BUFLEN];
285 
286 	*kt = NULL;
287 	len = snprintf(NULL, 0, "WRFILE:%s", fname) + 1;
288 	if ((ktname = malloc(len)) == NULL) {
289 		syslog(LOG_ERR, "smbns_ksetpwd: unable to open keytab %s: "
290 		    "possible transient memory shortage", fname);
291 		return (-1);
292 	}
293 
294 	(void) snprintf(ktname, len, "WRFILE:%s", fname);
295 
296 	if ((code = krb5_kt_resolve(ctx, ktname, kt)) != 0) {
297 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: %s", fname);
298 		smb_krb5_log_errmsg(ctx, msg, code);
299 		free(ktname);
300 		return (-1);
301 	}
302 
303 	free(ktname);
304 	return (0);
305 }
306 
307 /*
308  * Populate the keytab with keys of the specified key version for the
309  * specified set of krb5 principals.  All service keys will be salted by:
310  * host/<truncated@15_lower_case_hostname>.<fqdn>@<REALM>
311  */
312 int
313 smb_krb5_kt_populate(krb5_context ctx, const char *fqdn,
314     krb5_principal *princs, int count, char *fname, krb5_kvno kvno,
315     char *passwd, krb5_enctype *enctypes, int enctype_count)
316 {
317 	krb5_keytab kt = NULL;
318 	krb5_data salt;
319 	krb5_error_code code;
320 	krb5_principal salt_princ;
321 	int i, j;
322 
323 	if (smb_krb5_kt_open(ctx, fname, &kt) != 0)
324 		return (-1);
325 
326 	if (smb_krb5_get_kprinc(ctx, SMB_KRB5_PN_ID_SALT, SMB_PN_SALT,
327 	    fqdn, &salt_princ) != 0) {
328 		(void) krb5_kt_close(ctx, kt);
329 		return (-1);
330 	}
331 
332 	code = krb5_principal2salt(ctx, salt_princ, &salt);
333 	if (code != 0) {
334 		smb_krb5_log_errmsg(ctx, "smbns_ksetpwd: salt computation "
335 		    "failed", code);
336 		krb5_free_principal(ctx, salt_princ);
337 		(void) krb5_kt_close(ctx, kt);
338 		return (-1);
339 	}
340 
341 	for (j = 0; j < count; j++) {
342 		for (i = 0; i < enctype_count; i++) {
343 			if (smb_krb5_kt_addkey(ctx, kt, princs[j], enctypes[i],
344 			    kvno, &salt, passwd) != 0) {
345 				krb5_free_principal(ctx, salt_princ);
346 				krb5_xfree(salt.data);
347 				(void) krb5_kt_close(ctx, kt);
348 				return (-1);
349 			}
350 		}
351 
352 	}
353 	krb5_free_principal(ctx, salt_princ);
354 	krb5_xfree(salt.data);
355 	(void) krb5_kt_close(ctx, kt);
356 	return (0);
357 }
358 
359 boolean_t
360 smb_krb5_kt_find(smb_krb5_pn_id_t id, const char *fqdn, char *fname)
361 {
362 	krb5_context ctx;
363 	krb5_keytab kt;
364 	krb5_keytab_entry entry;
365 	krb5_principal princ;
366 	char ktname[MAXPATHLEN];
367 	boolean_t found = B_FALSE;
368 
369 	if (!fqdn || !fname)
370 		return (found);
371 
372 	if (smb_krb5_ctx_init(&ctx) != 0)
373 		return (found);
374 
375 	if (smb_krb5_get_kprinc(ctx, id, SMB_PN_KEYTAB_ENTRY, fqdn,
376 	    &princ) != 0) {
377 		smb_krb5_ctx_fini(ctx);
378 		return (found);
379 	}
380 
381 	(void) snprintf(ktname, MAXPATHLEN, "FILE:%s", fname);
382 	if (krb5_kt_resolve(ctx, ktname, &kt) == 0) {
383 		if (krb5_kt_get_entry(ctx, kt, princ, 0, 0, &entry) == 0) {
384 			found = B_TRUE;
385 			(void) krb5_kt_free_entry(ctx, &entry);
386 		}
387 
388 		(void) krb5_kt_close(ctx, kt);
389 	}
390 
391 	krb5_free_principal(ctx, princ);
392 	smb_krb5_ctx_fini(ctx);
393 	return (found);
394 }
395 
396 /*
397  * Add a key of the specified encryption type for the specified principal
398  * to the keytab file.
399  * Returns 0 on success. Otherwise, returns -1.
400  */
401 static int
402 smb_krb5_kt_addkey(krb5_context ctx, krb5_keytab kt, const krb5_principal princ,
403     krb5_enctype enctype, krb5_kvno kvno, const krb5_data *salt,
404     const char *pw)
405 {
406 	krb5_keytab_entry *entry;
407 	krb5_data password;
408 	krb5_keyblock key;
409 	krb5_error_code code;
410 	char buf[SMB_KRB5_MAX_BUFLEN], msg[SMB_KRB5_MAX_BUFLEN];
411 	int rc = 0;
412 
413 	if ((code = krb5_enctype_to_string(enctype, buf, sizeof (buf)))) {
414 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: unknown "
415 		    "encryption type (%d)", enctype);
416 		smb_krb5_log_errmsg(ctx, msg, code);
417 		return (-1);
418 	}
419 
420 	if ((entry = (krb5_keytab_entry *) malloc(sizeof (*entry))) == NULL) {
421 		syslog(LOG_ERR, "smbns_ksetpwd: possible transient "
422 		    "memory shortage");
423 		return (-1);
424 	}
425 
426 	(void) memset((char *)entry, 0, sizeof (*entry));
427 
428 	password.length = strlen(pw);
429 	password.data = (char *)pw;
430 
431 	code = krb5_c_string_to_key(ctx, enctype, &password, salt, &key);
432 	if (code != 0) {
433 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to "
434 		    "generate key (%d)", enctype);
435 		smb_krb5_log_errmsg(ctx, msg, code);
436 		free(entry);
437 		return (-1);
438 	}
439 
440 	(void) memcpy(&entry->key, &key, sizeof (krb5_keyblock));
441 	entry->vno = kvno;
442 	entry->principal = princ;
443 
444 	if ((code = krb5_kt_add_entry(ctx, kt, entry)) != 0) {
445 		(void) snprintf(msg, sizeof (msg), "smbns_ksetpwd: failed to "
446 		    "add key (%d)", enctype);
447 		smb_krb5_log_errmsg(ctx, msg, code);
448 		rc = -1;
449 	}
450 
451 	free(entry);
452 	if (key.length)
453 		krb5_free_keyblock_contents(ctx, &key);
454 	return (rc);
455 }
456 
457 static int
458 smb_krb5_spn_count(uint32_t type)
459 {
460 	int i, cnt;
461 
462 	for (i = 0, cnt = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) {
463 		if (smb_krb5_pn_tab[i].p_flags & type)
464 			cnt++;
465 	}
466 
467 	return (cnt);
468 }
469 
470 /*
471  * Generate the Kerberos Principal given a principal name format and the
472  * fully qualified domain name. On success, caller must free the allocated
473  * memory by calling krb5_free_principal().
474  */
475 static int
476 smb_krb5_get_kprinc(krb5_context ctx, smb_krb5_pn_id_t id, uint32_t type,
477     const char *fqdn, krb5_principal *princ)
478 {
479 	char *buf;
480 
481 	if ((buf = smb_krb5_get_pn_by_id(id, type, fqdn)) == NULL)
482 		return (-1);
483 
484 	if (krb5_parse_name(ctx, buf, princ) != 0) {
485 		free(buf);
486 		return (-1);
487 	}
488 
489 	free(buf);
490 	return (0);
491 }
492 
493 /*
494  * Looks up an entry in the principal name table given the ID.
495  */
496 static smb_krb5_pn_t *
497 smb_krb5_lookup_pn(smb_krb5_pn_id_t id)
498 {
499 	int i;
500 	smb_krb5_pn_t *tabent;
501 
502 	for (i = 0; i < SMB_KRB5_SPN_TAB_SZ; i++) {
503 		tabent = &smb_krb5_pn_tab[i];
504 		if (id == tabent->p_id)
505 			return (tabent);
506 	}
507 
508 	return (NULL);
509 }
510 
511 /*
512  * Construct the principal name given an ID, the requested type, and the
513  * fully-qualified name of the domain of which the principal is a member.
514  */
515 static char *
516 smb_krb5_get_pn_by_id(smb_krb5_pn_id_t id, uint32_t type,
517     const char *fqdn)
518 {
519 	char nbname[NETBIOS_NAME_SZ];
520 	char hostname[MAXHOSTNAMELEN];
521 	char *realm = NULL;
522 	smb_krb5_pn_t *pn;
523 	char *buf;
524 
525 	(void) smb_getnetbiosname(nbname, NETBIOS_NAME_SZ);
526 	(void) smb_gethostname(hostname, MAXHOSTNAMELEN, SMB_CASE_LOWER);
527 
528 	pn = smb_krb5_lookup_pn(id);
529 
530 	/* detect inconsistent requested format and type */
531 	if ((type & pn->p_flags) != type)
532 		return (NULL);
533 
534 	switch (id) {
535 	case SMB_KRB5_PN_ID_SALT:
536 		(void) asprintf(&buf, "%s/%s.%s",
537 		    pn->p_svc, smb_strlwr(nbname), fqdn);
538 		break;
539 
540 	case SMB_KRB5_PN_ID_HOST_FQHN:
541 	case SMB_KRB5_PN_ID_CIFS_FQHN:
542 	case SMB_KRB5_PN_ID_NFS_FQHN:
543 	case SMB_KRB5_PN_ID_HTTP_FQHN:
544 	case SMB_KRB5_PN_ID_ROOT_FQHN:
545 		(void) asprintf(&buf, "%s/%s.%s",
546 		    pn->p_svc, hostname, fqdn);
547 		break;
548 
549 	case SMB_KRB5_PN_ID_HOST_SHORT:
550 	case SMB_KRB5_PN_ID_CIFS_SHORT:
551 		(void) asprintf(&buf, "%s/%s",
552 		    pn->p_svc, nbname);
553 		break;
554 
555 	/*
556 	 * SPN for the machine account, which is simply the
557 	 * (short) machine name with a dollar sign appended.
558 	 */
559 	case SMB_KRB5_PN_ID_MACHINE:
560 		(void) asprintf(&buf, "%s$", nbname);
561 		break;
562 
563 	default:
564 		return (NULL);
565 	}
566 
567 	/*
568 	 * If the requested principal is either added to keytab / the machine
569 	 * account as the UPN attribute or used for key salt generation,
570 	 * the principal name must have the @<REALM> portion.
571 	 */
572 	if (type & (SMB_PN_KEYTAB_ENTRY | SMB_PN_UPN_ATTR | SMB_PN_SALT)) {
573 		if ((realm = strdup(fqdn)) == NULL) {
574 			free(buf);
575 			return (NULL);
576 		}
577 
578 		(void) smb_strupr(realm);
579 		if (buf != NULL) {
580 			char *tmp;
581 
582 			(void) asprintf(&tmp, "%s@%s", buf,
583 			    realm);
584 			free(buf);
585 			buf = tmp;
586 		}
587 
588 		free(realm);
589 	}
590 
591 	return (buf);
592 }
593