• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 20113771060

10 Dec 2025 09:30PM UTC coverage: 93.027% (+0.4%) from 92.636%
20113771060

push

github

51324 of 55171 relevant lines covered (93.03%)

233.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.94
/src/regexp.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5
 * Copyright (c) 2001-2026 The ProFTPD Project team
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
19
 *
20
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21
 * and other respective copyright holders give permission to link this program
22
 * with OpenSSL, and distribute the resulting executable, without including
23
 * the source code for OpenSSL in the source distribution.
24
 */
25

26
/* Regex management code. */
27

28
#include "conf.h"
29

30
#ifdef PR_USE_REGEX
31

32
#if defined(PR_USE_PCRE2)
33
struct regexp_rec {
34
  pool *regex_pool;
35

36
  /* Owning module */
37
  module *m;
38

39
  /* Copy of the original regular expression pattern, flags */
40
  const char *pattern;
41
  int flags;
42

43
  /* For callers wishing to use POSIX REs */
44
  regex_t *re;
45

46
  /* For callers wishing to use PCRE2 REs */
47
  pcre2_code *pcre2;
48
  pcre2_general_context *pcre2_general_ctx;
49
  pcre2_match_context *pcre2_match_ctx;
50

51
  PCRE2_UCHAR *pcre2_errstr;
52
  PCRE2_SIZE pcre2_errstrsz;
53
};
54

55
static uint32_t pcre2_match_limit = 0;
56
static uint32_t pcre2_match_limit_recursion = 0;
57

58
#elif defined(PR_USE_PCRE)
59
struct regexp_rec {
60
  pool *regex_pool;
61

62
  /* Owning module */
63
  module *m;
64

65
  /* Copy of the original regular expression pattern, flags */
66
  const char *pattern;
67
  int flags;
68

69
  /* For callers wishing to use POSIX REs */
70
  regex_t *re;
71

72
  /* For callers wishing to use PCRE REs */
73
  pcre *pcre;
74
  pcre_extra *pcre_extra;
75

76
  const char *pcre_errstr;
77
};
78

79
static unsigned long pcre_match_limit = 0;
80
static unsigned long pcre_match_limit_recursion = 0;
81

82
#else /* !PR_USE_PCRE2 and !PR_USE_PCRE */
83
struct regexp_rec {
84
  pool *regex_pool;
85

86
  /* Owning module */
87
  module *m;
88

89
  /* Copy of the original regular expression pattern, flags */
90
  const char *pattern;
91
  int flags;
92

93
  /* For callers wishing to use POSIX REs */
94
  regex_t *re;
95
};
96
#endif /* PR_USE_PCRE */
97

98
static pool *regexp_pool = NULL;
99
static array_header *regexp_list = NULL;
100

101
#if defined(PR_USE_PCRE2) || \
102
    defined(PR_USE_PCRE)
103
static int regexp_use_posix = FALSE;
104
#else
105
static int regexp_use_posix = TRUE;
106
#endif /* PR_USE_PCRE */
107

108
static const char *trace_channel = "regexp";
109

110
static void regexp_free(pr_regex_t *pre) {
111
#if defined(PR_USE_PCRE2)
14✔
112
  if (pre->pcre2 != NULL) {
113
    pcre2_code_free(pre->pcre2);
14✔
114
    pre->pcre2 = NULL;
10✔
115
  }
10✔
116

117
  if (pre->pcre2_general_ctx != NULL) {
118
    pcre2_general_context_free(pre->pcre2_general_ctx);
14✔
119
    pre->pcre2_general_ctx = NULL;
1✔
120
  }
1✔
121

122
  if (pre->pcre2_match_ctx != NULL) {
123
    pcre2_match_context_free(pre->pcre2_match_ctx);
14✔
124
    pre->pcre2_match_ctx = NULL;
1✔
125
  }
1✔
126
#endif /* PR_USE_PCRE2 */
127

128
#if defined(PR_USE_PCRE)
129
  if (pre->pcre != NULL) {
130
# if defined(HAVE_PCRE_PCRE_FREE_STUDY)
131
    pcre_free_study(pre->pcre_extra);
132
# endif /* HAVE_PCRE_PCRE_FREE_STUDY */
133
    pre->pcre_extra = NULL;
134
    pcre_free(pre->pcre);
135
    pre->pcre = NULL;
136
  }
137
#endif /* PR_USE_PCRE */
138

139
  if (pre->re != NULL) {
140
    /* This frees memory associated with this pointer by regcomp(3). */
14✔
141
# if defined(HAVE_PCRE2_PCRE2_REGCOMP)
142
    pcre2_regfree(pre->re);
143
# else
3✔
144
    regfree(pre->re);
145
# endif /* HAVE_PCRE2_PCRE2_REGCOMP */
146
    pre->re = NULL;
147
  }
3✔
148

149
  pre->pattern = NULL;
150
  destroy_pool(pre->regex_pool);
14✔
151
}
14✔
152

