1/* $OpenBSD: ldapclient.c,v 1.13 2021/09/02 21:09:29 deraadt Exp $ */ 2 3/* 4 * Copyright (c) 2018 Reyk Floeter <reyk@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/queue.h> 20#include <sys/socket.h> 21#include <sys/stat.h> 22#include <sys/tree.h> 23#include <sys/un.h> 24 25#include <netinet/in.h> 26#include <arpa/inet.h> 27 28#include <stdio.h> 29#include <stdlib.h> 30#include <stdint.h> 31#include <unistd.h> 32#include <ctype.h> 33#include <err.h> 34#include <errno.h> 35#include <event.h> 36#include <fcntl.h> 37#include <limits.h> 38#include <netdb.h> 39#include <pwd.h> 40#include <readpassphrase.h> 41#include <resolv.h> 42#include <signal.h> 43#include <string.h> 44#include <vis.h> 45 46#include "aldap.h" 47#include "log.h" 48 49#define F_STARTTLS 0x01 50#define F_TLS 0x02 51#define F_NEEDAUTH 0x04 52#define F_LDIF 0x08 53 54#define LDAPHOST "localhost" 55#define LDAPFILTER "(objectClass=*)" 56#define LDIF_LINELENGTH 79 57#define LDAPPASSMAX 1024 58 59#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) 60 61struct ldapc { 62 struct aldap *ldap_al; 63 char *ldap_host; 64 int ldap_port; 65 const char *ldap_capath; 66 char *ldap_binddn; 67 char *ldap_secret; 68 unsigned int ldap_flags; 69 enum protocol_op ldap_req; 70 enum aldap_protocol ldap_protocol; 71 struct aldap_url ldap_url; 72}; 73 74struct ldapc_search { 75 int ls_sizelimit; 76 int ls_timelimit; 77 char *ls_basedn; 78 char *ls_filter; 79 int ls_scope; 80 char **ls_attr; 81}; 82 83__dead void usage(void); 84int ldapc_connect(struct ldapc *); 85int ldapc_search(struct ldapc *, struct ldapc_search *); 86int ldapc_printattr(struct ldapc *, const char *, 87 const struct ber_octetstring *); 88void ldapc_disconnect(struct ldapc *); 89int ldapc_parseurl(struct ldapc *, struct ldapc_search *, 90 const char *); 91const char *ldapc_resultcode(enum result_code); 92const char *url_decode(char *); 93 94__dead void 95usage(void) 96{ 97 extern char *__progname; 98 99 fprintf(stderr, 100"usage: %s search [-LvWxZ] [-b basedn] [-c CAfile] [-D binddn] [-H host]\n" 101" [-l timelimit] [-s scope] [-w secret] [-y secretfile] [-z sizelimit]\n" 102" [filter] [attributes ...]\n", 103 __progname); 104 105 exit(1); 106} 107 108int 109main(int argc, char *argv[]) 110{ 111 char passbuf[LDAPPASSMAX]; 112 const char *errstr, *url = NULL, *secretfile = NULL; 113 struct stat st; 114 struct ldapc ldap; 115 struct ldapc_search ls; 116 int ch; 117 int verbose = 1; 118 FILE *fp; 119 120 if (pledge("stdio inet unix tty rpath dns", NULL) == -1) 121 err(1, "pledge"); 122 123 log_init(verbose, 0); 124 125 memset(&ldap, 0, sizeof(ldap)); 126 memset(&ls, 0, sizeof(ls)); 127 ls.ls_scope = -1; 128 ldap.ldap_port = -1; 129 130 /* 131 * Check the command. Currently only "search" is supported but 132 * it could be extended with others such as add, modify, or delete. 133 */ 134 if (argc < 2) 135 usage(); 136 else if (strcmp("search", argv[1]) == 0) 137 ldap.ldap_req = LDAP_REQ_SEARCH; 138 else 139 usage(); 140 argc--; 141 argv++; 142 143 while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) { 144 switch (ch) { 145 case 'b': 146 ls.ls_basedn = optarg; 147 break; 148 case 'c': 149 ldap.ldap_capath = optarg; 150 break; 151 case 'D': 152 ldap.ldap_binddn = optarg; 153 ldap.ldap_flags |= F_NEEDAUTH; 154 break; 155 case 'H': 156 url = optarg; 157 break; 158 case 'L': 159 ldap.ldap_flags |= F_LDIF; 160 break; 161 case 'l': 162 ls.ls_timelimit = strtonum(optarg, 0, INT_MAX, 163 &errstr); 164 if (errstr != NULL) 165 errx(1, "timelimit %s", errstr); 166 break; 167 case 's': 168 if (strcasecmp("base", optarg) == 0) 169 ls.ls_scope = LDAP_SCOPE_BASE; 170 else if (strcasecmp("one", optarg) == 0) 171 ls.ls_scope = LDAP_SCOPE_ONELEVEL; 172 else if (strcasecmp("sub", optarg) == 0) 173 ls.ls_scope = LDAP_SCOPE_SUBTREE; 174 else 175 errx(1, "invalid scope: %s", optarg); 176 break; 177 case 'v': 178 verbose++; 179 break; 180 case 'w': 181 ldap.ldap_secret = optarg; 182 ldap.ldap_flags |= F_NEEDAUTH; 183 break; 184 case 'W': 185 ldap.ldap_flags |= F_NEEDAUTH; 186 break; 187 case 'x': 188 /* provided for compatibility */ 189 break; 190 case 'y': 191 secretfile = optarg; 192 ldap.ldap_flags |= F_NEEDAUTH; 193 break; 194 case 'Z': 195 ldap.ldap_flags |= F_STARTTLS; 196 break; 197 case 'z': 198 ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX, 199 &errstr); 200 if (errstr != NULL) 201 errx(1, "sizelimit %s", errstr); 202 break; 203 default: 204 usage(); 205 } 206 } 207 argc -= optind; 208 argv += optind; 209 210 log_setverbose(verbose); 211 212 if (url != NULL && ldapc_parseurl(&ldap, &ls, url) == -1) 213 errx(1, "ldapurl"); 214 215 /* Set the default after parsing URL and/or options */ 216 if (ldap.ldap_host == NULL) 217 ldap.ldap_host = LDAPHOST; 218 if (ldap.ldap_port == -1) 219 ldap.ldap_port = ldap.ldap_protocol == LDAPS ? 220 LDAPS_PORT : LDAP_PORT; 221 if (ldap.ldap_protocol == LDAP && (ldap.ldap_flags & F_STARTTLS)) 222 ldap.ldap_protocol = LDAPTLS; 223 if (ldap.ldap_capath == NULL) 224 ldap.ldap_capath = tls_default_ca_cert_file(); 225 if (ls.ls_basedn == NULL) 226 ls.ls_basedn = ""; 227 if (ls.ls_scope == -1) 228 ls.ls_scope = LDAP_SCOPE_SUBTREE; 229 if (ls.ls_filter == NULL) 230 ls.ls_filter = LDAPFILTER; 231 232 if (ldap.ldap_flags & F_NEEDAUTH) { 233 if (ldap.ldap_binddn == NULL) { 234 log_warnx("missing -D binddn"); 235 usage(); 236 } 237 if (secretfile != NULL) { 238 if (ldap.ldap_secret != NULL) 239 errx(1, "conflicting -w/-y options"); 240 241 /* read password from stdin or file (first line) */ 242 if (strcmp(secretfile, "-") == 0) 243 fp = stdin; 244 else if (stat(secretfile, &st) == -1) 245 err(1, "failed to access %s", secretfile); 246 else if (S_ISREG(st.st_mode) && (st.st_mode & S_IROTH)) 247 errx(1, "%s is world-readable", secretfile); 248 else if ((fp = fopen(secretfile, "r")) == NULL) 249 err(1, "failed to open %s", secretfile); 250 if (fgets(passbuf, sizeof(passbuf), fp) == NULL) 251 err(1, "failed to read %s", secretfile); 252 if (fp != stdin) 253 fclose(fp); 254 255 passbuf[strcspn(passbuf, "\n")] = '\0'; 256 ldap.ldap_secret = passbuf; 257 } 258 if (ldap.ldap_secret == NULL) { 259 if (readpassphrase("Password: ", 260 passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) == NULL) 261 errx(1, "failed to read LDAP password"); 262 ldap.ldap_secret = passbuf; 263 } 264 } 265 266 if (pledge("stdio inet unix rpath dns", NULL) == -1) 267 err(1, "pledge"); 268 269 /* optional search filter */ 270 if (argc && strchr(argv[0], '=') != NULL) { 271 ls.ls_filter = argv[0]; 272 argc--; 273 argv++; 274 } 275 /* search attributes */ 276 if (argc) 277 ls.ls_attr = argv; 278 279 if (ldapc_connect(&ldap) == -1) 280 errx(1, "LDAP connection failed"); 281 282 if (pledge("stdio", NULL) == -1) 283 err(1, "pledge"); 284 285 if (ldapc_search(&ldap, &ls) == -1) 286 errx(1, "LDAP search failed"); 287 288 ldapc_disconnect(&ldap); 289 aldap_free_url(&ldap.ldap_url); 290 291 return (0); 292} 293 294int 295ldapc_search(struct ldapc *ldap, struct ldapc_search *ls) 296{ 297 struct aldap_page_control *pg = NULL; 298 struct aldap_message *m; 299 const char *errstr; 300 const char *searchdn, *dn = NULL; 301 char *outkey; 302 struct aldap_stringset *outvalues; 303 int ret, code, fail = 0; 304 size_t i; 305 306 if (ldap->ldap_flags & F_LDIF) 307 printf("version: 1\n"); 308 do { 309 if (aldap_search(ldap->ldap_al, ls->ls_basedn, ls->ls_scope, 310 ls->ls_filter, ls->ls_attr, 0, ls->ls_sizelimit, 311 ls->ls_timelimit, pg) == -1) { 312 aldap_get_errno(ldap->ldap_al, &errstr); 313 log_warnx("LDAP search failed: %s", errstr); 314 return (-1); 315 } 316 317 if (pg != NULL) { 318 aldap_freepage(pg); 319 pg = NULL; 320 } 321 322 while ((m = aldap_parse(ldap->ldap_al)) != NULL) { 323 if (ldap->ldap_al->msgid != m->msgid) { 324 goto fail; 325 } 326 327 if ((code = aldap_get_resultcode(m)) != LDAP_SUCCESS) { 328 log_warnx("LDAP search failed: %s(%d)", 329 ldapc_resultcode(code), code); 330 break; 331 } 332 333 if (m->message_type == LDAP_RES_SEARCH_RESULT) { 334 if (m->page != NULL && m->page->cookie_len != 0) 335 pg = m->page; 336 else 337 pg = NULL; 338 339 aldap_freemsg(m); 340 break; 341 } 342 343 if (m->message_type != LDAP_RES_SEARCH_ENTRY) { 344 goto fail; 345 } 346 347 if (aldap_count_attrs(m) < 1) { 348 aldap_freemsg(m); 349 continue; 350 } 351 352 if ((searchdn = aldap_get_dn(m)) == NULL) 353 goto fail; 354 355 if (dn != NULL) 356 printf("\n"); 357 else 358 dn = ls->ls_basedn; 359 if (strcmp(dn, searchdn) != 0) 360 printf("dn: %s\n", searchdn); 361 362 for (ret = aldap_first_attr(m, &outkey, &outvalues); 363 ret != -1; 364 ret = aldap_next_attr(m, &outkey, &outvalues)) { 365 for (i = 0; i < outvalues->len; i++) { 366 if (ldapc_printattr(ldap, outkey, 367 &(outvalues->str[i])) == -1) { 368 fail = 1; 369 break; 370 } 371 } 372 } 373 free(outkey); 374 aldap_free_attr(outvalues); 375 376 aldap_freemsg(m); 377 } 378 } while (pg != NULL && fail == 0); 379 380 if (fail) 381 return (-1); 382 return (0); 383 fail: 384 ldapc_disconnect(ldap); 385 return (-1); 386} 387 388int 389ldapc_printattr(struct ldapc *ldap, const char *key, 390 const struct ber_octetstring *value) 391{ 392 char *p = NULL, *out; 393 const unsigned char *cp; 394 int encode; 395 size_t i, inlen, outlen, left; 396 397 if (ldap->ldap_flags & F_LDIF) { 398 /* OpenLDAP encodes the userPassword by default */ 399 if (strcasecmp("userPassword", key) == 0) 400 encode = 1; 401 else 402 encode = 0; 403 404 /* 405 * The LDIF format a set of characters that can be included 406 * in SAFE-STRINGs. String value that do not match the 407 * criteria must be encoded as Base64. 408 */ 409 cp = (const unsigned char *)value->ostr_val; 410 /* !SAFE-INIT-CHAR: SAFE-CHAR minus %x20 %x3A %x3C */ 411 if (*cp == ' ' || 412 *cp == ':' || 413 *cp == '<') 414 encode = 1; 415 for (i = 0; encode == 0 && i < value->ostr_len - 1; i++) { 416 /* !SAFE-CHAR %x01-09 / %x0B-0C / %x0E-7F */ 417 if (cp[i] > 127 || 418 cp[i] == '\0' || 419 cp[i] == '\n' || 420 cp[i] == '\r') 421 encode = 1; 422 } 423 424 if (!encode) { 425 if (asprintf(&p, "%s: %s", key, 426 (const char *)value->ostr_val) == -1) { 427 log_warnx("asprintf"); 428 return (-1); 429 } 430 } else { 431 outlen = (((value->ostr_len + 2) / 3) * 4) + 1; 432 433 if ((out = calloc(1, outlen)) == NULL || 434 b64_ntop(value->ostr_val, value->ostr_len, out, 435 outlen) == -1) { 436 log_warnx("Base64 encoding failed"); 437 free(p); 438 return (-1); 439 } 440 441 /* Base64 is indicated with a double-colon */ 442 if (asprintf(&p, "%s:: %s", key, out) == -1) { 443 log_warnx("asprintf"); 444 free(out); 445 return (-1); 446 } 447 free(out); 448 } 449 450 /* Wrap lines */ 451 for (outlen = 0, inlen = strlen(p); 452 outlen < inlen; 453 outlen += LDIF_LINELENGTH - 1) { 454 if (outlen) 455 putchar(' '); 456 if (outlen > LDIF_LINELENGTH) 457 outlen--; 458 /* max. line length - newline - optional indent */ 459 left = MINIMUM(inlen - outlen, outlen ? 460 LDIF_LINELENGTH - 2 : 461 LDIF_LINELENGTH - 1); 462 fwrite(p + outlen, left, 1, stdout); 463 putchar('\n'); 464 } 465 } else { 466 /* 467 * Use vis(1) instead of base64 encoding of non-printable 468 * values. This is much nicer as it always prdocues a 469 * human-readable visual output. This can safely be done 470 * on all values no matter if they include non-printable 471 * characters. 472 */ 473 p = calloc(1, 4 * value->ostr_len + 1); 474 if (strvisx(p, value->ostr_val, value->ostr_len, 475 VIS_SAFE|VIS_NL) == -1) { 476 log_warn("visual encoding failed"); 477 return (-1); 478 } 479 480 printf("%s: %s\n", key, p); 481 } 482 483 free(p); 484 return (0); 485} 486 487int 488ldapc_connect(struct ldapc *ldap) 489{ 490 struct addrinfo ai, *res, *res0; 491 struct sockaddr_un un; 492 int ret = -1, saved_errno, fd = -1, code; 493 struct aldap_message *m; 494 const char *errstr; 495 struct tls_config *tls_config; 496 char port[6]; 497 498 if (ldap->ldap_protocol == LDAPI) { 499 memset(&un, 0, sizeof(un)); 500 un.sun_family = AF_UNIX; 501 if (strlcpy(un.sun_path, ldap->ldap_host, 502 sizeof(un.sun_path)) >= sizeof(un.sun_path)) { 503 log_warnx("socket '%s' too long", ldap->ldap_host); 504 goto done; 505 } 506 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 || 507 connect(fd, (struct sockaddr *)&un, sizeof(un)) == -1) 508 goto done; 509 goto init; 510 } 511 512 memset(&ai, 0, sizeof(ai)); 513 ai.ai_family = AF_UNSPEC; 514 ai.ai_socktype = SOCK_STREAM; 515 ai.ai_protocol = IPPROTO_TCP; 516 (void)snprintf(port, sizeof(port), "%u", ldap->ldap_port); 517 if ((code = getaddrinfo(ldap->ldap_host, port, 518 &ai, &res0)) != 0) { 519 log_warnx("%s", gai_strerror(code)); 520 goto done; 521 } 522 for (res = res0; res; res = res->ai_next, fd = -1) { 523 if ((fd = socket(res->ai_family, res->ai_socktype, 524 res->ai_protocol)) == -1) 525 continue; 526 527 if (connect(fd, res->ai_addr, res->ai_addrlen) >= 0) 528 break; 529 530 saved_errno = errno; 531 close(fd); 532 errno = saved_errno; 533 } 534 freeaddrinfo(res0); 535 if (fd == -1) 536 goto done; 537 538 init: 539 if ((ldap->ldap_al = aldap_init(fd)) == NULL) { 540 warn("LDAP init failed"); 541 close(fd); 542 goto done; 543 } 544 545 if (ldap->ldap_flags & F_STARTTLS) { 546 log_debug("%s: requesting STARTTLS", __func__); 547 if (aldap_req_starttls(ldap->ldap_al) == -1) { 548 log_warnx("failed to request STARTTLS"); 549 goto done; 550 } 551 552 if ((m = aldap_parse(ldap->ldap_al)) == NULL) { 553 log_warnx("failed to parse STARTTLS response"); 554 goto done; 555 } 556 557 if (ldap->ldap_al->msgid != m->msgid || 558 (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) { 559 log_warnx("STARTTLS failed: %s(%d)", 560 ldapc_resultcode(code), code); 561 aldap_freemsg(m); 562 goto done; 563 } 564 aldap_freemsg(m); 565 } 566 567 if (ldap->ldap_flags & (F_STARTTLS | F_TLS)) { 568 log_debug("%s: starting TLS", __func__); 569 570 if ((tls_config = tls_config_new()) == NULL) { 571 log_warnx("TLS config failed"); 572 goto done; 573 } 574 575 if (tls_config_set_ca_file(tls_config, 576 ldap->ldap_capath) == -1) { 577 log_warnx("unable to set CA %s", ldap->ldap_capath); 578 goto done; 579 } 580 581 if (aldap_tls(ldap->ldap_al, tls_config, ldap->ldap_host) < 0) { 582 aldap_get_errno(ldap->ldap_al, &errstr); 583 log_warnx("TLS failed: %s", errstr); 584 goto done; 585 } 586 } 587 588 if (ldap->ldap_flags & F_NEEDAUTH) { 589 log_debug("%s: bind request", __func__); 590 if (aldap_bind(ldap->ldap_al, ldap->ldap_binddn, 591 ldap->ldap_secret) == -1) { 592 log_warnx("bind request failed"); 593 goto done; 594 } 595 596 if ((m = aldap_parse(ldap->ldap_al)) == NULL) { 597 log_warnx("failed to parse bind response"); 598 goto done; 599 } 600 601 if (ldap->ldap_al->msgid != m->msgid || 602 (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) { 603 log_warnx("bind failed: %s(%d)", 604 ldapc_resultcode(code), code); 605 aldap_freemsg(m); 606 goto done; 607 } 608 aldap_freemsg(m); 609 } 610 611 log_debug("%s: connected", __func__); 612 613 ret = 0; 614 done: 615 if (ret != 0) 616 ldapc_disconnect(ldap); 617 if (ldap->ldap_secret != NULL) 618 explicit_bzero(ldap->ldap_secret, 619 strlen(ldap->ldap_secret)); 620 return (ret); 621} 622 623void 624ldapc_disconnect(struct ldapc *ldap) 625{ 626 if (ldap->ldap_al == NULL) 627 return; 628 aldap_close(ldap->ldap_al); 629 ldap->ldap_al = NULL; 630} 631 632const char * 633ldapc_resultcode(enum result_code code) 634{ 635#define CODE(_X) case _X:return (#_X) 636 switch (code) { 637 CODE(LDAP_SUCCESS); 638 CODE(LDAP_OPERATIONS_ERROR); 639 CODE(LDAP_PROTOCOL_ERROR); 640 CODE(LDAP_TIMELIMIT_EXCEEDED); 641 CODE(LDAP_SIZELIMIT_EXCEEDED); 642 CODE(LDAP_COMPARE_FALSE); 643 CODE(LDAP_COMPARE_TRUE); 644 CODE(LDAP_STRONG_AUTH_NOT_SUPPORTED); 645 CODE(LDAP_STRONG_AUTH_REQUIRED); 646 CODE(LDAP_REFERRAL); 647 CODE(LDAP_ADMINLIMIT_EXCEEDED); 648 CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION); 649 CODE(LDAP_CONFIDENTIALITY_REQUIRED); 650 CODE(LDAP_SASL_BIND_IN_PROGRESS); 651 CODE(LDAP_NO_SUCH_ATTRIBUTE); 652 CODE(LDAP_UNDEFINED_TYPE); 653 CODE(LDAP_INAPPROPRIATE_MATCHING); 654 CODE(LDAP_CONSTRAINT_VIOLATION); 655 CODE(LDAP_TYPE_OR_VALUE_EXISTS); 656 CODE(LDAP_INVALID_SYNTAX); 657 CODE(LDAP_NO_SUCH_OBJECT); 658 CODE(LDAP_ALIAS_PROBLEM); 659 CODE(LDAP_INVALID_DN_SYNTAX); 660 CODE(LDAP_ALIAS_DEREF_PROBLEM); 661 CODE(LDAP_INAPPROPRIATE_AUTH); 662 CODE(LDAP_INVALID_CREDENTIALS); 663 CODE(LDAP_INSUFFICIENT_ACCESS); 664 CODE(LDAP_BUSY); 665 CODE(LDAP_UNAVAILABLE); 666 CODE(LDAP_UNWILLING_TO_PERFORM); 667 CODE(LDAP_LOOP_DETECT); 668 CODE(LDAP_NAMING_VIOLATION); 669 CODE(LDAP_OBJECT_CLASS_VIOLATION); 670 CODE(LDAP_NOT_ALLOWED_ON_NONLEAF); 671 CODE(LDAP_NOT_ALLOWED_ON_RDN); 672 CODE(LDAP_ALREADY_EXISTS); 673 CODE(LDAP_NO_OBJECT_CLASS_MODS); 674 CODE(LDAP_AFFECTS_MULTIPLE_DSAS); 675 CODE(LDAP_OTHER); 676 default: 677 return ("UNKNOWN_ERROR"); 678 } 679}; 680 681int 682ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url) 683{ 684 struct aldap_url *lu = &ldap->ldap_url; 685 size_t i; 686 687 memset(lu, 0, sizeof(*lu)); 688 lu->scope = -1; 689 690 if (aldap_parse_url(url, lu) == -1) { 691 log_warnx("failed to parse LDAP URL"); 692 return (-1); 693 } 694 695 /* The protocol part is optional and we default to ldap:// */ 696 if (lu->protocol == -1) 697 lu->protocol = LDAP; 698 else if (lu->protocol == LDAPI) { 699 if (lu->port != 0 || 700 url_decode(lu->host) == NULL) { 701 log_warnx("invalid ldapi:// URL"); 702 return (-1); 703 } 704 } else if ((ldap->ldap_flags & F_STARTTLS) && 705 lu->protocol != LDAPTLS) { 706 log_warnx("conflicting protocol arguments"); 707 return (-1); 708 } else if (lu->protocol == LDAPTLS) 709 ldap->ldap_flags |= F_TLS|F_STARTTLS; 710 else if (lu->protocol == LDAPS) 711 ldap->ldap_flags |= F_TLS; 712 ldap->ldap_protocol = lu->protocol; 713 714 ldap->ldap_host = lu->host; 715 if (lu->port) 716 ldap->ldap_port = lu->port; 717 718 /* The distinguished name has to be URL-encoded */ 719 if (lu->dn != NULL && ls->ls_basedn != NULL && 720 strcasecmp(ls->ls_basedn, lu->dn) != 0) { 721 log_warnx("conflicting basedn arguments"); 722 return (-1); 723 } 724 if (lu->dn != NULL) { 725 if (url_decode(lu->dn) == NULL) 726 return (-1); 727 ls->ls_basedn = lu->dn; 728 } 729 730 if (lu->scope != -1) { 731 if (ls->ls_scope != -1 && (ls->ls_scope != lu->scope)) { 732 log_warnx("conflicting scope arguments"); 733 return (-1); 734 } 735 ls->ls_scope = lu->scope; 736 } 737 738 /* URL-decode optional attributes and the search filter */ 739 if (lu->attributes[0] != NULL) { 740 for (i = 0; i < MAXATTR && lu->attributes[i] != NULL; i++) 741 if (url_decode(lu->attributes[i]) == NULL) 742 return (-1); 743 ls->ls_attr = lu->attributes; 744 } 745 if (lu->filter != NULL) { 746 if (url_decode(lu->filter) == NULL) 747 return (-1); 748 ls->ls_filter = lu->filter; 749 } 750 751 return (0); 752} 753 754/* From usr.sbin/httpd/httpd.c */ 755const char * 756url_decode(char *url) 757{ 758 char *p, *q; 759 char hex[3]; 760 unsigned long x; 761 762 hex[2] = '\0'; 763 p = q = url; 764 765 while (*p != '\0') { 766 switch (*p) { 767 case '%': 768 /* Encoding character is followed by two hex chars */ 769 if (!(isxdigit((unsigned char)p[1]) && 770 isxdigit((unsigned char)p[2]))) 771 return (NULL); 772 773 hex[0] = p[1]; 774 hex[1] = p[2]; 775 776 /* 777 * We don't have to validate "hex" because it is 778 * guaranteed to include two hex chars followed by nul. 779 */ 780 x = strtoul(hex, NULL, 16); 781 *q = (char)x; 782 p += 2; 783 break; 784 default: 785 *q = *p; 786 break; 787 } 788 p++; 789 q++; 790 } 791 *q = '\0'; 792 793 return (url); 794} 795