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