Coverage Report

Created: 2026-08-08 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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