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

proftpd / proftpd / 29618379545

17 Jul 2026 10:35PM UTC coverage: 92.427% (-0.6%) from 93.032%
29618379545

push

github

web-flow
When populating the names of the quotatab limit/tally tables, use our own `sstrncpy()` function.

48818 of 52818 relevant lines covered (92.43%)

244.18 hits per line

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

87.89
/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_cleanup(void *user_data) {
705✔
79
  destroy_pool(trace_pool);
705✔
80
  trace_pool = NULL;
705✔
81
  trace_tab = NULL;
705✔
82
}
705✔
83

84
static void trace_restart_ev(const void *event_data, void *user_data) {
2✔
85
  trace_opts = PR_TRACE_OPT_DEFAULT;
2✔
86

87
  (void) close(trace_logfd);
2✔
88
  trace_logfd = -1;
2✔
89

90
  if (trace_pool != NULL) {
2✔
91
    destroy_pool(trace_pool);
2✔
92
    trace_pool = NULL;
2✔
93
    trace_tab = NULL;
2✔
94

95
    pr_event_unregister(NULL, "core.restart", trace_restart_ev);
2✔
96
  }
97
}
2✔
98

99
static int trace_write(const char *channel, int level, const char *msg,
5,836✔
100
    int discard) {
101
  pool *tmp_pool;
102
  char buf[TRACE_BUFFER_SIZE];
103
  size_t buflen = 0, len = 0;
5,836✔
104
  int use_conn_ips = FALSE;
5,836✔
105

106
  if (trace_logfd < 0) {
5,836✔
107
    return 0;
108
  }
109

110
  memset(buf, '\0', sizeof(buf));
5,836✔
111
  tmp_pool = make_sub_pool(trace_pool);
5,836✔
112
  pr_pool_tag(tmp_pool, "Trace message pool");
5,836✔
113

114
  if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP) {
5,836✔
115
    struct tm *tm;
116

117
    if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP_MILLIS) {
5,832✔
118
      struct timeval now;
119
      unsigned long millis;
120

121
      gettimeofday(&now, NULL);
5,832✔
122
      tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec));
5,832✔
123

124
      len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm);
5,832✔
125
      buflen = len;
5,832✔
126

127
      /* Convert microsecs to millisecs. */
128
      millis = now.tv_usec / 1000;
5,832✔
129

130
      len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, ",%03lu ", millis);
5,832✔
131
      buflen += len;
5,832✔
132

133
    } else {
134
      time_t now;
135

136
      now = time(NULL);
×
137
      tm = pr_localtime(tmp_pool, &now);
×
138

139
      len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S ", tm);
×
140
      buflen = len;
×
141
    }
142
  }
143

144
  if ((trace_opts & PR_TRACE_OPT_LOG_CONN_IPS) &&
5,839✔
145
      session.c != NULL) {
3✔
146
    /* We can only support the "+ConnIPs" TraceOption if there actually
147
     * is a client connected in this process.  We might be the daemon
148
     * process, in which there is no client.
149
     */
150
    use_conn_ips = TRUE;
1✔
151
  }
152

153
  if (use_conn_ips == FALSE) {
154
    len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "[%u] <%s:%d>: %s",
11,670✔
155
      (unsigned int) (session.pid ? session.pid : getpid()), channel, level,
11,670✔
156
      msg);
157
    buflen += len;
5,835✔
158

159
  } else {
160
    const char *client_ip, *server_ip;
161
    int server_port;
162

163
    client_ip = pr_netaddr_get_ipstr(session.c->remote_addr);
1✔
164
    server_ip = pr_netaddr_get_ipstr(session.c->local_addr);
1✔
165
    server_port = pr_netaddr_get_port(session.c->local_addr);
1✔
166

167
    len = pr_snprintf(buf + buflen, sizeof(buf) - buflen,
4✔
168
      "[%u] (client %s, server %s:%d) <%s:%d>: %s",
169
      (unsigned int) (session.pid ? session.pid : getpid()),
2✔
170
      client_ip != NULL ? client_ip : "none",
171
      server_ip != NULL ? server_ip : "none", server_port, channel, level, msg);
172
    buflen += len;
1✔
173
  }
