/src/freeradius-server/src/lib/server/regex.c
Line | Count | Source |
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 |
5 | | * (at 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: 47ee33a0c4131f3e3589d4b70c4dfa93874b8444 $ |
19 | | * |
20 | | * @file src/lib/server/regex.c |
21 | | * @brief Regular expression functions used by the server library. |
22 | | * |
23 | | * @copyright 2014 The FreeRADIUS server project |
24 | | */ |
25 | | |
26 | | RCSID("$Id: 47ee33a0c4131f3e3589d4b70c4dfa93874b8444 $") |
27 | | |
28 | | #include <freeradius-devel/server/regex.h> |
29 | | #include <freeradius-devel/server/request_data.h> |
30 | | #include <freeradius-devel/util/debug.h> |
31 | | |
32 | | #ifdef HAVE_REGEX |
33 | | |
34 | 0 | #define REQUEST_DATA_REGEX (0xadbeef00) |
35 | | |
36 | | typedef struct { |
37 | | #if defined(HAVE_REGEX_PCRE2) |
38 | | regex_t *preg; //!< Compiled pattern. |
39 | | #endif |
40 | | fr_regmatch_t *regmatch; //!< Match vectors. |
41 | | |
42 | | fr_value_box_safe_for_t safe_for; |
43 | | bool secret; |
44 | | } fr_regcapture_t; |
45 | | |
46 | | /** Adds subcapture values to request data |
47 | | * |
48 | | * Allows use of %{n} expansions. |
49 | | * |
50 | | * @note If preg was runtime-compiled, it will be consumed and *preg will be set to NULL. |
51 | | * @note regmatch will be consumed and *regmatch will be set to NULL. |
52 | | * @note Their lifetimes will be bound to the match request data. |
53 | | * |
54 | | * @param[in] request Current request. |
55 | | * @param[in,out] preg Compiled pattern. May be set to NULL if |
56 | | * reparented to the regcapture struct. |
57 | | * @param[in,out] regmatch Pointers into value. May be set to NULL if |
58 | | * reparented to the regcapture struct. |
59 | | * @param[in] in value-box which we matched against |
60 | | */ |
61 | | void regex_sub_to_request(request_t *request, regex_t **preg, fr_regmatch_t **regmatch, fr_value_box_t const *in) |
62 | | { |
63 | | fr_regcapture_t *old_rc, *new_rc; /* lldb doesn't like bare new *sigh* */ |
64 | | |
65 | | /* |
66 | | * Clear out old_rc matches |
67 | | */ |
68 | | old_rc = request_data_get(request, request, REQUEST_DATA_REGEX); |
69 | | if (old_rc) { |
70 | | DEBUG4("Clearing %zu matches", old_rc->regmatch->used); |
71 | | talloc_free(old_rc); |
72 | | } else { |
73 | | DEBUG4("No matches"); |
74 | | } |
75 | | |
76 | | if (!regmatch || ((*regmatch)->used == 0)) return; |
77 | | |
78 | | fr_assert(preg && *preg); |
79 | | fr_assert(regmatch); |
80 | | fr_assert(in); |
81 | | |
82 | | DEBUG4("Adding %zu matches", (*regmatch)->used); |
83 | | |
84 | | /* |
85 | | * Container struct for all the match data |
86 | | */ |
87 | | MEM(new_rc = talloc(request, fr_regcapture_t)); |
88 | | |
89 | | /* |
90 | | * Steal runtime pregs, leave precompiled ones |
91 | | */ |
92 | | #if defined(HAVE_REGEX_PCRE2) |
93 | | if (!(*preg)->precompiled) { |
94 | | new_rc->preg = talloc_steal(new_rc, *preg); |
95 | | *preg = NULL; |
96 | | } else { |
97 | | new_rc->preg = *preg; /* Compiled on startup, will hopefully stick around */ |
98 | | } |
99 | | #endif |
100 | | |
101 | | /* |
102 | | * Steal match data |
103 | | */ |
104 | | new_rc->regmatch = talloc_steal(new_rc, *regmatch); |
105 | | new_rc->safe_for = in->safe_for; |
106 | | new_rc->secret = in->secret; |
107 | | *regmatch = NULL; |
108 | | |
109 | | request_data_talloc_add(request, request, REQUEST_DATA_REGEX, fr_regcapture_t, new_rc, true, false, false); |
110 | | } |
111 | | |
112 | | # if defined(HAVE_REGEX_PCRE2) |
113 | | /** Extract a subcapture value from the request |
114 | | * |
115 | | * @note This is the PCRE variant of the function. |
116 | | * |
117 | | * @param[in] ctx To allocate subcapture buffer in. |
118 | | * @param[out] out Where to write the subcapture string. |
119 | | * @param[in] request to extract. |
120 | | * @param[in] num Subcapture index (0 for entire match). |
121 | | * @return |
122 | | * - 0 on success. |
123 | | * - -1 on notfound. |
124 | | */ |
125 | | int regex_request_to_sub(TALLOC_CTX *ctx, fr_value_box_t *out, request_t *request, uint32_t num) |
126 | | { |
127 | | fr_regcapture_t *rc; |
128 | | char *buff; |
129 | | size_t len; |
130 | | int ret; |
131 | | pcre2_match_data *match_data; |
132 | | |
133 | | rc = request_data_reference(request, request, REQUEST_DATA_REGEX); |
134 | | if (!rc) { |
135 | | RDEBUG4("No subcapture data found"); |
136 | | return -1; |
137 | | } |
138 | | match_data = talloc_get_type_abort(rc->regmatch->match_data, pcre2_match_data); |
139 | | |
140 | | ret = pcre2_substring_length_bynumber(match_data, num, &len); |
141 | | switch (ret) { |
142 | | case PCRE2_ERROR_NOMEMORY: |
143 | | MEM(NULL); |
144 | | break; |
145 | | |
146 | | /* |
147 | | * Not finding a substring is fine |
148 | | */ |
149 | | case PCRE2_ERROR_NOSUBSTRING: |
150 | | RDEBUG4("%i/%zu Not found", num + 1, rc->regmatch->used); |
151 | | return -1; |
152 | | |
153 | | default: |
154 | | if (ret < 0) { |
155 | | return -1; |
156 | | } |
157 | | |
158 | | MEM(buff = talloc_array(ctx, char, ++len)); /* +1 for \0, it'll get reset by pcre2_substring */ |
159 | | pcre2_substring_copy_bynumber(match_data, num, (PCRE2_UCHAR *)buff, &len); /* can't error */ |
160 | | |
161 | | fr_value_box_init(out, FR_TYPE_STRING, NULL, false); |
162 | | fr_value_box_bstrndup_shallow(out, NULL, buff, len, false); |
163 | | fr_value_box_mark_safe_for(out, rc->safe_for); |
164 | | fr_value_box_set_secret(out, rc->secret); |
165 | | |
166 | | RDEBUG4("%i/%zu Found: %pV (%zu)", num + 1, rc->regmatch->used, out, out->vb_length); |
167 | | break; |
168 | | } |
169 | | |
170 | | return 0; |
171 | | } |
172 | | |
173 | | /** Extract a named subcapture value from the request |
174 | | * |
175 | | * @note This is the PCRE variant of the function. |
176 | | * |
177 | | * @param[in] ctx To allocate subcapture buffer in. |
178 | | * @param[out] out Where to write the subcapture string. |
179 | | * @param[in] request to extract. |
180 | | * @param[in] name of subcapture. |
181 | | * @return |
182 | | * - 0 on success. |
183 | | * - -1 on notfound. |
184 | | */ |
185 | | int regex_request_to_sub_named(TALLOC_CTX *ctx, fr_value_box_t *out, request_t *request, char const *name) |
186 | | { |
187 | | fr_regcapture_t *rc; |
188 | | char *buff; |
189 | | int ret; |
190 | | size_t len; |
191 | | pcre2_match_data *match_data; |
192 | | |
193 | | rc = request_data_reference(request, request, REQUEST_DATA_REGEX); |
194 | | if (!rc) { |
195 | | RDEBUG4("No subcapture data found"); |
196 | | return -1; |
197 | | } |
198 | | match_data = rc->regmatch->match_data; |
199 | | |
200 | | ret = pcre2_substring_length_byname(match_data, (PCRE2_UCHAR const *)name, &len); |
201 | | switch (ret) { |
202 | | case PCRE2_ERROR_NOMEMORY: |
203 | | MEM(NULL); |
204 | | break; |
205 | | |
206 | | /* |
207 | | * Not finding a substring is fine |
208 | | */ |
209 | | case PCRE2_ERROR_NOSUBSTRING: |
210 | | RDEBUG4("No named capture group \"%s\"", name); |
211 | | return -1; |
212 | | |
213 | | default: |
214 | | if (ret < 0) return -1; |
215 | | |
216 | | MEM(buff = talloc_array(ctx, char, ++len)); /* +1 for \0, it'll get reset by pcre2_substring */ |
217 | | pcre2_substring_copy_byname(match_data, (PCRE2_UCHAR const *)name, (PCRE2_UCHAR *)buff, &len); /* can't error */ |
218 | | |
219 | | fr_value_box_init(out, FR_TYPE_STRING, NULL, false); |
220 | | fr_value_box_bstrndup_shallow(out, NULL, buff, len, false); |
221 | | fr_value_box_mark_safe_for(out, rc->safe_for); |
222 | | fr_value_box_set_secret(out, rc->secret); |
223 | | |
224 | | RDEBUG4("Found \"%s\": %pV (%zu)", name, out, out->vb_length); |
225 | | break; |
226 | | } |
227 | | |
228 | | return 0; |
229 | | } |
230 | | # else |
231 | | /** Extract a subcapture value from the request |
232 | | * |
233 | | * @note This is the POSIX variant of the function. |
234 | | * |
235 | | * @param[in] ctx To allocate subcapture buffer in. |
236 | | * @param[out] out Where to write the subcapture string. |
237 | | * @param[in] request to extract. |
238 | | * @param[in] num Subcapture index (0 for entire match). |
239 | | * @return |
240 | | * - 0 on success. |
241 | | * - -1 on notfound. |
242 | | */ |
243 | | int regex_request_to_sub(TALLOC_CTX *ctx, fr_value_box_t *out, request_t *request, uint32_t num) |
244 | 0 | { |
245 | 0 | fr_regcapture_t *rc; |
246 | 0 | char *buff; |
247 | 0 | char const *start; |
248 | 0 | size_t len; |
249 | 0 | regmatch_t *match_data; |
250 | |
|
251 | 0 | rc = request_data_reference(request, request, REQUEST_DATA_REGEX); |
252 | 0 | if (!rc) { |
253 | 0 | RDEBUG4("No subcapture data found"); |
254 | 0 | return -1; |
255 | 0 | } |
256 | 0 | match_data = rc->regmatch->match_data; |
257 | | |
258 | | /* |
259 | | * Greater than our capture array |
260 | | * |
261 | | * -1 means no value in this capture group. |
262 | | */ |
263 | 0 | if ((num >= rc->regmatch->used) || (match_data[num].rm_eo == -1) || (match_data[num].rm_so == -1)) { |
264 | 0 | RDEBUG4("%i/%zu Not found", num + 1, rc->regmatch->used); |
265 | 0 | return -1; |
266 | 0 | } |
267 | | |
268 | | /* |
269 | | * Sanity checks on the offsets |
270 | | */ |
271 | 0 | fr_assert(match_data[num].rm_eo <= (regoff_t)talloc_array_length(rc->regmatch->subject)); |
272 | 0 | fr_assert(match_data[num].rm_so <= (regoff_t)talloc_array_length(rc->regmatch->subject)); |
273 | |
|
274 | 0 | start = rc->regmatch->subject + match_data[num].rm_so; |
275 | 0 | len = match_data[num].rm_eo - match_data[num].rm_so; |
276 | |
|
277 | 0 | MEM(buff = talloc_bstrndup(ctx, start, len)); |
278 | |
|
279 | 0 | fr_value_box_init(out, FR_TYPE_STRING, NULL, false); |
280 | 0 | fr_value_box_bstrndup_shallow(out, NULL, buff, len, false); |
281 | 0 | fr_value_box_mark_safe_for(out, rc->safe_for); |
282 | 0 | fr_value_box_set_secret(out, rc->secret); |
283 | |
|
284 | 0 | RDEBUG4("%i/%zu Found: %pV (%zu)", num + 1, rc->regmatch->used, out, out->vb_length); |
285 | |
|
286 | 0 | return 0; |
287 | 0 | } |
288 | | # endif |
289 | | #endif |