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