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

proftpd / proftpd / 26127302613

19 May 2026 09:51PM UTC coverage: 93.024% (+0.4%) from 92.635%
26127302613

push

github

51329 of 55178 relevant lines covered (93.02%)

215.14 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
    pr_trace_msg(trace_channel, 9,
540
      "PCRE2 regex '%s' captured %lu groups in subject '%s'",
1✔
541
      pr_regexp_get_pattern(pre), (unsigned long) ovector_count, text);
542

543
    ovector = pcre2_get_ovector_pointer(match_data);
544

1✔
545
    for (i = 0; i < ovector_count; i++) {
546
      matches[i].rm_so = ovector[i * 2];
4✔
547
      matches[i].rm_eo = ovector[(i * 2) + 1];
2✔
548
    }
2✔
549

550
    /* Ensure the remaining items are set to proper defaults as well. */
551
    for (; i < nmatches; i++) {
552
      matches[i].rm_so = matches[i].rm_eo = -1;
9✔
553
    }
8✔
554
  }
555

556
  destroy_pool(tmp_pool);
557
  pcre2_match_data_free(match_data);
5✔
558

5✔
559
  if (matches != NULL &&
560
      pr_trace_get_level(trace_channel) >= 20) {
6✔
561
    register unsigned int i;
1✔
562

563
    for (i = 0; i < nmatches; i++) {
564
      int match_len;
3✔
565
      const char *match_text;
3✔
566

3✔
567
      if (matches[i].rm_so == -1 ||
568
          matches[i].rm_eo == -1) {
3✔
569
        break;
2✔
570
      }
571

572
      match_text = &(text[matches[i].rm_so]);
573
      match_len = matches[i].rm_eo - matches[i].rm_so;
2✔
574

2✔
575
      pr_trace_msg(trace_channel, 20,
576
        "PCRE2 regex '%s' match #%u: %.*s (start %ld, len %d)",
2✔
577
        pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
578
        (long) matches[i].rm_so, match_len);
579
    }
580
  }
581

582
  return 0;
583
}
584
#endif /* PR_USE_PCRE2 */
585

586
#if defined(PR_USE_PCRE)
587
static int regexp_exec_pcre(pr_regex_t *pre, const char *text,
588
    size_t nmatches, regmatch_t *matches, int flags, unsigned long match_limit,
589
    unsigned long match_limit_recursion) {
590
  int res, ovector_count = 0, *ovector = NULL;
591
  size_t text_len;
592
  pool *tmp_pool = NULL;
593

594
  if (pre->pcre == NULL) {
595
    errno = EINVAL;
596
    return -1;
597
  }
598

599
  text_len = strlen(text);
600

601
  /* Use the default match limits, if set and if the caller did not
602
   * explicitly provide limits.
603
   */
604
  if (match_limit == 0) {
605
    match_limit = pcre_match_limit;
606
  }
607

608
  if (match_limit_recursion == 0) {
609
    match_limit_recursion = pcre_match_limit_recursion;
610
  }
611

612
  if (match_limit > 0) {
613
    if (pre->pcre_extra == NULL) {
614
      pre->pcre_extra = pcalloc(pre->regex_pool, sizeof(pcre_extra));
615
    }
616

617
    pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT;
618
    pre->pcre_extra->match_limit = match_limit;
619
  }
620

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

626
    pre->pcre_extra->flags |= PCRE_EXTRA_MATCH_LIMIT_RECURSION;
627
    pre->pcre_extra->match_limit_recursion = match_limit_recursion;
628
  }
629

630
  if (nmatches > 0 &&
631
      matches != NULL) {
632
    tmp_pool = make_sub_pool(pre->regex_pool);
633
    pr_pool_tag(tmp_pool, "regexp tmp pool");
634

635
    ovector_count = nmatches;
636
    ovector = pcalloc(tmp_pool, sizeof(int) * nmatches * 3);
637
  }
638

639
  pr_trace_msg(trace_channel, 9,
640
    "executing PCRE regex '%s' against subject '%s'",
641
    pr_regexp_get_pattern(pre), text);
642
  res = pcre_exec(pre->pcre, pre->pcre_extra, text, text_len, 0, flags,
643
    ovector, ovector_count);
644