174

175
  destroy_pool(tmp_pool);
5,836✔
176
  buf[sizeof(buf)-1] = '\0';
5,836✔
177

178
  if (buflen < (sizeof(buf) - 1)) {
5,836✔
179
    buf[buflen] = '\n';
5,836✔
180
    buflen++;
5,836✔
181

182
  } else {
183
    buf[sizeof(buf)-5] = '.';
×
184
    buf[sizeof(buf)-4] = '.';
×
185
    buf[sizeof(buf)-3] = '.';
×
186
    buf[sizeof(buf)-2] = '.';
×
187
    buflen = sizeof(buf)-1;
×
188
  }
189

190
  pr_log_event_generate(PR_LOG_TYPE_TRACELOG, trace_logfd, level, buf, buflen);
5,836✔
191

192
  if (discard) {
5,836✔
193
    /* This log message would not have been written to disk, so just discard
194
     * it.  The discard value is TRUE when there's a log listener for
195
     * TraceLog logging events, and the Trace log level configuration would
196
     * otherwise have filtered out this log message.
197
     */
198
    return 0;
199
  }
200

201
  return write(trace_logfd, buf, buflen);
5,836✔
202
}
203

204
pr_table_t *pr_trace_get_table(void) {
2✔
205
  if (trace_tab == NULL) {
2✔
206
    errno = ENOENT;
1✔
207
    return NULL;
1✔
208
  }
209

210
  return trace_tab;
211
}
212

213
static const struct trace_levels *trace_get_levels(const char *channel) {
8,834✔
214
  const void *value;
215

216
  if (channel == NULL) {
8,834✔
217
    errno = EINVAL;
3✔
218
    return NULL;
3✔
219
  }
220

221
  if (trace_tab == NULL ||
17,659✔
222
      trace_logfd < 0) {
8,828✔
223
    errno = EPERM;
3✔
224
    return NULL;
3✔
225
  }
226

227
  value = pr_table_get(trace_tab, channel, NULL);
8,828✔
228
  if (value == NULL) {
8,828✔
229
    errno = ENOENT;
2,497✔
230
    return NULL;
2,497✔
231
  }
232

233
  return value;
234
}
235

236
int pr_trace_get_level(const char *channel) {
411✔
237
  return pr_trace_get_max_level(channel);
411✔
238
}
239

240
int pr_trace_get_max_level(const char *channel) {
4✔
241
  const struct trace_levels *levels;
242

243
  levels = trace_get_levels(channel);
415✔
244
  if (levels == NULL) {
415✔
245
    return -1;
246
  }
247

248
  return levels->max_level;
409✔
249
}
250

251
int pr_trace_get_min_level(const char *channel) {
4✔
252
  const struct trace_levels *levels;
253

254
  levels = trace_get_levels(channel);
4✔
255
  if (levels == NULL) {
4✔
256
    return -1;
257
  }
258

259
  return levels->min_level;
1✔
260
}
261