14✔
153
static void regexp_cleanup(void) {
154
  /* Only perform this cleanup if necessary */
2✔
155
  if (regexp_pool) {
156
    register unsigned int i = 0;
2✔
157
    pr_regex_t **pres = (pr_regex_t **) regexp_list->elts;
1✔
158

1✔
159
    for (i = 0; i < regexp_list->nelts; i++) {
160
      if (pres[i] != NULL) {
4✔
161
        regexp_free(pres[i]);
3✔
162
        pres[i] = NULL;
3✔
163
      }
3✔
164
    }
165

166
    destroy_pool(regexp_pool);
167
    regexp_pool = NULL;
1✔
168
    regexp_list = NULL;
1✔
169
  }
1✔
170
}
171

2✔
172
static void regexp_exit_ev(const void *event_data, void *user_data) {
173
  regexp_cleanup();
1✔
174
}
1✔
175

1✔
176
static void regexp_restart_ev(const void *event_data, void *user_data) {
177
  regexp_cleanup();
1✔
178
}
1✔
179

1✔
180
pr_regex_t *pr_regexp_alloc(module *m) {
181
  pr_regex_t *pre = NULL;
14✔
182
  pool *re_pool = NULL;
14✔
183

14✔
184
  /* If no regex-tracking list has been allocated, create one.  Register a
185
   * cleanup handler for this pool, to free up the data in the list.
186
   */
187
  if (regexp_pool == NULL) {
188
    regexp_pool = make_sub_pool(permanent_pool);
14✔
189
    pr_pool_tag(regexp_pool, "Regexp Pool");
9✔
190
    regexp_list = make_array(regexp_pool, 0, sizeof(pr_regex_t *));
9✔
191
  }
9✔
192

193
  re_pool = pr_pool_create_sz(regexp_pool, 128);
194
  pr_pool_tag(re_pool, "regexp pool");
14✔
195

14✔
196
  pre = pcalloc(re_pool, sizeof(pr_regex_t));
197
  pre->regex_pool = re_pool;
14✔
198
  pre->m = m;
14✔
199

14✔
200
  /* Add this pointer to the array. */
201
  *((pr_regex_t **) push_array(regexp_list)) = pre;
202

14✔
203
  return pre;
204
}
14✔
205

206
void pr_regexp_free(module *m, pr_regex_t *pre) {
207
  register unsigned int i = 0;
14✔
208
  pr_regex_t **pres = NULL;
14✔
209

14✔
210
  if (regexp_list == NULL) {
211
    return;
14✔
212
  }
213

214
  pres = (pr_regex_t **) regexp_list->elts;
215

11✔
216
  for (i = 0; i < regexp_list->nelts; i++) {
217
    if (pres[i] == NULL) {
25✔
218
      continue;
14✔
219
    }
3✔
220

221
    if ((pre != NULL && pres[i] == pre) ||
222
        (m != NULL && pres[i]->m == m)) {
11✔
223
      regexp_free(pres[i]);
×
224
      pres[i] = NULL;
11✔
225
    }
11✔
226
  }
227
}
228

229
#if defined(PR_USE_PCRE2)
230
static int regexp_compile_pcre2(pr_regex_t *pre, const char *pattern,
231
    int flags) {
13✔
232
  int res;
233
  PCRE2_SIZE err_offset;
13✔
234

13✔
235
  if (pre == NULL ||
236
      pattern == NULL) {
13✔
237
    errno = EINVAL;
13✔
238
    return -1;
2✔
239
  }
2✔
240

241
  pr_trace_msg(trace_channel, 9, "compiling pattern '%s' into PCRE2 regex",
242
    pattern);
11✔
243
  pre->pattern = pstrdup(pre->regex_pool, pattern);
244
  pre->flags = flags;
11✔
245

11✔
246
  pre->pcre2 = pcre2_compile((PCRE2_SPTR) pattern, PCRE2_ZERO_TERMINATED,
247
    flags, &res, &err_offset, NULL);
11✔
248
  if (pre->pcre2 == NULL) {
249
    if (pre->pcre2_errstr == NULL) {
11✔
250
      pre->pcre2_errstrsz = 128;
1✔
251
      pre->pcre2_errstr = pcalloc(pre->regex_pool, pre->pcre2_errstrsz);
1✔
252
    }
1✔
253

254
    pcre2_get_error_message(res, pre->pcre2_errstr, pre->pcre2_errstrsz);
255
    pr_trace_msg(trace_channel, 4,
1✔
256
      "error compiling pattern '%s' into PCRE2 regex: %s", pattern,
1✔
257
      pre->pcre2_errstr);
258
    return -1;
259
  }
1✔
260

261
  /* Prepare the JIT compiler as well. */
262
  res = pcre2_jit_compile(pre->pcre2, PCRE2_JIT_COMPLETE);
263
  if (res != 0) {
10✔
264
    if (pre->pcre2_errstr == NULL) {
10✔
265
      pre->pcre2_errstrsz = 128;
×
266
      pre->pcre2_errstr = pcalloc(pre->regex_pool, pre->pcre2_errstrsz);
×
267
    }
×
268

269
    pcre2_get_error_message(res, pre->pcre2_errstr, pre->pcre2_errstrsz);
270
    pr_trace_msg(trace_channel, 4,
×
271
      "error performing PCRE2 JIT compile for pattern '%s': %s", pattern,
×
272
      pre->pcre2_errstr);
273
  }
274

275
  return 0;
276
}
277
#endif /* PR_USE_PCRE2 */
278

