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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <sys/param.h> 27 #include <ldap.h> 28 #include <stdlib.h> 29 #include <sys/types.h> 30 #include <sys/socket.h> 31 #include <netinet/in.h> 32 #include <arpa/inet.h> 33 #include <sys/time.h> 34 #include <netdb.h> 35 #include <pthread.h> 36 #include <unistd.h> 37 #include <arpa/nameser.h> 38 #include <resolv.h> 39 #include <sys/synch.h> 40 #include <string.h> 41 #include <strings.h> 42 #include <fcntl.h> 43 #include <sys/types.h> 44 #include <sys/stat.h> 45 #include <assert.h> 46 #include <sasl/sasl.h> 47 #include <note.h> 48 49 #include <smbsrv/libsmbns.h> 50 #include <smbns_dyndns.h> 51 #include <smbns_krb.h> 52 53 #define SMB_ADS_MAXBUFLEN 100 54 #define SMB_ADS_DN_MAX 300 55 #define SMB_ADS_MAXMSGLEN 512 56 #define SMB_ADS_COMPUTERS_CN "Computers" 57 #define SMB_ADS_COMPUTER_NUM_ATTR 8 58 #define SMB_ADS_SHARE_NUM_ATTR 3 59 #define SMB_ADS_SITE_MAX MAXHOSTNAMELEN 60 61 #define SMB_ADS_MSDCS_SRV_DC_RR "_ldap._tcp.dc._msdcs" 62 #define SMB_ADS_MSDCS_SRV_SITE_RR "_ldap._tcp.%s._sites.dc._msdcs" 63 64 /* 65 * domainControllerFunctionality 66 * 67 * This rootDSE attribute indicates the functional level of the DC. 68 */ 69 #define SMB_ADS_ATTR_DCLEVEL "domainControllerFunctionality" 70 #define SMB_ADS_DCLEVEL_W2K 0 71 #define SMB_ADS_DCLEVEL_W2K3 2 72 #define SMB_ADS_DCLEVEL_W2K8 3 73 #define SMB_ADS_DCLEVEL_W2K8_R2 4 74 75 /* 76 * msDs-supportedEncryptionTypes (Windows Server 2008 only) 77 * 78 * This attribute defines the encryption types supported by the system. 79 * Encryption Types: 80 * - DES cbc mode with CRC-32 81 * - DES cbc mode with RSA-MD5 82 * - ArcFour with HMAC/md5 83 * - AES-128 84 * - AES-256 85 */ 86 #define SMB_ADS_ATTR_ENCTYPES "msDs-supportedEncryptionTypes" 87 #define SMB_ADS_ENC_DES_CRC 1 88 #define SMB_ADS_ENC_DES_MD5 2 89 #define SMB_ADS_ENC_RC4 4 90 #define SMB_ADS_ENC_AES128 8 91 #define SMB_ADS_ENC_AES256 16 92 93 #define SMB_ADS_ATTR_SAMACCT "sAMAccountName" 94 #define SMB_ADS_ATTR_UPN "userPrincipalName" 95 #define SMB_ADS_ATTR_SPN "servicePrincipalName" 96 #define SMB_ADS_ATTR_CTL "userAccountControl" 97 #define SMB_ADS_ATTR_DNSHOST "dNSHostName" 98 #define SMB_ADS_ATTR_KVNO "msDS-KeyVersionNumber" 99 #define SMB_ADS_ATTR_DN "distinguishedName" 100 101 /* 102 * UserAccountControl flags: manipulate user account properties. 103 * 104 * The hexadecimal value of the following property flags are based on MSDN 105 * article # 305144. 106 */ 107 #define SMB_ADS_USER_ACCT_CTL_SCRIPT 0x00000001 108 #define SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE 0x00000002 109 #define SMB_ADS_USER_ACCT_CTL_HOMEDIR_REQUIRED 0x00000008 110 #define SMB_ADS_USER_ACCT_CTL_LOCKOUT 0x00000010 111 #define SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD 0x00000020 112 #define SMB_ADS_USER_ACCT_CTL_PASSWD_CANT_CHANGE 0x00000040 113 #define SMB_ADS_USER_ACCT_CTL_ENCRYPTED_TEXT_PWD_ALLOWED 0x00000080 114 #define SMB_ADS_USER_ACCT_CTL_TMP_DUP_ACCT 0x00000100 115 #define SMB_ADS_USER_ACCT_CTL_NORMAL_ACCT 0x00000200 116 #define SMB_ADS_USER_ACCT_CTL_INTERDOMAIN_TRUST_ACCT 0x00000800 117 #define SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT 0x00001000 118 #define SMB_ADS_USER_ACCT_CTL_SRV_TRUST_ACCT 0x00002000 119 #define SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD 0x00010000 120 #define SMB_ADS_USER_ACCT_CTL_MNS_LOGON_ACCT 0x00020000 121 #define SMB_ADS_USER_ACCT_CTL_SMARTCARD_REQUIRED 0x00040000 122 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION 0x00080000 123 #define SMB_ADS_USER_ACCT_CTL_NOT_DELEGATED 0x00100000 124 #define SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY 0x00200000 125 #define SMB_ADS_USER_ACCT_CTL_DONT_REQ_PREAUTH 0x00400000 126 #define SMB_ADS_USER_ACCT_CTL_PASSWD_EXPIRED 0x00800000 127 #define SMB_ADS_USER_ACCT_CTL_TRUSTED_TO_AUTH_FOR_DELEGATION 0x01000000 128 129 /* 130 * Length of "dc=" prefix. 131 */ 132 #define SMB_ADS_DN_PREFIX_LEN 3 133 134 static char *smb_ads_computer_objcls[] = { 135 "top", "person", "organizationalPerson", 136 "user", "computer", NULL 137 }; 138 139 static char *smb_ads_share_objcls[] = { 140 "top", "leaf", "connectionPoint", "volume", NULL 141 }; 142 143 /* Cached ADS server to communicate with */ 144 static smb_ads_host_info_t *smb_ads_cached_host_info = NULL; 145 static mutex_t smb_ads_cached_host_mtx; 146 147 /* 148 * SMB ADS config cache is maintained to facilitate the detection of 149 * changes in configuration that is relevant to AD selection. 150 */ 151 typedef struct smb_ads_config { 152 char c_site[SMB_ADS_SITE_MAX]; 153 smb_inaddr_t c_pdc; 154 mutex_t c_mtx; 155 } smb_ads_config_t; 156 157 static smb_ads_config_t smb_ads_cfg; 158 159 160 /* attribute/value pair */ 161 typedef struct smb_ads_avpair { 162 char *avp_attr; 163 char *avp_val; 164 } smb_ads_avpair_t; 165 166 /* query status */ 167 typedef enum smb_ads_qstat { 168 SMB_ADS_STAT_ERR = -2, 169 SMB_ADS_STAT_DUP, 170 SMB_ADS_STAT_NOT_FOUND, 171 SMB_ADS_STAT_FOUND 172 } smb_ads_qstat_t; 173 174 typedef struct smb_ads_host_list { 175 int ah_cnt; 176 smb_ads_host_info_t *ah_list; 177 } smb_ads_host_list_t; 178 179 static smb_ads_handle_t *smb_ads_open_main(char *, char *, char *); 180 static int smb_ads_add_computer(smb_ads_handle_t *, int, char *); 181 static int smb_ads_modify_computer(smb_ads_handle_t *, int, char *); 182 static int smb_ads_computer_op(smb_ads_handle_t *, int, int, char *); 183 static smb_ads_qstat_t smb_ads_lookup_computer_n_attr(smb_ads_handle_t *, 184 smb_ads_avpair_t *, int, char *); 185 static int smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *, int, char *); 186 static krb5_kvno smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *, char *); 187 static void smb_ads_gen_machine_passwd(char *, int); 188 static void smb_ads_free_cached_host(void); 189 static int smb_ads_get_spnset(char *, char **); 190 static void smb_ads_free_spnset(char **); 191 static int smb_ads_alloc_attr(LDAPMod **, int); 192 static void smb_ads_free_attr(LDAPMod **); 193 static int smb_ads_get_dc_level(smb_ads_handle_t *); 194 static smb_ads_host_info_t *smb_ads_select_dc(smb_ads_host_list_t *); 195 static smb_ads_qstat_t smb_ads_find_computer(smb_ads_handle_t *, char *); 196 static smb_ads_qstat_t smb_ads_getattr(LDAP *, LDAPMessage *, 197 smb_ads_avpair_t *); 198 static smb_ads_qstat_t smb_ads_get_qstat(smb_ads_handle_t *, LDAPMessage *, 199 smb_ads_avpair_t *); 200 static boolean_t smb_ads_match_pdc(smb_ads_host_info_t *); 201 static boolean_t smb_ads_is_sought_host(smb_ads_host_info_t *, char *); 202 static boolean_t smb_ads_is_same_domain(char *, char *); 203 static boolean_t smb_ads_is_pdc_configured(void); 204 static smb_ads_host_info_t *smb_ads_dup_host_info(smb_ads_host_info_t *); 205 206 /* 207 * smb_ads_init 208 * 209 * Initializes the ADS config cache. 210 */ 211 void 212 smb_ads_init(void) 213 { 214 (void) mutex_lock(&smb_ads_cfg.c_mtx); 215 (void) smb_config_getstr(SMB_CI_ADS_SITE, 216 smb_ads_cfg.c_site, SMB_ADS_SITE_MAX); 217 (void) smb_config_getip(SMB_CI_DOMAIN_SRV, &smb_ads_cfg.c_pdc); 218 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 219 } 220 221 void 222 smb_ads_fini(void) 223 { 224 smb_ads_free_cached_host(); 225 } 226 227 /* 228 * smb_ads_refresh 229 * 230 * This function will be called when smb/server SMF service is refreshed. 231 * Clearing the smb_ads_cached_host_info would allow the next DC 232 * discovery process to pick up an AD based on the new AD configuration. 233 */ 234 void 235 smb_ads_refresh(void) 236 { 237 char new_site[SMB_ADS_SITE_MAX]; 238 smb_inaddr_t new_pdc; 239 boolean_t purge = B_FALSE; 240 241 (void) smb_config_getstr(SMB_CI_ADS_SITE, new_site, SMB_ADS_SITE_MAX); 242 (void) smb_config_getip(SMB_CI_DOMAIN_SRV, &new_pdc); 243 (void) mutex_lock(&smb_ads_cfg.c_mtx); 244 if (smb_strcasecmp(smb_ads_cfg.c_site, new_site, 0)) { 245 (void) strlcpy(smb_ads_cfg.c_site, new_site, SMB_ADS_SITE_MAX); 246 purge = B_TRUE; 247 } 248 249 smb_ads_cfg.c_pdc = new_pdc; 250 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 251 252 (void) mutex_lock(&smb_ads_cached_host_mtx); 253 if (smb_ads_cached_host_info && 254 smb_ads_is_pdc_configured() && 255 !smb_ads_match_pdc(smb_ads_cached_host_info)) 256 purge = B_TRUE; 257 (void) mutex_unlock(&smb_ads_cached_host_mtx); 258 259 if (purge) 260 smb_ads_free_cached_host(); 261 } 262 263 264 265 static boolean_t 266 smb_ads_is_pdc_configured(void) 267 { 268 boolean_t configured; 269 270 (void) mutex_lock(&smb_ads_cfg.c_mtx); 271 configured = !smb_inet_iszero(&smb_ads_cfg.c_pdc); 272 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 273 274 return (configured); 275 } 276 277 /* 278 * smb_ads_build_unc_name 279 * 280 * Construct the UNC name of the share object in the format of 281 * \\hostname.domain\shareUNC 282 * 283 * Returns 0 on success, -1 on error. 284 */ 285 int 286 smb_ads_build_unc_name(char *unc_name, int maxlen, 287 const char *hostname, const char *shareUNC) 288 { 289 char my_domain[MAXHOSTNAMELEN]; 290 291 if (smb_getfqdomainname(my_domain, sizeof (my_domain)) != 0) 292 return (-1); 293 294 (void) snprintf(unc_name, maxlen, "\\\\%s.%s\\%s", 295 hostname, my_domain, shareUNC); 296 return (0); 297 } 298 299 /* 300 * smb_ads_ldap_ping 301 * 302 * This is used to bind to an ADS server to see 303 * if it is still alive. 304 * 305 * Returns: 306 * -1: error 307 * 0: successful 308 */ 309 /*ARGSUSED*/ 310 static int 311 smb_ads_ldap_ping(smb_ads_host_info_t *ads_host) 312 { 313 int ldversion = LDAP_VERSION3, status, timeoutms = 5 * 1000; 314 LDAP *ld = NULL; 315 316 ld = ldap_init(ads_host->name, ads_host->port); 317 if (ld == NULL) 318 return (-1); 319 320 ldversion = LDAP_VERSION3; 321 (void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldversion); 322 /* setup TCP/IP connect timeout */ 323 (void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutms); 324 325 status = ldap_bind_s(ld, "", NULL, LDAP_AUTH_SIMPLE); 326 327 if (status != LDAP_SUCCESS) { 328 (void) ldap_unbind(ld); 329 return (-1); 330 } 331 332 (void) ldap_unbind(ld); 333 334 return (0); 335 } 336 337 /* 338 * The cached ADS host is no longer valid if one of the following criteria 339 * is satisfied: 340 * 341 * 1) not in the specified domain 342 * 2) not the sought host (if specified) 343 * 3) not reachable 344 * 345 * The caller is responsible for acquiring the smb_ads_cached_host_mtx lock 346 * prior to calling this function. 347 * 348 * Return B_TRUE if the cache host is still valid. Otherwise, return B_FALSE. 349 */ 350 static boolean_t 351 smb_ads_validate_cache_host(char *domain, char *srv) 352 { 353 if (!smb_ads_cached_host_info) 354 return (B_FALSE); 355 356 if (!smb_ads_is_same_domain(smb_ads_cached_host_info->name, domain)) 357 return (B_FALSE); 358 359 if (smb_ads_ldap_ping(smb_ads_cached_host_info) == 0) { 360 if (!srv) 361 return (B_TRUE); 362 363 if (smb_ads_is_sought_host(smb_ads_cached_host_info, srv)) 364 return (B_TRUE); 365 } 366 367 return (B_FALSE); 368 } 369 370 /* 371 * smb_ads_is_sought_host 372 * 373 * Returns true, if the sought host name matches the input host (host) name. 374 * The sought host is expected to be in Fully Qualified Domain Name (FQDN) 375 * format. 376 */ 377 static boolean_t 378 smb_ads_is_sought_host(smb_ads_host_info_t *host, char *sought_host_name) 379 { 380 if ((host == NULL) || (sought_host_name == NULL)) 381 return (B_FALSE); 382 383 if (smb_strcasecmp(host->name, sought_host_name, 0)) 384 return (B_FALSE); 385 386 return (B_TRUE); 387 } 388 389 /* 390 * smb_ads_match_hosts_same_domain 391 * 392 * Returns true, if the cached ADS host is in the same domain as the 393 * current (given) domain. 394 */ 395 static boolean_t 396 smb_ads_is_same_domain(char *cached_host_name, char *current_domain) 397 { 398 char *cached_host_domain; 399 400 if ((cached_host_name == NULL) || (current_domain == NULL)) 401 return (B_FALSE); 402 403 cached_host_domain = strchr(cached_host_name, '.'); 404 if (cached_host_domain == NULL) 405 return (B_FALSE); 406 407 ++cached_host_domain; 408 if (smb_strcasecmp(cached_host_domain, current_domain, 0)) 409 return (B_FALSE); 410 411 return (B_TRUE); 412 } 413 414 /* 415 * smb_ads_skip_ques_sec 416 * Skips the question section. 417 */ 418 static int 419 smb_ads_skip_ques_sec(int qcnt, uchar_t **ptr, uchar_t *eom) 420 { 421 int i, len; 422 423 for (i = 0; i < qcnt; i++) { 424 if ((len = dn_skipname(*ptr, eom)) < 0) 425 return (-1); 426 427 *ptr += len + QFIXEDSZ; 428 } 429 430 return (0); 431 } 432 433 /* 434 * smb_ads_decode_host_ans_sec 435 * Decodes ADS hosts, priority, weight and port number from the answer 436 * section based on the current buffer pointer. 437 */ 438 static int 439 smb_ads_decode_host_ans_sec(int ans_cnt, uchar_t **ptr, uchar_t *eom, 440 uchar_t *buf, smb_ads_host_info_t *ads_host_list) 441 { 442 int i, len; 443 smb_ads_host_info_t *ads_host; 444 445 for (i = 0; i < ans_cnt; i++) { 446 ads_host = &ads_host_list[i]; 447 448 if ((len = dn_skipname(*ptr, eom)) < 0) 449 return (-1); 450 451 452 *ptr += len; 453 454 /* skip type, class, ttl */ 455 *ptr += 8; 456 /* data size */ 457 *ptr += 2; 458 459 /* Get priority, weight */ 460 /* LINTED: E_CONSTANT_CONDITION */ 461 NS_GET16(ads_host->priority, *ptr); 462 /* LINTED: E_CONSTANT_CONDITION */ 463 NS_GET16(ads_host->weight, *ptr); 464 465 /* port */ 466 /* LINTED: E_CONSTANT_CONDITION */ 467 NS_GET16(ads_host->port, *ptr); 468 /* domain name */ 469 len = dn_expand(buf, eom, *ptr, ads_host->name, MAXHOSTNAMELEN); 470 if (len < 0) 471 return (-1); 472 473 *ptr += len; 474 } 475 476 return (0); 477 } 478 479 /* 480 * smb_ads_skip_auth_sec 481 * Skips the authority section. 482 */ 483 static int 484 smb_ads_skip_auth_sec(int ns_cnt, uchar_t **ptr, uchar_t *eom) 485 { 486 int i, len; 487 uint16_t size; 488 489 for (i = 0; i < ns_cnt; i++) { 490 if ((len = dn_skipname(*ptr, eom)) < 0) 491 return (-1); 492 493 *ptr += len; 494 /* skip type, class, ttl */ 495 *ptr += 8; 496 /* get len of data */ 497 /* LINTED: E_CONSTANT_CONDITION */ 498 NS_GET16(size, *ptr); 499 if ((*ptr + size) > eom) 500 return (-1); 501 502 *ptr += size; 503 } 504 505 return (0); 506 } 507 508 /* 509 * smb_ads_decode_host_ip 510 * 511 * Decodes ADS hosts and IP Addresses from the additional section based 512 * on the current buffer pointer. 513 */ 514 static int 515 smb_ads_decode_host_ip(int addit_cnt, int ans_cnt, uchar_t **ptr, 516 uchar_t *eom, uchar_t *buf, smb_ads_host_info_t *ads_host_list) 517 { 518 int i, j, len; 519 smb_inaddr_t ipaddr; 520 char hostname[MAXHOSTNAMELEN]; 521 char *name; 522 uint16_t size = 0; 523 524 for (i = 0; i < addit_cnt; i++) { 525 526 /* domain name */ 527 len = dn_expand(buf, eom, *ptr, hostname, MAXHOSTNAMELEN); 528 if (len < 0) 529 return (-1); 530 531 *ptr += len; 532 533 /* skip type, class, TTL, data len */ 534 *ptr += 8; 535 /* LINTED: E_CONSTANT_CONDITION */ 536 NS_GET16(size, *ptr); 537 538 if (size == INADDRSZ) { 539 /* LINTED: E_CONSTANT_CONDITION */ 540 NS_GET32(ipaddr.a_ipv4, *ptr); 541 ipaddr.a_ipv4 = htonl(ipaddr.a_ipv4); 542 ipaddr.a_family = AF_INET; 543 } else if (size == IN6ADDRSZ) { 544 #ifdef BIG_ENDIAN 545 bcopy(*ptr, &ipaddr.a_ipv6, IN6ADDRSZ); 546 #else 547 for (i = 0; i < IN6ADDRSZ; i++) 548 (uint8_t *)(ipaddr.a_ipv6) 549 [IN6ADDRSZ-1-i] = *(*ptr+i); 550 #endif 551 ipaddr.a_family = AF_INET6; 552 } 553 554 /* 555 * find the host in the list of DC records from 556 * the answer section, that matches the host in the 557 * additional section, and set its IP address. 558 */ 559 for (j = 0; j < ans_cnt; j++) { 560 if ((name = ads_host_list[j].name) == NULL) 561 continue; 562 if (smb_strcasecmp(name, hostname, 0) == 0) { 563 ads_host_list[j].ipaddr = ipaddr; 564 } 565 } 566 } 567 return (0); 568 } 569 570 /* 571 * smb_ads_dup_host_info 572 * 573 * Duplicates the passed smb_ads_host_info_t structure. 574 * Caller must free memory allocated by this method. 575 * 576 * Returns a reference to the duplicated smb_ads_host_info_t structure. 577 * Returns NULL on error. 578 */ 579 static smb_ads_host_info_t * 580 smb_ads_dup_host_info(smb_ads_host_info_t *ads_host) 581 { 582 smb_ads_host_info_t *dup_host; 583 584 if (ads_host == NULL) 585 return (NULL); 586 587 dup_host = malloc(sizeof (smb_ads_host_info_t)); 588 589 if (dup_host != NULL) 590 bcopy(ads_host, dup_host, sizeof (smb_ads_host_info_t)); 591 592 return (dup_host); 593 } 594 595 /* 596 * smb_ads_hlist_alloc 597 */ 598 static smb_ads_host_list_t * 599 smb_ads_hlist_alloc(int count) 600 { 601 int size; 602 smb_ads_host_list_t *hlist; 603 604 if (count == 0) 605 return (NULL); 606 607 size = sizeof (smb_ads_host_info_t) * count; 608 hlist = (smb_ads_host_list_t *)malloc(sizeof (smb_ads_host_list_t)); 609 if (hlist == NULL) 610 return (NULL); 611 612 hlist->ah_cnt = count; 613 hlist->ah_list = (smb_ads_host_info_t *)malloc(size); 614 if (hlist->ah_list == NULL) { 615 free(hlist); 616 return (NULL); 617 } 618 619 bzero(hlist->ah_list, size); 620 return (hlist); 621 } 622 623 /* 624 * smb_ads_hlist_free 625 */ 626 static void 627 smb_ads_hlist_free(smb_ads_host_list_t *host_list) 628 { 629 if (host_list == NULL) 630 return; 631 632 free(host_list->ah_list); 633 free(host_list); 634 } 635 636 /* 637 * smb_ads_query_dns_server 638 * 639 * This routine sends a DNS service location (SRV) query message to the 640 * DNS server via TCP to query it for a list of ADS server(s). Once a reply 641 * is received, the reply message is parsed to get the hostname. If there are IP 642 * addresses populated in the additional section then the additional section 643 * is parsed to obtain the IP addresses. 644 * 645 * The service location of _ldap._tcp.dc.msdcs.<ADS domain> is used to 646 * guarantee that Microsoft domain controllers are returned. Microsoft domain 647 * controllers are also ADS servers. 648 * 649 * The ADS hostnames are stored in the answer section of the DNS reply message. 650 * The IP addresses are stored in the additional section. 651 * 652 * The DNS reply message may be in compress formed. The compression is done 653 * on repeating domain name label in the message. i.e hostname. 654 * 655 * Upon successful completion, host list of ADS server(s) is returned. 656 */ 657 static smb_ads_host_list_t * 658 smb_ads_query_dns_server(char *domain, char *msdcs_svc_name) 659 { 660 smb_ads_host_list_t *hlist = NULL; 661 int len, qcnt, ans_cnt, ns_cnt, addit_cnt; 662 uchar_t *ptr, *eom; 663 struct __res_state res_state; 664 union { 665 HEADER hdr; 666 uchar_t buf[NS_MAXMSG]; 667 } msg; 668 669 bzero(&res_state, sizeof (struct __res_state)); 670 if (res_ninit(&res_state) < 0) 671 return (NULL); 672 673 /* use TCP */ 674 res_state.options |= RES_USEVC; 675 676 len = res_nquerydomain(&res_state, msdcs_svc_name, domain, 677 C_IN, T_SRV, msg.buf, sizeof (msg.buf)); 678 679 if (len < 0) { 680 syslog(LOG_NOTICE, "DNS query for %s failed: %s", 681 msdcs_svc_name, hstrerror(res_state.res_h_errno)); 682 res_ndestroy(&res_state); 683 return (NULL); 684 } 685 686 if (len > sizeof (msg.buf)) { 687 syslog(LOG_NOTICE, 688 "DNS query for %s failed: too big", msdcs_svc_name); 689 res_ndestroy(&res_state); 690 return (NULL); 691 } 692 693 /* parse the reply, skip header and question sections */ 694 ptr = msg.buf + sizeof (msg.hdr); 695 eom = msg.buf + len; 696 697 /* check truncated message bit */ 698 if (msg.hdr.tc) 699 syslog(LOG_NOTICE, 700 "DNS query for %s failed: truncated", msdcs_svc_name); 701 702 qcnt = ntohs(msg.hdr.qdcount); 703 ans_cnt = ntohs(msg.hdr.ancount); 704 ns_cnt = ntohs(msg.hdr.nscount); 705 addit_cnt = ntohs(msg.hdr.arcount); 706 707 if (smb_ads_skip_ques_sec(qcnt, &ptr, eom) != 0) { 708 res_ndestroy(&res_state); 709 return (NULL); 710 } 711 712 hlist = smb_ads_hlist_alloc(ans_cnt); 713 if (hlist == NULL) { 714 res_ndestroy(&res_state); 715 return (NULL); 716 } 717 718 /* walk through the answer section */ 719 if (smb_ads_decode_host_ans_sec(ans_cnt, &ptr, eom, msg.buf, 720 hlist->ah_list) != 0) { 721 smb_ads_hlist_free(hlist); 722 res_ndestroy(&res_state); 723 return (NULL); 724 } 725 726 /* check authority section */ 727 if (ns_cnt > 0) { 728 if (smb_ads_skip_auth_sec(ns_cnt, &ptr, eom) != 0) { 729 smb_ads_hlist_free(hlist); 730 res_ndestroy(&res_state); 731 return (NULL); 732 } 733 } 734 735 /* 736 * Check additional section to get IP address of ADS host. 737 */ 738 if (addit_cnt > 0) { 739 if (smb_ads_decode_host_ip(addit_cnt, ans_cnt, 740 &ptr, eom, msg.buf, hlist->ah_list) != 0) { 741 smb_ads_hlist_free(hlist); 742 res_ndestroy(&res_state); 743 return (NULL); 744 } 745 } 746 747 res_ndestroy(&res_state); 748 return (hlist); 749 } 750 751 /* 752 * smb_ads_get_site_service 753 * 754 * Gets the msdcs SRV RR for the specified site. 755 */ 756 static void 757 smb_ads_get_site_service(char *site_service, size_t len) 758 { 759 (void) mutex_lock(&smb_ads_cfg.c_mtx); 760 if (*smb_ads_cfg.c_site == '\0') 761 *site_service = '\0'; 762 else 763 (void) snprintf(site_service, len, 764 SMB_ADS_MSDCS_SRV_SITE_RR, smb_ads_cfg.c_site); 765 766 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 767 } 768 769 /* 770 * smb_ads_getipnodebyname 771 * 772 * This method gets the IP address by doing a host name lookup. 773 */ 774 static int 775 smb_ads_getipnodebyname(smb_ads_host_info_t *hentry) 776 { 777 struct hostent *h; 778 int error; 779 780 switch (hentry->ipaddr.a_family) { 781 case AF_INET6: 782 h = getipnodebyname(hentry->name, hentry->ipaddr.a_family, 783 AI_DEFAULT, &error); 784 if (h == NULL || h->h_length != IPV6_ADDR_LEN) 785 return (-1); 786 break; 787 788 case AF_INET: 789 h = getipnodebyname(hentry->name, hentry->ipaddr.a_family, 790 0, &error); 791 if (h == NULL || h->h_length != INADDRSZ) 792 return (-1); 793 break; 794 795 default: 796 return (-1); 797 } 798 bcopy(*(h->h_addr_list), &hentry->ipaddr.a_ip, h->h_length); 799 freehostent(h); 800 return (0); 801 } 802 803 /* 804 * smb_ads_find_host 805 * 806 * Finds an ADS host in a given domain. 807 * 808 * If the cached host is valid, it will be used. Otherwise, a DC will 809 * be selected based on the following criteria: 810 * 811 * 1) pdc (aka preferred DC) configuration 812 * 2) AD site configuration - the scope of the DNS lookup will be 813 * restricted to the specified site. 814 * 3) DC on the same subnet 815 * 4) DC with the lowest priority/highest weight 816 * 817 * The above items are listed in decreasing preference order. The selected 818 * DC must be online. 819 * 820 * If this function is called during domain join, the specified kpasswd server 821 * takes precedence over preferred DC, AD site, and so on. 822 * 823 * Parameters: 824 * domain: fully-qualified domain name. 825 * kpasswd_srv: fully-quailifed hostname of the kpasswd server. 826 * 827 * Returns: 828 * A copy of the cached host info is returned. The caller is responsible 829 * for deallocating the memory returned by this function. 830 */ 831 /*ARGSUSED*/ 832 smb_ads_host_info_t * 833 smb_ads_find_host(char *domain, char *kpasswd_srv) 834 { 835 int i; 836 char site_service[MAXHOSTNAMELEN]; 837 smb_ads_host_list_t *hlist, *hlist2; 838 smb_ads_host_info_t *hlistp = NULL, *host = NULL; 839 smb_ads_host_info_t *found_kpasswd_srv = NULL; 840 smb_ads_host_info_t *found_pdc = NULL; 841 842 if ((kpasswd_srv) && (*kpasswd_srv == '\0')) 843 kpasswd_srv = NULL; 844 845 (void) mutex_lock(&smb_ads_cached_host_mtx); 846 if (smb_ads_validate_cache_host(domain, kpasswd_srv)) { 847 host = smb_ads_dup_host_info(smb_ads_cached_host_info); 848 (void) mutex_unlock(&smb_ads_cached_host_mtx); 849 return (host); 850 } 851 852 (void) mutex_unlock(&smb_ads_cached_host_mtx); 853 smb_ads_free_cached_host(); 854 855 /* 856 * First look for ADS hosts in ADS site if configured. Then try 857 * without ADS site info. 858 */ 859 hlist = NULL; 860 smb_ads_get_site_service(site_service, MAXHOSTNAMELEN); 861 862 /* 863 * If we're given an AD, the DNS SRV RR lookup should not be restricted 864 * to the specified site since there is no guarantee that the specified 865 * AD is in the specified site. 866 */ 867 if (*site_service != '\0' && !kpasswd_srv && 868 !smb_ads_is_pdc_configured()) 869 hlist = smb_ads_query_dns_server(domain, site_service); 870 871 if (!hlist) 872 hlist = smb_ads_query_dns_server(domain, 873 SMB_ADS_MSDCS_SRV_DC_RR); 874 875 if ((hlist == NULL) || (hlist->ah_list == NULL) || (hlist->ah_cnt == 0)) 876 return (NULL); 877 878 for (i = 0, hlistp = hlist->ah_list; i < hlist->ah_cnt; i++) { 879 /* Do a host lookup by hostname to get the IP address */ 880 if (smb_inet_iszero(&hlistp[i].ipaddr)) { 881 if (smb_ads_getipnodebyname(&hlistp[i]) < 0) 882 continue; 883 } 884 885 if (smb_ads_is_sought_host(&hlistp[i], kpasswd_srv)) 886 found_kpasswd_srv = &hlistp[i]; 887 888 if (smb_ads_match_pdc(&hlistp[i])) 889 found_pdc = &hlistp[i]; 890 } 891 892 if (found_kpasswd_srv && smb_ads_ldap_ping(found_kpasswd_srv) == 0) { 893 host = found_kpasswd_srv; 894 goto update_cache; 895 } 896 897 if (found_pdc && smb_ads_ldap_ping(found_pdc) == 0) { 898 host = found_pdc; 899 goto update_cache; 900 } 901 902 /* 903 * If the specified DC (kpasswd_srv or pdc) is not found, fallback 904 * to find a DC in the specified AD site. 905 */ 906 if (*site_service != '\0' && 907 (kpasswd_srv || smb_ads_is_pdc_configured())) { 908 hlist2 = smb_ads_query_dns_server(domain, site_service); 909 if (hlist2 && hlist2->ah_list && hlist2->ah_cnt != 0) { 910 smb_ads_hlist_free(hlist); 911 hlist = hlist2; 912 hlistp = hlist->ah_list; 913 914 for (i = 0; i < hlist->ah_cnt; i++) { 915 if (smb_inet_iszero(&hlistp[i].ipaddr) && 916 smb_ads_getipnodebyname(&hlistp[i]) < 0) 917 continue; 918 } 919 } 920 } 921 922 /* Select DC from DC list */ 923 host = smb_ads_select_dc(hlist); 924 925 update_cache: 926 if (host) { 927 (void) mutex_lock(&smb_ads_cached_host_mtx); 928 if (!smb_ads_cached_host_info) 929 smb_ads_cached_host_info = smb_ads_dup_host_info(host); 930 host = smb_ads_dup_host_info(smb_ads_cached_host_info); 931 (void) mutex_unlock(&smb_ads_cached_host_mtx); 932 } 933 934 smb_ads_hlist_free(hlist); 935 return (host); 936 } 937 938 /* 939 * Return the number of dots in a string. 940 */ 941 static int 942 smb_ads_count_dots(const char *s) 943 { 944 int ndots = 0; 945 946 while (*s) { 947 if (*s++ == '.') 948 ndots++; 949 } 950 951 return (ndots); 952 } 953 954 /* 955 * Convert a domain name in dot notation to distinguished name format, 956 * for example: sun.com -> dc=sun,dc=com. 957 * 958 * Returns a pointer to an allocated buffer containing the distinguished 959 * name. 960 */ 961 static char * 962 smb_ads_convert_domain(const char *domain_name) 963 { 964 const char *s; 965 char *dn_name; 966 char buf[2]; 967 int ndots; 968 int len; 969 970 if (domain_name == NULL || *domain_name == 0) 971 return (NULL); 972 973 ndots = smb_ads_count_dots(domain_name); 974 ++ndots; 975 len = strlen(domain_name) + (ndots * SMB_ADS_DN_PREFIX_LEN) + 1; 976 977 if ((dn_name = malloc(len)) == NULL) 978 return (NULL); 979 980 bzero(dn_name, len); 981 (void) strlcpy(dn_name, "dc=", len); 982 983 buf[1] = '\0'; 984 s = domain_name; 985 986 while (*s) { 987 if (*s == '.') { 988 (void) strlcat(dn_name, ",dc=", len); 989 } else { 990 buf[0] = *s; 991 (void) strlcat(dn_name, buf, len); 992 } 993 ++s; 994 } 995 996 return (dn_name); 997 } 998 999 /* 1000 * smb_ads_free_cached_host 1001 * 1002 * Free the memory use by the global smb_ads_cached_host_info & set it to NULL. 1003 */ 1004 static void 1005 smb_ads_free_cached_host(void) 1006 { 1007 (void) mutex_lock(&smb_ads_cached_host_mtx); 1008 if (smb_ads_cached_host_info) { 1009 free(smb_ads_cached_host_info); 1010 smb_ads_cached_host_info = NULL; 1011 } 1012 (void) mutex_unlock(&smb_ads_cached_host_mtx); 1013 } 1014 1015 /* 1016 * smb_ads_open 1017 * Open a LDAP connection to an ADS server if the system is in domain mode. 1018 * Acquire both Kerberos TGT and LDAP service tickets for the host principal. 1019 * 1020 * This function should only be called after the system is successfully joined 1021 * to a domain. 1022 */ 1023 smb_ads_handle_t * 1024 smb_ads_open(void) 1025 { 1026 char domain[MAXHOSTNAMELEN]; 1027 1028 if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN) 1029 return (NULL); 1030 1031 if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) 1032 return (NULL); 1033 1034 return (smb_ads_open_main(domain, NULL, NULL)); 1035 } 1036 1037 static int 1038 smb_ads_saslcallback(LDAP *ld, unsigned flags, void *defaults, void *prompts) 1039 { 1040 NOTE(ARGUNUSED(ld, defaults)); 1041 sasl_interact_t *interact; 1042 1043 if (prompts == NULL || flags != LDAP_SASL_INTERACTIVE) 1044 return (LDAP_PARAM_ERROR); 1045 1046 /* There should be no extra arguemnts for SASL/GSSAPI authentication */ 1047 for (interact = prompts; interact->id != SASL_CB_LIST_END; 1048 interact++) { 1049 interact->result = NULL; 1050 interact->len = 0; 1051 } 1052 return (LDAP_SUCCESS); 1053 } 1054 1055 /* 1056 * smb_ads_open_main 1057 * Open a LDAP connection to an ADS server. 1058 * If ADS is enabled and the administrative username, password, and 1059 * ADS domain are defined then query DNS to find an ADS server if this is the 1060 * very first call to this routine. After an ADS server is found then this 1061 * server will be used everytime this routine is called until the system is 1062 * rebooted or the ADS server becomes unavailable then an ADS server will 1063 * be queried again. After the connection is made then an ADS handle 1064 * is created to be returned. 1065 * 1066 * After the LDAP connection, the LDAP version will be set to 3 using 1067 * ldap_set_option(). 1068 * 1069 * The LDAP connection is bound before the ADS handle is returned. 1070 * Parameters: 1071 * domain - fully-qualified domain name 1072 * user - the user account for whom the Kerberos TGT ticket and ADS 1073 * service tickets are acquired. 1074 * password - password of the specified user 1075 * 1076 * Returns: 1077 * NULL : can't connect to ADS server or other errors 1078 * smb_ads_handle_t* : handle to ADS server 1079 */ 1080 static smb_ads_handle_t * 1081 smb_ads_open_main(char *domain, char *user, char *password) 1082 { 1083 smb_ads_handle_t *ah; 1084 LDAP *ld; 1085 int version = 3; 1086 smb_ads_host_info_t *ads_host = NULL; 1087 int rc; 1088 1089 if (user != NULL) { 1090 if (smb_kinit(user, password) == 0) 1091 return (NULL); 1092 user = NULL; 1093 password = NULL; 1094 } 1095 1096 ads_host = smb_ads_find_host(domain, NULL); 1097 if (ads_host == NULL) 1098 return (NULL); 1099 1100 ah = (smb_ads_handle_t *)malloc(sizeof (smb_ads_handle_t)); 1101 if (ah == NULL) { 1102 free(ads_host); 1103 return (NULL); 1104 } 1105 1106 (void) memset(ah, 0, sizeof (smb_ads_handle_t)); 1107 1108 if ((ld = ldap_init(ads_host->name, ads_host->port)) == NULL) { 1109 smb_ads_free_cached_host(); 1110 free(ah); 1111 free(ads_host); 1112 return (NULL); 1113 } 1114 1115 if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version) 1116 != LDAP_SUCCESS) { 1117 smb_ads_free_cached_host(); 1118 free(ah); 1119 free(ads_host); 1120 (void) ldap_unbind(ld); 1121 return (NULL); 1122 } 1123 1124 (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); 1125 ah->ld = ld; 1126 ah->domain = strdup(domain); 1127 1128 if (ah->domain == NULL) { 1129 smb_ads_close(ah); 1130 free(ads_host); 1131 return (NULL); 1132 } 1133 1134 ah->domain_dn = smb_ads_convert_domain(domain); 1135 if (ah->domain_dn == NULL) { 1136 smb_ads_close(ah); 1137 free(ads_host); 1138 return (NULL); 1139 } 1140 1141 ah->hostname = strdup(ads_host->name); 1142 if (ah->hostname == NULL) { 1143 smb_ads_close(ah); 1144 free(ads_host); 1145 return (NULL); 1146 } 1147 (void) mutex_lock(&smb_ads_cfg.c_mtx); 1148 if (*smb_ads_cfg.c_site != '\0') { 1149 if ((ah->site = strdup(smb_ads_cfg.c_site)) == NULL) { 1150 smb_ads_close(ah); 1151 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 1152 free(ads_host); 1153 return (NULL); 1154 } 1155 } else { 1156 ah->site = NULL; 1157 } 1158 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 1159 1160 rc = ldap_sasl_interactive_bind_s(ah->ld, "", "GSSAPI", NULL, NULL, 1161 LDAP_SASL_INTERACTIVE, &smb_ads_saslcallback, NULL); 1162 if (rc != LDAP_SUCCESS) { 1163 syslog(LOG_ERR, "ldal_sasl_interactive_bind_s failed (%s)", 1164 ldap_err2string(rc)); 1165 smb_ads_close(ah); 1166 free(ads_host); 1167 return (NULL); 1168 } 1169 1170 free(ads_host); 1171 return (ah); 1172 } 1173 1174 /* 1175 * smb_ads_close 1176 * Close connection to ADS server and free memory allocated for ADS handle. 1177 * LDAP unbind is called here. 1178 * Parameters: 1179 * ah: handle to ADS server 1180 * Returns: 1181 * void 1182 */ 1183 void 1184 smb_ads_close(smb_ads_handle_t *ah) 1185 { 1186 if (ah == NULL) 1187 return; 1188 /* close and free connection resources */ 1189 if (ah->ld) 1190 (void) ldap_unbind(ah->ld); 1191 1192 free(ah->domain); 1193 free(ah->domain_dn); 1194 free(ah->hostname); 1195 free(ah->site); 1196 free(ah); 1197 } 1198 1199 /* 1200 * smb_ads_alloc_attr 1201 * 1202 * Since the attrs is a null-terminated array, all elements 1203 * in the array (except the last one) will point to allocated 1204 * memory. 1205 */ 1206 static int 1207 smb_ads_alloc_attr(LDAPMod *attrs[], int num) 1208 { 1209 int i; 1210 1211 bzero(attrs, num * sizeof (LDAPMod *)); 1212 for (i = 0; i < (num - 1); i++) { 1213 attrs[i] = (LDAPMod *)malloc(sizeof (LDAPMod)); 1214 if (attrs[i] == NULL) { 1215 smb_ads_free_attr(attrs); 1216 return (-1); 1217 } 1218 } 1219 1220 return (0); 1221 } 1222 1223 /* 1224 * smb_ads_free_attr 1225 * Free memory allocated when publishing a share. 1226 * Parameters: 1227 * attrs: an array of LDAPMod pointers 1228 * Returns: 1229 * None 1230 */ 1231 static void 1232 smb_ads_free_attr(LDAPMod *attrs[]) 1233 { 1234 int i; 1235 for (i = 0; attrs[i]; i++) { 1236 free(attrs[i]); 1237 } 1238 } 1239 1240 /* 1241 * smb_ads_get_spnset 1242 * 1243 * Derives the core set of SPNs based on the FQHN. 1244 * The spn_set is a null-terminated array of char pointers. 1245 * 1246 * Returns 0 upon success. Otherwise, returns -1. 1247 */ 1248 static int 1249 smb_ads_get_spnset(char *fqhost, char **spn_set) 1250 { 1251 int i; 1252 1253 bzero(spn_set, (SMBKRB5_SPN_IDX_MAX + 1) * sizeof (char *)); 1254 for (i = 0; i < SMBKRB5_SPN_IDX_MAX; i++) { 1255 if ((spn_set[i] = smb_krb5_get_spn(i, fqhost)) == NULL) { 1256 smb_ads_free_spnset(spn_set); 1257 return (-1); 1258 } 1259 } 1260 1261 return (0); 1262 } 1263 1264 /* 1265 * smb_ads_free_spnset 1266 * 1267 * Free the memory allocated for the set of SPNs. 1268 */ 1269 static void 1270 smb_ads_free_spnset(char **spn_set) 1271 { 1272 int i; 1273 for (i = 0; spn_set[i]; i++) 1274 free(spn_set[i]); 1275 } 1276 1277 /* 1278 * smb_ads_add_share 1279 * Call by smb_ads_publish_share to create share object in ADS. 1280 * This routine specifies the attributes of an ADS LDAP share object. The first 1281 * attribute and values define the type of ADS object, the share object. The 1282 * second attribute and value define the UNC of the share data for the share 1283 * object. The LDAP synchronous add command is used to add the object into ADS. 1284 * The container location to add the object needs to specified. 1285 * Parameters: 1286 * ah : handle to ADS server 1287 * adsShareName: name of share object to be created in ADS 1288 * shareUNC : share name on NetForce 1289 * adsContainer: location in ADS to create share object 1290 * 1291 * Returns: 1292 * -1 : error 1293 * 0 : success 1294 */ 1295 int 1296 smb_ads_add_share(smb_ads_handle_t *ah, const char *adsShareName, 1297 const char *unc_name, const char *adsContainer) 1298 { 1299 LDAPMod *attrs[SMB_ADS_SHARE_NUM_ATTR]; 1300 int j = 0; 1301 char *share_dn; 1302 int len, ret; 1303 char *unc_names[] = {(char *)unc_name, NULL}; 1304 1305 len = 5 + strlen(adsShareName) + strlen(adsContainer) + 1306 strlen(ah->domain_dn) + 1; 1307 1308 share_dn = (char *)malloc(len); 1309 if (share_dn == NULL) 1310 return (-1); 1311 1312 (void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName, 1313 adsContainer, ah->domain_dn); 1314 1315 if (smb_ads_alloc_attr(attrs, SMB_ADS_SHARE_NUM_ATTR) != 0) { 1316 free(share_dn); 1317 return (-1); 1318 } 1319 1320 attrs[j]->mod_op = LDAP_MOD_ADD; 1321 attrs[j]->mod_type = "objectClass"; 1322 attrs[j]->mod_values = smb_ads_share_objcls; 1323 1324 attrs[++j]->mod_op = LDAP_MOD_ADD; 1325 attrs[j]->mod_type = "uNCName"; 1326 attrs[j]->mod_values = unc_names; 1327 1328 if ((ret = ldap_add_s(ah->ld, share_dn, attrs)) != LDAP_SUCCESS) { 1329 smb_tracef("%s: ldap_add: %s", share_dn, ldap_err2string(ret)); 1330 smb_ads_free_attr(attrs); 1331 free(share_dn); 1332 return (ret); 1333 } 1334 free(share_dn); 1335 smb_ads_free_attr(attrs); 1336 1337 return (0); 1338 } 1339 1340 /* 1341 * smb_ads_del_share 1342 * Call by smb_ads_remove_share to remove share object from ADS. The container 1343 * location to remove the object needs to specified. The LDAP synchronous 1344 * delete command is used. 1345 * Parameters: 1346 * ah : handle to ADS server 1347 * adsShareName: name of share object in ADS to be removed 1348 * adsContainer: location of share object in ADS 1349 * Returns: 1350 * -1 : error 1351 * 0 : success 1352 */ 1353 static int 1354 smb_ads_del_share(smb_ads_handle_t *ah, const char *adsShareName, 1355 const char *adsContainer) 1356 { 1357 char *share_dn; 1358 int len, ret; 1359 1360 len = 5 + strlen(adsShareName) + strlen(adsContainer) + 1361 strlen(ah->domain_dn) + 1; 1362 1363 share_dn = (char *)malloc(len); 1364 if (share_dn == NULL) 1365 return (-1); 1366 1367 (void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName, 1368 adsContainer, ah->domain_dn); 1369 if ((ret = ldap_delete_s(ah->ld, share_dn)) != LDAP_SUCCESS) { 1370 smb_tracef("ldap_delete: %s", ldap_err2string(ret)); 1371 free(share_dn); 1372 return (-1); 1373 } 1374 free(share_dn); 1375 1376 return (0); 1377 } 1378 1379 1380 /* 1381 * smb_ads_escape_search_filter_chars 1382 * 1383 * This routine will escape the special characters found in a string 1384 * that will later be passed to the ldap search filter. 1385 * 1386 * RFC 1960 - A String Representation of LDAP Search Filters 1387 * 3. String Search Filter Definition 1388 * If a value must contain one of the characters '*' OR '(' OR ')', 1389 * these characters 1390 * should be escaped by preceding them with the backslash '\' character. 1391 * 1392 * RFC 2252 - LDAP Attribute Syntax Definitions 1393 * a backslash quoting mechanism is used to escape 1394 * the following separator symbol character (such as "'", "$" or "#") if 1395 * it should occur in that string. 1396 */ 1397 static int 1398 smb_ads_escape_search_filter_chars(const char *src, char *dst) 1399 { 1400 int avail = SMB_ADS_MAXBUFLEN - 1; /* reserve a space for NULL char */ 1401 1402 if (src == NULL || dst == NULL) 1403 return (-1); 1404 1405 while (*src) { 1406 if (!avail) { 1407 *dst = 0; 1408 return (-1); 1409 } 1410 1411 switch (*src) { 1412 case '\\': 1413 case '\'': 1414 case '$': 1415 case '#': 1416 case '*': 1417 case '(': 1418 case ')': 1419 *dst++ = '\\'; 1420 avail--; 1421 /* fall through */ 1422 1423 default: 1424 *dst++ = *src++; 1425 avail--; 1426 } 1427 } 1428 1429 *dst = 0; 1430 1431 return (0); 1432 } 1433 1434 /* 1435 * smb_ads_lookup_share 1436 * The search filter is set to search for a specific share name in the 1437 * specified ADS container. The LDSAP synchronous search command is used. 1438 * Parameters: 1439 * ah : handle to ADS server 1440 * adsShareName: name of share object in ADS to be searched 1441 * adsContainer: location of share object in ADS 1442 * Returns: 1443 * -1 : error 1444 * 0 : not found 1445 * 1 : found 1446 */ 1447 int 1448 smb_ads_lookup_share(smb_ads_handle_t *ah, const char *adsShareName, 1449 const char *adsContainer, char *unc_name) 1450 { 1451 char *attrs[4], filter[SMB_ADS_MAXBUFLEN]; 1452 char *share_dn; 1453 int len, ret; 1454 LDAPMessage *res; 1455 char tmpbuf[SMB_ADS_MAXBUFLEN]; 1456 1457 if (adsShareName == NULL || adsContainer == NULL) 1458 return (-1); 1459 1460 len = 5 + strlen(adsShareName) + strlen(adsContainer) + 1461 strlen(ah->domain_dn) + 1; 1462 1463 share_dn = (char *)malloc(len); 1464 if (share_dn == NULL) 1465 return (-1); 1466 1467 (void) snprintf(share_dn, len, "cn=%s,%s,%s", adsShareName, 1468 adsContainer, ah->domain_dn); 1469 1470 res = NULL; 1471 attrs[0] = "cn"; 1472 attrs[1] = "objectClass"; 1473 attrs[2] = "uNCName"; 1474 attrs[3] = NULL; 1475 1476 if (smb_ads_escape_search_filter_chars(unc_name, tmpbuf) != 0) { 1477 free(share_dn); 1478 return (-1); 1479 } 1480 1481 (void) snprintf(filter, sizeof (filter), 1482 "(&(objectClass=volume)(uNCName=%s))", tmpbuf); 1483 1484 if ((ret = ldap_search_s(ah->ld, share_dn, 1485 LDAP_SCOPE_BASE, filter, attrs, 0, &res)) != LDAP_SUCCESS) { 1486 if (ret != LDAP_NO_SUCH_OBJECT) 1487 smb_tracef("%s: ldap_search: %s", share_dn, 1488 ldap_err2string(ret)); 1489 1490 (void) ldap_msgfree(res); 1491 free(share_dn); 1492 return (0); 1493 } 1494 1495 (void) free(share_dn); 1496 1497 /* no match is found */ 1498 if (ldap_count_entries(ah->ld, res) == 0) { 1499 (void) ldap_msgfree(res); 1500 return (0); 1501 } 1502 1503 /* free the search results */ 1504 (void) ldap_msgfree(res); 1505 1506 return (1); 1507 } 1508 1509 /* 1510 * smb_ads_publish_share 1511 * Publish share into ADS. If a share name already exist in ADS in the same 1512 * container then the existing share object is removed before adding the new 1513 * share object. 1514 * Parameters: 1515 * ah : handle return from smb_ads_open 1516 * adsShareName: name of share to be added to ADS directory 1517 * shareUNC : name of share on client, can be NULL to use the same name 1518 * as adsShareName 1519 * adsContainer: location for share to be added in ADS directory, ie 1520 * ou=share_folder 1521 * uncType : use UNC_HOSTNAME to use hostname for UNC, use UNC_HOSTADDR 1522 * to use host ip addr for UNC. 1523 * Returns: 1524 * -1 : error 1525 * 0 : success 1526 */ 1527 int 1528 smb_ads_publish_share(smb_ads_handle_t *ah, const char *adsShareName, 1529 const char *shareUNC, const char *adsContainer, const char *hostname) 1530 { 1531 int ret; 1532 char unc_name[SMB_ADS_MAXBUFLEN]; 1533 1534 if (adsShareName == NULL || adsContainer == NULL) 1535 return (-1); 1536 1537 if (shareUNC == 0 || *shareUNC == 0) 1538 shareUNC = adsShareName; 1539 1540 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name), 1541 hostname, shareUNC) < 0) 1542 return (-1); 1543 1544 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name); 1545 1546 switch (ret) { 1547 case 1: 1548 (void) smb_ads_del_share(ah, adsShareName, adsContainer); 1549 ret = smb_ads_add_share(ah, adsShareName, unc_name, 1550 adsContainer); 1551 break; 1552 1553 case 0: 1554 ret = smb_ads_add_share(ah, adsShareName, unc_name, 1555 adsContainer); 1556 if (ret == LDAP_ALREADY_EXISTS) 1557 ret = -1; 1558 1559 break; 1560 1561 case -1: 1562 default: 1563 /* return with error code */ 1564 ret = -1; 1565 } 1566 1567 return (ret); 1568 } 1569 1570 /* 1571 * smb_ads_remove_share 1572 * Remove share from ADS. A search is done first before explicitly removing 1573 * the share. 1574 * Parameters: 1575 * ah : handle return from smb_ads_open 1576 * adsShareName: name of share to be removed from ADS directory 1577 * adsContainer: location for share to be removed from ADS directory, ie 1578 * ou=share_folder 1579 * Returns: 1580 * -1 : error 1581 * 0 : success 1582 */ 1583 int 1584 smb_ads_remove_share(smb_ads_handle_t *ah, const char *adsShareName, 1585 const char *shareUNC, const char *adsContainer, const char *hostname) 1586 { 1587 int ret; 1588 char unc_name[SMB_ADS_MAXBUFLEN]; 1589 1590 if (adsShareName == NULL || adsContainer == NULL) 1591 return (-1); 1592 if (shareUNC == 0 || *shareUNC == 0) 1593 shareUNC = adsShareName; 1594 1595 if (smb_ads_build_unc_name(unc_name, sizeof (unc_name), 1596 hostname, shareUNC) < 0) 1597 return (-1); 1598 1599 ret = smb_ads_lookup_share(ah, adsShareName, adsContainer, unc_name); 1600 if (ret == 0) 1601 return (0); 1602 if (ret == -1) 1603 return (-1); 1604 1605 return (smb_ads_del_share(ah, adsShareName, adsContainer)); 1606 } 1607 1608 /* 1609 * smb_ads_get_default_comp_container_dn 1610 * 1611 * Build the distinguished name for the default computer conatiner (i.e. the 1612 * pre-defined Computers container). 1613 */ 1614 static void 1615 smb_ads_get_default_comp_container_dn(smb_ads_handle_t *ah, char *buf, 1616 size_t buflen) 1617 { 1618 (void) snprintf(buf, buflen, "cn=%s,%s", SMB_ADS_COMPUTERS_CN, 1619 ah->domain_dn); 1620 } 1621 1622 /* 1623 * smb_ads_get_default_comp_dn 1624 * 1625 * Build the distinguished name for this system. 1626 */ 1627 static void 1628 smb_ads_get_default_comp_dn(smb_ads_handle_t *ah, char *buf, size_t buflen) 1629 { 1630 char nbname[NETBIOS_NAME_SZ]; 1631 char container_dn[SMB_ADS_DN_MAX]; 1632 1633 (void) smb_getnetbiosname(nbname, sizeof (nbname)); 1634 smb_ads_get_default_comp_container_dn(ah, container_dn, SMB_ADS_DN_MAX); 1635 (void) snprintf(buf, buflen, "cn=%s,%s", nbname, container_dn); 1636 } 1637 1638 /* 1639 * smb_ads_add_computer 1640 * 1641 * Returns 0 upon success. Otherwise, returns -1. 1642 */ 1643 static int 1644 smb_ads_add_computer(smb_ads_handle_t *ah, int dclevel, char *dn) 1645 { 1646 return (smb_ads_computer_op(ah, LDAP_MOD_ADD, dclevel, dn)); 1647 } 1648 1649 /* 1650 * smb_ads_modify_computer 1651 * 1652 * Returns 0 upon success. Otherwise, returns -1. 1653 */ 1654 static int 1655 smb_ads_modify_computer(smb_ads_handle_t *ah, int dclevel, char *dn) 1656 { 1657 return (smb_ads_computer_op(ah, LDAP_MOD_REPLACE, dclevel, dn)); 1658 } 1659 1660 /* 1661 * smb_ads_get_dc_level 1662 * 1663 * Returns the functional level of the DC upon success. 1664 * Otherwise, -1 is returned. 1665 */ 1666 static int 1667 smb_ads_get_dc_level(smb_ads_handle_t *ah) 1668 { 1669 LDAPMessage *res, *entry; 1670 char *attr[2]; 1671 char **vals; 1672 int rc = -1; 1673 1674 res = NULL; 1675 attr[0] = SMB_ADS_ATTR_DCLEVEL; 1676 attr[1] = NULL; 1677 if (ldap_search_s(ah->ld, "", LDAP_SCOPE_BASE, NULL, attr, 1678 0, &res) != LDAP_SUCCESS) { 1679 (void) ldap_msgfree(res); 1680 return (-1); 1681 } 1682 1683 /* no match for the specified attribute is found */ 1684 if (ldap_count_entries(ah->ld, res) == 0) { 1685 (void) ldap_msgfree(res); 1686 return (-1); 1687 } 1688 1689 entry = ldap_first_entry(ah->ld, res); 1690 if (entry) { 1691 if ((vals = ldap_get_values(ah->ld, entry, 1692 SMB_ADS_ATTR_DCLEVEL)) == NULL) { 1693 /* 1694 * Observed the values aren't populated 1695 * by the Windows 2000 server. 1696 */ 1697 (void) ldap_msgfree(res); 1698 return (SMB_ADS_DCLEVEL_W2K); 1699 } 1700 1701 if (vals[0] != NULL) 1702 rc = atoi(vals[0]); 1703 1704 ldap_value_free(vals); 1705 } 1706 1707 (void) ldap_msgfree(res); 1708 return (rc); 1709 } 1710 1711 static int 1712 smb_ads_getfqhostname(smb_ads_handle_t *ah, char *fqhost, int len) 1713 { 1714 if (smb_gethostname(fqhost, len, 0) != 0) 1715 return (-1); 1716 1717 (void) snprintf(fqhost, len, "%s.%s", fqhost, 1718 ah->domain); 1719 1720 return (0); 1721 } 1722 1723 static int 1724 smb_ads_computer_op(smb_ads_handle_t *ah, int op, int dclevel, char *dn) 1725 { 1726 LDAPMod *attrs[SMB_ADS_COMPUTER_NUM_ATTR]; 1727 char *sam_val[2], *usr_val[2]; 1728 char *spn_set[SMBKRB5_SPN_IDX_MAX + 1], *ctl_val[2], *fqh_val[2]; 1729 char *encrypt_val[2]; 1730 int j = -1; 1731 int ret, usrctl_flags = 0; 1732 char sam_acct[SMB_SAMACCT_MAXLEN]; 1733 char fqhost[MAXHOSTNAMELEN]; 1734 char *user_principal; 1735 char usrctl_buf[16]; 1736 char encrypt_buf[16]; 1737 int max; 1738 1739 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0) 1740 return (-1); 1741 1742 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN)) 1743 return (-1); 1744 1745 if (smb_ads_get_spnset(fqhost, spn_set) != 0) 1746 return (-1); 1747 1748 user_principal = smb_krb5_get_upn(spn_set[SMBKRB5_SPN_IDX_HOST], 1749 ah->domain); 1750 1751 if (user_principal == NULL) { 1752 smb_ads_free_spnset(spn_set); 1753 return (-1); 1754 } 1755 1756 max = (SMB_ADS_COMPUTER_NUM_ATTR - ((op != LDAP_MOD_ADD) ? 1 : 0)) 1757 - (dclevel >= SMB_ADS_DCLEVEL_W2K8 ? 0 : 1); 1758 1759 if (smb_ads_alloc_attr(attrs, max) != 0) { 1760 free(user_principal); 1761 smb_ads_free_spnset(spn_set); 1762 return (-1); 1763 } 1764 1765 /* objectClass attribute is not modifiable. */ 1766 if (op == LDAP_MOD_ADD) { 1767 attrs[++j]->mod_op = op; 1768 attrs[j]->mod_type = "objectClass"; 1769 attrs[j]->mod_values = smb_ads_computer_objcls; 1770 } 1771 1772 attrs[++j]->mod_op = op; 1773 attrs[j]->mod_type = SMB_ADS_ATTR_SAMACCT; 1774 sam_val[0] = sam_acct; 1775 sam_val[1] = 0; 1776 attrs[j]->mod_values = sam_val; 1777 1778 attrs[++j]->mod_op = op; 1779 attrs[j]->mod_type = SMB_ADS_ATTR_UPN; 1780 usr_val[0] = user_principal; 1781 usr_val[1] = 0; 1782 attrs[j]->mod_values = usr_val; 1783 1784 attrs[++j]->mod_op = op; 1785 attrs[j]->mod_type = SMB_ADS_ATTR_SPN; 1786 attrs[j]->mod_values = spn_set; 1787 1788 attrs[++j]->mod_op = op; 1789 attrs[j]->mod_type = SMB_ADS_ATTR_CTL; 1790 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | 1791 SMB_ADS_USER_ACCT_CTL_PASSWD_NOTREQD | 1792 SMB_ADS_USER_ACCT_CTL_ACCOUNTDISABLE); 1793 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", usrctl_flags); 1794 ctl_val[0] = usrctl_buf; 1795 ctl_val[1] = 0; 1796 attrs[j]->mod_values = ctl_val; 1797 1798 attrs[++j]->mod_op = op; 1799 attrs[j]->mod_type = SMB_ADS_ATTR_DNSHOST; 1800 fqh_val[0] = fqhost; 1801 fqh_val[1] = 0; 1802 attrs[j]->mod_values = fqh_val; 1803 1804 /* enctypes support starting in Windows Server 2008 */ 1805 if (dclevel > SMB_ADS_DCLEVEL_W2K3) { 1806 attrs[++j]->mod_op = op; 1807 attrs[j]->mod_type = SMB_ADS_ATTR_ENCTYPES; 1808 (void) snprintf(encrypt_buf, sizeof (encrypt_buf), "%d", 1809 SMB_ADS_ENC_AES256 + SMB_ADS_ENC_AES128 + SMB_ADS_ENC_RC4 + 1810 SMB_ADS_ENC_DES_MD5 + SMB_ADS_ENC_DES_CRC); 1811 encrypt_val[0] = encrypt_buf; 1812 encrypt_val[1] = 0; 1813 attrs[j]->mod_values = encrypt_val; 1814 } 1815 1816 switch (op) { 1817 case LDAP_MOD_ADD: 1818 if ((ret = ldap_add_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { 1819 syslog(LOG_NOTICE, "ldap_add: %s", 1820 ldap_err2string(ret)); 1821 ret = -1; 1822 } 1823 break; 1824 1825 case LDAP_MOD_REPLACE: 1826 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { 1827 syslog(LOG_NOTICE, "ldap_modify: %s", 1828 ldap_err2string(ret)); 1829 ret = -1; 1830 } 1831 break; 1832 1833 default: 1834 ret = -1; 1835 1836 } 1837 1838 smb_ads_free_attr(attrs); 1839 free(user_principal); 1840 smb_ads_free_spnset(spn_set); 1841 1842 return (ret); 1843 } 1844 1845 /* 1846 * Delete an ADS computer account. 1847 */ 1848 static void 1849 smb_ads_del_computer(smb_ads_handle_t *ah, char *dn) 1850 { 1851 int rc; 1852 1853 if ((rc = ldap_delete_s(ah->ld, dn)) != LDAP_SUCCESS) 1854 smb_tracef("ldap_delete: %s", ldap_err2string(rc)); 1855 } 1856 1857 /* 1858 * Gets the value of the given attribute. 1859 */ 1860 static smb_ads_qstat_t 1861 smb_ads_getattr(LDAP *ld, LDAPMessage *entry, smb_ads_avpair_t *avpair) 1862 { 1863 char **vals; 1864 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND; 1865 1866 assert(avpair); 1867 avpair->avp_val = NULL; 1868 vals = ldap_get_values(ld, entry, avpair->avp_attr); 1869 if (!vals) 1870 return (SMB_ADS_STAT_NOT_FOUND); 1871 1872 if (!vals[0]) { 1873 ldap_value_free(vals); 1874 return (SMB_ADS_STAT_NOT_FOUND); 1875 } 1876 1877 avpair->avp_val = strdup(vals[0]); 1878 if (!avpair->avp_val) 1879 rc = SMB_ADS_STAT_ERR; 1880 1881 ldap_value_free(vals); 1882 return (rc); 1883 } 1884 1885 /* 1886 * Process query's result. 1887 */ 1888 static smb_ads_qstat_t 1889 smb_ads_get_qstat(smb_ads_handle_t *ah, LDAPMessage *res, 1890 smb_ads_avpair_t *avpair) 1891 { 1892 char fqhost[MAXHOSTNAMELEN]; 1893 smb_ads_avpair_t dnshost_avp; 1894 smb_ads_qstat_t rc = SMB_ADS_STAT_FOUND; 1895 LDAPMessage *entry; 1896 1897 if (smb_ads_getfqhostname(ah, fqhost, MAXHOSTNAMELEN)) 1898 return (SMB_ADS_STAT_ERR); 1899 1900 if (ldap_count_entries(ah->ld, res) == 0) 1901 return (SMB_ADS_STAT_NOT_FOUND); 1902 1903 if ((entry = ldap_first_entry(ah->ld, res)) == NULL) 1904 return (SMB_ADS_STAT_ERR); 1905 1906 dnshost_avp.avp_attr = SMB_ADS_ATTR_DNSHOST; 1907 rc = smb_ads_getattr(ah->ld, entry, &dnshost_avp); 1908 1909 switch (rc) { 1910 case SMB_ADS_STAT_FOUND: 1911 /* 1912 * Returns SMB_ADS_STAT_DUP to avoid overwriting 1913 * the computer account of another system whose 1914 * NetBIOS name collides with that of the current 1915 * system. 1916 */ 1917 if (strcasecmp(dnshost_avp.avp_val, fqhost)) 1918 rc = SMB_ADS_STAT_DUP; 1919 1920 free(dnshost_avp.avp_val); 1921 break; 1922 1923 case SMB_ADS_STAT_NOT_FOUND: 1924 /* 1925 * Pre-created computer account doesn't have 1926 * the dNSHostname attribute. It's been observed 1927 * that the dNSHostname attribute is only set after 1928 * a successful domain join. 1929 * Returns SMB_ADS_STAT_FOUND as the account is 1930 * pre-created for the current system. 1931 */ 1932 rc = SMB_ADS_STAT_FOUND; 1933 break; 1934 1935 default: 1936 break; 1937 } 1938 1939 if (rc != SMB_ADS_STAT_FOUND) 1940 return (rc); 1941 1942 if (avpair) 1943 rc = smb_ads_getattr(ah->ld, entry, avpair); 1944 1945 return (rc); 1946 1947 } 1948 1949 /* 1950 * smb_ads_lookup_computer_n_attr 1951 * 1952 * If avpair is NULL, checks the status of the specified computer account. 1953 * Otherwise, looks up the value of the specified computer account's attribute. 1954 * If found, the value field of the avpair will be allocated and set. The 1955 * caller should free the allocated buffer. 1956 * 1957 * Return: 1958 * SMB_ADS_STAT_FOUND - if both the computer and the specified attribute is 1959 * found. 1960 * SMB_ADS_STAT_NOT_FOUND - if either the computer or the specified attribute 1961 * is not found. 1962 * SMB_ADS_STAT_DUP - if the computer account is already used by other systems 1963 * in the AD. This could happen if the hostname of multiple 1964 * systems resolved to the same NetBIOS name. 1965 * SMB_ADS_STAT_ERR - any failure. 1966 */ 1967 static smb_ads_qstat_t 1968 smb_ads_lookup_computer_n_attr(smb_ads_handle_t *ah, smb_ads_avpair_t *avpair, 1969 int scope, char *dn) 1970 { 1971 char *attrs[3], filter[SMB_ADS_MAXBUFLEN]; 1972 LDAPMessage *res; 1973 char sam_acct[SMB_SAMACCT_MAXLEN], sam_acct2[SMB_SAMACCT_MAXLEN]; 1974 smb_ads_qstat_t rc; 1975 1976 if (smb_getsamaccount(sam_acct, sizeof (sam_acct)) != 0) 1977 return (SMB_ADS_STAT_ERR); 1978 1979 res = NULL; 1980 attrs[0] = SMB_ADS_ATTR_DNSHOST; 1981 attrs[1] = NULL; 1982 attrs[2] = NULL; 1983 1984 if (avpair) { 1985 if (!avpair->avp_attr) 1986 return (SMB_ADS_STAT_ERR); 1987 1988 attrs[1] = avpair->avp_attr; 1989 } 1990 1991 if (smb_ads_escape_search_filter_chars(sam_acct, sam_acct2) != 0) 1992 return (SMB_ADS_STAT_ERR); 1993 1994 (void) snprintf(filter, sizeof (filter), 1995 "(&(objectClass=computer)(%s=%s))", SMB_ADS_ATTR_SAMACCT, 1996 sam_acct2); 1997 1998 if (ldap_search_s(ah->ld, dn, scope, filter, attrs, 0, 1999 &res) != LDAP_SUCCESS) { 2000 (void) ldap_msgfree(res); 2001 return (SMB_ADS_STAT_NOT_FOUND); 2002 } 2003 2004 rc = smb_ads_get_qstat(ah, res, avpair); 2005 /* free the search results */ 2006 (void) ldap_msgfree(res); 2007 return (rc); 2008 } 2009 2010 /* 2011 * smb_ads_find_computer 2012 * 2013 * Starts by searching for the system's AD computer object in the default 2014 * container (i.e. cn=Computers). If not found, searches the entire directory. 2015 * If found, 'dn' will be set to the distinguished name of the system's AD 2016 * computer object. 2017 */ 2018 static smb_ads_qstat_t 2019 smb_ads_find_computer(smb_ads_handle_t *ah, char *dn) 2020 { 2021 smb_ads_qstat_t stat; 2022 smb_ads_avpair_t avpair; 2023 2024 avpair.avp_attr = SMB_ADS_ATTR_DN; 2025 smb_ads_get_default_comp_container_dn(ah, dn, SMB_ADS_DN_MAX); 2026 stat = smb_ads_lookup_computer_n_attr(ah, &avpair, LDAP_SCOPE_ONELEVEL, 2027 dn); 2028 2029 if (stat == SMB_ADS_STAT_NOT_FOUND) { 2030 (void) strlcpy(dn, ah->domain_dn, SMB_ADS_DN_MAX); 2031 stat = smb_ads_lookup_computer_n_attr(ah, &avpair, 2032 LDAP_SCOPE_SUBTREE, dn); 2033 } 2034 2035 if (stat == SMB_ADS_STAT_FOUND) { 2036 (void) strlcpy(dn, avpair.avp_val, SMB_ADS_DN_MAX); 2037 free(avpair.avp_val); 2038 } 2039 2040 return (stat); 2041 } 2042 2043 /* 2044 * smb_ads_update_computer_cntrl_attr 2045 * 2046 * Modify the user account control attribute of an existing computer 2047 * object on AD. 2048 * 2049 * Returns LDAP error code. 2050 */ 2051 static int 2052 smb_ads_update_computer_cntrl_attr(smb_ads_handle_t *ah, int flags, char *dn) 2053 { 2054 LDAPMod *attrs[2]; 2055 char *ctl_val[2]; 2056 int ret = 0; 2057 char usrctl_buf[16]; 2058 2059 if (smb_ads_alloc_attr(attrs, sizeof (attrs) / sizeof (LDAPMod *)) != 0) 2060 return (LDAP_NO_MEMORY); 2061 2062 attrs[0]->mod_op = LDAP_MOD_REPLACE; 2063 attrs[0]->mod_type = SMB_ADS_ATTR_CTL; 2064 2065 (void) snprintf(usrctl_buf, sizeof (usrctl_buf), "%d", flags); 2066 ctl_val[0] = usrctl_buf; 2067 ctl_val[1] = 0; 2068 attrs[0]->mod_values = ctl_val; 2069 if ((ret = ldap_modify_s(ah->ld, dn, attrs)) != LDAP_SUCCESS) { 2070 syslog(LOG_NOTICE, "ldap_modify: %s", ldap_err2string(ret)); 2071 } 2072 2073 smb_ads_free_attr(attrs); 2074 return (ret); 2075 } 2076 2077 /* 2078 * smb_ads_lookup_computer_attr_kvno 2079 * 2080 * Lookup the value of the Kerberos version number attribute of the computer 2081 * account. 2082 */ 2083 static krb5_kvno 2084 smb_ads_lookup_computer_attr_kvno(smb_ads_handle_t *ah, char *dn) 2085 { 2086 smb_ads_avpair_t avpair; 2087 int kvno = 1; 2088 2089 avpair.avp_attr = SMB_ADS_ATTR_KVNO; 2090 if (smb_ads_lookup_computer_n_attr(ah, &avpair, 2091 LDAP_SCOPE_BASE, dn) == SMB_ADS_STAT_FOUND) { 2092 kvno = atoi(avpair.avp_val); 2093 free(avpair.avp_val); 2094 } 2095 2096 return (kvno); 2097 } 2098 2099 /* 2100 * smb_ads_gen_machine_passwd 2101 * 2102 * Returned a null-terminated machine password generated randomly 2103 * from [0-9a-zA-Z] character set. In order to pass the password 2104 * quality check (three character classes), an uppercase letter is 2105 * used as the first character of the machine password. 2106 */ 2107 static void 2108 smb_ads_gen_machine_passwd(char *machine_passwd, int bufsz) 2109 { 2110 char *data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJK" 2111 "LMNOPQRSTUVWXYZ"; 2112 int datalen = strlen(data); 2113 int i, data_idx; 2114 2115 assert(machine_passwd); 2116 assert(bufsz > 0); 2117 2118 /* 2119 * The decimal value of upper case 'A' is 65. Randomly pick 2120 * an upper-case letter from the ascii table. 2121 */ 2122 machine_passwd[0] = (random() % 26) + 65; 2123 for (i = 1; i < bufsz - 1; i++) { 2124 data_idx = random() % datalen; 2125 machine_passwd[i] = data[data_idx]; 2126 } 2127 2128 machine_passwd[bufsz - 1] = 0; 2129 } 2130 2131 /* 2132 * smb_ads_join 2133 * 2134 * Besides the NT-4 style domain join (using MS-RPC), CIFS server also 2135 * provides the domain join using Kerberos Authentication, Keberos 2136 * Change & Set password, and LDAP protocols. Basically, AD join 2137 * operation would require the following tickets to be acquired for the 2138 * the user account that is provided for the domain join. 2139 * 2140 * 1) a Keberos TGT ticket, 2141 * 2) a ldap service ticket, and 2142 * 3) kadmin/changpw service ticket 2143 * 2144 * The ADS client first sends a ldap search request to find out whether 2145 * or not the workstation trust account already exists in the Active Directory. 2146 * The existing computer object for this workstation will be removed and 2147 * a new one will be added. The machine account password is randomly 2148 * generated and set for the newly created computer object using KPASSWD 2149 * protocol (See RFC 3244). Once the password is set, our ADS client 2150 * finalizes the machine account by modifying the user acount control 2151 * attribute of the computer object. Kerberos keys derived from the machine 2152 * account password will be stored locally in /etc/krb5/krb5.keytab file. 2153 * That would be needed while acquiring Kerberos TGT ticket for the host 2154 * principal after the domain join operation. 2155 */ 2156 smb_adjoin_status_t 2157 smb_ads_join(char *domain, char *user, char *usr_passwd, char *machine_passwd, 2158 int len) 2159 { 2160 smb_ads_handle_t *ah = NULL; 2161 krb5_context ctx = NULL; 2162 krb5_principal krb5princs[SMBKRB5_SPN_IDX_MAX]; 2163 krb5_kvno kvno; 2164 boolean_t des_only, delete = B_TRUE; 2165 smb_adjoin_status_t rc = SMB_ADJOIN_SUCCESS; 2166 boolean_t new_acct; 2167 int dclevel, num, usrctl_flags = 0; 2168 smb_ads_qstat_t qstat; 2169 char dn[SMB_ADS_DN_MAX]; 2170 char *tmpfile; 2171 2172 /* 2173 * Call library functions that can be used to get 2174 * the list of encryption algorithms available on the system. 2175 * (similar to what 'encrypt -l' CLI does). For now, 2176 * unless someone has modified the configuration of the 2177 * cryptographic framework (very unlikely), the following is the 2178 * list of algorithms available on any system running Nevada 2179 * by default. 2180 */ 2181 krb5_enctype w2k8enctypes[] = {ENCTYPE_AES256_CTS_HMAC_SHA1_96, 2182 ENCTYPE_AES128_CTS_HMAC_SHA1_96, ENCTYPE_ARCFOUR_HMAC, 2183 ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, 2184 }; 2185 2186 krb5_enctype pre_w2k8enctypes[] = {ENCTYPE_ARCFOUR_HMAC, 2187 ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, 2188 }; 2189 2190 krb5_enctype *encptr; 2191 2192 if ((ah = smb_ads_open_main(domain, user, usr_passwd)) == NULL) { 2193 smb_ccache_remove(SMB_CCACHE_PATH); 2194 return (SMB_ADJOIN_ERR_GET_HANDLE); 2195 } 2196 2197 smb_ads_gen_machine_passwd(machine_passwd, len); 2198 2199 if ((dclevel = smb_ads_get_dc_level(ah)) == -1) { 2200 smb_ads_close(ah); 2201 smb_ccache_remove(SMB_CCACHE_PATH); 2202 return (SMB_ADJOIN_ERR_GET_DCLEVEL); 2203 } 2204 2205 qstat = smb_ads_find_computer(ah, dn); 2206 switch (qstat) { 2207 case SMB_ADS_STAT_FOUND: 2208 new_acct = B_FALSE; 2209 if (smb_ads_modify_computer(ah, dclevel, dn) != 0) { 2210 smb_ads_close(ah); 2211 smb_ccache_remove(SMB_CCACHE_PATH); 2212 return (SMB_ADJOIN_ERR_MOD_TRUST_ACCT); 2213 } 2214 break; 2215 2216 case SMB_ADS_STAT_NOT_FOUND: 2217 new_acct = B_TRUE; 2218 smb_ads_get_default_comp_dn(ah, dn, SMB_ADS_DN_MAX); 2219 if (smb_ads_add_computer(ah, dclevel, dn) != 0) { 2220 smb_ads_close(ah); 2221 smb_ccache_remove(SMB_CCACHE_PATH); 2222 return (SMB_ADJOIN_ERR_ADD_TRUST_ACCT); 2223 } 2224 break; 2225 2226 default: 2227 if (qstat == SMB_ADS_STAT_DUP) 2228 rc = SMB_ADJOIN_ERR_DUP_TRUST_ACCT; 2229 else 2230 rc = SMB_ADJOIN_ERR_TRUST_ACCT; 2231 smb_ads_close(ah); 2232 smb_ccache_remove(SMB_CCACHE_PATH); 2233 return (rc); 2234 } 2235 2236 des_only = B_FALSE; 2237 2238 if (smb_krb5_ctx_init(&ctx) != 0) { 2239 rc = SMB_ADJOIN_ERR_INIT_KRB_CTX; 2240 goto adjoin_cleanup; 2241 } 2242 2243 if (smb_krb5_get_principals(ah->domain, ctx, krb5princs) != 0) { 2244 rc = SMB_ADJOIN_ERR_GET_SPNS; 2245 goto adjoin_cleanup; 2246 } 2247 2248 if (smb_krb5_setpwd(ctx, krb5princs[SMBKRB5_SPN_IDX_HOST], 2249 machine_passwd) != 0) { 2250 rc = SMB_ADJOIN_ERR_KSETPWD; 2251 goto adjoin_cleanup; 2252 } 2253 2254 kvno = smb_ads_lookup_computer_attr_kvno(ah, dn); 2255 2256 /* 2257 * Only members of Domain Admins and Enterprise Admins can set 2258 * the TRUSTED_FOR_DELEGATION userAccountControl flag. 2259 */ 2260 if (smb_ads_update_computer_cntrl_attr(ah, 2261 SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | 2262 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION, dn) 2263 == LDAP_INSUFFICIENT_ACCESS) { 2264 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | 2265 SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD); 2266 2267 syslog(LOG_NOTICE, "Unable to set the " 2268 "TRUSTED_FOR_DELEGATION userAccountControl flag on " 2269 "the machine account in Active Directory. Please refer " 2270 "to the Troubleshooting guide for more information."); 2271 2272 } else { 2273 usrctl_flags |= (SMB_ADS_USER_ACCT_CTL_WKSTATION_TRUST_ACCT | 2274 SMB_ADS_USER_ACCT_CTL_TRUSTED_FOR_DELEGATION | 2275 SMB_ADS_USER_ACCT_CTL_DONT_EXPIRE_PASSWD); 2276 } 2277 2278 if (des_only) 2279 usrctl_flags |= SMB_ADS_USER_ACCT_CTL_USE_DES_KEY_ONLY; 2280 2281 if (smb_ads_update_computer_cntrl_attr(ah, usrctl_flags, dn) 2282 != 0) { 2283 rc = SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR; 2284 goto adjoin_cleanup; 2285 } 2286 2287 if (dclevel >= SMB_ADS_DCLEVEL_W2K8) { 2288 num = sizeof (w2k8enctypes) / sizeof (krb5_enctype); 2289 encptr = w2k8enctypes; 2290 } else { 2291 num = sizeof (pre_w2k8enctypes) / sizeof (krb5_enctype); 2292 encptr = pre_w2k8enctypes; 2293 } 2294 2295 tmpfile = mktemp(SMBNS_KRB5_KEYTAB_TMP); 2296 if (tmpfile == NULL) 2297 tmpfile = SMBNS_KRB5_KEYTAB_TMP; 2298 2299 if (smb_krb5_add_keytab_entries(ctx, krb5princs, tmpfile, 2300 kvno, machine_passwd, encptr, num) != 0) { 2301 rc = SMB_ADJOIN_ERR_WRITE_KEYTAB; 2302 goto adjoin_cleanup; 2303 } 2304 2305 delete = B_FALSE; 2306 adjoin_cleanup: 2307 if (new_acct && delete) 2308 smb_ads_del_computer(ah, dn); 2309 2310 if (rc != SMB_ADJOIN_ERR_INIT_KRB_CTX) { 2311 if (rc != SMB_ADJOIN_ERR_GET_SPNS) 2312 smb_krb5_free_principals(ctx, krb5princs, 2313 SMBKRB5_SPN_IDX_MAX); 2314 smb_krb5_ctx_fini(ctx); 2315 } 2316 2317 /* commit keytab file */ 2318 if (rc == SMB_ADJOIN_SUCCESS) { 2319 if (rename(tmpfile, SMBNS_KRB5_KEYTAB) != 0) { 2320 (void) unlink(tmpfile); 2321 rc = SMB_ADJOIN_ERR_COMMIT_KEYTAB; 2322 } else { 2323 /* Set IDMAP config */ 2324 if (smb_config_set_idmap_domain(ah->domain) != 0) { 2325 rc = SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN; 2326 } else { 2327 2328 /* Refresh IDMAP service */ 2329 if (smb_config_refresh_idmap() != 0) 2330 rc = SMB_ADJOIN_ERR_IDMAP_REFRESH; 2331 } 2332 } 2333 } else { 2334 (void) unlink(tmpfile); 2335 } 2336 2337 smb_ads_close(ah); 2338 smb_ccache_remove(SMB_CCACHE_PATH); 2339 return (rc); 2340 } 2341 2342 /* 2343 * smb_ads_join_errmsg 2344 * 2345 * Display error message for the specific adjoin error code. 2346 */ 2347 void 2348 smb_ads_join_errmsg(smb_adjoin_status_t status) 2349 { 2350 int i; 2351 struct xlate_table { 2352 smb_adjoin_status_t status; 2353 char *msg; 2354 } adjoin_table[] = { 2355 { SMB_ADJOIN_ERR_GET_HANDLE, "Failed to connect to an " 2356 "Active Directory server." }, 2357 { SMB_ADJOIN_ERR_GET_DCLEVEL, "Unknown functional level of " 2358 "the domain controller. The rootDSE attribute named " 2359 "\"domainControllerFunctionality\" is missing from the " 2360 "Active Directory." }, 2361 { SMB_ADJOIN_ERR_ADD_TRUST_ACCT, "Failed to create the " 2362 "workstation trust account." }, 2363 { SMB_ADJOIN_ERR_MOD_TRUST_ACCT, "Failed to modify the " 2364 "workstation trust account." }, 2365 { SMB_ADJOIN_ERR_DUP_TRUST_ACCT, "Failed to create the " 2366 "workstation trust account because its name is already " 2367 "in use." }, 2368 { SMB_ADJOIN_ERR_TRUST_ACCT, "Error in querying the " 2369 "workstation trust account" }, 2370 { SMB_ADJOIN_ERR_INIT_KRB_CTX, "Failed to initialize Kerberos " 2371 "context." }, 2372 { SMB_ADJOIN_ERR_GET_SPNS, "Failed to get Kerberos " 2373 "principals." }, 2374 { SMB_ADJOIN_ERR_KSETPWD, "Failed to set machine password." }, 2375 { SMB_ADJOIN_ERR_UPDATE_CNTRL_ATTR, "Failed to modify " 2376 "userAccountControl attribute of the workstation trust " 2377 "account." }, 2378 { SMB_ADJOIN_ERR_WRITE_KEYTAB, "Error in writing to local " 2379 "keytab file (i.e /etc/krb5/krb5.keytab)." }, 2380 { SMB_ADJOIN_ERR_IDMAP_SET_DOMAIN, "Failed to update idmap " 2381 "configuration." }, 2382 { SMB_ADJOIN_ERR_IDMAP_REFRESH, "Failed to refresh idmap " 2383 "service." }, 2384 { SMB_ADJOIN_ERR_COMMIT_KEYTAB, "Failed to commit changes to " 2385 "local keytab file (i.e. /etc/krb5/krb5.keytab)." } 2386 }; 2387 2388 for (i = 0; i < sizeof (adjoin_table) / sizeof (adjoin_table[0]); i++) { 2389 if (adjoin_table[i].status == status) 2390 syslog(LOG_NOTICE, "%s", adjoin_table[i].msg); 2391 } 2392 } 2393 2394 /* 2395 * smb_ads_match_pdc 2396 * 2397 * Returns B_TRUE if the given host's IP address matches the preferred DC's 2398 * IP address. Otherwise, returns B_FALSE. 2399 */ 2400 static boolean_t 2401 smb_ads_match_pdc(smb_ads_host_info_t *host) 2402 { 2403 boolean_t match = B_FALSE; 2404 2405 if (!host) 2406 return (match); 2407 2408 (void) mutex_lock(&smb_ads_cfg.c_mtx); 2409 if (smb_inet_equal(&host->ipaddr, &smb_ads_cfg.c_pdc)) 2410 match = B_TRUE; 2411 (void) mutex_unlock(&smb_ads_cfg.c_mtx); 2412 2413 return (match); 2414 } 2415 2416 /* 2417 * smb_ads_select_dcfromsubnet 2418 * 2419 * This method walks the list of DCs and returns the first DC record that 2420 * responds to ldap ping and is in the same subnet as the host. 2421 * 2422 * Returns a pointer to the found DC record. 2423 * Returns NULL, on error or if no DC record is found. 2424 */ 2425 static smb_ads_host_info_t * 2426 smb_ads_select_dcfromsubnet(smb_ads_host_list_t *hlist) 2427 { 2428 smb_ads_host_info_t *hentry; 2429 smb_nic_t *lnic; 2430 smb_niciter_t ni; 2431 size_t cnt; 2432 int i; 2433 2434 if (smb_nic_getfirst(&ni) != 0) 2435 return (NULL); 2436 do { 2437 lnic = &ni.ni_nic; 2438 cnt = hlist->ah_cnt; 2439 2440 for (i = 0; i < cnt; i++) { 2441 hentry = &hlist->ah_list[i]; 2442 if ((hentry->ipaddr.a_family == AF_INET) && 2443 (lnic->nic_ip.a_family == AF_INET)) { 2444 if ((hentry->ipaddr.a_ipv4 & 2445 lnic->nic_mask) == 2446 (lnic->nic_ip.a_ipv4 & 2447 lnic->nic_mask)) 2448 if (smb_ads_ldap_ping(hentry) == 0) 2449 return (hentry); 2450 } 2451 } 2452 } while (smb_nic_getnext(&ni) == 0); 2453 2454 return (NULL); 2455 } 2456 2457 /* 2458 * smb_ads_select_dcfromlist 2459 * 2460 * This method walks the list of DCs and returns the first DC that 2461 * responds to ldap ping. 2462 * 2463 * Returns a pointer to the found DC record. 2464 * Returns NULL if no DC record is found. 2465 */ 2466 static smb_ads_host_info_t * 2467 smb_ads_select_dcfromlist(smb_ads_host_list_t *hlist) 2468 { 2469 smb_ads_host_info_t *hentry; 2470 size_t cnt; 2471 int i; 2472 2473 cnt = hlist->ah_cnt; 2474 for (i = 0; i < cnt; i++) { 2475 hentry = &hlist->ah_list[i]; 2476 if (smb_ads_ldap_ping(hentry) == 0) 2477 return (hentry); 2478 } 2479 2480 return (NULL); 2481 } 2482 2483 /* 2484 * smb_ads_dc_compare 2485 * 2486 * Comparision function for sorting host entries (SRV records of DC) via qsort. 2487 * RFC 2052/2782 are taken as reference, while implementing this algorithm. 2488 * 2489 * Domain Controllers(DCs) with lowest priority in their SRV DNS records 2490 * are selected first. If they have equal priorities, then DC with highest 2491 * weight in its SRV DNS record is selected. If the priority and weight are 2492 * both equal, then the DC at the top of the list is selected. 2493 */ 2494 static int 2495 smb_ads_dc_compare(const void *p, const void *q) 2496 { 2497 smb_ads_host_info_t *h1 = (smb_ads_host_info_t *)p; 2498 smb_ads_host_info_t *h2 = (smb_ads_host_info_t *)q; 2499 2500 if (h1->priority < h2->priority) 2501 return (-1); 2502 if (h1->priority > h2->priority) 2503 return (1); 2504 2505 /* Priorities are equal */ 2506 if (h1->weight < h2->weight) 2507 return (1); 2508 if (h1->weight > h2->weight) 2509 return (-1); 2510 2511 return (0); 2512 } 2513 2514 /* 2515 * smb_ads_select_dc 2516 * 2517 * The list of ADS hosts returned by ADS lookup, is sorted by lowest priority 2518 * and highest weight. On this sorted list, following additional rules are 2519 * applied, to select a DC. 2520 * 2521 * - If there is a DC in the same subnet, then return the DC, 2522 * if it responds to ldap ping. 2523 * - Else, return first DC that responds to ldap ping. 2524 * 2525 * A reference to the host entry from input host list is returned. 2526 * 2527 * Returns NULL on error. 2528 */ 2529 static smb_ads_host_info_t * 2530 smb_ads_select_dc(smb_ads_host_list_t *hlist) 2531 { 2532 smb_ads_host_info_t *hentry = NULL; 2533 2534 if (hlist->ah_cnt == 0) 2535 return (NULL); 2536 2537 if (hlist->ah_cnt == 1) { 2538 hentry = hlist->ah_list; 2539 if (smb_ads_ldap_ping(hentry) == 0) 2540 return (hentry); 2541 } 2542 2543 /* Sort the list by priority and weight */ 2544 qsort(hlist->ah_list, hlist->ah_cnt, 2545 sizeof (smb_ads_host_info_t), smb_ads_dc_compare); 2546 2547 if ((hentry = smb_ads_select_dcfromsubnet(hlist)) != NULL) 2548 return (hentry); 2549 2550 if ((hentry = smb_ads_select_dcfromlist(hlist)) != NULL) 2551 return (hentry); 2552 2553 return (NULL); 2554 } 2555 2556 /* 2557 * smb_ads_lookup_msdcs 2558 * 2559 * If server argument is set, try to locate the specified DC. 2560 * If it is set to empty string, locate any DCs in the specified domain. 2561 * Returns the discovered DC via buf. 2562 * 2563 * fqdn - fully-qualified domain name 2564 * server - fully-qualifed hostname of a DC 2565 * buf - the hostname of the discovered DC 2566 */ 2567 boolean_t 2568 smb_ads_lookup_msdcs(char *fqdn, char *server, char *buf, uint32_t buflen) 2569 { 2570 smb_ads_host_info_t *hinfo = NULL; 2571 char *p; 2572 char *sought_host; 2573 char ipstr[INET6_ADDRSTRLEN]; 2574 2575 if (!fqdn || !buf) 2576 return (B_FALSE); 2577 2578 ipstr[0] = '\0'; 2579 *buf = '\0'; 2580 sought_host = (*server == 0 ? NULL : server); 2581 if ((hinfo = smb_ads_find_host(fqdn, sought_host)) == NULL) 2582 return (B_FALSE); 2583 2584 (void) smb_inet_ntop(&hinfo->ipaddr, ipstr, 2585 SMB_IPSTRLEN(hinfo->ipaddr.a_family)); 2586 smb_tracef("msdcsLookupADS: %s [%s]", hinfo->name, ipstr); 2587 2588 (void) strlcpy(buf, hinfo->name, buflen); 2589 /* 2590 * Remove the domain extension 2591 */ 2592 if ((p = strchr(buf, '.')) != 0) 2593 *p = '\0'; 2594 2595 free(hinfo); 2596 return (B_TRUE); 2597 } 2598