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
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.
13 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
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>
37 #include "adutils_impl.h"
38 #include "addisc_impl.h"
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
65 NEXT_CLOSEST_SITE_NAME
,
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
*);
84 cldap_escape_le64(char *buf
, uint64_t val
, int bytes
)
89 p
+= sprintf(p
, "\\%.2x", (uint8_t)(val
& 0xff));
97 * Construct CLDAPMessage PDU for NetLogon search request.
99 * CLDAPMessage ::= SEQUENCE {
100 * messageID MessageID,
101 * protocolOp searchRequest SearchRequest;
105 * [APPLICATION 3] SEQUENCE {
112 * derefAliases ENUMERATED {
113 * neverDerefAliases (0),
114 * derefInSearching (1),
115 * derefFindingBaseObj (2),
118 * sizeLimit INTEGER (0 .. MaxInt),
119 * timeLimit INTEGER (0 .. MaxInt),
122 * attributes SEQUENCE OF AttributeType
126 cldap_build_request(const char *dname
,
127 const char *host
, uint32_t ntver
, uint16_t msgid
)
132 int scope
= LDAP_SCOPE_BASE
, deref
= LDAP_DEREF_NEVER
,
133 sizelimit
= 0, timelimit
= 0, attrsonly
= 0;
139 * Construct search filter in LDAP format.
142 pend
= p
+ sizeof (filter
);
144 len
= snprintf(p
, pend
- p
, "(&(DnsDomain=%s)", dname
);
145 if (len
>= (pend
- p
))
150 len
= snprintf(p
, (pend
- p
), "(Host=%s)", host
);
151 if (len
>= (pend
- p
))
158 * Format NtVer as little-endian with LDAPv3 escapes.
160 cldap_escape_le64(ntver_esc
, ntver
, sizeof (ntver
));
161 len
= snprintf(p
, (pend
- p
), "(NtVer=%s)", ntver_esc
);
162 if (len
>= (pend
- p
))
167 len
= snprintf(p
, pend
- p
, ")");
168 if (len
>= (pend
- p
))
173 * Encode CLDAPMessage and beginning of SearchRequest sequence.
176 if ((ber
= ber_alloc()) == NULL
)
179 if (ber_printf(ber
, "{it{seeiib", msgid
,
180 LDAP_REQ_SEARCH
, basedn
, scope
, deref
,
181 sizelimit
, timelimit
, attrsonly
) < 0)
185 * Encode Filter sequence.
187 if (ldap_put_filter(ber
, filter
) < 0)
190 * Encode attribute and close Filter and SearchRequest sequences.
192 if (ber_printf(ber
, "{s}}}", NETLOGON_ATTR_NAME
) < 0)
207 * Parse incoming search responses and attribute to correct hosts.
209 * CLDAPMessage ::= SEQUENCE {
210 * messageID MessageID,
211 * searchResponse SEQUENCE OF
217 * entry [APPLICATION 4] SEQUENCE {
219 * attributes SEQUENCE OF SEQUENCE {
225 * resultCode [APPLICATION 5] LDAPResult
230 decode_name(uchar_t
*base
, uchar_t
*cp
, char *str
)
232 uchar_t
*tmp
= NULL
, *st
= cp
;
236 * there should probably be some boundary checks on str && cp
237 * maybe pass in strlen && msglen ?
243 cp
= base
+ *(cp
+ 1);
245 for (len
= *cp
++; len
> 0; len
--)
254 return ((tmp
== NULL
? cp
+ 1 : tmp
) - st
);
258 cldap_parse(ad_disc_t ctx
, ad_disc_cds_t
*cds
, BerElement
*ber
)
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;
265 field_5ex_t f
= OPCODE
;
268 * Later, compare msgid's/some validation?
271 if (ber_scanf(ber
, "{i{x{{x[la", &msgid
, &l
, &cp
) == LBER_ERROR
) {
276 for (base
= cp
; ((cp
- base
) < l
) && (f
<= LM_20_TOKEN
); f
++) {
280 /* opcode = *(uint16_t *)cp; */
283 opcode
|= (*cp
++ << 8);
289 /* dci->Flags = *(uint32_t *)cp; */
292 dc
->flags
|= (*cp
++ << 8);
293 dc
->flags
|= (*cp
++ << 16);
294 dc
->flags
|= (*cp
++ << 26);
298 auto_set_DomainGUID(ctx
, cp
);
302 cp
+= decode_name(base
, cp
, val
);
304 auto_set_ForestName(ctx
, val
);
306 case DNS_DOMAIN_NAME
:
308 * We always have this already.
309 * (Could validate it here.)
311 cp
+= decode_name(base
, cp
, val
);
314 cp
+= decode_name(base
, cp
, val
);
315 if (0 != strcasecmp(val
, dc
->host
)) {
316 logger(LOG_ERR
, "DC name %s != %s?",
320 case NET_DOMAIN_NAME
:
322 * This is the "Flat" domain name.
323 * (i.e. the NetBIOS name)
326 cp
+= decode_name(base
, cp
, val
);
330 cp
+= decode_name(base
, cp
, val
);
334 cp
+= decode_name(base
, cp
, val
);
337 cp
+= decode_name(base
, cp
, val
);
338 (void) strlcpy(dc
->site
, val
, sizeof (dc
->site
));
340 case CLIENT_SITE_NAME
:
341 cp
+= decode_name(base
, cp
, val
);
343 auto_set_SiteName(ctx
, val
);
346 * These are all possible, but we don't really care about them.
347 * Sockaddr_size && sockaddr might be useful at some point
351 case NEXT_CLOSEST_SITE_NAME
:
371 * Filter out unresponsive servers, and save the domain info
372 * returned by the "LDAP ping" in the returned object.
373 * If ctx != NULL, this is a query for a DC, in which case we
374 * also save the Domain GUID, Site name, and Forest name as
375 * "auto" (discovered) values in the ctx.
377 * Only return the "winner". (We only want one DC/GC)
380 ldap_ping(ad_disc_t ctx
, ad_disc_cds_t
*dclist
, char *dname
, int reqflags
)
382 struct sockaddr_in6 addr6
;
384 struct pollfd pingchk
;
385 ad_disc_cds_t
*send_ds
;
386 ad_disc_cds_t
*recv_ds
= NULL
;
387 ad_disc_ds_t
*ret_ds
= NULL
;
388 BerElement
*req
= NULL
;
389 BerElement
*res
= NULL
;
390 struct _berelement
*be
, *rbe
;
391 size_t be_len
, rbe_len
;
398 /* One plus a null entry. */
399 ret_ds
= calloc(2, sizeof (ad_disc_ds_t
));
403 if ((fd
= socket(PF_INET6
, SOCK_DGRAM
, 0)) < 0)
406 (void) memset(&addr6
, 0, sizeof (addr6
));
407 addr6
.sin6_family
= AF_INET6
;
408 addr6
.sin6_addr
= in6addr_any
;
409 if (bind(fd
, (struct sockaddr
*)&addr6
, sizeof (addr6
)) < 0)
413 * semi-unique msgid...
415 msgid
= gethrtime() & 0xffff;
418 * Is ntver right? It certainly works on w2k8... If others are needed,
419 * that might require changes to cldap_parse
421 req
= cldap_build_request(dname
, NULL
,
422 NETLOGON_NT_VERSION_5EX
, msgid
);
425 be
= (struct _berelement
*)req
;
426 be_len
= be
->ber_end
- be
->ber_buf
;
428 if ((res
= ber_alloc()) == NULL
)
430 rbe
= (struct _berelement
*)res
;
431 rbe_len
= rbe
->ber_end
- rbe
->ber_buf
;
434 pingchk
.events
= POLLIN
;
440 while (recv_ds
== NULL
&& waitsec
> 0) {
443 * If there is another candidate, send to it.
445 if (send_ds
->cds_ds
.host
[0] != '\0') {
446 send_to_cds(send_ds
, be
->ber_buf
, be_len
, fd
);
450 * Wait 1/10 sec. before the next send.
452 r
= poll(&pingchk
, 1, 100);
454 /* Drop all responses 1st pass. */
460 * No more candidates to "ping", so
461 * just wait a sec for responses.
463 r
= poll(&pingchk
, 1, 1000);
472 (void) memset(&addr6
, 0, addrlen
= sizeof (addr6
));
473 r
= recvfrom(fd
, rbe
->ber_buf
, rbe_len
, 0,
474 (struct sockaddr
*)&addr6
, &addrlen
);
476 recv_ds
= find_cds_by_addr(dclist
, &addr6
);
480 (void) cldap_parse(ctx
, recv_ds
, res
);
481 if ((recv_ds
->cds_ds
.flags
& reqflags
) != reqflags
) {
482 logger(LOG_ERR
, "Skip %s"
484 recv_ds
->cds_ds
.host
,
485 recv_ds
->cds_ds
.flags
);
491 if (recv_ds
== NULL
) {
497 (void) memcpy(ret_ds
, recv_ds
, sizeof (*ret_ds
));
513 * Attempt a send of the LDAP request to all known addresses
514 * for this candidate server.
517 send_to_cds(ad_disc_cds_t
*send_cds
, char *ber_buf
, size_t be_len
, int fd
)
519 struct sockaddr_in6 addr6
;
524 logger(LOG_DEBUG
, "send to: %s", send_cds
->cds_ds
.host
);
527 for (ai
= send_cds
->cds_ai
; ai
!= NULL
; ai
= ai
->ai_next
) {
530 * Build the "to" address.
532 (void) memset(&addr6
, 0, sizeof (addr6
));
533 if (ai
->ai_family
== AF_INET6
) {
534 (void) memcpy(&addr6
, ai
->ai_addr
, sizeof (addr6
));
535 } else if (ai
->ai_family
== AF_INET
) {
536 struct sockaddr_in
*sin
=
538 addr6
.sin6_family
= AF_INET6
;
539 IN6_INADDR_TO_V4MAPPED(&sin
->sin_addr
,
544 addr6
.sin6_port
= htons(LDAP_PORT
);
547 * Send the "ping" to this address.
549 err
= sendto(fd
, ber_buf
, be_len
, 0,
550 (struct sockaddr
*)&addr6
, sizeof (addr6
));
551 err
= (err
< 0) ? errno
: 0;
554 char abuf
[INET6_ADDRSTRLEN
];
557 pa
= inet_ntop(AF_INET6
,
559 abuf
, sizeof (abuf
));
560 logger(LOG_ERR
, " > %s rc=%d",
567 * We have a response from some address. Find the candidate with
568 * this address. In case a candidate had multiple addresses, we
569 * keep track of which the response came from.
571 static ad_disc_cds_t
*
572 find_cds_by_addr(ad_disc_cds_t
*dclist
, struct sockaddr_in6
*sin6from
)
574 char abuf
[INET6_ADDRSTRLEN
];
580 eai
= getnameinfo((void *)sin6from
, sizeof (*sin6from
),
581 abuf
, sizeof (abuf
), NULL
, 0, NI_NUMERICHOST
);
583 (void) strlcpy(abuf
, "?", sizeof (abuf
));
584 logger(LOG_DEBUG
, "LDAP ping resp: addr=%s", abuf
);
588 * Find the DS this response came from.
589 * (don't accept unexpected responses)
591 for (ds
= dclist
; ds
->cds_ds
.host
[0] != '\0'; ds
++) {
594 if (addrmatch(ai
, sin6from
))
600 logger(LOG_DEBUG
, " (unexpected)");
606 logger(LOG_DEBUG
, " from %s", ds
->cds_ds
.host
);
613 addrmatch(struct addrinfo
*ai
, struct sockaddr_in6
*sin6from
)
617 * Note: on a GC query, the ds->addr port numbers are
618 * the GC port, and our from addr has the LDAP port.
619 * Just compare the IP addresses.
622 if (ai
->ai_family
== AF_INET6
) {
623 struct sockaddr_in6
*sin6p
= (void *)ai
->ai_addr
;
625 if (!memcmp(&sin6from
->sin6_addr
, &sin6p
->sin6_addr
,
626 sizeof (struct in6_addr
)))
630 if (ai
->ai_family
== AF_INET
) {
632 struct sockaddr_in
*sin4p
= (void *)ai
->ai_addr
;
634 IN6_INADDR_TO_V4MAPPED(&sin4p
->sin_addr
, &in6
);
635 if (!memcmp(&sin6from
->sin6_addr
, &in6
,
636 sizeof (struct in6_addr
)))
644 save_ai(ad_disc_cds_t
*cds
, struct addrinfo
*ai
)
646 ad_disc_ds_t
*ds
= &cds
->cds_ds
;
647 struct sockaddr_in
*sin
;
648 struct sockaddr_in6
*sin6
;
651 * If this DS already saw a response, keep the first
652 * address from which we received a response.
654 if (ds
->addr
.ss_family
!= 0) {
656 logger(LOG_DEBUG
, "already have an address");
660 switch (ai
->ai_family
) {
662 sin
= (void *)&ds
->addr
;
663 (void) memcpy(sin
, ai
->ai_addr
, sizeof (*sin
));
664 sin
->sin_port
= htons(ds
->port
);
668 sin6
= (void *)&ds
->addr
;
669 (void) memcpy(sin6
, ai
->ai_addr
, sizeof (*sin6
));
670 sin6
->sin6_port
= htons(ds
->port
);
674 logger(LOG_ERR
, "bad AF %d", ai
->ai_family
);