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

proftpd / proftpd / 26127302554

19 May 2026 07:34PM UTC coverage: 92.635% (-0.4%) from 93.024%
26127302554

push

github

Castaglia
Make a flaky, racy mod_copy regression test more reliable.

47303 of 51064 relevant lines covered (92.63%)

241.07 hits per line

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

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

24
/* Trace functions. */
25

26
#include "conf.h"
27
#include "privs.h"
28

29
#ifdef PR_USE_TRACE
30

31
#define TRACE_BUFFER_SIZE                (PR_TUNABLE_BUFFER_SIZE * 8)
32

33
static int trace_logfd = -1;
34
static unsigned long trace_opts = PR_TRACE_OPT_DEFAULT;
35
static pool *trace_pool = NULL;
36
static pr_table_t *trace_tab = NULL;
37

38
struct trace_levels {
39
  int min_level;
40
  int max_level;
41
};
42

43
static const char *trace_channels[] = {
44
  "auth",
45
  "binding",
46
  "command",
47
  "config",
48
  "ctrls",
49
  "data",
50
  "delay",
51
  "directory",
52
  "dns",
53
  "dso",
54
  "encode",
55
  "event",
56
  "facl",
57
  "fsio",
58
  "ident",
59
  "inet",
60
  "lock",
61
  "log",
62
  "module",
63
  "netacl",
64
  "netio",
65
  "pam",
66
  "pool",
67
  "regexp",
68
  "response",
69
  "scoreboard",
70
  "signal",
71
  "site",
72
  "timer",
73
  "var",
74
  "xfer",
75
  NULL
76
};
77

78
static void trace_restart_ev(const void *event_data, void *user_data) {
2✔
79
  trace_opts = PR_TRACE_OPT_DEFAULT;
2✔
80

81
  (void) close(trace_logfd);
2✔
82
  trace_logfd = -1;
2✔
83

84
  if (trace_pool != NULL) {
2✔
85
    destroy_pool(trace_pool);
2✔
86
    trace_pool = NULL;
2✔
87
    trace_tab = NULL;
2✔
88

89
    pr_event_unregister(NULL, "core.restart", trace_restart_ev);
2✔
90
  }
91
}
2✔
92

93
static int trace_write(const char *channel, int level, const char *msg,
4,949✔
94
    int discard) {
95
  pool *tmp_pool;
96
  char buf[TRACE_BUFFER_SIZE];
97
  size_t buflen = 0, len = 0;
4,949✔
98
  int use_conn_ips = FALSE;
4,949✔
99

100
  if (trace_logfd < 0) {
4,949✔
101
    return 0;
102
  }
103

104
  memset(buf, '\0', sizeof(buf));
4,949✔
105
  tmp_pool = make_sub_pool(trace_pool);
4,949✔
106
  pr_pool_tag(tmp_pool, "Trace message pool");
4,949✔
107

108
  if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP) {
4,949✔
109
    struct tm *tm;
110

111
    if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP_MILLIS) {
4,945✔
112
      struct timeval now;
113
      unsigned long millis;
114

115
      gettimeofday(&now, NULL);
4,945✔
116
      tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec));
4,945✔
117

118
      len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm);
4,945✔
119
      buflen = len;
4,945✔
120

121
      /* Convert microsecs to millisecs. */
122
      millis = now.tv_usec / 1000;
4,945✔
123

124
      len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, ",%03lu ", millis);
4,945✔
125
      buflen += len;
4,945✔
126

127
    } else {
128
      time_t now;
129

130
      now = time(NULL);
×
131
      tm = pr_localtime(tmp_pool, &now);
×
132

133
      len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S ", tm);
×
134
      buflen = len;
×
135
    }
136
  }
137

138
  if ((trace_opts & PR_TRACE_OPT_LOG_CONN_IPS) &&
4,952✔
139
      session.c != NULL) {
3✔
140
    /* We can only support the "+ConnIPs" TraceOption if there actually
141
     * is a client connected in this process.  We might be the daemon
142
     * process, in which there is no client.
143
     */
144
    use_conn_ips = TRUE;
1✔
145
  }
146

147
  if (use_conn_ips == FALSE) {
148
    len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "[%u] <%s:%d>: %s",
9,896✔
149
      (unsigned int) (session.pid ? session.pid : getpid()), channel, level,
9,896✔
150
      msg);