279
#if defined(PR_USE_PCRE)
280
static int regexp_compile_pcre(pr_regex_t *pre, const char *pattern,
281
    int flags) {
282
  int err_offset, study_flags = 0;
283

284
  if (pre == NULL ||
285
      pattern == NULL) {
286
    errno = EINVAL;
287
    return -1;
288
  }
289

290
  pr_trace_msg(trace_channel, 9, "compiling pattern '%s' into PCRE regex",
291
    pattern);
292
  pre->pattern = pstrdup(pre->regex_pool, pattern);
293
  pre->flags = flags;
294

295
  pre->pcre = pcre_compile(pattern, flags, &(pre->pcre_errstr), &err_offset,
296
    NULL);
297
  if (pre->pcre == NULL) {
298
    pr_trace_msg(trace_channel, 4,
299
      "error compiling pattern '%s' into PCRE regex: %s", pattern,
300
      pre->pcre_errstr);
301
    return -1;
302
  }
303

304
  /* Study the pattern as well, just in case. */
305
#ifdef PCRE_STUDY_JIT_COMPILE
306
  study_flags = PCRE_STUDY_JIT_COMPILE;
307
#endif /* PCRE_STUDY_JIT_COMPILE */
308
  pr_trace_msg(trace_channel, 9, "studying pattern '%s' for PCRE extra data",
309
    pattern);
310
  pre->pcre_extra = pcre_study(pre->pcre, study_flags, &(pre->pcre_errstr));
311
  if (pre->pcre_extra == NULL) {
312
    if (pre->pcre_errstr != NULL) {
313
      pr_trace_msg(trace_channel, 4,
314
        "error studying pattern '%s' for PCRE regex: %s", pattern,
315
        pre->pcre_errstr);
316
    }
317
  }
318

319
  return 0;
320
}
321
#endif /* PR_USE_PCRE */
322

323
int pr_regexp_compile_posix(pr_regex_t *pre, const char *pattern, int flags) {
324
  int res;
7✔
325

7✔
326
  if (pre == NULL ||
327
      pattern == NULL) {
7✔
328
    errno = EINVAL;
7✔
329
    return -1;
2✔
330
  }
2✔
331

332
  if (pre->re != NULL) {
333
    regfree(pre->re);
5✔
334
    pre->re = NULL;
2✔
335
  }
2✔
336

337
  pr_trace_msg(trace_channel, 9, "compiling pattern '%s' into POSIX regex",
338
    pattern);
5✔
339
  pre->pattern = pstrdup(pre->regex_pool, pattern);
340

5✔
341
#if defined(REG_EXTENDED)
342
  /* Enable modern ("extended") POSIX regular expressions by default. */
343
  flags |= REG_EXTENDED;
344
#endif /* REG_EXTENDED */
5✔
345

346
  pre->flags = flags;
347

5✔
348
  pre->re = pcalloc(pre->regex_pool, sizeof(regex_t));
349
# if defined(HAVE_PCRE2_PCRE2_REGCOMP)
5✔
350
  res = pcre2_regcomp(pre->re, pattern, flags);
351
# else
5✔
352
  res = regcomp(pre->re, pattern, flags);
353
# endif /* HAVE_PCRE2_PCRE2_REGCOMP */
354

355
  return res;
356
}
5✔
357

358
int pr_regexp_compile(pr_regex_t *pre, const char *pattern, int flags) {
359
#if defined(PR_USE_PCRE2) || \
13✔
360
    defined(PR_USE_PCRE)
361
# if defined(PR_USE_PCRE2)
362
  int pcre2_flags = 0;
363
# elif defined(PR_USE_PCRE)
13✔
364
  int pcre_flags = 0;
365
# endif
366

367
  if (regexp_use_posix == TRUE) {
368
    return pr_regexp_compile_posix(pre, pattern, flags);
13✔
369
  }
×
370

371
  /* Provide a simple mapping of POSIX regcomp(3) flags to
372
   * PCRE pcre_compile() flags.  The ProFTPD code tends not to use many
373
   * of these flags.
374
   */
375
  if (flags & REG_ICASE) {
376
# if defined(PR_USE_PCRE2)
13✔
377
    pcre2_flags |= PCRE2_CASELESS;
378
# elif defined(PR_USE_PCRE)
3✔
379
    pcre_flags |= PCRE_CASELESS;
380
# endif
381
  }
382

383
# if defined(PR_USE_PCRE2)
384
  return regexp_compile_pcre2(pre, pattern, pcre2_flags);
385
# else
13✔
386
  return regexp_compile_pcre(pre, pattern, pcre_flags);
387
# endif /* PR_USE_PCRE2 */
388
#else
389
  return pr_regexp_compile_posix(pre, pattern, flags);
390
#endif /* PR_USE_PCRE */
391
}
392