262
int pr_trace_parse_levels(char *str, int *min_level, int *max_level) {
15✔
263
  int low = 1, high = -1;
15✔
264
  char *ptr = NULL, *tmp = NULL;
15✔
265

266
  if (str == NULL ||
30✔
267
      min_level == NULL ||
29✔
268
      max_level == NULL) {
269
    errno = EINVAL;
1✔
270
    return -1;
1✔
271
  }
272

273
  /* Watch for blank strings for levels (i.e. misconfigured/typo in config). */
274
  if (*str == '\0') {
14✔
275
    errno = EINVAL;
1✔
276
    return -1;
1✔
277
  }
278

279
  /* Check for a value range. */
280
  if (*str == '-') {
13✔
281
    errno = EINVAL;
2✔
282
    return -1;
2✔
283
  }
284

285
  ptr = strchr(str, '-');
11✔
286
  if (ptr == NULL) {
11✔
287
    /* Just a single value. */
288
    errno = 0;
4✔
289
    high = (int) strtol(str, &ptr, 10);
4✔
290
    if (errno == ERANGE) {
4✔
291
      errno = EINVAL;
×
292
      return -1;
×
293
    }
294

295
    if (ptr && *ptr) {
4✔
296
      errno = EINVAL;
1✔
297
      return -1;
1✔
298
    }
299

300
    if (high < 0) {
3✔
301
      errno = EINVAL;
1✔
302
      return -1;
1✔
303
    }
304

305
    /* A special case is where the single value is zero.  If this is the
306
     * case, we make sure that the min value is the same.
307
     */
308
    if (high != 0) {
2✔
309
      *min_level = 1;
1✔
310

311
    } else {
312
      *min_level = 0;
1✔
313
    }
314

315
    *max_level = high;
2✔
316
    return 0;
2✔
317
  }
318

319
  /* We have a range of values. */
320
  *ptr = '\0';
7✔
321

322
  low = (int) strtol(str, &tmp, 10);
7✔
323
  if (errno == ERANGE) {
7✔
324
    errno = EINVAL;
×
325
    return -1;
×
326
  }
327

328
  if (tmp && *tmp) {
7✔
329
    *ptr = '-';
1✔
330
    errno = EINVAL;
1✔
331
    return -1;
1✔
332
  }
333
  *ptr = '-';
6✔
334

335
  if (low < 0) {
6✔
336
    errno = EINVAL;
1✔
337
    return -1;
1✔
338
  }
339

340
  tmp = NULL;
5✔
341
  high = (int) strtol(ptr + 1, &tmp, 10);
5✔
342
  if (errno == ERANGE) {
5✔
343
    errno = EINVAL;
×
344
    return -1;
×
345
  }
346

347
  if (tmp && *tmp) {
5✔
348
    errno = EINVAL;
1✔
349
    return -1;
1✔
350
  }
351

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

357
  if (high < low) {
2✔
358
    errno = EINVAL;
1✔
359
    return -1;
1✔
360
  }
361

362
  *min_level = low;
1✔
363
  *max_level = high;
1✔
364
  return 0;
1✔
365
}
366

367
int pr_trace_set_file(const char *path) {
4✔
368
  int res, xerrno;
369

370
  if (path == NULL) {
4✔
371
    if (trace_logfd < 0) {
1✔
372
      errno = EINVAL;
×
373
      return -1;
×
374
    }
375

376
    (void) close(trace_logfd);
1✔
377
    trace_logfd = -1;
1✔
378
    return 0;
1✔
379
  }
380

381
  pr_signals_block();
3✔
382
  PRIVS_ROOT
3✔
383
  res = pr_log_openfile(path, &trace_logfd, 0660);
3✔
384
  xerrno = errno;
3✔
385
  PRIVS_RELINQUISH
3✔
386
  pr_signals_unblock();
3✔
387

388
  if (res < 0) {
3✔
389
    if (res == -1) {
2✔
390
      pr_log_debug(DEBUG1, "unable to open TraceLog '%s': %s", path,
2✔
391
        strerror(xerrno));
392
      errno = xerrno;
2✔
393

394
    } else if (res == PR_LOG_WRITABLE_DIR) {
×
395
      pr_log_debug(DEBUG1,
×
396
        "unable to open TraceLog '%s': parent directory is world-writable",
397
        path);
398
      errno = EPERM;
×
399

400
    } else if (res == PR_LOG_SYMLINK) {
×
401
      pr_log_debug(DEBUG1,
×
402
        "unable to open TraceLog '%s': cannot log to a symbolic link",
403
        path);
404
      errno = EPERM;
×
405
    }
406

407
    return res;
408
  }
409

410
  return 0;
411
}
412