151
    buflen += len;
4,948✔
152

153
  } else {
154
    const char *client_ip, *server_ip;
155
    int server_port;
156

157
    client_ip = pr_netaddr_get_ipstr(session.c->remote_addr);
1✔
158
    server_ip = pr_netaddr_get_ipstr(session.c->local_addr);
1✔
159
    server_port = pr_netaddr_get_port(session.c->local_addr);
1✔
160

161
    len = pr_snprintf(buf + buflen, sizeof(buf) - buflen,
4✔
162
      "[%u] (client %s, server %s:%d) <%s:%d>: %s",
163
      (unsigned int) (session.pid ? session.pid : getpid()),
2✔
164
      client_ip != NULL ? client_ip : "none",
165
      server_ip != NULL ? server_ip : "none", server_port, channel, level, msg);
166
    buflen += len;
1✔
167
  }
168

169
  destroy_pool(tmp_pool);
4,949✔
170
  buf[sizeof(buf)-1] = '\0';
4,949✔
171

172
  if (buflen < (sizeof(buf) - 1)) {
4,949✔
173
    buf[buflen] = '\n';
4,949✔
174
    buflen++;
4,949✔
175

176
  } else {
177
    buf[sizeof(buf)-5] = '.';
×
178
    buf[sizeof(buf)-4] = '.';
×
179
    buf[sizeof(buf)-3] = '.';
×
180
    buf[sizeof(buf)-2] = '.';
×
181
    buflen = sizeof(buf)-1;
×
182
  }
183

184
  pr_log_event_generate(PR_LOG_TYPE_TRACELOG, trace_logfd, level, buf, buflen);
4,949✔
185

186
  if (discard) {
4,949✔
187
    /* This log message would not have been written to disk, so just discard
188
     * it.  The discard value is TRUE when there's a log listener for
189
     * TraceLog logging events, and the Trace log level configuration would
190
     * otherwise have filtered out this log message.
191
     */
192
    return 0;
193
  }
194

195
  return write(trace_logfd, buf, buflen);
4,949✔
196
}
197

198
pr_table_t *pr_trace_get_table(void) {
2✔
199
  if (trace_tab == NULL) {
2✔
200
    errno = ENOENT;
1✔
201
    return NULL;
1✔
202
  }
203

204
  return trace_tab;
205
}
206

207
static const struct trace_levels *trace_get_levels(const char *channel) {
7,864✔
208
  const void *value;
209

210
  if (channel == NULL) {
7,864✔
211
    errno = EINVAL;
3✔
212
    return NULL;
3✔
213
  }
214

215
  if (trace_tab == NULL ||
15,719✔
216
      trace_logfd < 0) {
7,858✔
217
    errno = EPERM;
3✔
218
    return NULL;
3✔
219
  }
220

221
  value = pr_table_get(trace_tab, channel, NULL);
7,858✔
222
  if (value == NULL) {
7,858✔
223
    errno = ENOENT;
2,439✔
224
    return NULL;
2,439✔
225
  }
226

227
  return value;
228
}
229

230
int pr_trace_get_level(const char *channel) {
389✔
231
  return pr_trace_get_max_level(channel);
389✔
232
}
233

234
int pr_trace_get_max_level(const char *channel) {
4✔
235
  const struct trace_levels *levels;
236

237
  levels = trace_get_levels(channel);
393✔
238
  if (levels == NULL) {
393✔
239
    return -1;
240
  }
241

242
  return levels->max_level;
387✔
243
}
244

245
int pr_trace_get_min_level(const char *channel) {
4✔
246
  const struct trace_levels *levels;
247

248
  levels = trace_get_levels(channel);
4✔
249
  if (levels == NULL) {
4✔
250
    return -1;
251
  }
252

253
  return levels->min_level;
1✔
254
}
255

