The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
ldap_util_tests.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/** Tests for the LDAP DN and filter escape functions
18 *
19 * @file src/lib/ldap/test/ldap_util_tests.c
20 *
21 * @copyright 2026 Network RADIUS SAS (legal@networkradius.com)
22 */
23#include <freeradius-devel/util/test/acutest_common_init.h>
24#include <freeradius-devel/util/test/acutest_helpers.h>
25
26#include <freeradius-devel/ldap/base.h>
27
28/** Check a value box holds exactly the expected bytes, NUL bytes included
29 *
30 * A strcmp based check stops at the first NUL, which is the failure mode
31 * these tests exist to catch.
32 */
33#define TEST_CHECK_BOX_BYTES(_vb, _exp) \
34do { \
35 fr_value_box_t const *_our_vb = (_vb); \
36 TEST_CHECK_LEN(_our_vb->vb_length, sizeof(_exp) - 1); \
37 TEST_CHECK(memcmp(_our_vb->vb_strvalue, _exp, sizeof(_exp) - 1) == 0); \
38 TEST_MSG("Expected : \"%s\"", _exp); \
39 TEST_MSG("Got : \"%s\"", _our_vb->vb_strvalue); \
40} while (0)
41
42/** Allocate a tainted string box from a literal, NUL bytes included
43 */
44#define BOX_FROM_LITERAL(_str) box_from_bytes(_str, sizeof(_str) - 1)
45
46static fr_value_box_t *box_from_bytes(char const *bytes, size_t len)
47{
49
50 vb = fr_value_box_alloc(NULL, FR_TYPE_STRING, NULL);
51 TEST_ASSERT(vb != NULL);
52 TEST_ASSERT(fr_value_box_bstrndup(vb, vb, NULL, bytes, len, true) == 0);
53
54 return vb;
55}
56
58{
60
61 TEST_CASE("Filter value with no special characters is unchanged");
63 TEST_CHECK_BOX_BYTES(vb, "bob");
64
65 talloc_free(vb);
66}
67
69{
70 fr_value_box_t *vb = BOX_FROM_LITERAL("*)(\\");
71
72 TEST_CASE("RFC 4515 special characters are hex escaped");
74 TEST_CHECK_BOX_BYTES(vb, "\\2a\\29\\28\\5c");
75
76 talloc_free(vb);
77}
78
80{
81 fr_value_box_t *vb = BOX_FROM_LITERAL("victim\0junk");
82
83 TEST_CASE("Embedded NUL is escaped rather than truncating the value");
85 TEST_CHECK_BOX_BYTES(vb, "victim\\00junk");
86
87 talloc_free(vb);
88}
89
91{
92 /*
93 * Escaping "*)" yields six bytes, the same as the six byte
94 * input. A length comparison cannot tell the two apart.
95 */
96 fr_value_box_t *vb = BOX_FROM_LITERAL("*)\0AAA");
97
98 TEST_CASE("Specials before an embedded NUL are escaped when lengths collide");
100 TEST_CHECK_BOX_BYTES(vb, "\\2a\\29\\00AAA");
101
102 talloc_free(vb);
103}
104
106{
108
109 TEST_CASE("Empty filter value stays empty");
111 TEST_CHECK_BOX_BYTES(vb, "");
112
113 talloc_free(vb);
114}
115
117{
118 fr_value_box_t *vb = BOX_FROM_LITERAL("bob smith");
119
120 TEST_CASE("DN value with no special characters is unchanged");
122 TEST_CHECK_BOX_BYTES(vb, "bob smith");
123
124 talloc_free(vb);
125}
126
128{
129 fr_value_box_t *vb = BOX_FROM_LITERAL("a,b+c\"d\\e<f>g;h*i=j(k)");
130
131 TEST_CASE("RFC 4514 special characters are hex escaped");
133 TEST_CHECK_BOX_BYTES(vb, "a\\2cb\\2bc\\22d\\5ce\\3cf\\3eg\\3bh\\2ai\\3dj\\28k\\29");
134
135 talloc_free(vb);
136}
137
139{
140 fr_value_box_t *vb;
141
142 TEST_CASE("Leading space is escaped");
143 vb = BOX_FROM_LITERAL(" bob");
145 TEST_CHECK_BOX_BYTES(vb, "\\20bob");
146 talloc_free(vb);
147
148 TEST_CASE("Leading '#' is escaped");
149 vb = BOX_FROM_LITERAL("#bob");
151 TEST_CHECK_BOX_BYTES(vb, "\\23bob");
152 talloc_free(vb);
153
154 TEST_CASE("Interior '#' is not escaped");
155 vb = BOX_FROM_LITERAL("bob#1");
157 TEST_CHECK_BOX_BYTES(vb, "bob#1");
158 talloc_free(vb);
159}
160
162{
163 /*
164 * Escaping "x,ou=other" yields fourteen bytes, the same as the
165 * fourteen byte input.
166 */
167 fr_value_box_t *vb = BOX_FROM_LITERAL("x,ou=other\0AAA");
168
169 TEST_CASE("Specials before an embedded NUL are escaped when lengths collide");
171 TEST_CHECK_BOX_BYTES(vb, "x\\2cou\\3dother\\00AAA");
172
173 talloc_free(vb);
174}
175
177{
178 char buff[32] = {};
179 fr_sbuff_t out = FR_SBUFF_OUT(buff, sizeof(buff));
180 fr_sbuff_t in = FR_SBUFF_IN("a*b\0c", sizeof("a*b\0c") - 1);
181
182 TEST_CASE("Escaping consumes the whole input, NUL included");
186 TEST_CHECK(memcmp(buff, "a\\2ab\\00c", 9) == 0);
187 TEST_CHECK(buff[9] == '\0');
188}
189
191{
192 char buff[6];
193 fr_sbuff_t out = FR_SBUFF_OUT(buff, sizeof(buff));
194 fr_sbuff_t in = FR_SBUFF_IN_STR("ab*cd");
195
196 TEST_CASE("Escaping into a buffer that is too small fails and advances neither sbuff");
200}
201
202static void test_dn_escape_sbuff(void)
203{
204 char buff[32] = {};
205 fr_sbuff_t out = FR_SBUFF_OUT(buff, sizeof(buff));
206 fr_sbuff_t in = FR_SBUFF_IN_STR("#a,b");
207
208 TEST_CASE("Leading '#' and interior ',' are escaped");
211 TEST_CHECK_STRCMP(buff, "\\23a\\2cb");
212}
213
215{
216 char buff[8];
217 fr_sbuff_t out = FR_SBUFF_OUT(buff, sizeof(buff));
219
220 TEST_CASE("Empty DN value writes nothing");
223}
224
226{
227 char const * const dn_list[] = { "cn=a*b,dc=example", "cn=c(d),dc=example", NULL };
228 char *filter;
229
230 TEST_CASE("DN list filter escapes each DN and ANDs the extra filter");
231 filter = fr_ldap_filter_afrom_dn_list(NULL, "entryDN", "(objectClass=groupOfNames)", dn_list);
232 TEST_CHECK_STRCMP(filter,
233 "(&(objectClass=groupOfNames)(|(entryDN=cn=a\\2ab,dc=example)(entryDN=cn=c\\28d\\29,dc=example)))");
234 talloc_free(filter);
235
236 TEST_CASE("DN list filter without an extra filter");
237 filter = fr_ldap_filter_afrom_dn_list(NULL, "entryDN", NULL, dn_list);
238 TEST_CHECK_STRCMP(filter, "(|(entryDN=cn=a\\2ab,dc=example)(entryDN=cn=c\\28d\\29,dc=example))");
239 talloc_free(filter);
240}
241
243 { "filter_box_escape_plain", test_filter_box_escape_plain },
244 { "filter_box_escape_specials", test_filter_box_escape_specials },
245 { "filter_box_escape_nul_truncation", test_filter_box_escape_nul_truncation },
246 { "filter_box_escape_nul_length_collision", test_filter_box_escape_nul_length_collision },
247 { "filter_box_escape_empty", test_filter_box_escape_empty },
248 { "dn_box_escape_plain", test_dn_box_escape_plain },
249 { "dn_box_escape_specials", test_dn_box_escape_specials },
250 { "dn_box_escape_leading", test_dn_box_escape_leading },
251 { "dn_box_escape_nul_length_collision", test_dn_box_escape_nul_length_collision },
252 { "filter_escape_sbuff", test_filter_escape_sbuff },
253 { "filter_escape_sbuff_no_space", test_filter_escape_sbuff_no_space },
254 { "dn_escape_sbuff", test_dn_escape_sbuff },
255 { "dn_escape_sbuff_empty", test_dn_escape_sbuff_empty },
256 { "filter_afrom_dn_list", test_filter_afrom_dn_list },
258};
#define TEST_CHECK(cond)
Definition acutest.h:87
#define TEST_CASE(name)
Definition acutest.h:186
#define TEST_ASSERT(cond)
Definition acutest.h:110
#define TEST_TERMINATOR
Definition acutest.h:64
#define TEST_CHECK_SLEN(_got, _exp)
#define TEST_CHECK_LEN(_got, _exp)
#define TEST_CHECK_RET(_got, _exp)
#define TEST_CHECK_STRCMP(_got, _exp)
static fr_slen_t in
Definition dict.h:906
talloc_free(hp)
int fr_ldap_filter_box_escape(fr_value_box_t *vb, UNUSED void *uctx)
Escape a value box for use as an RFC 4515 filter assertion value.
Definition util.c:172
fr_slen_t fr_ldap_dn_escape(fr_sbuff_t *out, fr_sbuff_t *in)
Escape a value for use as an RFC 4514 DN attribute value.
Definition util.c:95
fr_slen_t fr_ldap_filter_escape(fr_sbuff_t *out, fr_sbuff_t *in)
Escape a value for use as an RFC 4515 filter assertion value.
Definition util.c:144
int fr_ldap_dn_box_escape(fr_value_box_t *vb, UNUSED void *uctx)
Escape a value box for use as an RFC 4514 DN attribute value.
Definition util.c:161
char * fr_ldap_filter_afrom_dn_list(TALLOC_CTX *ctx, char const *dn_attr, char const *filter, char const *const *dn_list)
Build a filter matching a set of objects by DN.
Definition util.c:876
TEST_LIST
static void test_filter_afrom_dn_list(void)
static void test_filter_escape_sbuff_no_space(void)
static void test_filter_box_escape_specials(void)
static void test_dn_escape_sbuff_empty(void)
static void test_dn_box_escape_plain(void)
static fr_value_box_t * box_from_bytes(char const *bytes, size_t len)
static void test_dn_box_escape_nul_length_collision(void)
#define TEST_CHECK_BOX_BYTES(_vb, _exp)
Check a value box holds exactly the expected bytes, NUL bytes included.
static void test_filter_box_escape_plain(void)
static void test_filter_box_escape_nul_length_collision(void)
#define BOX_FROM_LITERAL(_str)
Allocate a tainted string box from a literal, NUL bytes included.
static void test_filter_escape_sbuff(void)
static void test_filter_box_escape_nul_truncation(void)
static void test_filter_box_escape_empty(void)
static void test_dn_box_escape_specials(void)
static void test_dn_box_escape_leading(void)
static void test_dn_escape_sbuff(void)
@ FR_TYPE_STRING
String of printable characters.
#define FR_SBUFF_IN(_start, _len_or_end)
#define FR_SBUFF_IN_STR(_start)
#define fr_sbuff_remaining(_sbuff_or_marker)
#define FR_SBUFF_OUT(_start, _len_or_end)
#define fr_sbuff_used(_sbuff_or_marker)
static char buff[sizeof("18446744073709551615")+3]
Definition size_tests.c:37
int fr_value_box_bstrndup(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_dict_attr_t const *enumv, char const *src, size_t len, bool tainted)
Copy a string to to a fr_value_box_t.
Definition value.c:4900
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:669
static size_t char ** out
Definition value.h:1062