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