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
smb_krb5_get_pn_set(smb_krb5_pn_set_t * set,uint32_t type,char * fqdn)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
smb_krb5_free_pn_set(smb_krb5_pn_set_t * set)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
smb_krb5_ctx_init(krb5_context * ctx)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
smb_krb5_ctx_fini(krb5_context ctx)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
smb_krb5_get_kprincs(krb5_context ctx,char ** names,size_t num,krb5_principal ** krb5princs)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
smb_krb5_free_kprincs(krb5_context ctx,krb5_principal * krb5princs,size_t num)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
smb_krb5_setpwd(krb5_context ctx,const char * fqdn,char * passwd)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
smb_krb5_kt_open(krb5_context ctx,char * fname,krb5_keytab * kt)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
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)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
smb_krb5_kt_find(smb_krb5_pn_id_t id,const char * fqdn,char * fname)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
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)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
smb_krb5_spn_count(uint32_t type)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
smb_krb5_get_kprinc(krb5_context ctx,smb_krb5_pn_id_t id,uint32_t type,const char * fqdn,krb5_principal * princ)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 *
smb_krb5_lookup_pn(smb_krb5_pn_id_t id)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 *
smb_krb5_get_pn_by_id(smb_krb5_pn_id_t id,uint32_t type,const char * fqdn)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