256
int pr_trace_parse_levels(char *str, int *min_level, int *max_level) {
15✔
257
  int low = 1, high = -1;
15✔
258
  char *ptr = NULL, *tmp = NULL;
15✔
259

260
  if (str == NULL ||
30✔
261
      min_level == NULL ||
29✔
262
      max_level == NULL) {
263
    errno = EINVAL;
1✔
264
    return -1;
1✔
265
  }
266

267
  /* Watch for blank strings for levels (i.e. misconfigured/typo in config). */
268
  if (*str == '\0') {
14✔
269
    errno = EINVAL;
1✔
270
    return -1;
1✔
271
  }
272

273
  /* Check for a value range. */
274
  if (*str == '-') {
13✔
275
    errno = EINVAL;
2✔
276
    return -1;
2✔
277
  }
278

279
  ptr = strchr(str, '-');
11✔
280
  if (ptr == NULL) {
11✔
281
    /* Just a single value. */
282
    errno = 0;
4✔
283
    high = (int) strtol(str, &ptr, 10);
4✔
284
    if (errno == ERANGE) {
4✔
285
      errno = EINVAL;
×
286
      return -1;
×
287
    }
288

289
    if (ptr && *ptr) {
4✔
290
      errno = EINVAL;
1✔
291
      return -1;
1✔
292
    }
293

294
    if (high < 0) {
3✔
295
      errno = EINVAL;
1✔
296
      return -1;
1✔
297
    }
298

299
    /* A special case is where the single value is zero.  If this is the
300
     * case, we make sure that the min value is the same.
301
     */
302
    if (high != 0) {
2✔
303
      *min_level = 1;
1✔
304

305
    } else {
306
      *min_level = 0;
1✔
307
    }
308

309
    *max_level = high;
2✔
310
    return 0;
2✔
311
  }
312

313
  /* We have a range of values. */
314
  *ptr = '\0';
7✔
315

316
  low = (int) strtol(str, &tmp, 10);
7✔
317
  if (errno == ERANGE) {
7✔
318
    errno = EINVAL;
×
319
    return -1;
×
320
  }
321

322
  if (tmp && *tmp) {
7✔
323
    *ptr = '-';
1✔
324
    errno = EINVAL;
1✔
325
    return -1;
1✔
326
  }
327
  *ptr = '-';
6✔
328

329
  if (low < 0) {
6✔
330
    errno = EINVAL;
1✔
331
    return -1;
1✔
332
  }
333

334
  tmp = NULL;
5✔
335
  high = (int) strtol(ptr + 1, &tmp, 10);
5✔
336
  if (errno == ERANGE) {
5✔
337
    errno = EINVAL;
×
338
    return -1;
×
339
  }
340

341
  if (tmp && *tmp) {
5✔
342
    errno = EINVAL;
1✔
343
    return -1;
1✔
344
  }
345

346
  if (high < 0) {
4✔
347
    errno = EINVAL;
2✔
348
    return -1;
2✔
349
  }
350

351
  if (high < low) {
2✔
352
    errno = EINVAL;
1✔
353
    return -1;
1✔
354
  }
355

356
  *min_level = low;
1✔
357
  *max_level = high;
1✔
358
  return 0;
1✔
359
}
360

361
int pr_trace_set_file(const char *path) {
4✔
362
  int res, xerrno;
363

364
  if (path == NULL) {
4✔
365
    if (trace_logfd < 0) {
1✔
366
      errno = EINVAL;
×
367
      return -1;
×
368
    }
369

370
    (void) close(trace_logfd);
1✔
371
    trace_logfd = -1;
1✔
372
    return 0;
1✔
373
  }
374

375
  pr_signals_block();
3✔
376
  PRIVS_ROOT
3✔
377
  res = pr_log_openfile(path, &trace_logfd, 0660);
3✔
378
  xerrno = errno;
3✔
379
  PRIVS_RELINQUISH
3✔
380
  pr_signals_unblock();
3✔
381

382
  if (res < 0) {
3✔
383
    if (res == -1) {
2✔
384
      pr_log_debug(DEBUG1, "unable to open TraceLog '%s': %s", path,
2✔
385
        strerror(xerrno));
386
      errno = xerrno;
2✔
387

388
    } else if (res == PR_LOG_WRITABLE_DIR) {
×
389
      pr_log_debug(DEBUG1,
×
390
        "unable to open TraceLog '%s': parent directory is world-writable",
391
        path);
392
      errno = EPERM;
×
393

394
    } else if (res == PR_LOG_SYMLINK) {
×
395
      pr_log_debug(DEBUG1,
×
396
        "unable to open TraceLog '%s': cannot log to a symbolic link",
397
        path);
398
      errno = EPERM;
×
399
    }
400

401
    return res;
402
  }
403

404
  return 0;
405
}
406

