dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / lib / libadutils / common / ldap_ping.c
blobea02071ad9e900a93f70a6d4548153f8433a2619
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.
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.
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"
40 #define LDAP_PORT 389
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
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;
71 struct _berelement {
72 char *ber_buf;
73 char *ber_ptr;
74 char *ber_end;
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 *);
83 static void
84 cldap_escape_le64(char *buf, uint64_t val, int bytes)
86 char *p = buf;
88 while (bytes != 0) {
89 p += sprintf(p, "\\%.2x", (uint8_t)(val & 0xff));
90 val >>= 8;
91 bytes--;
93 *p = '\0';
97 * Construct CLDAPMessage PDU for NetLogon search request.
99 * CLDAPMessage ::= SEQUENCE {
100 * messageID MessageID,
101 * protocolOp searchRequest SearchRequest;
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
125 BerElement *
126 cldap_build_request(const char *dname,
127 const char *host, uint32_t ntver, uint16_t msgid)
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;
139 * Construct search filter in LDAP format.
141 p = filter;
142 pend = p + sizeof (filter);
144 len = snprintf(p, pend - p, "(&(DnsDomain=%s)", dname);
145 if (len >= (pend - p))
146 goto fail;
147 p += len;
149 if (host != NULL) {
150 len = snprintf(p, (pend - p), "(Host=%s)", host);
151 if (len >= (pend - p))
152 goto fail;
153 p += len;
156 if (ntver != 0) {
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))
163 goto fail;
164 p += len;
167 len = snprintf(p, pend - p, ")");
168 if (len >= (pend - p))
169 goto fail;
170 p += len;
173 * Encode CLDAPMessage and beginning of SearchRequest sequence.
176 if ((ber = ber_alloc()) == NULL)
177 goto fail;
179 if (ber_printf(ber, "{it{seeiib", msgid,
180 LDAP_REQ_SEARCH, basedn, scope, deref,
181 sizelimit, timelimit, attrsonly) < 0)
182 goto fail;
185 * Encode Filter sequence.
187 if (ldap_put_filter(ber, filter) < 0)
188 goto fail;
190 * Encode attribute and close Filter and SearchRequest sequences.
192 if (ber_printf(ber, "{s}}}", NETLOGON_ATTR_NAME) < 0)
193 goto fail;
196 * Success
198 return (ber);
200 fail:
201 if (ber != NULL)
202 ber_free(ber, 1);
203 return (NULL);
207 * Parse incoming search responses and attribute to correct hosts.
209 * CLDAPMessage ::= SEQUENCE {
210 * messageID MessageID,
211 * searchResponse SEQUENCE OF
212 * SearchResponse;
215 * SearchResponse ::=
216 * CHOICE {
217 * entry [APPLICATION 4] SEQUENCE {
218 * objectName LDAPDN,
219 * attributes SEQUENCE OF SEQUENCE {
220 * AttributeType,
221 * SET OF
222 * AttributeValue
224 * },
225 * resultCode [APPLICATION 5] LDAPResult
229 static int
230 decode_name(uchar_t *base, uchar_t *cp, char *str)
232 uchar_t *tmp = NULL, *st = cp;
233 uint8_t len;
236 * there should probably be some boundary checks on str && cp
237 * maybe pass in strlen && msglen ?
239 while (*cp != 0) {
240 if (*cp == 0xc0) {
241 if (tmp == NULL)
242 tmp = cp + 2;
243 cp = base + *(cp + 1);
245 for (len = *cp++; len > 0; len--)
246 *str++ = *cp++;
247 *str++ = '.';
249 if (cp != st)
250 *(str-1) = '\0';
251 else
252 *str = '\0';
254 return ((tmp == NULL ? cp + 1 : tmp) - st);
257 static int
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;
264 uint16_t opcode;
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) {
272 rc = 1;
273 goto out;
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:
308 * We always have this already.
309 * (Could validate it here.)
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);
319 break;
320 case NET_DOMAIN_NAME:
322 * This is the "Flat" domain name.
323 * (i.e. the NetBIOS name)
324 * ignore for now.
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;
346 * These are all possible, but we don't really care about them.
347 * Sockaddr_size && sockaddr might be useful at some point
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;
362 out:
363 if (base)
364 free(base);
365 else free(cp);
366 return (rc);
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)
379 ad_disc_ds_t *
380 ldap_ping(ad_disc_t ctx, ad_disc_cds_t *dclist, char *dname, int reqflags)
382 struct sockaddr_in6 addr6;
383 socklen_t addrlen;
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;
392 int fd = -1;
393 int tries = 3;
394 int waitsec;
395 int r;
396 uint16_t msgid;
398 /* One plus a null entry. */
399 ret_ds = calloc(2, sizeof (ad_disc_ds_t));
400 if (ret_ds == NULL)
401 goto fail;
403 if ((fd = socket(PF_INET6, SOCK_DGRAM, 0)) < 0)
404 goto fail;
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)
410 goto fail;
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);
423 if (req == NULL)
424 goto fail;
425 be = (struct _berelement *)req;
426 be_len = be->ber_end - be->ber_buf;
428 if ((res = ber_alloc()) == NULL)
429 goto fail;
430 rbe = (struct _berelement *)res;
431 rbe_len = rbe->ber_end - rbe->ber_buf;
433 pingchk.fd = fd;
434 pingchk.events = POLLIN;
435 pingchk.revents = 0;
437 try_again:
438 send_ds = dclist;
439 waitsec = 5;
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);
447 send_ds++;
450 * Wait 1/10 sec. before the next send.
452 r = poll(&pingchk, 1, 100);
453 #if 0 /* DEBUG */
454 /* Drop all responses 1st pass. */
455 if (waitsec == 5)
456 r = 0;
457 #endif
458 } else {
460 * No more candidates to "ping", so
461 * just wait a sec for responses.
463 r = poll(&pingchk, 1, 1000);
464 if (r == 0)
465 --waitsec;
468 if (r > 0) {
470 * Got a response.
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);
477 if (recv_ds == NULL)
478 continue;
480 (void) cldap_parse(ctx, recv_ds, res);
481 if ((recv_ds->cds_ds.flags & reqflags) != reqflags) {
482 logger(LOG_ERR, "Skip %s"
483 "due to flags 0x%X",
484 recv_ds->cds_ds.host,
485 recv_ds->cds_ds.flags);
486 recv_ds = NULL;
491 if (recv_ds == NULL) {
492 if (--tries <= 0)
493 goto fail;
494 goto try_again;
497 (void) memcpy(ret_ds, recv_ds, sizeof (*ret_ds));
499 ber_free(res, 1);
500 ber_free(req, 1);
501 (void) close(fd);
502 return (ret_ds);
504 fail:
505 ber_free(res, 1);
506 ber_free(req, 1);
507 (void) close(fd);
508 free(ret_ds);
509 return (NULL);
513 * Attempt a send of the LDAP request to all known addresses
514 * for this candidate server.
516 static void
517 send_to_cds(ad_disc_cds_t *send_cds, char *ber_buf, size_t be_len, int fd)
519 struct sockaddr_in6 addr6;
520 struct addrinfo *ai;
521 int err;
523 if (DBG(DISC, 2)) {
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 =
537 (void *)ai->ai_addr;
538 addr6.sin6_family = AF_INET6;
539 IN6_INADDR_TO_V4MAPPED(&sin->sin_addr,
540 &addr6.sin6_addr);
541 } else {
542 continue;
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;
553 if (DBG(DISC, 2)) {
554 char abuf[INET6_ADDRSTRLEN];
555 const char *pa;
557 pa = inet_ntop(AF_INET6,
558 &addr6.sin6_addr,
559 abuf, sizeof (abuf));
560 logger(LOG_ERR, " > %s rc=%d",
561 pa ? pa : "?", err);
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];
575 ad_disc_cds_t *ds;
576 struct addrinfo *ai;
577 int eai;
579 if (DBG(DISC, 1)) {
580 eai = getnameinfo((void *)sin6from, sizeof (*sin6from),
581 abuf, sizeof (abuf), NULL, 0, NI_NUMERICHOST);
582 if (eai != 0)
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++) {
592 ai = ds->cds_ai;
593 while (ai != NULL) {
594 if (addrmatch(ai, sin6from))
595 goto found;
596 ai = ai->ai_next;
599 if (DBG(DISC, 1)) {
600 logger(LOG_DEBUG, " (unexpected)");
602 return (NULL);
604 found:
605 if (DBG(DISC, 2)) {
606 logger(LOG_DEBUG, " from %s", ds->cds_ds.host);
608 save_ai(ds, ai);
609 return (ds);
612 static boolean_t
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)))
627 return (B_TRUE);
630 if (ai->ai_family == AF_INET) {
631 struct in6_addr in6;
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)))
637 return (B_TRUE);
640 return (B_FALSE);
643 static void
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) {
655 if (DBG(DISC, 2))
656 logger(LOG_DEBUG, "already have an address");
657 return;
660 switch (ai->ai_family) {
661 case AF_INET:
662 sin = (void *)&ds->addr;
663 (void) memcpy(sin, ai->ai_addr, sizeof (*sin));
664 sin->sin_port = htons(ds->port);
665 break;
667 case AF_INET6:
668 sin6 = (void *)&ds->addr;
669 (void) memcpy(sin6, ai->ai_addr, sizeof (*sin6));
670 sin6->sin6_port = htons(ds->port);
671 break;
673 default:
674 logger(LOG_ERR, "bad AF %d", ai->ai_family);