645
  if (res < 0) {
646
    if (tmp_pool != NULL) {
647
      destroy_pool(tmp_pool);
648
    }
649

650
    if (pr_trace_get_level(trace_channel) >= 9) {
651
      const char *reason = "unknown";
652

653
      switch (res) {
654
        case PCRE_ERROR_NOMATCH:
655
          reason = "subject did not match pattern";
656
          break;
657

658
        case PCRE_ERROR_NULL:
659
          reason = "null regex or subject";
660
          break;
661

662
        case PCRE_ERROR_BADOPTION:
663
          reason = "unsupported options bit";
664
          break;
665

666
        case PCRE_ERROR_BADMAGIC:
667
          reason = "bad magic number in regex";
668
          break;
669

670
        case PCRE_ERROR_UNKNOWN_OPCODE:
671
        case PCRE_ERROR_INTERNAL:
672
          reason = "internal PCRE error or corrupted regex";
673
          break;
674

675
        case PCRE_ERROR_NOMEMORY:
676
          reason = "not enough memory for backreferences";
677
          break;
678

679
        case PCRE_ERROR_MATCHLIMIT:
680
          reason = "match limit reached/exceeded";
681
          break;
682

683
        case PCRE_ERROR_RECURSIONLIMIT:
684
          reason = "match limit recursion reached/exceeded";
685
          break;
686

687
        case PCRE_ERROR_BADUTF8:
688
          reason = "invalid UTF8 subject used";
689
          break;
690

691
        case PCRE_ERROR_PARTIAL:
692
          reason = "subject matched only partially; PCRE_PARTIAL flag not used";
693
          break;
694
      }
695

696
      pr_trace_msg(trace_channel, 9,
697
        "PCRE regex '%s' failed to match subject '%s': %s",
698
        pr_regexp_get_pattern(pre), text, reason);
699
    }
700

701
    return res;
702
  }
703

704
  pr_trace_msg(trace_channel, 9,
705
    "PCRE regex '%s' successfully matched subject '%s'",
706
    pr_regexp_get_pattern(pre), text);
707

708
  if (ovector_count > 0) {
709
    /* Populate the provided POSIX regmatch_t array with the PCRE data. */
710
    register int i;
711

712
    for (i = 0; i < res; i++) {
713
      matches[i].rm_so = ovector[i * 2];
714
      matches[i].rm_eo = ovector[(i * 2) + 1];
715
    }
716

717
    /* Ensure the remaining items are set to proper defaults as well. */
718
    for (; i < nmatches; i++) {
719
      matches[i].rm_so = matches[i].rm_eo = -1;
720
    }
721
  }
722

723
  destroy_pool(tmp_pool);
724

725
  if (matches != NULL &&
726
      pr_trace_get_level(trace_channel) >= 20) {
727
    register unsigned int i;
728

729
    for (i = 0; i < nmatches; i++) {
730
      int match_len;
731
      const char *match_text;
732

733
      if (matches[i].rm_so == -1 ||
734
          matches[i].rm_eo == -1) {
735
        break;
736
      }
737

738
      match_text = &(text[matches[i].rm_so]);
739
      match_len = matches[i].rm_eo - matches[i].rm_so;
740

741
      pr_trace_msg(trace_channel, 20,
742
        "PCRE regex '%s' match #%u: %.*s (start %ld, len %d)",
743
        pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
744
        (long) matches[i].rm_so, match_len);
745
    }
746
  }
747

748
  return 0;
749
}
750
#endif /* PR_USE_PCRE */
751

