1 /* $OpenBSD: ldapclient.c,v 1.11 2018/11/29 14:25:07 tedu 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 free(p); 481 } 482 483 free(p); 484 return (0); 485 } 486 487 int 488 ldapc_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 623 void 624 ldapc_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 632 const char * 633 ldapc_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 681 int 682 ldapc_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 */ 755 const char * 756 url_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