393
size_t pr_regexp_error(int errcode, const pr_regex_t *pre, char *buf,
394
    size_t bufsz) {
8✔
395
  size_t res = 0;
396

8✔
397
  if (pre == NULL ||
398
      buf == NULL ||
8✔
399
      bufsz == 0) {
8✔
400
    return 0;
401
  }
402

403
#if defined(PR_USE_PCRE2)
404
  if (pre->pcre2_errstr != NULL) {
405
    sstrncpy(buf, (const char *) pre->pcre2_errstr, bufsz);
2✔
406
    return strlen((const char *) pre->pcre2_errstr) + 1;
1✔
407
  }
1✔
408
#elif defined(PR_USE_PCRE)
409
  if (pre->pcre_errstr != NULL) {
410
    sstrncpy(buf, pre->pcre_errstr, bufsz);
411
    return strlen(pre->pcre_errstr) + 1;
412
  }
413
#endif /* PR_USE_PCRE */
414

415
  if (pre->re != NULL) {
416
    /* Make sure the given buffer is always zeroed out first. */
1✔
417
    memset(buf, '\0', bufsz);
418
# if defined(HAVE_PCRE2_PCRE2_REGCOMP)
1✔
419
    res = pcre2_regerror(errcode, pre->re, buf, bufsz-1);
420
# else
1✔
421
    res = regerror(errcode, pre->re, buf, bufsz-1);
422
# endif /* HAVE_PCRE2_PCRE2_REGCOMP */
423
  }
424

425
  return res;
426
}
427

428
const char *pr_regexp_get_pattern(const pr_regex_t *pre) {
429
  if (pre == NULL) {
27✔
430
    errno = EINVAL;
27✔
431
    return NULL;
1✔
432
  }
1✔
433

434
  if (pre->pattern == NULL) {
435
    errno = ENOENT;
26✔
436
    return NULL;
1✔
437
  }
1✔
438

439
  return pre->pattern;
440
}
441