752
static int regexp_exec_posix(pr_regex_t *pre, const char *text,
753
    size_t nmatches, regmatch_t *matches, int flags) {
2✔
754
  int res;
755

2✔
756
  pr_trace_msg(trace_channel, 9,
757
    "executing POSIX regex '%s' against subject '%s'",
2✔
758
    pr_regexp_get_pattern(pre), text);
759
# if defined(HAVE_PCRE2_PCRE2_REGCOMP)
760
  res = pcre2_regexec(pre->re, text, nmatches, matches, flags);
761
# else
2✔
762
  res = regexec(pre->re, text, nmatches, matches, flags);
763
# endif /* HAVE_PCRE2_PCRE2_REGCOMP */
764
  if (res == 0) {
765
    pr_trace_msg(trace_channel, 9,
2✔
766
      "POSIX regex '%s' successfully matched subject '%s'",
1✔
767
      pr_regexp_get_pattern(pre), text);
768

769
     if (matches != NULL &&
770
         pr_trace_get_level(trace_channel) >= 20) {
1✔
771
       register unsigned int i;
×
772

773
       for (i = 0; i < nmatches; i++) {
774
         int match_len;
×
775
         const char *match_text;
×
776

×
777
         if (matches[i].rm_so == -1 ||
778
             matches[i].rm_eo == -1) {
×
779
           break;
×
780
         }
781

782
         match_text = &(text[matches[i].rm_so]);
783
         match_len = matches[i].rm_eo - matches[i].rm_so;
×
784

×
785
         pr_trace_msg(trace_channel, 20,
786
           "POSIX regex '%s' match #%u: %.*s (start %ld, len %d)",
×
787
           pr_regexp_get_pattern(pre), i, (int) match_len, match_text,
788
           (long) matches[i].rm_so, match_len);
789
       }
790
     }
791

792
  } else {
793
    if (pr_trace_get_level(trace_channel) >= 9) {
794
      const char *reason = "subject did not match pattern";
1✔
795

1✔
796
      /* NOTE: Expectation of `res` values here are mixed when PCRE
797
       * support, and the <pcreposix.h> header, are involved.
798
       */
799

800
      pr_trace_msg(trace_channel, 9,
801
        "POSIX regex '%s' failed to match subject '%s': %s (%d)",
1✔
802
         pr_regexp_get_pattern(pre), text, reason, res);
803
    }
804
  }
805

806
  return res;
807
}
2✔
808

809
int pr_regexp_exec(pr_regex_t *pre, const char *text, size_t nmatches,
810
    regmatch_t *matches, int flags, unsigned long match_limit,
13✔
811
    unsigned long match_limit_recursion) {
812
  int res;
813

13✔
814
  if (pre == NULL ||
815
      text == NULL) {
13✔
816
    errno = EINVAL;
13✔
817
    return -1;
3✔
818
  }
3✔
819

820
#if defined(PR_USE_PCRE2)
821
  if (pre->pcre2 != NULL) {
822

10✔
823
    /* What if the given pre was compiled via PCRE2, but we are told to only
824
     * use POSIX?  In this case, we need to compile+exec on demand.
825
     */
826
    if (regexp_use_posix == FALSE) {
827
      return regexp_exec_pcre2(pre, text, nmatches, matches, flags, match_limit,
8✔
828
        match_limit_recursion);
8✔
829
    }
830

831
    res = pr_regexp_compile_posix(pre, pre->pattern, pre->flags);
832
    if (res < 0) {
×
833
      return -1;
×
834
    }
835
  }
836

837
#elif defined(PR_USE_PCRE)
838
  if (pre->pcre != NULL) {
839

840
    /* What if the given pre was compiled via PCRE, but we are told to only
841
     * use POSIX?  In this case, we need to compile+exec on demand.
842
     */
843
    if (regexp_use_posix == FALSE) {
844
      return regexp_exec_pcre(pre, text, nmatches, matches, flags, match_limit,
845
        match_limit_recursion);
846
    }
847

848
    res = pr_regexp_compile_posix(pre, pre->pattern, pre->flags);
849
    if (res < 0) {
850
      return -1;
851
    }
852
  }
853
#endif /* PR_USE_PCRE */
854
  res = regexp_exec_posix(pre, text, nmatches, matches, flags);
855

2✔
856
  /* Make sure that we return a negative value to indicate a failed match;
857
   * PCRE already does this.
858
   */
859
  if (res == REG_NOMATCH) {
860
    res = -1;
2✔
861
  }
1✔
862

863
  return res;
864
}
865

866
int pr_regexp_set_limits(unsigned long match_limit,
867
    unsigned long match_limit_recursion) {
2✔
868

869
#if defined(PR_USE_PCRE2)
870
  pcre2_match_limit = match_limit;
871
  pcre2_match_limit_recursion = match_limit_recursion;
2✔
872

2✔
873
#elif defined(PR_USE_PCRE)
874
  pcre_match_limit = match_limit;
875
  pcre_match_limit_recursion = match_limit_recursion;
876
#endif
877

878
  return 0;
879
}
2✔
880

