The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
util.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: fe4986ced7c32fae48d7da6a3c708692ceb76d3f $
19 * @file lib/ldap/util.c
20 * @brief Utility functions to escape and parse DNs
21 *
22 * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 * @copyright 2017 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24 * @copyright 2017 The FreeRADIUS Server Project.
25 */
26RCSID("$Id: fe4986ced7c32fae48d7da6a3c708692ceb76d3f $")
27
29
30#include <freeradius-devel/ldap/base.h>
31#include <freeradius-devel/util/base16.h>
32
33#include <freeradius-devel/util/value.h>
34
35#include <stdarg.h>
36
37/* RFC 4514 DN attribute value special characters */
38static const char dn_specials[] = ",+\"\\<>;*=()";
39static const char hextab[] = "0123456789abcdef";
40static const bool escapes[SBUFF_CHAR_CLASS] = {
41 [' '] = true,
42 ['#'] = true,
43 ['='] = true,
44 ['"'] = true,
45 ['+'] = true,
46 [','] = true,
47 [';'] = true,
48 ['<'] = true,
49 ['>'] = true,
50 ['\''] = true
51};
52
53/* RFC 4515 filter assertion value special characters */
54static const char filter_specials[] = "*()\\";
55;
56
57/** Escape a string for use as an RFC 4514 DN attribute value
58 *
59 * Escapes characters that have special meaning in DNs. Leading space and
60 * '#' are also escaped as required by RFC 4514.
61 * Escape sequence is @verbatim <hex><hex> @endverbatim.
62 *
63 * @param request The current request.
64 * @param out Pointer to output buffer.
65 * @param outlen Size of the output buffer.
66 * @param in Raw unescaped string.
67 * @param arg Any additional arguments (unused).
68 */
69size_t fr_ldap_dn_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
70{
71 size_t left = outlen;
72
73 if ((*in == ' ') || (*in == '#')) goto encode;
74
75 while (*in) {
76 /*
77 * Encode unsafe characters.
78 */
79 if (memchr(dn_specials, *in, sizeof(dn_specials) - 1)) {
80 encode:
81 /*
82 * Only 3 or less bytes available.
83 */
84 if (left <= 3) break;
85
86 *out++ = '\\';
87 *out++ = hextab[(*in >> 4) & 0x0f];
88 *out++ = hextab[*in & 0x0f];
89 in++;
90 left -= 3;
91
92 continue;
93 }
94
95 if (left <= 1) break;
96
97 /*
98 * Doesn't need encoding
99 */
100 *out++ = *in++;
101 left--;
102 }
103
104 *out = '\0';
105
106 return outlen - left;
107}
108
110{
111 fr_sbuff_t sbuff;
112 fr_sbuff_uctx_talloc_t sbuff_ctx;
113 size_t len;
114
116
117 if ((vb->type != FR_TYPE_STRING) && (fr_value_box_cast_in_place(vb, vb, FR_TYPE_STRING, NULL) < 0)) {
118 return -1;
119 }
120
121 if (!fr_sbuff_init_talloc(vb, &sbuff, &sbuff_ctx, vb->vb_length * 3, vb->vb_length * 3)) {
122 fr_strerror_printf_push("Failed to allocate buffer for escaped DN");
123 return -1;
124 }
125
126 len = fr_ldap_dn_escape_func(NULL, fr_sbuff_buff(&sbuff), vb->vb_length * 3 + 1, vb->vb_strvalue, NULL);
127
128 /*
129 * If the returned length is unchanged, the value was already safe
130 */
131 if (len == vb->vb_length) {
132 talloc_free(fr_sbuff_buff(&sbuff));
133 } else {
134 fr_sbuff_trim_talloc(&sbuff, len);
136 }
137
138 return 0;
139}
140
141/** Escape a string for use as an RFC 4515 filter assertion value
142 *
143 * Escapes only the characters that MUST be escaped in filter assertion values
144 * per RFC 4515: '*', '(', ')', '\'. Other characters (including ',', '+',
145 * '=') must NOT be escaped -- some LDAP implementations do not decode
146 * non-required \HH sequences in assertion values and will fail to match.
147 * Escape sequence is @verbatim <hex><hex> @endverbatim.
148 *
149 * @param request The current request.
150 * @param out Pointer to output buffer.
151 * @param outlen Size of the output buffer.
152 * @param in Raw unescaped string.
153 * @param arg Any additional arguments (unused).
154 */
155size_t fr_ldap_filter_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
156{
157 size_t left = outlen;
158
159 while (*in) {
160 if (memchr(filter_specials, *in, sizeof(filter_specials) - 1)) {
161 if (left <= 3) break;
162
163 *out++ = '\\';
164 *out++ = hextab[(*in >> 4) & 0x0f];
165 *out++ = hextab[*in & 0x0f];
166 in++;
167 left -= 3;
168
169 continue;
170 }
171
172 if (left <= 1) break;
173
174 *out++ = *in++;
175 left--;
176 }
177
178 *out = '\0';
179
180 return outlen - left;
181}
182
184{
185 fr_sbuff_t sbuff;
186 fr_sbuff_uctx_talloc_t sbuff_ctx;
187 size_t len;
188
190
191 if ((vb->type != FR_TYPE_STRING) && (fr_value_box_cast_in_place(vb, vb, FR_TYPE_STRING, NULL) < 0)) {
192 return -1;
193 }
194
195 if (!fr_sbuff_init_talloc(vb, &sbuff, &sbuff_ctx, vb->vb_length * 3, vb->vb_length * 3)) {
196 fr_strerror_printf_push("Failed to allocate buffer for escaped filter");
197 return -1;
198 }
199
200 len = fr_ldap_filter_escape_func(NULL, fr_sbuff_buff(&sbuff), vb->vb_length * 3 + 1, vb->vb_strvalue, NULL);
201
202 if (len == vb->vb_length) {
203 talloc_free(fr_sbuff_buff(&sbuff));
204 } else {
205 fr_sbuff_trim_talloc(&sbuff, len);
207 }
208
209 return 0;
210}
211
212/** Converts escaped DNs and filter strings into normal
213 *
214 * @note RFC 4515 says filter strings can only use the @verbatim <hex><hex> @endverbatim
215 * format, whereas RFC 4514 indicates that some chars in DNs, may be escaped simply
216 * with a backslash..
217 *
218 * Will unescape any special characters in strings, or @verbatim <hex><hex> @endverbatim
219 * sequences.
220 *
221 * @param request The current request.
222 * @param out Pointer to output buffer.
223 * @param outlen Size of the output buffer.
224 * @param in Escaped string string.
225 * @param arg Any additional arguments (unused).
226 */
227size_t fr_ldap_uri_unescape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
228{
229 char const *p;
230 char const *c1, *c2;
231 char c3;
232 size_t freespace = outlen;
233
234 if (outlen <= 1) return 0;
235
236 p = in;
237 while (*p && (--freespace > 0)) {
238 if (*p != '\\') {
239 next:
240 *out++ = *p++;
241 continue;
242 }
243
244 p++;
245
246 /* It's an escaped special, just remove the slash */
247 if (memchr(dn_specials, *p, sizeof(dn_specials) - 1)) {
248 *out++ = *p++;
249 continue;
250 }
251
252 /* Is a hex sequence */
253 if (!(c1 = memchr(hextab, tolower(p[0]), 16)) ||
254 !(c2 = memchr(hextab, tolower(p[1]), 16))) goto next;
255 c3 = ((c1 - hextab) << 4) + (c2 - hextab);
256
257 *out++ = c3;
258 p += 2;
259 }
260
261 *out = '\0';
262
263 return outlen - freespace;
264}
265
266
267/** Check whether a string looks like a DN
268 *
269 * @param[in] in Str to check.
270 * @param[in] inlen Length of string to check.
271 * @return
272 * - true if string looks like a DN.
273 * - false if string does not look like DN.
274 */
275bool fr_ldap_util_is_dn(char const *in, size_t inlen)
276{
277 char const *p;
278
279 char want = '=';
280 bool too_soon = true;
281 int comp = 1;
282
283 for (p = in; inlen > 0; p++, inlen--) {
284 if (p[0] == '\\') {
285 char c;
286
287 too_soon = false;
288
289 /*
290 * Invalid escape sequence, not a DN
291 */
292 if (inlen < 2) return false;
293
294 /*
295 * Double backslash, consume two chars
296 */
297 if (p[1] == '\\') {
298 inlen--;
299 p++;
300 continue;
301 }
302
303 /*
304 * Special, consume two chars
305 */
306 if (escapes[(uint8_t) p[1]]) {
307 inlen -= 1;
308 p += 1;
309 continue;
310 }
311
312 /*
313 * Invalid escape sequence, not a DN
314 */
315 if (inlen < 3) return false;
316
317 /*
318 * Hex encoding, consume three chars
319 */
320 if (fr_base16_decode(NULL, &FR_DBUFF_TMP((uint8_t *) &c, 1), &FR_SBUFF_IN(p + 1, 2), false) == 1) {
321 inlen -= 2;
322 p += 2;
323 continue;
324 }
325
326 /*
327 * Invalid escape sequence, not a DN
328 */
329 return false;
330 }
331
332 switch (*p) {
333 case '=':
334 if (too_soon || (*p != want)) return false; /* Too soon after last , or = */
335 want = ',';
336 too_soon = true;
337 break;
338
339 case ',':
340 if (too_soon || (*p != want)) return false; /* Too soon after last , or = */
341 want = '=';
342 too_soon = true;
343 comp++;
344 break;
345
346 default:
347 too_soon = false;
348 break;
349 }
350 }
351
352 /*
353 * If the string ended with , or =, or the number
354 * of components was less than 2
355 *
356 * i.e. we don't have <attr>=<val>,<attr>=<val>
357 */
358 if (too_soon || (comp < 2)) return false;
359
360 return true;
361}
362
363/** Parse a subset (just server side sort and virtual list view for now) of LDAP URL extensions
364 *
365 * @param[out] sss Array of LDAPControl * pointers to add controls to.
366 * @param[in] sss_len How many elements remain in the sss array.
367 * @param[in] extensions A NULL terminated array of extensions.
368 * @return
369 * - >0 the number of controls added.
370 * - 0 if no controls added.
371 * - -1 on failure.
372 */
373int fr_ldap_parse_url_extensions(LDAPControl **sss, size_t sss_len, char *extensions[])
374{
375 LDAPControl **sss_p = sss, **sss_end = sss_p + sss_len;
376 int i;
377
378 if (!extensions) {
379 *sss_p = NULL;
380 return 0;
381 }
382
383 /*
384 * Parse extensions in the LDAP URL
385 */
386 for (i = 0; extensions[i]; i++) {
387 fr_sbuff_t sbuff = FR_SBUFF_IN(extensions[i], strlen(extensions[i]));
388 bool is_critical = false;
389
390 if (sss_p == sss_end) {
391 fr_strerror_printf("Too many extensions. Maximum is %ld", sss_len);
392 goto error;
393 }
394
395 if (fr_sbuff_next_if_char(&sbuff, '!')) is_critical = true;
396
397 /*
398 * Server side sort control
399 */
400 if (fr_sbuff_adv_past_str(&sbuff, "sss", 3)) {
401 LDAPSortKey **keys;
402 int ret;
403
404 if (!fr_sbuff_next_if_char(&sbuff, '=')) {
405 LDAPControl **s;
406 fr_strerror_const("Server side sort extension must be "
407 "in the format \"[!]sss=<key>[,key]\"");
408 error:
409 s = sss;
410 while (s < sss_p) {
411 if (*s) ldap_control_free(*s);
412 s++;
413 }
414 return -1;
415 }
416
417 ret = ldap_create_sort_keylist(&keys, fr_sbuff_current(&sbuff));
418 if (ret != LDAP_SUCCESS) {
419 fr_strerror_printf("Invalid server side sort value \"%s\": %s",
420 fr_sbuff_current(&sbuff), ldap_err2string(ret));
421 goto error;
422 }
423
424 if (*sss_p) ldap_control_free(*sss_p);
425
426 ret = ldap_create_sort_control(fr_ldap_handle_thread_local(), keys, is_critical ? 1 : 0, sss_p);
427 ldap_free_sort_keylist(keys);
428 if (ret != LDAP_SUCCESS) {
429 fr_strerror_printf("Failed creating server sort control: %s",
430 ldap_err2string(ret));
431 goto error;
432 }
433 sss_p++;
434 *sss_p = NULL; /* Terminate */
435 continue;
436 }
437
438 if (fr_sbuff_adv_past_str(&sbuff, "vlv", 3)) {
439 LDAPVLVInfo vlvinfo;
440 uint32_t ext_value;
441 struct berval attr_value;
442 int ret;
443
444 if (!fr_sbuff_next_if_char(&sbuff, '=')) {
445 vlv_error:
446 fr_strerror_const("Virtual list view extension must be "
447 "in the format \"[!]vlv=<before>/<after>(/<offset>/<count>|:<value>)");
448 goto error;
449 }
450
451 vlvinfo.ldvlv_context = NULL;
452
453 if (fr_sbuff_out(NULL, &ext_value, &sbuff) <= 0) goto vlv_error;
454 if (!fr_sbuff_next_if_char(&sbuff, '/')) goto vlv_error;
455 vlvinfo.ldvlv_before_count = ext_value;
456
457 if (fr_sbuff_out(NULL, &ext_value, &sbuff) <= 0) goto vlv_error;
458 vlvinfo.ldvlv_after_count = ext_value;
459
460 /* offset/count syntax */
461 if (fr_sbuff_next_if_char(&sbuff, '/')) {
462 /* Ensure attrvalue is null - this is how the type of vlv control is determined */
463 vlvinfo.ldvlv_attrvalue = NULL;
464
465 if (fr_sbuff_out(NULL, &ext_value, &sbuff) <= 0) goto vlv_error;
466 if (!fr_sbuff_next_if_char(&sbuff, '/')) goto error;
467 vlvinfo.ldvlv_offset = ext_value;
468
469 if (fr_sbuff_out(NULL, &ext_value, &sbuff) <= 0) goto vlv_error;
470 vlvinfo.ldvlv_count = ext_value;
471
472 /* greaterThanOrEqual attribute syntax*/
473 } else if (fr_sbuff_next_if_char(&sbuff, ':')) {
474 attr_value.bv_val = fr_sbuff_current(&sbuff);
475 attr_value.bv_len = fr_sbuff_remaining(&sbuff);
476 vlvinfo.ldvlv_attrvalue = &attr_value;
477
478 } else goto error;
479
480 ret = ldap_create_vlv_control(fr_ldap_handle_thread_local(), &vlvinfo, sss_p);
481
482 if (ret != LDAP_SUCCESS) {
483 fr_strerror_printf("Failed creating virtual list view control: %s",
484 ldap_err2string(ret));
485 goto error;
486 }
487
488 sss_p++;
489 *sss_p = NULL; /* Terminate */
490 continue;
491 }
492
493 fr_strerror_printf("URL extension \"%s\" not supported", extensions[i]);
494 return -1;
495 }
496
497 return (sss_end - sss_p);
498}
499
500/** Convert a berval to a talloced string
501 *
502 * The ldap_get_values function is deprecated, and ldap_get_values_len
503 * does not guarantee the berval buffers it returns are \0 terminated.
504 *
505 * For some cases this is fine, for others we require a \0 terminated
506 * buffer (feeding DNs back into libldap for example).
507 *
508 * @param ctx to allocate in.
509 * @param in Berval to copy.
510 * @return \0 terminated buffer containing in->bv_val.
511 */
512char *fr_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in)
513{
514 char *out;
515
516 out = talloc_array(ctx, char, in->bv_len + 1);
517 if (!out) return NULL;
518
519 memcpy(out, in->bv_val, in->bv_len);
520 out[in->bv_len] = '\0';
521
522 return out;
523}
524
525/** Convert a berval to a talloced buffer
526 *
527 * @param ctx to allocate in.
528 * @param in Berval to copy.
529 * @return buffer containing in->bv_val.
530 */
531uint8_t *fr_ldap_berval_to_bin(TALLOC_CTX *ctx, struct berval const *in)
532{
533 uint8_t *out;
534
535 out = talloc_array(ctx, uint8_t, in->bv_len);
536 if (!out) return NULL;
537
538 memcpy(out, in->bv_val, in->bv_len);
539
540 return out;
541}
542
543/** Normalise escape sequences in a DN
544 *
545 * Characters in a DN can either be escaped as
546 * @verbatim <hex><hex> @endverbatim or @verbatim <special> @endverbatim
547 *
548 * The LDAP directory chooses how characters are escaped, which can make
549 * local comparisons of DNs difficult.
550 *
551 * Here we search for hex sequences that match special chars, and convert
552 * them to the @verbatim <special> @endverbatim form.
553 *
554 * @note the resulting output string will only ever be shorter than the
555 * input, so it's fine to use the same buffer for both out and in.
556 *
557 * @param out Where to write the normalised DN.
558 * @param in The input DN.
559 * @return The number of bytes written to out.
560 */
561size_t fr_ldap_util_normalise_dn(char *out, char const *in)
562{
563 char const *p;
564 char *o = out;
565
566 for (p = in; *p != '\0'; p++) {
567 if (p[0] == '\\') {
568 char c = '\0';
569
570 /*
571 * Double backslashes get passed through as-is.
572 * Copy both and let the for loop advance past the second.
573 */
574 if (p[1] == '\\') {
575 *o++ = p[0];
576 *o++ = p[1];
577 p++;
578 continue;
579 }
580
581 /*
582 * Hex encodings that have an alternative
583 * special encoding, get rewritten to the
584 * special encoding.
585 */
586 if (fr_base16_decode(NULL, &FR_DBUFF_TMP((uint8_t *) &c, 1), &FR_SBUFF_IN(p + 1, 2), false) == 1 &&
587 escapes[(uint8_t) c]) {
588 *o++ = '\\';
589 *o++ = c;
590 p += 2;
591 continue;
592 }
593 }
594 *o++ = *p;
595 }
596 *o = '\0';
597
598 return o - out;
599}
600
601/** Find the place at which the two DN strings diverge
602 *
603 * Returns the length of the non matching string in full.
604 *
605 * @param full DN.
606 * @param part Partial DN as returned by ldap_parse_result.
607 * @return
608 * - Length of the portion of full which wasn't matched
609 * - -1 on failure.
610 */
611size_t fr_ldap_common_dn(char const *full, char const *part)
612{
613 size_t f_len, p_len, i;
614
615 if (!full) return -1;
616
617 f_len = strlen(full);
618
619 if (!part) return -1;
620
621 p_len = strlen(part);
622 if (!p_len) return f_len;
623
624 if ((f_len < p_len) || !f_len) return -1;
625
626 for (i = 0; i < p_len; i++) if (part[p_len - 1 - i] != full[f_len - 1 - i]) return -1;
627
628 return f_len - p_len;
629}
630
631/** Combine filters and tokenize to a tmpl
632 *
633 * @param ctx To allocate combined filter in
634 * @param t_rules Rules for parsing combined filter.
635 * @param sub Array of subfilters (may contain NULLs).
636 * @param sublen Number of potential subfilters in array.
637 * @param out Where to write a pointer to the resulting tmpl.
638 * @return length of combined data.
639 */
640int fr_ldap_filter_to_tmpl(TALLOC_CTX *ctx, tmpl_rules_t const *t_rules, char const **sub, size_t sublen, tmpl_t **out)
641{
642 char *buffer = NULL;
643 char const *in = NULL;
644 ssize_t len = 0;
645 size_t i;
646 int cnt = 0;
647 tmpl_t *parsed;
648
649 *out = NULL;
650
651 /*
652 * Figure out how many filter elements we need to integrate
653 */
654 for (i = 0; i < sublen; i++) {
655 if (sub[i] && *sub[i]) {
656 in = sub[i];
657 cnt++;
658 len += strlen(sub[i]);
659 }
660 }
661
662 if (!cnt) return 0;
663
664 if (cnt > 1) {
665 /*
666 * Allocate a buffer large enough, allowing for (& ... ) plus trailing '\0'
667 */
668 buffer = talloc_array(ctx, char, len + 4);
669
670 strcpy(buffer, "(&");
671 for (i = 0; i < sublen; i++) {
672 if (sub[i] && (*sub[i] != '\0')) {
673 strcat(buffer, sub[i]);
674 }
675 }
676 strcat(buffer, ")");
677 in = buffer;
678 }
679
680 len = tmpl_afrom_substr(ctx, &parsed, &FR_SBUFF_IN_STR(in), T_DOUBLE_QUOTED_STRING, NULL, t_rules);
681
683
684 if (len < 0) {
685 EMARKER(in, -len, fr_strerror());
686 return -1;
687 }
688
689 *out = parsed;
690 return 0;
691}
692
693/** Check that a particular attribute is included in an attribute list
694 *
695 * @param[in] attrs list to check
696 * @param[in] attr to look for
697 * @return
698 * - 1 if attr is in list
699 * - 0 if attr is missing
700 * - -1 if checks not possible
701 */
702int fr_ldap_attrs_check(char const **attrs, char const *attr)
703{
704 size_t len, i;
705
706 if (!attr) return -1;
707
708 len = talloc_array_length(attrs);
709
710 for (i = 0; i < len; i++) {
711 if (!attrs[i]) continue;
712 if (strcasecmp(attrs[i], attr) == 0) return 1;
713 if (strcasecmp(attrs[i], "*") == 0) return 1;
714 }
715
716 return 0;
717}
718
719/** Check an LDAP server entry in URL format is valid
720 *
721 * @param[in,out] handle_config LDAP handle config being built
722 * @param[in] server string to parse
723 * @param[in] cs in which the server is defined
724 * @return
725 * - 0 for valid server definition
726 * - -1 for invalid server definition
727 */
728int fr_ldap_server_url_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION const *cs)
729{
730 LDAPURLDesc *ldap_url;
731 bool set_port_maybe = true;
732 int default_port = LDAP_PORT;
733 char const *p;
734 char *url;
735 CONF_ITEM *ci = (CONF_ITEM *)cf_pair_find(cs, "server");
736
737 if (ldap_url_parse(server, &ldap_url)) {
738 cf_log_err(ci, "Parsing LDAP URL \"%s\" failed", server);
739 ldap_url_error:
740 ldap_free_urldesc(ldap_url);
741 return -1;
742 }
743
744 if (ldap_url->lud_dn && (ldap_url->lud_dn[0] != '\0')) {
745 cf_log_err(ci, "Base DN cannot be specified via server URL");
746 goto ldap_url_error;
747 }
748
749 if (ldap_url->lud_attrs && ldap_url->lud_attrs[0]) {
750 cf_log_err(ci, "Attribute list cannot be speciried via server URL");
751 goto ldap_url_error;
752 }
753
754 /*
755 * ldap_url_parse sets this to base by default.
756 */
757 if (ldap_url->lud_scope != LDAP_SCOPE_BASE) {
758 cf_log_err(ci, "Scope cannot be specified via server URL");
759 goto ldap_url_error;
760 }
761 ldap_url->lud_scope = -1; /* Otherwise LDAP adds ?base */
762
763 /*
764 * The public ldap_url_parse function sets the default
765 * port, so we have to discover whether a port was
766 * included ourselves.
767 */
768 if ((p = strchr(server, ']')) && (p[1] == ':')) { /* IPv6 */
769 set_port_maybe = false;
770 } else if ((p = strchr(server, ':')) && (strchr(p+1, ':') != NULL)) { /* IPv4 */
771 set_port_maybe = false;
772 }
773
774 /*
775 * Figure out the default port from the URL
776 */
777 if (ldap_url->lud_scheme) {
778 if (strcmp(ldap_url->lud_scheme, "ldaps") == 0) {
779 if (handle_config->start_tls == true) {
780 cf_log_err(ci, "ldaps:// scheme is not compatible with 'start_tls'");
781 goto ldap_url_error;
782 }
783 default_port = LDAPS_PORT;
784 handle_config->tls_mode = LDAP_OPT_X_TLS_HARD;
785 } else if (strcmp(ldap_url->lud_scheme, "ldapi") == 0) {
786 set_port_maybe = false;
787 }
788 }
789
790 if (set_port_maybe) {
791 /*
792 * URL port overrides configured port.
793 */
794 ldap_url->lud_port = handle_config->port;
795
796 /*
797 * If there's no URL port, then set it to the default
798 * this is so debugging messages show explicitly
799 * the port we're connecting to.
800 */
801 if (!ldap_url->lud_port) ldap_url->lud_port = default_port;
802 }
803
804 url = ldap_url_desc2str(ldap_url);
805 if (!url) {
806 cf_log_err(ci, "Failed recombining URL components");
807 goto ldap_url_error;
808 }
809 handle_config->server = talloc_asprintf_append(handle_config->server, "%s ", url);
810
811 ldap_free_urldesc(ldap_url);
812 ldap_memfree(url);
813 return (0);
814}
815
816/** Check an LDAP server config in server:port format is valid
817 *
818 * @param[in,out] handle_config LDAP handle config being built
819 * @param[in] server string to parse
820 * @param[in] cs in which the server is defined
821 * @return
822 * - 0 for valid server definition
823 * - -1 for invalid server definition
824 */
825int fr_ldap_server_config_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION *cs)
826{
827 char const *p;
828 char *q;
829 int port = 0;
830 size_t len;
831
832 port = handle_config->port;
833
834 /*
835 * We don't support URLs if the library didn't provide
836 * URL parsing functions.
837 */
838 if (strchr(server, '/')) {
839 CONF_ITEM *ci;
840 bad_server_fmt:
841 ci = (CONF_ITEM *)cf_pair_find(cs, "server");
842 cf_log_err(ci, "Invalid 'server' entry, must be in format <server>[:<port>] or "
843 "an ldap URI (ldap|cldap|ldaps|ldapi)://<server>:<port>");
844 return -1;
845 }
846
847 p = strrchr(server, ':');
848 if (p) {
849 port = (int)strtol((p + 1), &q, 10);
850 if ((p == server) || ((p + 1) == q) || (*q != '\0')) goto bad_server_fmt;
851 len = p - server;
852 } else {
853 len = strlen(server);
854 }
855 if (port == 0) port = LDAP_PORT;
856
857 handle_config->server = talloc_asprintf_append(handle_config->server, "ldap://%.*s:%i ",
858 (int)len, server, port);
859 return 0;
860}
861
862/** Translate the error code emitted from ldap_url_parse and friends into something accessible with fr_strerror()
863 *
864 * @param[in] ldap_url_err The error code returned
865 */
866char const *fr_ldap_url_err_to_str(int ldap_url_err)
867{
868 switch (ldap_url_err) {
869 case LDAP_URL_SUCCESS:
870 return "success";
871
872 case LDAP_URL_ERR_MEM:
873 return "no memory";
874
875 case LDAP_URL_ERR_PARAM:
876 return "parameter is bad";
877
878 case LDAP_URL_ERR_BADSCHEME:
879 return "URL doesn't begin with \"[c]ldap[si]://\"";
880
881 case LDAP_URL_ERR_BADENCLOSURE:
882 return "URL is missing trailing \">\"";
883
884 case LDAP_URL_ERR_BADURL:
885 return "URL is bad";
886
887 case LDAP_URL_ERR_BADHOST:
888 return "host/port is bad";
889
890 case LDAP_URL_ERR_BADATTRS:
891 return "bad (or missing) attributes";
892
893 case LDAP_URL_ERR_BADSCOPE:
894 return "scope string is invalid (or missing)";
895
896 case LDAP_URL_ERR_BADFILTER:
897 return "bad or missing filter";
898
899 case LDAP_URL_ERR_BADEXTS:
900 return "bad or missing extensions";
901
902 default:
903 return "unknown reason";
904 }
905}
906
907/** Dump out the contents of an LDAPMessage
908 *
909 * Intended to be called from a debugger.
910 *
911 * @param[in] entry LDAPMessage to dump.
912 */
913void fr_ldap_entry_dump(LDAPMessage *entry)
914{
915 char *dn;
916 BerElement *ber = NULL;
917 char *attr;
918 struct berval **vals;
919 int i;
920 LDAP *ld = fr_ldap_handle_thread_local();
921 int msgtype;
922
923 msgtype = ldap_msgtype(entry);
924 switch (msgtype) {
925 case LDAP_RES_SEARCH_ENTRY:
926 dn = ldap_get_dn(ld, entry);
927 if (dn) {
928 DEBUG("dn: %s", dn);
929 ldap_memfree(dn);
930 }
931
932 for (attr = ldap_first_attribute(ld, entry, &ber);
933 attr != NULL;
934 attr = ldap_next_attribute(ld, entry, ber)) {
935 vals = ldap_get_values_len(ld, entry, attr);
936 if (!vals) {
937 DEBUG("%s: no values", attr);
938 ldap_memfree(attr);
939 continue;
940 }
941
942 for (i = 0; vals[i] != NULL; i++) {
943 bool binary = false;
944 ber_len_t j;
945
946 for (j = 0; j < vals[i]->bv_len; j++) {
947 char c = vals[i]->bv_val[j];
948 if ((c < 32) || (c > 126)) {
949 binary = true;
950 break;
951 }
952 }
953
954 if (binary) {
955 DEBUG("%s[%u]: %pV", attr, i, fr_box_octets((uint8_t *)vals[i]->bv_val, vals[i]->bv_len));
956 continue;
957 }
958
959 DEBUG("%s[%u]: %pV", attr, i, fr_box_strvalue_len(vals[i]->bv_val, vals[i]->bv_len));
960 }
961
962 ldap_value_free_len(vals);
963 ldap_memfree(attr);
964 }
965 break;
966
967 case LDAP_RES_SEARCH_RESULT:
968 case LDAP_RES_BIND:
969 case LDAP_RES_MODIFY:
970 case LDAP_RES_ADD:
971 case LDAP_RES_DELETE:
972 case LDAP_RES_COMPARE:
973 case LDAP_RES_EXTENDED:
974 {
975 int rc;
976 char *matched = NULL;
977 char *errmsg = NULL;
978 char **refs = NULL;
979
980 rc = ldap_parse_result(ld, entry, &msgtype, &matched, &errmsg, &refs, NULL, 0);
981 if (rc != LDAP_SUCCESS) {
982 DEBUG("failed to parse result: %s", ldap_err2string(rc));
983 break;
984 }
985
986 DEBUG("result code: %d (%s)", msgtype, ldap_err2string(msgtype));
987
988 if (matched && *matched) {
989 DEBUG("matched DN: %s", matched);
990 }
991 if (errmsg && *errmsg) {
992 DEBUG("error message: %s", errmsg);
993 }
994 if (refs) {
995 for (i = 0; refs[i] != NULL; i++) {
996 DEBUG("referral: %s", refs[i]);
997 }
998 }
999
1000 if (matched) ldap_memfree(matched);
1001 if (errmsg) ldap_memfree(errmsg);
1002 if (refs) ldap_memvfree((void **)refs);
1003 }
1004 break;
1005
1006 default:
1007 DEBUG("unhandled LDAP message type: %d", msgtype);
1008 break;
1009 }
1010
1011 if (ber) ber_free(ber, 0);
1012}
static int const char char buffer[256]
Definition acutest.h:576
strcpy(log_entry->msg, buffer)
#define fr_base16_decode(_err, _out, _in, _no_trailing)
Definition base16.h:92
#define USES_APPLE_DEPRECATED_API
Definition build.h:499
#define RCSID(id)
Definition build.h:512
#define UNUSED
Definition build.h:336
Common header for all CONF_* types.
Definition cf_priv.h:54
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
CONF_PAIR * cf_pair_find(CONF_SECTION const *cs, char const *attr)
Search for a CONF_PAIR with a specific name.
Definition cf_util.c:1587
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:345
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:522
#define DEBUG(fmt,...)
Definition dhcpclient.c:38
static fr_slen_t in
Definition dict.h:882
talloc_free(hp)
char * server
Initial server to bind to.
Definition base.h:224
bool start_tls
Send the Start TLS message to the LDAP directory to start encrypted communications using the standard...
Definition base.h:258
uint16_t port
Port to use when binding to the server.
Definition base.h:227
Connection configuration.
Definition base.h:221
LDAP * fr_ldap_handle_thread_local(void)
Get a thread local dummy LDAP handle.
Definition base.c:1129
char const * fr_ldap_url_err_to_str(int ldap_url_err)
Translate the error code emitted from ldap_url_parse and friends into something accessible with fr_st...
Definition util.c:866
static const char hextab[]
Definition util.c:39
size_t fr_ldap_util_normalise_dn(char *out, char const *in)
Normalise escape sequences in a DN.
Definition util.c:561
static const bool escapes[SBUFF_CHAR_CLASS]
Definition util.c:40
int fr_ldap_filter_box_escape(fr_value_box_t *vb, UNUSED void *uctx)
Definition util.c:183
bool fr_ldap_util_is_dn(char const *in, size_t inlen)
Check whether a string looks like a DN.
Definition util.c:275
size_t fr_ldap_common_dn(char const *full, char const *part)
Find the place at which the two DN strings diverge.
Definition util.c:611
int fr_ldap_attrs_check(char const **attrs, char const *attr)
Check that a particular attribute is included in an attribute list.
Definition util.c:702
uint8_t * fr_ldap_berval_to_bin(TALLOC_CTX *ctx, struct berval const *in)
Convert a berval to a talloced buffer.
Definition util.c:531
int fr_ldap_server_url_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION const *cs)
Check an LDAP server entry in URL format is valid.
Definition util.c:728
static USES_APPLE_DEPRECATED_API const char dn_specials[]
Definition util.c:38
char * fr_ldap_berval_to_string(TALLOC_CTX *ctx, struct berval const *in)
Convert a berval to a talloced string.
Definition util.c:512
static const char filter_specials[]
Definition util.c:54
int fr_ldap_server_config_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION *cs)
Check an LDAP server config in server:port format is valid.
Definition util.c:825
int fr_ldap_dn_box_escape(fr_value_box_t *vb, UNUSED void *uctx)
Definition util.c:109
size_t fr_ldap_dn_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
Escape a string for use as an RFC 4514 DN attribute value.
Definition util.c:69
size_t fr_ldap_uri_unescape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
Converts escaped DNs and filter strings into normal.
Definition util.c:227
void fr_ldap_entry_dump(LDAPMessage *entry)
Dump out the contents of an LDAPMessage.
Definition util.c:913
int fr_ldap_parse_url_extensions(LDAPControl **sss, size_t sss_len, char *extensions[])
Parse a subset (just server side sort and virtual list view for now) of LDAP URL extensions.
Definition util.c:373
int fr_ldap_filter_to_tmpl(TALLOC_CTX *ctx, tmpl_rules_t const *t_rules, char const **sub, size_t sublen, tmpl_t **out)
Combine filters and tokenize to a tmpl.
Definition util.c:640
size_t fr_ldap_filter_escape_func(UNUSED request_t *request, char *out, size_t outlen, char const *in, UNUSED void *arg)
Escape a string for use as an RFC 4515 filter assertion value.
Definition util.c:155
#define EMARKER(_str, _marker_idx, _marker)
Definition log.h:232
@ FR_TYPE_STRING
String of printable characters.
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
int strcasecmp(char *s1, char *s2)
Definition missing.c:65
static int encode(bio_handle_t *h, request_t *request, bio_request_t *u, uint8_t id)
Definition bio.c:1259
static char const * url[FR_RADIUS_FAIL_MAX+1]
#define fr_assert(_expr)
Definition rad_assert.h:37
static int8_t comp(void const *a, void const *b)
Definition rbmonkey.c:13
int fr_sbuff_trim_talloc(fr_sbuff_t *sbuff, size_t len)
Trim a talloced sbuff to the minimum length required to represent the contained string.
Definition sbuff.c:433
size_t fr_sbuff_adv_past_str(fr_sbuff_t *sbuff, char const *needle, size_t needle_len)
Return true and advance past the end of the needle if needle occurs next in the sbuff.
Definition sbuff.c:1757
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
Definition sbuff.c:2133
#define SBUFF_CHAR_CLASS
Definition sbuff.h:203
#define FR_SBUFF_IN(_start, _len_or_end)
#define fr_sbuff_current(_sbuff_or_marker)
#define fr_sbuff_buff(_sbuff_or_marker)
#define FR_SBUFF_IN_STR(_start)
#define fr_sbuff_out(_err, _out, _in)
#define fr_sbuff_remaining(_sbuff_or_marker)
Talloc sbuff extension structure.
Definition sbuff.h:137
ssize_t tmpl_afrom_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff_t *in, fr_token_t quote, fr_sbuff_parse_rules_t const *p_rules, tmpl_rules_t const *t_rules))
Convert an arbitrary string into a tmpl_t.
Optional arguments passed to vp_tmpl functions.
Definition tmpl.h:336
@ T_DOUBLE_QUOTED_STRING
Definition token.h:119
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:558
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_printf_push(_fmt,...)
Add a message to an existing stack of messages at the tail.
Definition strerror.h:84
#define fr_strerror_const(_msg)
Definition strerror.h:223
int fr_value_box_cast_in_place(TALLOC_CTX *ctx, fr_value_box_t *vb, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv)
Convert one type of fr_value_box_t to another in place.
Definition value.c:4196
void fr_value_box_strdup_shallow_replace(fr_value_box_t *vb, char const *src, ssize_t len)
Free the existing buffer (if talloced) associated with the valuebox, and replace it with a new one.
Definition value.c:4745
#define fr_box_strvalue_len(_val, _len)
Definition value.h:309
#define fr_value_box_is_safe_for(_box, _safe_for)
Definition value.h:1100
static size_t char fr_sbuff_t size_t inlen
Definition value.h:1030
static size_t char ** out
Definition value.h:1030
#define fr_box_octets(_val, _len)
Definition value.h:311