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