Bug Summary

File:src/usr.bin/ldap/ldapclient.c
Warning:line 476, column 4
Potential leak of memory pointed to by 'p'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name ldapclient.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/ldap/obj -resource-dir /usr/local/lib/clang/13.0.0 -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/ldap/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.bin/ldap/ldapclient.c
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
61struct ldapc {
62 struct aldap *ldap_al;
63 char *ldap_host;
64 int ldap_port;
65 const char *ldap_capath;
66 char *ldap_binddn;
67 char *ldap_secret;
68 unsigned int ldap_flags;
69 enum protocol_op ldap_req;
70 enum aldap_protocol ldap_protocol;
71 struct aldap_url ldap_url;
72};
73
74struct ldapc_search {
75 int ls_sizelimit;
76 int ls_timelimit;
77 char *ls_basedn;
78 char *ls_filter;
79 int ls_scope;
80 char **ls_attr;
81};
82
83__dead__attribute__((__noreturn__)) void usage(void);
84int ldapc_connect(struct ldapc *);
85int ldapc_search(struct ldapc *, struct ldapc_search *);
86int ldapc_printattr(struct ldapc *, const char *,
87 const struct ber_octetstring *);
88void ldapc_disconnect(struct ldapc *);
89int ldapc_parseurl(struct ldapc *, struct ldapc_search *,
90 const char *);
91const char *ldapc_resultcode(enum result_code);
92const char *url_decode(char *);
93
94__dead__attribute__((__noreturn__)) void
95usage(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
108int
109main(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)
1
Assuming the condition is false
2
Taking false branch
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)
3
Assuming 'argc' is >= 2
4
Taking false branch
135 usage();
136 else if (strcmp("search", argv[1]) == 0)
5
Taking true branch
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) {
6
Assuming the condition is false
7
Loop condition is false. Execution continues on line 207
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_MAX2147483647,
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_MAX2147483647,
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
7.1
'url' is equal to NULL
!= NULL((void*)0) && ldapc_parseurl(&ldap, &ls, url) == -1)
213 errx(1, "ldapurl");
214
215 /* Set the default after parsing URL and/or options */
216 if (ldap.ldap_host
7.2
Field 'ldap_host' is equal to NULL
== NULL((void*)0))
8
Taking true branch
217 ldap.ldap_host = LDAPHOST"localhost";
218 if (ldap.ldap_port == -1)
9
Taking true branch
219 ldap.ldap_port = ldap.ldap_protocol
9.1
Field 'ldap_protocol' is not equal to LDAPS
== LDAPS ?
10
'?' condition is false
220 LDAPS_PORT636 : LDAP_PORT389;
221 if (ldap.ldap_protocol
10.1
Field 'ldap_protocol' is equal to LDAP
== LDAP && (ldap.ldap_flags & F_STARTTLS0x01))
11
Taking false branch
222 ldap.ldap_protocol = LDAPTLS;
223 if (ldap.ldap_capath
11.1
Field 'ldap_capath' is equal to NULL
== NULL((void*)0))
12
Taking true branch
224 ldap.ldap_capath = tls_default_ca_cert_file();
225 if (ls.ls_basedn
12.1
Field 'ls_basedn' is equal to NULL
== NULL((void*)0))
13
Taking true branch
226 ls.ls_basedn = "";
227 if (ls.ls_scope == -1)
14
Taking true branch
228 ls.ls_scope = LDAP_SCOPE_SUBTREE;
229 if (ls.ls_filter
14.1
Field 'ls_filter' is equal to NULL
== NULL((void*)0))
15
Taking true branch
230 ls.ls_filter = LDAPFILTER"(objectClass=*)";
231
232 if (ldap.ldap_flags & F_NEEDAUTH0x04) {
16
Taking false branch
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)
17
Assuming the condition is false
18
Taking false branch
267 err(1, "pledge");
268
269 /* optional search filter */
270 if (argc && strchr(argv[0], '=') != NULL((void*)0)) {
19
Assuming 'argc' is 0
271 ls.ls_filter = argv[0];
272 argc--;
273 argv++;
274 }
275 /* search attributes */
276 if (argc
19.1
'argc' is 0
)
20
Taking false branch
277 ls.ls_attr = argv;
278
279 if (ldapc_connect(&ldap) == -1)
21
Taking false branch
280 errx(1, "LDAP connection failed");
281
282 if (pledge("stdio", NULL((void*)0)) == -1)
22
Assuming the condition is false
23
Taking false branch
283 err(1, "pledge");
284
285 if (ldapc_search(&ldap, &ls) == -1)
24
Calling 'ldapc_search'
286 errx(1, "LDAP search failed");
287
288 ldapc_disconnect(&ldap);
289 aldap_free_url(&ldap.ldap_url);
290
291 return (0);
292}
293
294int
295ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
296{
297 struct aldap_page_control *pg = NULL((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)
25
Taking false branch
307 printf("version: 1\n");
308 do {
309 if (aldap_search(ldap->ldap_al, ls->ls_basedn, ls->ls_scope,
26
Assuming the condition is false
27
Taking false branch
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
27.1
'pg' is equal to NULL
!= NULL((void*)0)) {
28
Taking false branch
318 aldap_freepage(pg);
319 pg = NULL((void*)0);
320 }
321
322 while ((m = aldap_parse(ldap->ldap_al)) != NULL((void*)0)) {
29
Assuming the condition is true
30
Loop condition is true. Entering loop body
323 if (ldap->ldap_al->msgid != m->msgid) {
31
Assuming 'ldap->ldap_al->msgid' is equal to 'm->msgid'
32
Taking false branch
324 goto fail;
325 }
326
327 if ((code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
33
Assuming the condition is false
34
Taking false branch
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) {
35
Assuming field 'message_type' is not equal to LDAP_RES_SEARCH_RESULT
36
Taking false branch
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) {
37
Assuming field 'message_type' is equal to LDAP_RES_SEARCH_ENTRY
38
Taking false branch
344 goto fail;
345 }
346
347 if (aldap_count_attrs(m) < 1) {
39
Assuming the condition is false
40
Taking false branch
348 aldap_freemsg(m);
349 continue;
350 }
351
352 if ((searchdn = aldap_get_dn(m)) == NULL((void*)0))
41
Assuming the condition is false
42
Taking false branch
353 goto fail;
354
355 if (dn
42.1
'dn' is equal to NULL
!= NULL((void*)0))
43
Taking false branch
356 printf("\n");
357 else
358 dn = ls->ls_basedn;
359 if (strcmp(dn, searchdn) != 0)
44
Taking false branch
360 printf("dn: %s\n", searchdn);
361
362 for (ret = aldap_first_attr(m, &outkey, &outvalues);
46
Loop condition is true. Entering loop body
363 ret != -1;
45
Assuming the condition is true
364 ret = aldap_next_attr(m, &outkey, &outvalues)) {
365 for (i = 0; i < outvalues->len; i++) {
47
Assuming 'i' is < field 'len'
48
Loop condition is true. Entering loop body
366 if (ldapc_printattr(ldap, outkey,
49
Calling 'ldapc_printattr'
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
388int
389ldapc_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) {
50
Taking false branch
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);
51
Memory is allocated
474 if (strvisx(p, value->ostr_val, value->ostr_len,
52
Assuming the condition is true
53
Taking true branch
475 VIS_SAFE0x20|VIS_NL0x10) == -1) {
476 log_warn("visual encoding failed");
54
Potential leak of memory pointed to by 'p'
477 return (-1);
478 }
479
480 printf("%s: %s\n", key, p);
481 }
482
483 free(p);
484 return (0);
485}
486
487int
488ldapc_connect(struct ldapc *ldap)
489{
490 struct addrinfo ai, *res, *res0;
491 struct sockaddr_un un;
492 int ret = -1, saved_errno, fd = -1, code;
493 struct aldap_message *m;
494 const char *errstr;
495 struct tls_config *tls_config;
496 char port[6];
497
498 if (ldap->ldap_protocol == LDAPI) {
499 memset(&un, 0, sizeof(un));
500 un.sun_family = AF_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
623void
624ldapc_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
632const char *
633ldapc_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
681int
682ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
683{
684 struct aldap_url *lu = &ldap->ldap_url;
685 size_t i;
686
687 memset(lu, 0, sizeof(*lu));
688 lu->scope = -1;
689
690 if (aldap_parse_url(url, lu) == -1) {
691 log_warnx("failed to parse LDAP URL");
692 return (-1);
693 }
694
695 /* The protocol part is optional and we default to ldap:// */
696 if (lu->protocol == -1)
697 lu->protocol = LDAP;
698 else if (lu->protocol == LDAPI) {
699 if (lu->port != 0 ||
700 url_decode(lu->host) == NULL((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 */
755const char *
756url_decode(char *url)
757{
758 char *p, *q;
759 char hex[3];
760 unsigned long x;
761
762 hex[2] = '\0';
763 p = q = url;
764
765 while (*p != '\0') {
766 switch (*p) {
767 case '%':
768 /* Encoding character is followed by two hex chars */
769 if (!(isxdigit((unsigned char)p[1]) &&
770 isxdigit((unsigned char)p[2])))
771 return (NULL((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}