407
int pr_trace_set_levels(const char *channel, int min_level, int max_level) {
2,368✔
408
  if (channel == NULL) {
2,368✔
409
    if (trace_tab == NULL) {
1✔
410
      errno = EINVAL;
1✔
411
      return -1;
1✔
412
    }
413

414
    return 0;
415
  }
416

417
  if (min_level > max_level) {
2,367✔
418
    errno = EINVAL;
1✔
419
    return -1;
1✔
420
  }
421

422
  if (trace_tab == NULL &&
2,366✔
423
      min_level < 0) {
424
    return 0;
425
  }
426

427
  if (trace_pool == NULL) {
2,366✔
428
    trace_pool = make_sub_pool(permanent_pool);
743✔
429
    pr_pool_tag(trace_pool, "Trace API");
743✔
430

431
    trace_tab = pr_table_alloc(trace_pool, 0);
743✔
432

433
    /* Register a handler for churning the log pool during HUP. */
434
    pr_event_register(NULL, "core.restart", trace_restart_ev, NULL);
743✔
435
  }
436

437
  if (min_level >= 0) {
2,366✔
438
    struct trace_levels *levels;
439

440
    levels = pcalloc(trace_pool, sizeof(struct trace_levels));
2,366✔
441
    levels->min_level = min_level;
2,366✔
442
    levels->max_level = max_level;
2,366✔
443

444
    if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) {
2,366✔
445
      int count;
446

447
      count = pr_table_exists(trace_tab, channel);
2,364✔
448
      if (count <= 0) {
2,364✔
449
        if (pr_table_add(trace_tab, pstrdup(trace_pool, channel), levels,
1,188✔
450
            sizeof(struct trace_levels)) < 0) {
451
          return -1;
452
        }
453

454
      } else {
455
        if (pr_table_set(trace_tab, pstrdup(trace_pool, channel), levels,
1,176✔
456
            sizeof(struct trace_levels)) < 0) {
457
          return -1;
458
        }
459
      }
460

461
    } else {
462
      register unsigned int i;
463

464
      for (i = 0; trace_channels[i]; i++) {
62✔
465
        (void) pr_trace_set_levels(trace_channels[i], min_level, max_level);
62✔
466
      }
467
    }
468

469
  } else {
470
    if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) {
×
471
      (void) pr_table_remove(trace_tab, channel, NULL);
×
472

473
    } else {
474
      register unsigned int i;
475

476
      for (i = 0; trace_channels[i]; i++) {
×
477
        (void) pr_table_remove(trace_tab, trace_channels[i], NULL);
×
478
      }
479
    }
480
  }
481

482
  return 0;
483
}
484

485
int pr_trace_set_options(unsigned long opts) {
13✔
486
  trace_opts = opts;
13✔
487
  return 0;
13✔
488
}
489

490
int pr_trace_use_stderr(int use_stderr) {
914✔
491
  if (use_stderr) {
914✔
492
    int res;
493

494
    res = dup(STDERR_FILENO);
904✔
495
    if (res < 0) {
904✔
496
      return -1;
497
    }
498

499
    /* Avoid a file descriptor leak by closing any existing fd. */
500
    (void) close(trace_logfd);
904✔
501
    trace_logfd = res;
904✔
502

503
  } else {
504
    (void) close(trace_logfd);
10✔
505
    trace_logfd = -1;
10✔
506
  }
507

508
  return 0;
509
}
510

511
int pr_trace_msg(const char *channel, int level, const char *fmt, ...) {
9,220✔
512
  int res;
513
  va_list msg;
514

515
  if (channel == NULL ||
18,440✔
516
      fmt == NULL ||
18,437✔
517
      level <= 0) {
518
    errno = EINVAL;
3✔
519
    return -1;
3✔
520
  }
521

522
  /* If no one's listening... */
523
  if (trace_logfd < 0 &&
9,221✔
524
      pr_log_event_listening(PR_LOG_TYPE_TRACELOG) <= 0) {
4✔
525
    return 0;
526
  }
527

528
  va_start(msg, fmt);
9,213✔
529
  res = pr_trace_vmsg(channel, level, fmt, msg);
9,213✔
530
  va_end(msg);
9,213✔
531

532
  return res;
9,213✔
533
}
534

