| File: | src/usr.bin/ldap/ldapclient.c |
| Warning: | line 476, column 4 Potential leak of memory pointed to by 'p' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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(*__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_STARTTLS0x01 0x01 | |||
| 50 | #define F_TLS0x02 0x02 | |||
| 51 | #define F_NEEDAUTH0x04 0x04 | |||
| 52 | #define F_LDIF0x08 0x08 | |||
| 53 | ||||
| 54 | #define LDAPHOST"localhost" "localhost" | |||
| 55 | #define LDAPFILTER"(objectClass=*)" "(objectClass=*)" | |||
| 56 | #define LDIF_LINELENGTH79 79 | |||
| 57 | #define LDAPPASSMAX1024 1024 | |||
| 58 | ||||
| 59 | #define MINIMUM(a, b)(((a) < (b)) ? (a) : (b)) (((a) < (b)) ? (a) : (b)) | |||
| 60 | ||||
| 61 | struct 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 | ||||
| 74 | struct 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__attribute__((__noreturn__)) void usage(void); | |||
| 84 | int ldapc_connect(struct ldapc *); | |||
| 85 | int ldapc_search(struct ldapc *, struct ldapc_search *); | |||
| 86 | int ldapc_printattr(struct ldapc *, const char *, | |||
| 87 | const struct ber_octetstring *); | |||
| 88 | void ldapc_disconnect(struct ldapc *); | |||
| 89 | int ldapc_parseurl(struct ldapc *, struct ldapc_search *, | |||
| 90 | const char *); | |||
| 91 | const char *ldapc_resultcode(enum result_code); | |||
| 92 | const char *url_decode(char *); | |||
| 93 | ||||
| 94 | __dead__attribute__((__noreturn__)) void | |||
| 95 | usage(void) | |||
| 96 | { | |||
| 97 | extern char *__progname; | |||
| 98 | ||||
| 99 | fprintf(stderr(&__sF[2]), | |||
| 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 | ||||
| 108 | int | |||
| 109 | main(int argc, char *argv[]) | |||
| 110 | { | |||
| 111 | char passbuf[LDAPPASSMAX1024]; | |||
| 112 | const char *errstr, *url = NULL((void *)0), *secretfile = NULL((void *)0); | |||
| 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((void *)0)) == -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_NEEDAUTH0x04; | |||
| 154 | break; | |||
| 155 | case 'H': | |||
| 156 | url = optarg; | |||
| 157 | break; | |||
| 158 | case 'L': | |||
| 159 | ldap.ldap_flags |= F_LDIF0x08; | |||
| 160 | break; | |||
| 161 | case 'l': | |||
| 162 | ls.ls_timelimit = strtonum(optarg, 0, INT_MAX0x7fffffff, | |||
| 163 | &errstr); | |||
| 164 | if (errstr != NULL((void *)0)) | |||
| 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_NEEDAUTH0x04; | |||
| 183 | break; | |||
| 184 | case 'W': | |||
| 185 | ldap.ldap_flags |= F_NEEDAUTH0x04; | |||
| 186 | break; | |||
| 187 | case 'x': | |||
| 188 | /* provided for compatibility */ | |||
| 189 | break; | |||
| 190 | case 'y': | |||
| 191 | secretfile = optarg; | |||
| 192 | ldap.ldap_flags |= F_NEEDAUTH0x04; | |||
| 193 | break; | |||
| 194 | case 'Z': | |||
| 195 | ldap.ldap_flags |= F_STARTTLS0x01; | |||
| 196 | break; | |||
| 197 | case 'z': | |||
| 198 | ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX0x7fffffff, | |||
| 199 | &errstr); | |||
| 200 | if (errstr != NULL((void *)0)) | |||
| 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
| |||
| 213 | errx(1, "ldapurl"); | |||
| 214 | ||||
| 215 | /* Set the default after parsing URL and/or options */ | |||
| 216 | if (ldap.ldap_host
| |||
| 217 | ldap.ldap_host = LDAPHOST"localhost"; | |||
| 218 | if (ldap.ldap_port == -1) | |||
| 219 | ldap.ldap_port = ldap.ldap_protocol
| |||
| 220 | LDAPS_PORT636 : LDAP_PORT389; | |||
| 221 | if (ldap.ldap_protocol
| |||
| 222 | ldap.ldap_protocol = LDAPTLS; | |||
| 223 | if (ldap.ldap_capath
| |||
| 224 | ldap.ldap_capath = tls_default_ca_cert_file(); | |||
| 225 | if (ls.ls_basedn
| |||
| 226 | ls.ls_basedn = ""; | |||
| 227 | if (ls.ls_scope == -1) | |||
| 228 | ls.ls_scope = LDAP_SCOPE_SUBTREE; | |||
| 229 | if (ls.ls_filter
| |||
| 230 | ls.ls_filter = LDAPFILTER"(objectClass=*)"; | |||
| 231 | ||||
| 232 | if (ldap.ldap_flags & F_NEEDAUTH0x04) { | |||
| 233 | if (ldap.ldap_binddn == NULL((void *)0)) { | |||
| 234 | log_warnx("missing -D binddn"); | |||
| 235 | usage(); | |||
| 236 | } | |||
| 237 | if (secretfile != NULL((void *)0)) { | |||
| 238 | if (ldap.ldap_secret != NULL((void *)0)) | |||
| 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(&__sF[0]); | |||
| 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 & 0170000) == 0100000) && (st.st_mode & S_IROTH0000004)) | |||
| 247 | errx(1, "%s is world-readable", secretfile); | |||
| 248 | else if ((fp = fopen(secretfile, "r")) == NULL((void *)0)) | |||
| 249 | err(1, "failed to open %s", secretfile); | |||
| 250 | if (fgets(passbuf, sizeof(passbuf), fp) == NULL((void *)0)) | |||
| 251 | err(1, "failed to read %s", secretfile); | |||
| 252 | if (fp != stdin(&__sF[0])) | |||
| 253 | fclose(fp); | |||
| 254 | ||||
| 255 | passbuf[strcspn(passbuf, "\n")] = '\0'; | |||
| 256 | ldap.ldap_secret = passbuf; | |||
| 257 | } | |||
| 258 | if (ldap.ldap_secret == NULL((void *)0)) { | |||
| 259 | if (readpassphrase("Password: ", | |||
| 260 | passbuf, sizeof(passbuf), RPP_REQUIRE_TTY0x02) == NULL((void *)0)) | |||
| 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((void *)0)) == -1) | |||
| 267 | err(1, "pledge"); | |||
| 268 | ||||
| 269 | /* optional search filter */ | |||
| 270 | if (argc && strchr(argv[0], '=') != NULL((void *)0)) { | |||
| 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((void *)0)) == -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 | ||||
| 294 | int | |||
| 295 | ldapc_search(struct ldapc *ldap, struct ldapc_search *ls) | |||
| 296 | { | |||
| 297 | struct aldap_page_control *pg = NULL((void *)0); | |||
| 298 | struct aldap_message *m; | |||
| 299 | const char *errstr; | |||
| 300 | const char *searchdn, *dn = NULL((void *)0); | |||
| 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_LDIF0x08) | |||
| 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
| |||
| 318 | aldap_freepage(pg); | |||
| 319 | pg = NULL((void *)0); | |||
| 320 | } | |||
| 321 | ||||
| 322 | while ((m = aldap_parse(ldap->ldap_al)) != NULL((void *)0)) { | |||
| 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((void *)0) && m->page->cookie_len != 0) | |||
| 335 | pg = m->page; | |||
| 336 | else | |||
| 337 | pg = NULL((void *)0); | |||
| 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((void *)0)) | |||
| 353 | goto fail; | |||
| 354 | ||||
| 355 | if (dn
| |||
| 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((void *)0) && fail == 0); | |||
| 379 | ||||
| 380 | if (fail) | |||
| 381 | return (-1); | |||
| 382 | return (0); | |||
| 383 | fail: | |||
| 384 | ldapc_disconnect(ldap); | |||
| 385 | return (-1); | |||
| 386 | } | |||
| 387 | ||||
| 388 | int | |||
| 389 | ldapc_printattr(struct ldapc *ldap, const char *key, | |||
| 390 | const struct ber_octetstring *value) | |||
| 391 | { | |||
| 392 | char *p = NULL((void *)0), *out; | |||
| 393 | const unsigned char *cp; | |||
| 394 | int encode; | |||
| 395 | size_t i, inlen, outlen, left; | |||
| 396 | ||||
| 397 | if (ldap->ldap_flags & F_LDIF0x08) { | |||
| 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((void *)0) || | |||
| 434 | b64_ntop__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_LINELENGTH79 - 1) { | |||
| 454 | if (outlen) | |||
| 455 | putchar(' ')(!__isthreaded ? __sputc(' ', (&__sF[1])) : (putc)(' ', ( &__sF[1]))); | |||
| 456 | if (outlen > LDIF_LINELENGTH79) | |||
| 457 | outlen--; | |||
| 458 | /* max. line length - newline - optional indent */ | |||
| 459 | left = MINIMUM(inlen - outlen, outlen ?(((inlen - outlen) < (outlen ? 79 - 2 : 79 - 1)) ? (inlen - outlen) : (outlen ? 79 - 2 : 79 - 1)) | |||
| 460 | LDIF_LINELENGTH - 2 :(((inlen - outlen) < (outlen ? 79 - 2 : 79 - 1)) ? (inlen - outlen) : (outlen ? 79 - 2 : 79 - 1)) | |||
| 461 | LDIF_LINELENGTH - 1)(((inlen - outlen) < (outlen ? 79 - 2 : 79 - 1)) ? (inlen - outlen) : (outlen ? 79 - 2 : 79 - 1)); | |||
| 462 | fwrite(p + outlen, left, 1, stdout(&__sF[1])); | |||
| 463 | putchar('\n')(!__isthreaded ? __sputc('\n', (&__sF[1])) : (putc)('\n', (&__sF[1]))); | |||
| 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_SAFE0x20|VIS_NL0x10) == -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 | ||||
| 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_UNIX1; | |||
| 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_UNIX1, SOCK_STREAM1, 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_UNSPEC0; | |||
| 514 | ai.ai_socktype = SOCK_STREAM1; | |||
| 515 | ai.ai_protocol = IPPROTO_TCP6; | |||
| 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(*__errno()); | |||
| 531 | close(fd); | |||
| 532 | errno(*__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((void *)0)) { | |||
| 540 | warn("LDAP init failed"); | |||
| 541 | close(fd); | |||
| 542 | goto done; | |||
| 543 | } | |||
| 544 | ||||
| 545 | if (ldap->ldap_flags & F_STARTTLS0x01) { | |||
| 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((void *)0)) { | |||
| 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_STARTTLS0x01 | F_TLS0x02)) { | |||
| 568 | log_debug("%s: starting TLS", __func__); | |||
| 569 | ||||
| 570 | if ((tls_config = tls_config_new()) == NULL((void *)0)) { | |||
| 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_NEEDAUTH0x04) { | |||
| 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((void *)0)) { | |||
| 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((void *)0)) | |||
| 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((void *)0)) | |||
| 627 | return; | |||
| 628 | aldap_close(ldap->ldap_al); | |||
| 629 | ldap->ldap_al = NULL((void *)0); | |||
| 630 | } | |||
| 631 | ||||
| 632 | const char * | |||
| 633 | ldapc_resultcode(enum result_code code) | |||
| 634 | { | |||
| 635 | #define CODE(_X)case 0x40:return ("_X") case _X0x40:return (#_X0x40) | |||
| 636 | switch (code) { | |||
| 637 | CODE(LDAP_SUCCESS)case LDAP_SUCCESS:return ("LDAP_SUCCESS"); | |||
| 638 | CODE(LDAP_OPERATIONS_ERROR)case LDAP_OPERATIONS_ERROR:return ("LDAP_OPERATIONS_ERROR"); | |||
| 639 | CODE(LDAP_PROTOCOL_ERROR)case LDAP_PROTOCOL_ERROR:return ("LDAP_PROTOCOL_ERROR"); | |||
| 640 | CODE(LDAP_TIMELIMIT_EXCEEDED)case LDAP_TIMELIMIT_EXCEEDED:return ("LDAP_TIMELIMIT_EXCEEDED" ); | |||
| 641 | CODE(LDAP_SIZELIMIT_EXCEEDED)case LDAP_SIZELIMIT_EXCEEDED:return ("LDAP_SIZELIMIT_EXCEEDED" ); | |||
| 642 | CODE(LDAP_COMPARE_FALSE)case LDAP_COMPARE_FALSE:return ("LDAP_COMPARE_FALSE"); | |||
| 643 | CODE(LDAP_COMPARE_TRUE)case LDAP_COMPARE_TRUE:return ("LDAP_COMPARE_TRUE"); | |||
| 644 | CODE(LDAP_STRONG_AUTH_NOT_SUPPORTED)case LDAP_STRONG_AUTH_NOT_SUPPORTED:return ("LDAP_STRONG_AUTH_NOT_SUPPORTED" ); | |||
| 645 | CODE(LDAP_STRONG_AUTH_REQUIRED)case LDAP_STRONG_AUTH_REQUIRED:return ("LDAP_STRONG_AUTH_REQUIRED" ); | |||
| 646 | CODE(LDAP_REFERRAL)case LDAP_REFERRAL:return ("LDAP_REFERRAL"); | |||
| 647 | CODE(LDAP_ADMINLIMIT_EXCEEDED)case LDAP_ADMINLIMIT_EXCEEDED:return ("LDAP_ADMINLIMIT_EXCEEDED" ); | |||
| 648 | CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION)case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:return ("LDAP_UNAVAILABLE_CRITICAL_EXTENSION" ); | |||
| 649 | CODE(LDAP_CONFIDENTIALITY_REQUIRED)case LDAP_CONFIDENTIALITY_REQUIRED:return ("LDAP_CONFIDENTIALITY_REQUIRED" ); | |||
| 650 | CODE(LDAP_SASL_BIND_IN_PROGRESS)case LDAP_SASL_BIND_IN_PROGRESS:return ("LDAP_SASL_BIND_IN_PROGRESS" ); | |||
| 651 | CODE(LDAP_NO_SUCH_ATTRIBUTE)case LDAP_NO_SUCH_ATTRIBUTE:return ("LDAP_NO_SUCH_ATTRIBUTE"); | |||
| 652 | CODE(LDAP_UNDEFINED_TYPE)case LDAP_UNDEFINED_TYPE:return ("LDAP_UNDEFINED_TYPE"); | |||
| 653 | CODE(LDAP_INAPPROPRIATE_MATCHING)case LDAP_INAPPROPRIATE_MATCHING:return ("LDAP_INAPPROPRIATE_MATCHING" ); | |||
| 654 | CODE(LDAP_CONSTRAINT_VIOLATION)case LDAP_CONSTRAINT_VIOLATION:return ("LDAP_CONSTRAINT_VIOLATION" ); | |||
| 655 | CODE(LDAP_TYPE_OR_VALUE_EXISTS)case LDAP_TYPE_OR_VALUE_EXISTS:return ("LDAP_TYPE_OR_VALUE_EXISTS" ); | |||
| 656 | CODE(LDAP_INVALID_SYNTAX)case LDAP_INVALID_SYNTAX:return ("LDAP_INVALID_SYNTAX"); | |||
| 657 | CODE(LDAP_NO_SUCH_OBJECT)case LDAP_NO_SUCH_OBJECT:return ("LDAP_NO_SUCH_OBJECT"); | |||
| 658 | CODE(LDAP_ALIAS_PROBLEM)case LDAP_ALIAS_PROBLEM:return ("LDAP_ALIAS_PROBLEM"); | |||
| 659 | CODE(LDAP_INVALID_DN_SYNTAX)case LDAP_INVALID_DN_SYNTAX:return ("LDAP_INVALID_DN_SYNTAX"); | |||
| 660 | CODE(LDAP_ALIAS_DEREF_PROBLEM)case LDAP_ALIAS_DEREF_PROBLEM:return ("LDAP_ALIAS_DEREF_PROBLEM" ); | |||
| 661 | CODE(LDAP_INAPPROPRIATE_AUTH)case LDAP_INAPPROPRIATE_AUTH:return ("LDAP_INAPPROPRIATE_AUTH" ); | |||
| 662 | CODE(LDAP_INVALID_CREDENTIALS)case LDAP_INVALID_CREDENTIALS:return ("LDAP_INVALID_CREDENTIALS" ); | |||
| 663 | CODE(LDAP_INSUFFICIENT_ACCESS)case LDAP_INSUFFICIENT_ACCESS:return ("LDAP_INSUFFICIENT_ACCESS" ); | |||
| 664 | CODE(LDAP_BUSY)case LDAP_BUSY:return ("LDAP_BUSY"); | |||
| 665 | CODE(LDAP_UNAVAILABLE)case LDAP_UNAVAILABLE:return ("LDAP_UNAVAILABLE"); | |||
| 666 | CODE(LDAP_UNWILLING_TO_PERFORM)case LDAP_UNWILLING_TO_PERFORM:return ("LDAP_UNWILLING_TO_PERFORM" ); | |||
| 667 | CODE(LDAP_LOOP_DETECT)case LDAP_LOOP_DETECT:return ("LDAP_LOOP_DETECT"); | |||
| 668 | CODE(LDAP_NAMING_VIOLATION)case LDAP_NAMING_VIOLATION:return ("LDAP_NAMING_VIOLATION"); | |||
| 669 | CODE(LDAP_OBJECT_CLASS_VIOLATION)case LDAP_OBJECT_CLASS_VIOLATION:return ("LDAP_OBJECT_CLASS_VIOLATION" ); | |||
| 670 | CODE(LDAP_NOT_ALLOWED_ON_NONLEAF)case LDAP_NOT_ALLOWED_ON_NONLEAF:return ("LDAP_NOT_ALLOWED_ON_NONLEAF" ); | |||
| 671 | CODE(LDAP_NOT_ALLOWED_ON_RDN)case LDAP_NOT_ALLOWED_ON_RDN:return ("LDAP_NOT_ALLOWED_ON_RDN" ); | |||
| 672 | CODE(LDAP_ALREADY_EXISTS)case LDAP_ALREADY_EXISTS:return ("LDAP_ALREADY_EXISTS"); | |||
| 673 | CODE(LDAP_NO_OBJECT_CLASS_MODS)case LDAP_NO_OBJECT_CLASS_MODS:return ("LDAP_NO_OBJECT_CLASS_MODS" ); | |||
| 674 | CODE(LDAP_AFFECTS_MULTIPLE_DSAS)case LDAP_AFFECTS_MULTIPLE_DSAS:return ("LDAP_AFFECTS_MULTIPLE_DSAS" ); | |||
| 675 | CODE(LDAP_OTHER)case LDAP_OTHER:return ("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((void *)0)) { | |||
| 701 | log_warnx("invalid ldapi:// URL"); | |||
| 702 | return (-1); | |||
| 703 | } | |||
| 704 | } else if ((ldap->ldap_flags & F_STARTTLS0x01) && | |||
| 705 | lu->protocol != LDAPTLS) { | |||
| 706 | log_warnx("conflicting protocol arguments"); | |||
| 707 | return (-1); | |||
| 708 | } else if (lu->protocol == LDAPTLS) | |||
| 709 | ldap->ldap_flags |= F_TLS0x02|F_STARTTLS0x01; | |||
| 710 | else if (lu->protocol == LDAPS) | |||
| 711 | ldap->ldap_flags |= F_TLS0x02; | |||
| 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((void *)0) && ls->ls_basedn != NULL((void *)0) && | |||
| 720 | strcasecmp(ls->ls_basedn, lu->dn) != 0) { | |||
| 721 | log_warnx("conflicting basedn arguments"); | |||
| 722 | return (-1); | |||
| 723 | } | |||
| 724 | if (lu->dn != NULL((void *)0)) { | |||
| 725 | if (url_decode(lu->dn) == NULL((void *)0)) | |||
| 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((void *)0)) { | |||
| 740 | for (i = 0; i < MAXATTR1024 && lu->attributes[i] != NULL((void *)0); i++) | |||
| 741 | if (url_decode(lu->attributes[i]) == NULL((void *)0)) | |||
| 742 | return (-1); | |||
| 743 | ls->ls_attr = lu->attributes; | |||
| 744 | } | |||
| 745 | if (lu->filter != NULL((void *)0)) { | |||
| 746 | if (url_decode(lu->filter) == NULL((void *)0)) | |||
| 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((void *)0)); | |||
| 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((void *)0), 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 | } |