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

proftpd / proftpd / 28259826111

26 Jun 2026 07:20PM UTC coverage: 93.032% (+0.6%) from 92.469%
28259826111

push

github

51363 of 55210 relevant lines covered (93.03%)

200.05 hits per line

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

91.05
/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)
15✔
112
  if (pre->pcre2 != NULL) {
113
    pcre2_code_free(pre->pcre2);
15✔
114
    pre->pcre2 = NULL;
11✔
115
  }
11✔
116

117
  if (pre->pcre2_general_ctx != NULL) {
118
    pcre2_general_context_free(pre->pcre2_general_ctx);
15✔
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);
15✔
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). */
15✔
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);
15✔
151
}
15✔
152

15✔
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;
15✔
182
  pool *re_pool = NULL;
15✔
183

15✔
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);
15✔
189
    pr_pool_tag(regexp_pool, "Regexp Pool");
10✔
190
    regexp_list = make_array(regexp_pool, 0, sizeof(pr_regex_t *));
10✔
191
  }
10✔
192

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

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

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

15✔
203
  return pre;
204
}
15✔
205

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

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

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

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

221
    if ((pre != NULL && pres[i] == pre) ||
222
        (m != NULL && pres[i]->m == m)) {
12✔
223
      regexp_free(pres[i]);
×
224
      pres[i] = NULL;
12✔
225
    }
12✔
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) {
14✔
232
  int res;
233
  PCRE2_SIZE err_offset;
14✔
234

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

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

12✔
246
  pre->pcre2 = pcre2_compile((PCRE2_SPTR) pattern, PCRE2_ZERO_TERMINATED,
247
    flags, &res, &err_offset, NULL);
12✔
248
  if (pre->pcre2 == NULL) {
249
    if (pre->pcre2_errstr == NULL) {
12✔
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) {
11✔
264
    if (pre->pcre2_errstr == NULL) {
11✔
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) || \
14✔
360
    defined(PR_USE_PCRE)
361
# if defined(PR_USE_PCRE2)
362
  int pcre2_flags = 0;
363
# elif defined(PR_USE_PCRE)
14✔
364
  int pcre_flags = 0;
365
# endif
366

367
  if (regexp_use_posix == TRUE) {
368
    return pr_regexp_compile_posix(pre, pattern, flags);
14✔
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)
14✔
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
14✔
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) {
31✔
430
    errno = EINVAL;
31✔
431
    return NULL;
1✔
432
  }
1✔
433

434
  if (pre->pattern == NULL) {
435
    errno = ENOENT;
30✔
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,
9✔
445
    unsigned long match_limit_recursion) {
446
  pool *tmp_pool = NULL;
447
  int res;
9✔
448
  uint32_t ovector_count = 0;
9✔
449
  pcre2_match_data *match_data = NULL;
9✔
450

9✔
451
  if (pre->pcre2 == NULL) {
452
    errno = EINVAL;
9✔
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;
9✔
461
  }
9✔
462

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

467
  if (match_limit > 0) {
468
    if (pre->pcre2_general_ctx == NULL) {
9✔
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) {
9✔
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) {
9✔
493
    tmp_pool = make_sub_pool(pre->regex_pool);
9✔
494
    pr_pool_tag(tmp_pool, "regexp tmp pool");
2✔
495
  }
2✔
496

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

505
  if (res < 0) {
506
    if (tmp_pool != NULL) {
9✔
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'",
6✔
526
    pr_regexp_get_pattern(pre), text);
527

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

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

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

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

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

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

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

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

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

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

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

3✔
585
      pr_trace_msg(trace_channel, 20,
586
        "PCRE2 regex '%s' match #%u: %.*s (start %ld, len %d)",
3✔
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,
754
        (long) matches[i].rm_so, match_len);
755
    }
756
  }
757

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

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

2✔
766
  pr_trace_msg(trace_channel, 9,
767
    "executing POSIX regex '%s' against subject '%s'",
2✔
768
    pr_regexp_get_pattern(pre), text);
769
# if defined(HAVE_PCRE2_PCRE2_REGCOMP)
770
  res = pcre2_regexec(pre->re, text, nmatches, matches, flags);
771
# else
2✔
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,
2✔
776
      "POSIX regex '%s' successfully matched subject '%s'",
1✔
777
      pr_regexp_get_pattern(pre), text);
778

779
     if (matches != NULL &&
780
         pr_trace_get_level(trace_channel) >= 20) {
1✔
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

×
795
         pr_trace_msg(trace_channel, 20,
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

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

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

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

816
  return res;
817
}
2✔
818

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,
14✔
821
    unsigned long match_limit_recursion) {
822
  int res;
823

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

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

11✔
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,
9✔
838
        match_limit_recursion);
9✔
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);
856
    }
857

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

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

873
  return res;
874
}
875

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

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

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

888
  return 0;
889
}
2✔
890

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

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

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

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

926
    regexp_use_posix = TRUE;
927

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

934
    regexp_use_posix = FALSE;
935
  }
1✔
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;
968
  }
969

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

973
  return 0;
974
}
975

976
void init_regexp(void) {
977

12✔
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
981
   * daemon.
982
   *
983
   * This registration is done here so that it only happens once.
984
   */
985
  pr_event_register(NULL, "core.restart", regexp_restart_ev, NULL);
986
  pr_event_register(NULL, "core.exit", regexp_exit_ev, NULL);
12✔
987

12✔
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)
12✔
991
  pr_log_debug(DEBUG2, "using PCRE %s", pcre_version());
992
#endif /* PR_USE_PCRE */
993
}
994

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

© 2026 Coveralls, Inc