442
#if defined(PR_USE_PCRE2)
443
static int regexp_exec_pcre2(pr_regex_t *pre, const char *text,
444
    size_t nmatches, regmatch_t *matches, int flags, unsigned long match_limit,
8✔
445
    unsigned long match_limit_recursion) {
446
  pool *tmp_pool = NULL;
447
  int res;
8✔
448
  uint32_t ovector_count = 0;
8✔
449
  pcre2_match_data *match_data = NULL;
8✔
450

8✔
451
  if (pre->pcre2 == NULL) {
452
    errno = EINVAL;
8✔
453
    return -1;
×
454
  }
×
455

456
  /* Use the default match limits, if set and if the caller did not
457
   * explicitly provide limits.
458
   */
459
  if (match_limit == 0) {
460
    match_limit = pcre2_match_limit;
8✔
461
  }
8✔
462

463
  if (match_limit_recursion == 0) {
464
    match_limit_recursion = pcre2_match_limit_recursion;
8✔
465
  }
8✔
466

467
  if (match_limit > 0) {
468
    if (pre->pcre2_general_ctx == NULL) {
8✔
469
      pre->pcre2_general_ctx = pcre2_general_context_create(NULL, NULL, NULL);
1✔
470
    }
1✔
471

472
    if (pre->pcre2_match_ctx == NULL) {
473
      pre->pcre2_match_ctx = pcre2_match_context_create(pre->pcre2_general_ctx);
1✔
474
    }
1✔
475

476
    pcre2_set_match_limit(pre->pcre2_match_ctx, match_limit);
477
  }
1✔
478

479
  if (match_limit_recursion > 0) {
480
    if (pre->pcre2_general_ctx == NULL) {
8✔
481
      pre->pcre2_general_ctx = pcre2_general_context_create(NULL, NULL, NULL);
1✔
482
    }
×
483

484
    if (pre->pcre2_match_ctx == NULL) {
485
      pre->pcre2_match_ctx = pcre2_match_context_create(pre->pcre2_general_ctx);
1✔
486
    }
×
487

488
    pcre2_set_depth_limit(pre->pcre2_match_ctx, match_limit_recursion);
489
  }
1✔
490

491
  if (nmatches > 0 &&
492
      matches != NULL) {
8✔
493
    tmp_pool = make_sub_pool(pre->regex_pool);
8✔
494
    pr_pool_tag(tmp_pool, "regexp tmp pool");
1✔
495
  }
1✔
496

497
  pr_trace_msg(trace_channel, 9,
498
    "executing PCRE2 regex '%s' against subject '%s'",
8✔
499
    pr_regexp_get_pattern(pre), text);
500
  match_data = pcre2_match_data_create_from_pattern(pre->pcre2,
501
    pre->pcre2_general_ctx);
8✔
502
  res = pcre2_match(pre->pcre2, (PCRE2_SPTR) text, PCRE2_ZERO_TERMINATED, 0,
503
    flags, match_data, pre->pcre2_match_ctx);
8✔
504

505
  if (res < 0) {
506
    if (tmp_pool != NULL) {
8✔
507
      destroy_pool(tmp_pool);
3✔
508
    }
×
509

510
    if (pre->pcre2_errstr == NULL) {
511
      pre->pcre2_errstrsz = 128;
3✔
512
      pre->pcre2_errstr = pcalloc(pre->regex_pool, pre->pcre2_errstrsz);
3✔
513
    }
3✔
514

515
    pcre2_get_error_message(res, pre->pcre2_errstr, pre->pcre2_errstrsz);
516
    pr_trace_msg(trace_channel, 9,
3✔
517
      "PCRE2 regex '%s' failed to match subject '%s': %s",
3✔
518
      pr_regexp_get_pattern(pre), text, pre->pcre2_errstr);
519
    pcre2_match_data_free(match_data);
520

3✔
521
    return -1;
522
  }
3✔
523

524
  pr_trace_msg(trace_channel, 9,
525
    "PCRE2 regex '%s' successfully matched subject '%s'",
5✔
526
    pr_regexp_get_pattern(pre), text);
527

528
  if (nmatches > 0 &&
529
      matches != NULL) {
5✔
530
    /* If matches/capture groups are requested, do the processing for them. */
531
    ovector_count = pcre2_get_ovector_count(match_data);
532
  }
1✔
533

534
  if (ovector_count > 0) {
535
    /* Populate the provided POSIX regmatch_t array with the PCRE2 data. */
1✔
536
    register uint32_t i;
537
    PCRE2_SIZE *ovector = NULL;
1✔
538

1✔
539
    /* Make sure the caller provided enough matches for the captured groups. */
540
    if (ovector_count <= nmatches) {
1✔
541
      pr_trace_msg(trace_channel, 9,
542
        "PCRE2 regex '%s' captured %lu groups in subject '%s'",
543
        pr_regexp_get_pattern(pre), (unsigned long) ovector_count, text);
544

1✔
545
    } else {
546
      pr_trace_msg(trace_channel, 9,
4✔
547
        "PCRE2 regex '%s' captured %lu groups in subject '%s', but %lu %s "
2✔
548
        "provided", pr_regexp_get_pattern(pre),
2✔
549
        (unsigned long) ovector_count, text, (unsigned long) nmatches,
550
        nmatches != 1 ? "matches" : "match");
551
      ovector_count = nmatches;
552
    }
9✔
553

8✔
554
    ovector = pcre2_get_ovector_pointer(match_data);
555
    for (i = 0; i < ovector_count; i++) {
556
      matches[i].rm_so = ovector[i * 2];
557
      matches[i].rm_eo = ovector[(i * 2) + 1];
5✔
558
    }
5✔
559

560
    /* Ensure the remaining items are set to proper defaults as well. */
6✔
561
    for (; i < nmatches; i++) {
1✔
562
      matches[i].rm_so = matches[i].rm_eo = -1;
563
    }
564
  }
3✔
565

3✔
566
  destroy_pool(tmp_pool);
3✔
567
  pcre2_match_data_free(match_data);
568

3✔
569
  if (matches != NULL &&
2✔
570
      pr_trace_get_level(trace_channel) >= 20) {
571
    register unsigned int i;
572

573
    for (i = 0; i < nmatches; i++) {
2✔
574
      int match_len;
2✔
575
      const char *match_text;
576

2✔
577
      if (matches[i].rm_so == -1 ||
578
          matches[i].rm_eo == -1) {
579
        break;
580
      }
581

582
      match_text = &(text[matches[i].rm_so]);
583
      match_len = matches[i].rm_eo - matches[i].rm_so;
584

585
      pr_trace_msg(trace_channel, 20,
586
        "PCRE2 regex '%s' match #%u: %.*s (start %ld, len %d)",
587
        pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
588
        (long) matches[i].rm_so, match_len);
589
    }
590
  }
591

592
  return 0;
593
}
594
#endif /* PR_USE_PCRE2 */
595

