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 | } |