535
int pr_trace_vmsg(const char *channel, int level, const char *fmt,
9,213✔
536
    va_list msg) {
537
  char buf[TRACE_BUFFER_SIZE];
538
  const struct trace_levels *levels;
539
  int buflen, discard = FALSE, listening;
9,213✔
540

541
  /* Writing a trace message at level zero is NOT helpful; this makes it
542
   * impossible to quell messages to that trace channel by setting the level
543
   * filter to zero.  That being the case, treat level of zero as an invalid
544
   * level.
545
   */
546

547
  if (channel == NULL ||
18,426✔
548
      fmt == NULL ||
18,426✔
549
      level <= 0) {
550
    errno = EINVAL;
×
551
    return -1;
×
552
  }
553

554
  if (trace_tab == NULL) {
9,213✔
555
    errno = EPERM;
1,746✔
556
    return -1;
1,746✔
557
  }
558

559
  /* If no one's listening... */
560
  if (trace_logfd < 0) {
7,467✔
561
    return 0;
562
  }
563

564
  listening = pr_log_event_listening(PR_LOG_TYPE_TRACELOG);
7,467✔
565

566
  levels = trace_get_levels(channel);
7,467✔
567
  if (levels == NULL) {
7,467✔
568
    discard = TRUE;
2,436✔
569

570
    if (listening <= 0) {
2,436✔
571
      return 0;
572
    }
573
  }
574

575
  if (discard == FALSE &&
5,031✔
576
      level < levels->min_level) {
5,031✔
577
    discard = TRUE;
×
578

579
    if (listening <= 0) {
×
580
      return 0;
581
    }
582
  }
583

584
  if (discard == FALSE &&
10,062✔
585
      level > levels->max_level) {
5,031✔
586
    discard = TRUE;
82✔
587

588
    if (listening <= 0) {
82✔
589
      return 0;
590
    }
591
  }
592

593
  buflen = pr_vsnprintf(buf, sizeof(buf)-1, fmt, msg);
4,949✔
594

595
  /* Always make sure the buffer is NUL-terminated. */
596
  buf[sizeof(buf)-1] = '\0';
4,949✔
597

598
  if (buflen > 0 &&
4,949✔
599
      (size_t) buflen < sizeof(buf)) {
600
    buf[buflen] = '\0';
4,948✔
601

602
  } else {
603
    /* Note that vsnprintf() returns the number of characters _that would have
604
     * been printed if buffer were unlimited_.  Be careful of this.
605
     */
606
    buflen = sizeof(buf)-1;
607
  }
608

609
  /* Trim trailing newlines. */
610
  while (buflen >= 1 &&
9,912✔
611
         buf[buflen-1] == '\n') {
4,956✔
612
    pr_signals_handle();
7✔
613
    buf[buflen-1] = '\0';
7✔
614
    buflen--;
7✔
615
  }
616

617
  return trace_write(channel, level, buf, discard);
4,949✔
618
}
619

620
#else
621

622
pr_table_t *pr_trace_get_table(void) {
623
  errno = ENOSYS;
624
  return NULL;
625
}
626

627
int pr_trace_get_level(const char *channel) {
628
  errno = ENOSYS;
629
  return -1;
630
}
631

632
int pr_trace_get_max_level(const char *channel) {
633
  errno = ENOSYS;
634
  return -1;
635
}
636

637
int pr_trace_get_min_level(const char *channel) {
638
  errno = ENOSYS;
639
  return -1;
640
}
641

642
int pr_trace_parse_levels(char *str, int *min_level, int *max_level) {
643
  errno = ENOSYS;
644
  return -1;
645
}
646

647
int pr_trace_set_file(const char *path) {
648
  errno = ENOSYS;
649
  return -1;
650
}
651

652
int pr_trace_set_levels(const char *channel, int min_level, int max_level) {
653
  errno = ENOSYS;
654
  return -1;
655
}
656

657
int pr_trace_set_options(unsigned long opts) {
658
  errno = ENOSYS;
659
  return -1;
660
}
661

662
int pr_trace_msg(const char *channel, int level, const char *fmt, ...) {
663
  errno = ENOSYS;
664
  return -1;
665
}
666

667
int pr_trace_vmsg(const char *channel, int level, const char *fmt,
668
    va_list vargs) {
669
  errno = ENOSYS;
670
  return -1;
671
}
672

673
#endif /* PR_USE_TRACE */
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