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