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