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