596
#if defined(PR_USE_PCRE)
597
static int regexp_exec_pcre(pr_regex_t *pre, const char *text,
598
    size_t nmatches, regmatch_t *matches, int flags, unsigned long match_limit,
599
    unsigned long match_limit_recursion) {
600
  int res, ovector_count = 0, *ovector = NULL;
601
  size_t text_len;
602
  pool *tmp_pool = NULL;
603

604
  if (pre->pcre == NULL) {
605
    errno = EINVAL;
606
    return -1;
607
  }
608

609
  text_len = strlen(text);
610

611
  /* Use the default match limits, if set and if the caller did not
612
   * explicitly provide limits.
613
   */
614
  if (match_limit == 0) {
615
    match_limit = pcre_match_limit;
616
  }
617

618
  if (match_limit_recursion == 0) {
619
    match_limit_recursion = pcre_match_limit_recursion;
620
  }
621

622
  if (match_limit > 0) {
623
    if (pre->pcre_extra == NULL) {
624
      pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
625
    }
626

627
    pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT;
628
    pre->pcre_extra->match_limit = match_limit;
629
  }
630

631
  if (match_limit_recursion > 0) {
632
    if (pre->pcre_extra == NULL) {
633
      pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
634
    }
635

636
    pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
637
    pre->pcre_extra->match_limit_recursion = match_limit_recursion;
638
  }
639

640
  if (nmatches > 0 &&
641
      matches != NULL) {
642
    tmp_pool = make_sub_pool(pre->regex_pool);
643
    pr_pool_tag(tmp_pool, "regexp tmp pool");
644

645
    ovector_count = nmatches;
646
    ovector = pcalloc(tmp_pool, sizeof(int) * nmatches * 3);
647
  }
648

649
  pr_trace_msg(trace_channel, 9,
650
    "executing PCRE regex '%s' against subject '%s'",
651
    pr_regexp_get_pattern(pre), text);
652
  res = pcre_exec(pre->pcre, pre->pcre_extra, text, text_len, 0, flags,
653
    ovector, ovector_count);
654

655
  if (res < 0) {
656
    if (tmp_pool != NULL) {
657
      destroy_pool(tmp_pool);
658
    }
659

660
    if (pr_trace_get_level(trace_channel) >= 9) {
661
      const char *reason = "unknown";
662

663
      switch (res) {
664
        case PCRE_ERROR_NOMATCH:
665
          reason = "subject did not match pattern";
666
          break;
667

668
        case PCRE_ERROR_NULL:
669
          reason = "null regex or subject";
670
          break;
671

672
        case PCRE_ERROR_BADOPTION:
673
          reason = "unsupported options bit";
674
          break;
675

676
        case PCRE_ERROR_BADMAGIC:
677
          reason = "bad magic number in regex";
678
          break;
679

680
        case PCRE_ERROR_UNKNOWN_OPCODE:
681
        case PCRE_ERROR_INTERNAL:
682
          reason = "internal PCRE error or corrupted regex";
683
          break;
684

685
        case PCRE_ERROR_NOMEMORY:
686
          reason = "not enough memory for backreferences";
687
          break;
688

689
        case PCRE_ERROR_MATCHLIMIT:
690
          reason = "match limit reached/exceeded";
691
          break;
692

693
        case PCRE_ERROR_RECURSIONLIMIT:
694
          reason = "match limit recursion reached/exceeded";
695
          break;
696

697
        case PCRE_ERROR_BADUTF8:
698
          reason = "invalid UTF8 subject used";
699
          break;
700

701
        case PCRE_ERROR_PARTIAL:
702
          reason = "subject matched only partially; PCRE_PARTIAL flag not used";
703
          break;
704
      }
705

706
      pr_trace_msg(trace_channel, 9,
707
        "PCRE regex '%s' failed to match subject '%s': %s",
708
        pr_regexp_get_pattern(pre), text, reason);
709
    }
710

711
    return res;
712
  }
713

714
  pr_trace_msg(trace_channel, 9,
715
    "PCRE regex '%s' successfully matched subject '%s'",
716
    pr_regexp_get_pattern(pre), text);
717

718
  if (ovector_count > 0) {
719
    /* Populate the provided POSIX regmatch_t array with the PCRE data. */
720
    register int i;
721

722
    for (i = 0; i < res; i++) {
723
      matches[i].rm_so = ovector[i * 2];
724
      matches[i].rm_eo = ovector[(i * 2) + 1];
725
    }
726

727
    /* Ensure the remaining items are set to proper defaults as well. */
728
    for (; i < nmatches; i++) {
729
      matches[i].rm_so = matches[i].rm_eo = -1;
730
    }
731
  }
732

733
  destroy_pool(tmp_pool);
734

735
  if (matches != NULL &&
736
      pr_trace_get_level(trace_channel) >= 20) {
737
    register unsigned int i;
738

739
    for (i = 0; i < nmatches; i++) {
740
      int match_len;
741
      const char *match_text;
742

743
      if (matches[i].rm_so == -1 ||
744
          matches[i].rm_eo == -1) {
745
        break;
746
      }
747

748
      match_text = &(text[matches[i].rm_so]);
749
      match_len = matches[i].rm_eo - matches[i].rm_so;
750

751
      pr_trace_msg(trace_channel, 20,
752
        "PCRE regex '%s' match #%u: %.*s (start %ld, len %d)",
753
        pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
2✔
754
        (long) matches[i].rm_so, match_len);
755
    }
2✔
756
  }
757

2✔
758
  return 0;
759
}
760
#endif /* PR_USE_PCRE */
761