413
int pr_trace_set_levels(const char *channel, int min_level, int max_level) {
2,426✔
414
  if (channel == NULL) {
2,426✔
415
    if (trace_tab == NULL) {
1✔
416
      errno = EINVAL;
1✔
417
      return -1;
1✔
418
    }
419

420
    return 0;
421
  }
422

423
  if (min_level > max_level) {
2,425✔
424
    errno = EINVAL;
1✔
425
    return -1;
1✔
426
  }
427

428
  if (trace_tab == NULL &&
2,424✔
429
      min_level < 0) {
430
    return 0;
431
  }
432

433
  if (trace_pool == NULL) {
2,424✔
434
    trace_pool = make_sub_pool(permanent_pool);
769✔
435
    pr_pool_tag(trace_pool, "Trace API");
769✔
436

437
    trace_tab = pr_table_alloc(trace_pool, 0);
769✔
438

439
    /* Register a handler for churning the log pool during HUP. */
440
    pr_event_register(NULL, "core.restart", trace_restart_ev, NULL);
769✔
441

442
    /* Make sure to clean our things up when the permanent_pool is destroyed. */
443
    register_cleanup2(permanent_pool, NULL, trace_cleanup);
769✔
444
  }
445

446
  if (min_level >= 0) {
2,424✔
447
    struct trace_levels *levels;
448

449
    levels = pcalloc(trace_pool, sizeof(struct trace_levels));
2,424✔
450
    levels->min_level = min_level;
2,424✔
451
    levels->max_level = max_level;
2,424✔
452

453
    if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) {
2,424✔
454
      int count;
455

456
      count = pr_table_exists(trace_tab, channel);
2,422✔
457
      if (count <= 0) {
2,422✔
458
        if (pr_table_add(trace_tab, pstrdup(trace_pool, channel), levels,
1,217✔
459
            sizeof(struct trace_levels)) < 0) {
460
          return -1;
461
        }
462

463
      } else {
464
        if (pr_table_set(trace_tab, pstrdup(trace_pool, channel), levels,
1,205✔
465
            sizeof(struct trace_levels)) < 0) {
466
          return -1;
467
        }
468
      }
469

470
    } else {
471
      register unsigned int i;
472

473
      for (i = 0; trace_channels[i]; i++) {
62✔
474
        (void) pr_trace_set_levels(trace_channels[i], min_level, max_level);
62✔
475
      }
476
    }
477

478
  } else {
479
    if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) {
×
480
      (void) pr_table_remove(trace_tab, channel, NULL);
×
481

482
    } else {
483
      register unsigned int i;
484

485
      for (i = 0; trace_channels[i]; i++) {
×
486
        (void) pr_table_remove(trace_tab, trace_channels[i], NULL);
×
487
      }
488
    }
489
  }
490

491
  return 0;
492
}
493

494
int pr_trace_set_options(unsigned long opts) {
13✔
495
  trace_opts = opts;
13✔
496
  return 0;
13✔
497
}
498

499
int pr_trace_use_stderr(int use_stderr) {
940✔
500
  if (use_stderr) {
940✔
501
    int res;
502

503
    res = dup(STDERR_FILENO);
930✔
504
    if (res < 0) {
930✔
505
      return -1;
506
    }
507

508
    /* Avoid a file descriptor leak by closing any existing fd. */
509
    (void) close(trace_logfd);
930✔
510
    trace_logfd = res;
930✔
511

512
  } else {
513
    (void) close(trace_logfd);
10✔
514
    trace_logfd = -1;
10✔
515
  }
516

517
  return 0;
518
}
519

520
int pr_trace_msg(const char *channel, int level, const char *fmt, ...) {
10,172✔
521
  int res;
522
  va_list msg;
523

524
  if (channel == NULL ||
20,344✔
525
      fmt == NULL ||
20,341✔
526
      level <= 0) {
527
    errno = EINVAL;
3✔
528
    return -1;
3✔
529
  }
530

531
  /* If no one's listening... */
532
  if (trace_logfd < 0 &&
10,173✔
533
      pr_log_event_listening(PR_LOG_TYPE_TRACELOG) <= 0) {
4✔
534
    return 0;
535
  }
536

537
  va_start(msg, fmt);
10,165✔
538
  res = pr_trace_vmsg(channel, level, fmt, msg);
10,165✔
539
  va_end(msg);
10,165✔
540

541
  return res;
10,165✔
542
}
543

