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 (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 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 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 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 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 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 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 * 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 * 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