1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2014 Nexenta Systems, Inc. All rights reserved. 14 */ 15 16 #include <stdio.h> 17 #include <string.h> 18 #include <strings.h> 19 #include <unistd.h> 20 #include <assert.h> 21 #include <stdlib.h> 22 #include <net/if.h> 23 #include <net/if.h> 24 #include <sys/types.h> 25 #include <sys/socket.h> 26 #include <sys/sockio.h> 27 #include <netinet/in.h> 28 #include <arpa/inet.h> 29 #include <arpa/nameser.h> 30 #include <resolv.h> 31 #include <netdb.h> 32 #include <ctype.h> 33 #include <errno.h> 34 #include <ldap.h> 35 #include <lber.h> 36 #include <syslog.h> 37 #include "adutils_impl.h" 38 #include "addisc_impl.h" 39 40 #define LDAP_PORT 389 41 42 #define NETLOGON_ATTR_NAME "NetLogon" 43 #define NETLOGON_NT_VERSION_1 0x00000001 44 #define NETLOGON_NT_VERSION_5 0x00000002 45 #define NETLOGON_NT_VERSION_5EX 0x00000004 46 #define NETLOGON_NT_VERSION_5EX_WITH_IP 0x00000008 47 #define NETLOGON_NT_VERSION_WITH_CLOSEST_SITE 0x00000010 48 #define NETLOGON_NT_VERSION_AVOID_NT4EMUL 0x01000000 49 50 typedef enum { 51 OPCODE = 0, 52 SBZ, 53 FLAGS, 54 DOMAIN_GUID, 55 FOREST_NAME, 56 DNS_DOMAIN_NAME, 57 DNS_HOST_NAME, 58 NET_DOMAIN_NAME, 59 NET_COMP_NAME, 60 USER_NAME, 61 DC_SITE_NAME, 62 CLIENT_SITE_NAME, 63 SOCKADDR_SIZE, 64 SOCKADDR, 65 NEXT_CLOSEST_SITE_NAME, 66 NTVER, 67 LM_NT_TOKEN, 68 LM_20_TOKEN 69 } field_5ex_t; 70 71 struct _berelement { 72 char *ber_buf; 73 char *ber_ptr; 74 char *ber_end; 75 }; 76 77 extern int ldap_put_filter(BerElement *ber, char *); 78 static void send_to_cds(ad_disc_cds_t *, char *, size_t, int); 79 static ad_disc_cds_t *find_cds_by_addr(ad_disc_cds_t *, struct sockaddr_in6 *); 80 static boolean_t addrmatch(struct addrinfo *, struct sockaddr_in6 *); 81 static void save_ai(ad_disc_cds_t *, struct addrinfo *); 82 83 static void 84 cldap_escape_le64(char *buf, uint64_t val, int bytes) 85 { 86 char *p = buf; 87 88 while (bytes != 0) { 89 p += sprintf(p, "\\%.2x", (uint8_t)(val & 0xff)); 90 val >>= 8; 91 bytes--; 92 } 93 *p = '\0'; 94 } 95 96 /* 97 * Construct CLDAPMessage PDU for NetLogon search request. 98 * 99 * CLDAPMessage ::= SEQUENCE { 100 * messageID MessageID, 101 * protocolOp searchRequest SearchRequest; 102 * } 103 * 104 * SearchRequest ::= 105 * [APPLICATION 3] SEQUENCE { 106 * baseObject LDAPDN, 107 * scope ENUMERATED { 108 * baseObject (0), 109 * singleLevel (1), 110 * wholeSubtree (2) 111 * }, 112 * derefAliases ENUMERATED { 113 * neverDerefAliases (0), 114 * derefInSearching (1), 115 * derefFindingBaseObj (2), 116 * derefAlways (3) 117 * }, 118 * sizeLimit INTEGER (0 .. MaxInt), 119 * timeLimit INTEGER (0 .. MaxInt), 120 * attrsOnly BOOLEAN, 121 * filter Filter, 122 * attributes SEQUENCE OF AttributeType 123 * } 124 */ 125 BerElement * 126 cldap_build_request(const char *dname, 127 const char *host, uint32_t ntver, uint16_t msgid) 128 { 129 BerElement *ber; 130 int len = 0; 131 char *basedn = ""; 132 int scope = LDAP_SCOPE_BASE, deref = LDAP_DEREF_NEVER, 133 sizelimit = 0, timelimit = 0, attrsonly = 0; 134 char filter[512]; 135 char ntver_esc[13]; 136 char *p, *pend; 137 138 /* 139 * Construct search filter in LDAP format. 140 */ 141 p = filter; 142 pend = p + sizeof (filter); 143 144 len = snprintf(p, pend - p, "(&(DnsDomain=%s)", dname); 145 if (len >= (pend - p)) 146 goto fail; 147 p += len; 148 149 if (host != NULL) { 150 len = snprintf(p, (pend - p), "(Host=%s)", host); 151 if (len >= (pend - p)) 152 goto fail; 153 p += len; 154 } 155 156 if (ntver != 0) { 157 /* 158 * Format NtVer as little-endian with LDAPv3 escapes. 159 */ 160 cldap_escape_le64(ntver_esc, ntver, sizeof (ntver)); 161 len = snprintf(p, (pend - p), "(NtVer=%s)", ntver_esc); 162 if (len >= (pend - p)) 163 goto fail; 164 p += len; 165 } 166 167 len = snprintf(p, pend - p, ")"); 168 if (len >= (pend - p)) 169 goto fail; 170 p += len; 171 172 /* 173 * Encode CLDAPMessage and beginning of SearchRequest sequence. 174 */ 175 176 if ((ber = ber_alloc()) == NULL) 177 goto fail; 178 179 if (ber_printf(ber, "{it{seeiib", msgid, 180 LDAP_REQ_SEARCH, basedn, scope, deref, 181 sizelimit, timelimit, attrsonly) < 0) 182 goto fail; 183 184 /* 185 * Encode Filter sequence. 186 */ 187 if (ldap_put_filter(ber, filter) < 0) 188 goto fail; 189 /* 190 * Encode attribute and close Filter and SearchRequest sequences. 191 */ 192 if (ber_printf(ber, "{s}}}", NETLOGON_ATTR_NAME) < 0) 193 goto fail; 194 195 /* 196 * Success 197 */ 198 return (ber); 199 200 fail: 201 if (ber != NULL) 202 ber_free(ber, 1); 203 return (NULL); 204 } 205 206 /* 207 * Parse incoming search responses and attribute to correct hosts. 208 * 209 * CLDAPMessage ::= SEQUENCE { 210 * messageID MessageID, 211 * searchResponse SEQUENCE OF 212 * SearchResponse; 213 * } 214 * 215 * SearchResponse ::= 216 * CHOICE { 217 * entry [APPLICATION 4] SEQUENCE { 218 * objectName LDAPDN, 219 * attributes SEQUENCE OF SEQUENCE { 220 * AttributeType, 221 * SET OF 222 * AttributeValue 223 * } 224 * }, 225 * resultCode [APPLICATION 5] LDAPResult 226 * } 227 */ 228 229 static int 230 decode_name(uchar_t *base, uchar_t *cp, char *str) 231 { 232 uchar_t *tmp = NULL, *st = cp; 233 uint8_t len; 234 235 /* 236 * there should probably be some boundary checks on str && cp 237 * maybe pass in strlen && msglen ? 238 */ 239 while (*cp != 0) { 240 if (*cp == 0xc0) { 241 if (tmp == NULL) 242 tmp = cp + 2; 243 cp = base + *(cp + 1); 244 } 245 for (len = *cp++; len > 0; len--) 246 *str++ = *cp++; 247 *str++ = '.'; 248 } 249 if (cp != st) 250 *(str-1) = '\0'; 251 else 252 *str = '\0'; 253 254 return ((tmp == NULL ? cp + 1 : tmp) - st); 255 } 256 257 static int 258 cldap_parse(ad_disc_t ctx, ad_disc_cds_t *cds, BerElement *ber) 259 { 260 ad_disc_ds_t *dc = &cds->cds_ds; 261 uchar_t *base = NULL, *cp = NULL; 262 char val[512]; /* how big should val be? */ 263 int l, msgid, rc = 0; 264 uint16_t opcode; 265 field_5ex_t f = OPCODE; 266 267 /* 268 * Later, compare msgid's/some validation? 269 */ 270 271 if (ber_scanf(ber, "{i{x{{x[la", &msgid, &l, &cp) == LBER_ERROR) { 272 rc = 1; 273 goto out; 274 } 275 276 for (base = cp; ((cp - base) < l) && (f <= LM_20_TOKEN); f++) { 277 val[0] = '\0'; 278 switch (f) { 279 case OPCODE: 280 /* opcode = *(uint16_t *)cp; */ 281 /* cp +=2; */ 282 opcode = *cp++; 283 opcode |= (*cp++ << 8); 284 break; 285 case SBZ: 286 cp += 2; 287 break; 288 case FLAGS: 289 /* dci->Flags = *(uint32_t *)cp; */ 290 /* cp +=4; */ 291 dc->flags = *cp++; 292 dc->flags |= (*cp++ << 8); 293 dc->flags |= (*cp++ << 16); 294 dc->flags |= (*cp++ << 26); 295 break; 296 case DOMAIN_GUID: 297 if (ctx != NULL) 298 auto_set_DomainGUID(ctx, cp); 299 cp += 16; 300 break; 301 case FOREST_NAME: 302 cp += decode_name(base, cp, val); 303 if (ctx != NULL) 304 auto_set_ForestName(ctx, val); 305 break; 306 case DNS_DOMAIN_NAME: 307 /* 308 * We always have this already. 309 * (Could validate it here.) 310 */ 311 cp += decode_name(base, cp, val); 312 break; 313 case DNS_HOST_NAME: 314 cp += decode_name(base, cp, val); 315 if (0 != strcasecmp(val, dc->host)) { 316 logger(LOG_ERR, "DC name %s != %s?", 317 val, dc->host); 318 } 319 break; 320 case NET_DOMAIN_NAME: 321 /* 322 * This is the "Flat" domain name. 323 * (i.e. the NetBIOS name) 324 * ignore for now. 325 */ 326 cp += decode_name(base, cp, val); 327 break; 328 case NET_COMP_NAME: 329 /* not needed */ 330 cp += decode_name(base, cp, val); 331 break; 332 case USER_NAME: 333 /* not needed */ 334 cp += decode_name(base, cp, val); 335 break; 336 case DC_SITE_NAME: 337 cp += decode_name(base, cp, val); 338 (void) strlcpy(dc->site, val, sizeof (dc->site)); 339 break; 340 case CLIENT_SITE_NAME: 341 cp += decode_name(base, cp, val); 342 if (ctx != NULL) 343 auto_set_SiteName(ctx, val); 344 break; 345 /* 346 * These are all possible, but we don't really care about them. 347 * Sockaddr_size && sockaddr might be useful at some point 348 */ 349 case SOCKADDR_SIZE: 350 case SOCKADDR: 351 case NEXT_CLOSEST_SITE_NAME: 352 case NTVER: 353 case LM_NT_TOKEN: 354 case LM_20_TOKEN: 355 break; 356 default: 357 rc = 3; 358 goto out; 359 } 360 } 361 362 out: 363 if (base) 364 free(base); 365 else if (cp) 366 free(cp); 367 return (rc); 368 } 369 370 371 /* 372 * Filter out unresponsive servers, and save the domain info 373 * returned by the "LDAP ping" in the returned object. 374 * If ctx != NULL, this is a query for a DC, in which case we 375 * also save the Domain GUID, Site name, and Forest name as 376 * "auto" (discovered) values in the ctx. 377 * 378 * Only return the "winner". (We only want one DC/GC) 379 */ 380 ad_disc_ds_t * 381 ldap_ping(ad_disc_t ctx, ad_disc_cds_t *dclist, char *dname, int reqflags) 382 { 383 struct sockaddr_in6 addr6; 384 socklen_t addrlen; 385 struct pollfd pingchk; 386 ad_disc_cds_t *send_ds; 387 ad_disc_cds_t *recv_ds = NULL; 388 ad_disc_ds_t *ret_ds = NULL; 389 BerElement *req = NULL; 390 BerElement *res = NULL; 391 struct _berelement *be, *rbe; 392 size_t be_len, rbe_len; 393 int fd = -1; 394 int tries = 3; 395 int waitsec; 396 int r; 397 uint16_t msgid; 398 399 /* One plus a null entry. */ 400 ret_ds = calloc(2, sizeof (ad_disc_ds_t)); 401 if (ret_ds == NULL) 402 goto fail; 403 404 if ((fd = socket(PF_INET6, SOCK_DGRAM, 0)) < 0) 405 goto fail; 406 407 (void) memset(&addr6, 0, sizeof (addr6)); 408 addr6.sin6_family = AF_INET6; 409 addr6.sin6_addr = in6addr_any; 410 if (bind(fd, (struct sockaddr *)&addr6, sizeof (addr6)) < 0) 411 goto fail; 412 413 /* 414 * semi-unique msgid... 415 */ 416 msgid = gethrtime() & 0xffff; 417 418 /* 419 * Is ntver right? It certainly works on w2k8... If others are needed, 420 * that might require changes to cldap_parse 421 */ 422 req = cldap_build_request(dname, NULL, 423 NETLOGON_NT_VERSION_5EX, msgid); 424 if (req == NULL) 425 goto fail; 426 be = (struct _berelement *)req; 427 be_len = be->ber_end - be->ber_buf; 428 429 if ((res = ber_alloc()) == NULL) 430 goto fail; 431 rbe = (struct _berelement *)res; 432 rbe_len = rbe->ber_end - rbe->ber_buf; 433 434 pingchk.fd = fd; 435 pingchk.events = POLLIN; 436 pingchk.revents = 0; 437 438 try_again: 439 send_ds = dclist; 440 waitsec = 5; 441 while (recv_ds == NULL && waitsec > 0) { 442 443 /* 444 * If there is another candidate, send to it. 445 */ 446 if (send_ds->cds_ds.host[0] != '\0') { 447 send_to_cds(send_ds, be->ber_buf, be_len, fd); 448 send_ds++; 449 450 /* 451 * Wait 1/10 sec. before the next send. 452 */ 453 r = poll(&pingchk, 1, 100); 454 #if 0 /* DEBUG */ 455 /* Drop all responses 1st pass. */ 456 if (waitsec == 5) 457 r = 0; 458 #endif 459 } else { 460 /* 461 * No more candidates to "ping", so 462 * just wait a sec for responses. 463 */ 464 r = poll(&pingchk, 1, 1000); 465 if (r == 0) 466 --waitsec; 467 } 468 469 if (r > 0) { 470 /* 471 * Got a response. 472 */ 473 (void) memset(&addr6, 0, addrlen = sizeof (addr6)); 474 r = recvfrom(fd, rbe->ber_buf, rbe_len, 0, 475 (struct sockaddr *)&addr6, &addrlen); 476 477 recv_ds = find_cds_by_addr(dclist, &addr6); 478 if (recv_ds == NULL) 479 continue; 480 481 (void) cldap_parse(ctx, recv_ds, res); 482 if ((recv_ds->cds_ds.flags & reqflags) != reqflags) { 483 logger(LOG_ERR, "Skip %s" 484 "due to flags 0x%X", 485 recv_ds->cds_ds.host, 486 recv_ds->cds_ds.flags); 487 recv_ds = NULL; 488 } 489 } 490 } 491 492 if (recv_ds == NULL) { 493 if (--tries <= 0) 494 goto fail; 495 goto try_again; 496 } 497 498 (void) memcpy(ret_ds, recv_ds, sizeof (*ret_ds)); 499 500 ber_free(res, 1); 501 ber_free(req, 1); 502 (void) close(fd); 503 return (ret_ds); 504 505 fail: 506 ber_free(res, 1); 507 ber_free(req, 1); 508 (void) close(fd); 509 free(ret_ds); 510 return (NULL); 511 } 512 513 /* 514 * Attempt a send of the LDAP request to all known addresses 515 * for this candidate server. 516 */ 517 static void 518 send_to_cds(ad_disc_cds_t *send_cds, char *ber_buf, size_t be_len, int fd) 519 { 520 struct sockaddr_in6 addr6; 521 struct addrinfo *ai; 522 int err; 523 524 if (DBG(DISC, 2)) { 525 logger(LOG_DEBUG, "send to: %s", send_cds->cds_ds.host); 526 } 527 528 for (ai = send_cds->cds_ai; ai != NULL; ai = ai->ai_next) { 529 530 /* 531 * Build the "to" address. 532 */ 533 (void) memset(&addr6, 0, sizeof (addr6)); 534 if (ai->ai_family == AF_INET6) { 535 (void) memcpy(&addr6, ai->ai_addr, sizeof (addr6)); 536 } else if (ai->ai_family == AF_INET) { 537 struct sockaddr_in *sin = 538 (void *)ai->ai_addr; 539 addr6.sin6_family = AF_INET6; 540 IN6_INADDR_TO_V4MAPPED(&sin->sin_addr, 541 &addr6.sin6_addr); 542 } else { 543 continue; 544 } 545 addr6.sin6_port = htons(LDAP_PORT); 546 547 /* 548 * Send the "ping" to this address. 549 */ 550 err = sendto(fd, ber_buf, be_len, 0, 551 (struct sockaddr *)&addr6, sizeof (addr6)); 552 err = (err < 0) ? errno : 0; 553 554 if (DBG(DISC, 2)) { 555 char abuf[INET6_ADDRSTRLEN]; 556 const char *pa; 557 558 pa = inet_ntop(AF_INET6, 559 &addr6.sin6_addr, 560 abuf, sizeof (abuf)); 561 logger(LOG_ERR, " > %s rc=%d", 562 pa ? pa : "?", err); 563 } 564 } 565 } 566 567 /* 568 * We have a response from some address. Find the candidate with 569 * this address. In case a candidate had multiple addresses, we 570 * keep track of which the response came from. 571 */ 572 static ad_disc_cds_t * 573 find_cds_by_addr(ad_disc_cds_t *dclist, struct sockaddr_in6 *sin6from) 574 { 575 char abuf[INET6_ADDRSTRLEN]; 576 ad_disc_cds_t *ds; 577 struct addrinfo *ai; 578 int eai; 579 580 if (DBG(DISC, 1)) { 581 eai = getnameinfo((void *)sin6from, sizeof (*sin6from), 582 abuf, sizeof (abuf), NULL, 0, NI_NUMERICHOST); 583 if (eai != 0) 584 (void) strlcpy(abuf, "?", sizeof (abuf)); 585 logger(LOG_DEBUG, "LDAP ping resp: addr=%s", abuf); 586 } 587 588 /* 589 * Find the DS this response came from. 590 * (don't accept unexpected responses) 591 */ 592 for (ds = dclist; ds->cds_ds.host[0] != '\0'; ds++) { 593 ai = ds->cds_ai; 594 while (ai != NULL) { 595 if (addrmatch(ai, sin6from)) 596 goto found; 597 ai = ai->ai_next; 598 } 599 } 600 if (DBG(DISC, 1)) { 601 logger(LOG_DEBUG, " (unexpected)"); 602 } 603 return (NULL); 604 605 found: 606 if (DBG(DISC, 2)) { 607 logger(LOG_DEBUG, " from %s", ds->cds_ds.host); 608 } 609 save_ai(ds, ai); 610 return (ds); 611 } 612 613 static boolean_t 614 addrmatch(struct addrinfo *ai, struct sockaddr_in6 *sin6from) 615 { 616 617 /* 618 * Note: on a GC query, the ds->addr port numbers are 619 * the GC port, and our from addr has the LDAP port. 620 * Just compare the IP addresses. 621 */ 622 623 if (ai->ai_family == AF_INET6) { 624 struct sockaddr_in6 *sin6p = (void *)ai->ai_addr; 625 626 if (!memcmp(&sin6from->sin6_addr, &sin6p->sin6_addr, 627 sizeof (struct in6_addr))) 628 return (B_TRUE); 629 } 630 631 if (ai->ai_family == AF_INET) { 632 struct in6_addr in6; 633 struct sockaddr_in *sin4p = (void *)ai->ai_addr; 634 635 IN6_INADDR_TO_V4MAPPED(&sin4p->sin_addr, &in6); 636 if (!memcmp(&sin6from->sin6_addr, &in6, 637 sizeof (struct in6_addr))) 638 return (B_TRUE); 639 } 640 641 return (B_FALSE); 642 } 643 644 static void 645 save_ai(ad_disc_cds_t *cds, struct addrinfo *ai) 646 { 647 ad_disc_ds_t *ds = &cds->cds_ds; 648 struct sockaddr_in *sin; 649 struct sockaddr_in6 *sin6; 650 651 /* 652 * If this DS already saw a response, keep the first 653 * address from which we received a response. 654 */ 655 if (ds->addr.ss_family != 0) { 656 if (DBG(DISC, 2)) 657 logger(LOG_DEBUG, "already have an address"); 658 return; 659 } 660 661 switch (ai->ai_family) { 662 case AF_INET: 663 sin = (void *)&ds->addr; 664 (void) memcpy(sin, ai->ai_addr, sizeof (*sin)); 665 sin->sin_port = htons(ds->port); 666 break; 667 668 case AF_INET6: 669 sin6 = (void *)&ds->addr; 670 (void) memcpy(sin6, ai->ai_addr, sizeof (*sin6)); 671 sin6->sin6_port = htons(ds->port); 672 break; 673 674 default: 675 logger(LOG_ERR, "bad AF %d", ai->ai_family); 676 } 677 } 678