2✔
762
static int regexp_exec_posix(pr_regex_t *pre, const char *text,
763
    size_t nmatches, regmatch_t *matches, int flags) {
764
  int res;
765

2✔
766
  pr_trace_msg(trace_channel, 9,
1✔
767
    "executing POSIX regex '%s' against subject '%s'",
768
    pr_regexp_get_pattern(pre), text);
769
# if defined(HAVE_PCRE2_PCRE2_REGCOMP)
770
  res = pcre2_regexec(pre->re, text, nmatches, matches, flags);
1✔
771
# else
×
772
  res = regexec(pre->re, text, nmatches, matches, flags);
773
# endif /* HAVE_PCRE2_PCRE2_REGCOMP */
774
  if (res == 0) {
×
775
    pr_trace_msg(trace_channel, 9,
×
776
      "POSIX regex '%s' successfully matched subject '%s'",
×
777
      pr_regexp_get_pattern(pre), text);
778

×
779
     if (matches != NULL &&
×
780
         pr_trace_get_level(trace_channel) >= 20) {
781
       register unsigned int i;
782

783
       for (i = 0; i < nmatches; i++) {
×
784
         int match_len;
×
785
         const char *match_text;
786

×
787
         if (matches[i].rm_so == -1 ||
788
             matches[i].rm_eo == -1) {
789
           break;
790
         }
791

792
         match_text = &(text[matches[i].rm_so]);
793
         match_len = matches[i].rm_eo - matches[i].rm_so;
794

1✔
795
         pr_trace_msg(trace_channel, 20,
1✔
796
           "POSIX regex '%s' match #%u: %.*s (start %ld, len %d)",
797
           pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
798
           (long) matches[i].rm_so, match_len);
799
       }
800
     }
801

1✔
802
  } else {
803
    if (pr_trace_get_level(trace_channel) >= 9) {
804
      const char *reason = "subject did not match pattern";
805

806
      /* NOTE: Expectation of `res` values here are mixed when PCRE
807
       * support, and the <pcreposix.h> header, are involved.
2✔
808
       */
809

810
      pr_trace_msg(trace_channel, 9,
13✔
811
        "POSIX regex '%s' failed to match subject '%s': %s (%d)",
812
         pr_regexp_get_pattern(pre), text, reason, res);
813
    }
13✔
814
  }
815

13✔
816
  return res;
13✔
817
}
3✔
818

3✔
819
int pr_regexp_exec(pr_regex_t *pre, const char *text, size_t nmatches,
820
    regmatch_t *matches, int flags, unsigned long match_limit,
821
    unsigned long match_limit_recursion) {
822
  int res;
10✔
823

824
  if (pre == NULL ||
825
      text == NULL) {
826
    errno = EINVAL;
827
    return -1;
8✔
828
  }
8✔
829

830
#if defined(PR_USE_PCRE2)
831
  if (pre->pcre2 != NULL) {
832

×
833
    /* What if the given pre was compiled via PCRE2, but we are told to only
×
834
     * use POSIX?  In this case, we need to compile+exec on demand.
835
     */
836
    if (regexp_use_posix == FALSE) {
837
      return regexp_exec_pcre2(pre, text, nmatches, matches, flags, match_limit,
838
        match_limit_recursion);
839
    }
840

841
    res = pr_regexp_compile_posix(pre, pre->pattern, pre->flags);
842
    if (res < 0) {
843
      return -1;
844
    }
845
  }
846

847
#elif defined(PR_USE_PCRE)
848
  if (pre->pcre != NULL) {
849

850
    /* What if the given pre was compiled via PCRE, but we are told to only
851
     * use POSIX?  In this case, we need to compile+exec on demand.
852
     */
853
    if (regexp_use_posix == FALSE) {
854
      return regexp_exec_pcre(pre, text, nmatches, matches, flags, match_limit,
855
        match_limit_recursion);
2✔
856
    }
857

858
    res = pr_regexp_compile_posix(pre, pre->pattern, pre->flags);
859
    if (res < 0) {
860
      return -1;
2✔
861
    }
1✔
862
  }
863
#endif /* PR_USE_PCRE */
864
  res = regexp_exec_posix(pre, text, nmatches, matches, flags);
865

866
  /* Make sure that we return a negative value to indicate a failed match;
867
   * PCRE already does this.
2✔
868
   */
869
  if (res == REG_NOMATCH) {
870
    res = -1;
871
  }
2✔
872

2✔
873
  return res;
874
}
875

