8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / lib / libadutils / common / ldap_ping.c
blob5008372d74271f14a4d971ba78eba9bac6e80336
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 if (cp)
366 free(cp);
367 return (rc);
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.
378 * Only return the "winner". (We only want one DC/GC)
380 ad_disc_ds_t *
381 ldap_ping(ad_disc_t ctx, ad_disc_cds_t *dclist, char *dname, int reqflags)
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;
399 /* One plus a null entry. */
400 ret_ds = calloc(2, sizeof (ad_disc_ds_t));
401 if (ret_ds == NULL)
402 goto fail;
404 if ((fd = socket(PF_INET6, SOCK_DGRAM, 0)) < 0)
405 goto fail;
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;
414 * semi-unique msgid...
416 msgid = gethrtime() & 0xffff;
419 * Is ntver right? It certainly works on w2k8... If others are needed,
420 * that might require changes to cldap_parse
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;
429 if ((res = ber_alloc()) == NULL)
430 goto fail;
431 rbe = (struct _berelement *)res;
432 rbe_len = rbe->ber_end - rbe->ber_buf;
434 pingchk.fd = fd;
435 pingchk.events = POLLIN;
436 pingchk.revents = 0;
438 try_again:
439 send_ds = dclist;
440 waitsec = 5;
441 while (recv_ds == NULL && waitsec > 0) {
444 * If there is another candidate, send to it.
446 if (send_ds->cds_ds.host[0] != '\0') {
447 send_to_cds(send_ds, be->ber_buf, be_len, fd);
448 send_ds++;
451 * Wait 1/10 sec. before the next send.
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 {
461 * No more candidates to "ping", so
462 * just wait a sec for responses.
464 r = poll(&pingchk, 1, 1000);
465 if (r == 0)
466 --waitsec;
469 if (r > 0) {
471 * Got a response.
473 (void) memset(&addr6, 0, addrlen = sizeof (addr6));
474 r = recvfrom(fd, rbe->ber_buf, rbe_len, 0,
475 (struct sockaddr *)&addr6, &addrlen);
477 recv_ds = find_cds_by_addr(dclist, &addr6);
478 if (recv_ds == NULL)
479 continue;
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;
492 if (recv_ds == NULL) {
493 if (--tries <= 0)
494 goto fail;
495 goto try_again;
498 (void) memcpy(ret_ds, recv_ds, sizeof (*ret_ds));
500 ber_free(res, 1);
501 ber_free(req, 1);
502 (void) close(fd);
503 return (ret_ds);
505 fail:
506 ber_free(res, 1);
507 ber_free(req, 1);
508 (void) close(fd);
509 free(ret_ds);
510 return (NULL);
514 * Attempt a send of the LDAP request to all known addresses
515 * for this candidate server.
517 static void
518 send_to_cds(ad_disc_cds_t *send_cds, char *ber_buf, size_t be_len, int fd)
520 struct sockaddr_in6 addr6;
521 struct addrinfo *ai;
522 int err;
524 if (DBG(DISC, 2)) {
525 logger(LOG_DEBUG, "send to: %s", send_cds->cds_ds.host);
528 for (ai = send_cds->cds_ai; ai != NULL; ai = ai->ai_next) {
531 * Build the "to" address.
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;
545 addr6.sin6_port = htons(LDAP_PORT);
548 * Send the "ping" to this address.
550 err = sendto(fd, ber_buf, be_len, 0,
551 (struct sockaddr *)&addr6, sizeof (addr6));
552 err = (err < 0) ? errno : 0;
554 if (DBG(DISC, 2)) {
555 char abuf[INET6_ADDRSTRLEN];
556 const char *pa;
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);
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.
572 static ad_disc_cds_t *
573 find_cds_by_addr(ad_disc_cds_t *dclist, struct sockaddr_in6 *sin6from)
575 char abuf[INET6_ADDRSTRLEN];
576 ad_disc_cds_t *ds;
577 struct addrinfo *ai;
578 int eai;
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);
589 * Find the DS this response came from.
590 * (don't accept unexpected responses)
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;
600 if (DBG(DISC, 1)) {
601 logger(LOG_DEBUG, " (unexpected)");
603 return (NULL);
605 found:
606 if (DBG(DISC, 2)) {
607 logger(LOG_DEBUG, " from %s", ds->cds_ds.host);
609 save_ai(ds, ai);
610 return (ds);
613 static boolean_t
614 addrmatch(struct addrinfo *ai, struct sockaddr_in6 *sin6from)
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.
623 if (ai->ai_family == AF_INET6) {
624 struct sockaddr_in6 *sin6p = (void *)ai->ai_addr;
626 if (!memcmp(&sin6from->sin6_addr, &sin6p->sin6_addr,
627 sizeof (struct in6_addr)))
628 return (B_TRUE);
631 if (ai->ai_family == AF_INET) {
632 struct in6_addr in6;
633 struct sockaddr_in *sin4p = (void *)ai->ai_addr;
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);
641 return (B_FALSE);
644 static void
645 save_ai(ad_disc_cds_t *cds, struct addrinfo *ai)
647 ad_disc_ds_t *ds = &cds->cds_ds;
648 struct sockaddr_in *sin;
649 struct sockaddr_in6 *sin6;
652 * If this DS already saw a response, keep the first
653 * address from which we received a response.
655 if (ds->addr.ss_family != 0) {
656 if (DBG(DISC, 2))
657 logger(LOG_DEBUG, "already have an address");
658 return;
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;
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;
674 default:
675 logger(LOG_ERR, "bad AF %d", ai->ai_family);