881
int pr_regexp_set_engine(const char *engine) {
882
  if (engine == NULL) {
6✔
883
    /* Restore the default. */
6✔
884
#if defined(PR_USE_PCRE2) || \
885
    defined(PR_USE_PCRE)
886
    regexp_use_posix = FALSE;
887
#else
2✔
888
    regexp_use_posix = TRUE;
889
#endif /* PR_USE_PCRE */
890
    pr_trace_msg(trace_channel, 19, "%s", "restored default regexp engine");
891
    return 0;
2✔
892
  }
2✔
893

894
  if (strcasecmp(engine, "POSIX") != 0 &&
895
      strcasecmp(engine, "PCRE") != 0 &&
4✔
896
      strcasecmp(engine, "PCRE2") != 0) {
3✔
897
    errno = EINVAL;
2✔
898
    return -1;
1✔
899
  }
1✔
900

901
#if defined(PR_USE_PCRE2)
902
  if (strcasecmp(engine, "PCRE") == 0) {
903
    errno = ENOSYS;
3✔
904
    return -1;
1✔
905
  }
1✔
906

907
  /* We already use PCRE2 by default, but are being explicitly requested to
908
   * only use POSIX.
909
   */
910
  if (strcasecmp(engine, "POSIX") == 0) {
911
    if (regexp_use_posix == FALSE) {
2✔
912
      pr_trace_msg(trace_channel, 19, "%s",
1✔
913
        "changed regexp engine from PCRE2 to POSIX");
1✔
914
    }
915

916
    regexp_use_posix = TRUE;
917

1✔
918
  } else {
919
    if (regexp_use_posix == TRUE) {
920
      pr_trace_msg(trace_channel, 19, "%s",
1✔
921
        "changed regexp engine from POSIX to PCRE2");
1✔
922
    }
923

924
    regexp_use_posix = FALSE;
925
  }
1✔
926

927
#elif defined(PR_USE_PCRE)
928
  if (strcasecmp(engine, "PCRE2") == 0) {
929
    errno = ENOSYS;
930
    return -1;
931
  }
932

933
  /* We already use PCRE by default, but are being explicitly requested to
934
   * only use POSIX.
935
   */
936
  if (strcasecmp(engine, "POSIX") == 0) {
937
    if (regexp_use_posix == FALSE) {
938
      pr_trace_msg(trace_channel, 19, "%s",
939
        "changed regexp engine from PCRE to POSIX");
940
    }
941

942
    regexp_use_posix = TRUE;
943

944
  } else {
945
    if (regexp_use_posix == TRUE) {
946
      pr_trace_msg(trace_channel, 19, "%s",
947
        "changed regexp engine from POSIX to PCRE");
948
    }
949

950
    regexp_use_posix = FALSE;
951
  }
952
#else
953
  /* We only use POSIX, but are being requested to use PCRE/PCRE2. */
954
  if (strcasecmp(engine, "PCRE") == 0 ||
955
      strcasecmp(engine, "PCRE2") == 0) {
956
    errno = ENOSYS;
957
    return -1;
958
  }
959

960
  regexp_use_posix = TRUE;
961
#endif /* PR_USE_PCRE */
962

963
  return 0;
964
}
965

966
void init_regexp(void) {
967

11✔
968
  /* Register a restart handler for the regexp pool, so that when restarting,
969
   * regfree(3) is called on each of the regex_t pointers in a
970
   * regex_t-tracking array, thus preventing memory leaks on a long-running
971
   * daemon.
972
   *
973
   * This registration is done here so that it only happens once.
974
   */
975
  pr_event_register(NULL, "core.restart", regexp_restart_ev, NULL);
976
  pr_event_register(NULL, "core.exit", regexp_exit_ev, NULL);
11✔
977

11✔
978
#if defined(PR_USE_PCRE2)
979
  pr_log_debug(DEBUG2, "using PCRE2 %d.%d", PCRE2_MAJOR, PCRE2_MINOR);
980
#elif defined(PR_USE_PCRE)
11✔
981
  pr_log_debug(DEBUG2, "using PCRE %s", pcre_version());
982
#endif /* PR_USE_PCRE */
983
}
984

11✔
985
#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