876
int pr_regexp_set_limits(unsigned long match_limit,
877
    unsigned long match_limit_recursion) {
878

879
#if defined(PR_USE_PCRE2)
2✔
880
  pcre2_match_limit = match_limit;
881
  pcre2_match_limit_recursion = match_limit_recursion;
882

6✔
883
#elif defined(PR_USE_PCRE)
6✔
884
  pcre_match_limit = match_limit;
885
  pcre_match_limit_recursion = match_limit_recursion;
886
#endif
887

2✔
888
  return 0;
889
}
890

891
int pr_regexp_set_engine(const char *engine) {
2✔
892
  if (engine == NULL) {
2✔
893
    /* Restore the default. */
894
#if defined(PR_USE_PCRE2) || \
895
    defined(PR_USE_PCRE)
4✔
896
    regexp_use_posix = FALSE;
3✔
897
#else
2✔
898
    regexp_use_posix = TRUE;
1✔
899
#endif /* PR_USE_PCRE */
1✔
900
    pr_trace_msg(trace_channel, 19, "%s", "restored default regexp engine");
901
    return 0;
902
  }
903

3✔
904
  if (strcasecmp(engine, "POSIX") != 0 &&
1✔
905
      strcasecmp(engine, "PCRE") != 0 &&
1✔
906
      strcasecmp(engine, "PCRE2") != 0) {
907
    errno = EINVAL;
908
    return -1;
909
  }
910

911
#if defined(PR_USE_PCRE2)
2✔
912
  if (strcasecmp(engine, "PCRE") == 0) {
1✔
913
    errno = ENOSYS;
1✔
914
    return -1;
915
  }
916

917
  /* We already use PCRE2 by default, but are being explicitly requested to
1✔
918
   * only use POSIX.
919
   */
920
  if (strcasecmp(engine, "POSIX") == 0) {
1✔
921
    if (regexp_use_posix == FALSE) {
1✔
922
      pr_trace_msg(trace_channel, 19, "%s",
923
        "changed regexp engine from PCRE2 to POSIX");
924
    }
925

1✔
926
    regexp_use_posix = TRUE;
927

928
  } else {
929
    if (regexp_use_posix == TRUE) {
930
      pr_trace_msg(trace_channel, 19, "%s",
931
        "changed regexp engine from POSIX to PCRE2");
932
    }
933

934
    regexp_use_posix = FALSE;
935
  }
936

937
#elif defined(PR_USE_PCRE)
938
  if (strcasecmp(engine, "PCRE2") == 0) {
939
    errno = ENOSYS;
940
    return -1;
941
  }
942

943
  /* We already use PCRE by default, but are being explicitly requested to
944
   * only use POSIX.
945
   */
946
  if (strcasecmp(engine, "POSIX") == 0) {
947
    if (regexp_use_posix == FALSE) {
948
      pr_trace_msg(trace_channel, 19, "%s",
949
        "changed regexp engine from PCRE to POSIX");
950
    }
951

952
    regexp_use_posix = TRUE;
953

954
  } else {
955
    if (regexp_use_posix == TRUE) {
956
      pr_trace_msg(trace_channel, 19, "%s",
957
        "changed regexp engine from POSIX to PCRE");
958
    }
959

960
    regexp_use_posix = FALSE;
961
  }
962
#else
963
  /* We only use POSIX, but are being requested to use PCRE/PCRE2. */
964
  if (strcasecmp(engine, "PCRE") == 0 ||
965
      strcasecmp(engine, "PCRE2") == 0) {
966
    errno = ENOSYS;
967
    return -1;
11✔
968
  }
969

970
  regexp_use_posix = TRUE;
971
#endif /* PR_USE_PCRE */
972

973
  return 0;
974
}
975

976
void init_regexp(void) {
11✔
977

11✔
978
  /* Register a restart handler for the regexp pool, so that when restarting,
979
   * regfree(3) is called on each of the regex_t pointers in a
980
   * regex_t-tracking array, thus preventing memory leaks on a long-running
11✔
981
   * daemon.
982
   *
983
   * This registration is done here so that it only happens once.
984
   */
11✔
985
  pr_event_register(NULL, "core.restart", regexp_restart_ev, NULL);
986
  pr_event_register(NULL, "core.exit", regexp_exit_ev, NULL);
987

988
#if defined(PR_USE_PCRE2)
989
  pr_log_debug(DEBUG2, "using PCRE2 %d.%d", PCRE2_MAJOR, PCRE2_MINOR);
990
#elif defined(PR_USE_PCRE)
991
  pr_log_debug(DEBUG2, "using PCRE %s", pcre_version());
992
#endif /* PR_USE_PCRE */
993
}
994

995
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc