smbns_ksetpwd.c revision 12508:edb7861a1533
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 */
45static 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
74static int smb_krb5_kt_open(krb5_context, char *, krb5_keytab *);
75static int smb_krb5_kt_addkey(krb5_context, krb5_keytab, const krb5_principal,
76    krb5_enctype, krb5_kvno, const krb5_data *, const char *);
77static int smb_krb5_spn_count(uint32_t);
78static smb_krb5_pn_t *smb_krb5_lookup_pn(smb_krb5_pn_id_t);
79static char *smb_krb5_get_pn_by_id(smb_krb5_pn_id_t, uint32_t,
80    const char *);
81static 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 */
97uint32_t
98smb_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
141void
142smb_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 */
160int
161smb_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 */
172void
173smb_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 */
185int
186smb_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
205void
206smb_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 */
221int
222smb_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 */
269static int
270smb_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 */
303int
304smb_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
350boolean_t
351smb_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 */
392static int
393smb_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
448static int
449smb_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 */
466static int
467smb_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 */
487static smb_krb5_pn_t *
488smb_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 */
506static char *
507smb_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