544
int pr_trace_vmsg(const char *channel, int level, const char *fmt,
10,165✔
545
    va_list msg) {
546
  char buf[TRACE_BUFFER_SIZE];
547
  const struct trace_levels *levels;
548
  int buflen, discard = FALSE, listening;
10,165✔
549

550
  /* Writing a trace message at level zero is NOT helpful; this makes it
551
   * impossible to quell messages to that trace channel by setting the level
552
   * filter to zero.  That being the case, treat level of zero as an invalid
553
   * level.
554
   */
555

556
  if (channel == NULL ||
20,330✔
557
      fmt == NULL ||
20,330✔
558
      level <= 0) {
559
    errno = EINVAL;
×
560
    return -1;
×
561
  }
562

563
  if (trace_tab == NULL) {
10,165✔
564
    errno = EPERM;
1,750✔
565
    return -1;
1,750✔
566
  }
567

568
  /* If no one's listening... */
569
  if (trace_logfd < 0) {
8,415✔
570
    return 0;
571
  }
572

573
  listening = pr_log_event_listening(PR_LOG_TYPE_TRACELOG);
8,415✔
574

575
  levels = trace_get_levels(channel);
8,415✔
576
  if (levels == NULL) {
8,415✔
577
    discard = TRUE;
2,494✔
578

579
    if (listening <= 0) {
2,494✔
580
      return 0;
581
    }
582
  }
583

584
  if (discard == FALSE &&
5,921✔
585
      level < levels->min_level) {
5,921✔
586
    discard = TRUE;
×
587

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

593
  if (discard == FALSE &&
11,842✔
594
      level > levels->max_level) {
5,921✔
595
    discard = TRUE;
85✔
596

597
    if (listening <= 0) {
85✔
598
      return 0;
599
    }
600
  }
601

602
  buflen = pr_vsnprintf(buf, sizeof(buf)-1, fmt, msg);
5,836✔
603

604
  /* Always make sure the buffer is NUL-terminated. */
605
  buf[sizeof(buf)-1] = '\0';
5,836✔
606

607
  if (buflen > 0 &&
5,836✔
608
      (size_t) buflen < sizeof(buf)) {
609
    buf[buflen] = '\0';
5,835✔
610

611
  } else {
612
    /* Note that vsnprintf() returns the number of characters _that would have
613
     * been printed if buffer were unlimited_.  Be careful of this.
614
     */
615
    buflen = sizeof(buf)-1;
616
  }
617

618
  /* Trim trailing newlines. */
619
  while (buflen >= 1 &&
11,686✔
620
         buf[buflen-1] == '\n') {
5,843✔
621
    pr_signals_handle();
7✔
622
    buf[buflen-1] = '\0';
7✔
623
    buflen--;
7✔
624
  }
625

626
  return trace_write(channel, level, buf, discard);
5,836✔
627
}
628

629
#else
630

631
pr_table_t *pr_trace_get_table(void) {
632
  errno = ENOSYS;
633
  return NULL;
634
}
635

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

641
int pr_trace_get_max_level(const char *channel) {
642
  errno = ENOSYS;
643
  return -1;
644
}
645

646
int pr_trace_get_min_level(const char *channel) {
647
  errno = ENOSYS;
648
  return -1;
649
}
650

651
int pr_trace_parse_levels(char *str, int *min_level, int *max_level) {
652
  errno = ENOSYS;
653
  return -1;
654
}
655

656
int pr_trace_set_file(const char *path) {
657
  errno = ENOSYS;
658
  return -1;
659
}
660

661
int pr_trace_set_levels(const char *channel, int min_level, int max_level) {
662
  errno = ENOSYS;
663
  return -1;
664
}
665

666
int pr_trace_set_options(unsigned long opts) {
667
  errno = ENOSYS;
668
  return -1;
669
}
670

671
int pr_trace_msg(const char *channel, int level, const char *fmt, ...) {
672
  errno = ENOSYS;
673
  return -1;
674
}
675

676
int pr_trace_vmsg(const char *channel, int level, const char *fmt,
677
    va_list vargs) {
678
  errno = ENOSYS;
679
  return -1;
680
}
681

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