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

proftpd / proftpd / 14552756858

19 Apr 2025 08:53PM UTC coverage: 93.03% (+0.4%) from 92.667%
14552756858

push

github

51358 of 55206 relevant lines covered (93.03%)

213.67 hits per line

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

87.55
/src/trace.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2006-2022 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, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18
 *
19
 * As a special exemption, the ProFTPD Project and other respective copyright
20
 * holders give permission to link this program with OpenSSL, and distribute
21
 * the resulting executable, without including the source code for OpenSSL
22
 * in the source distribution.
23
 */
24

25
/* Trace functions. */
26

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

30
#ifdef PR_USE_TRACE
31

32
#define TRACE_BUFFER_SIZE                (PR_TUNABLE_BUFFER_SIZE * 8)
33

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

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

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

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

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

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

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

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

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

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

109
  if (trace_opts & PR_TRACE_OPT_USE_TIMESTAMP) {
4,904✔
110
    struct tm *tm;
4,900✔
111

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

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

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

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

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

128
    } else {
129
      time_t now;
×
130

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

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

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

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

154
  } else {
155
    const char *client_ip, *server_ip;
1✔
156
    int server_port;
1✔
157

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

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

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

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

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

185
  pr_log_event_generate(PR_LOG_TYPE_TRACELOG, trace_logfd, level, buf, buflen);
4,904✔
186

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

196
  return write(trace_logfd, buf, buflen);
4,904✔
197
}
198

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

205
  return trace_tab;
206
}
207

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

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

216
  if (trace_tab == NULL ||
7,692✔
217
      trace_logfd < 0) {
7,689✔
218
    errno = EPERM;
3✔
219
    return NULL;
3✔
220
  }
221

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

228
  return value;
229
}
230

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

402
    return res;
2✔
403
  }
404

405
  return 0;
406
}
407

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

415
    return 0;
416
  }
417

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

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

428
  if (trace_pool == NULL) {
2,350✔
429
    trace_pool = make_sub_pool(permanent_pool);
739✔
430
    pr_pool_tag(trace_pool, "Trace API");
739✔
431

432
    trace_tab = pr_table_alloc(trace_pool, 0);
739✔
433

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

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

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

445
    if (strcmp(channel, PR_TRACE_DEFAULT_CHANNEL) != 0) {
2,350✔
446
      int count;
2,348✔
447

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

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

462
    } else {
463
      register unsigned int i;
464

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

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

474
    } else {
475
      register unsigned int i;
476

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

483
  return 0;
484
}
485

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

491
int pr_trace_use_stderr(int use_stderr) {
910✔
492
  if (use_stderr) {
910✔
493
    int res;
900✔
494

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

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

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

509
  return 0;
510
}
511

512
int pr_trace_msg(const char *channel, int level, const char *fmt, ...) {
8,854✔
513
  int res;
8,854✔
514
  va_list msg;
8,854✔
515

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

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

529
  va_start(msg, fmt);
8,847✔
530
  res = pr_trace_vmsg(channel, level, fmt, msg);
8,847✔
531
  va_end(msg);
8,847✔
532

533
  return res;
8,847✔
534
}
535

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

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

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

555
  if (trace_tab == NULL) {
8,847✔
556
    errno = EPERM;
1,549✔
557
    return -1;
1,549✔
558
  }
559

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

565
  listening = pr_log_event_listening(PR_LOG_TYPE_TRACELOG);
7,298✔
566

567
  levels = trace_get_levels(channel);
7,298✔
568
  if (levels == NULL) {
7,298✔
569
    discard = TRUE;
2,339✔
570

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

576
  if (discard == FALSE &&
4,959✔
577
      level < levels->min_level) {
4,959✔
578
    discard = TRUE;
×
579

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

585
  if (discard == FALSE &&
4,959✔
586
      level > levels->max_level) {
4,959✔
587
    discard = TRUE;
55✔
588

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

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

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

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

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

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

618
  return trace_write(channel, level, buf, discard);
4,904✔
619
}
620

621
#else
622

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

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

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

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

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

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

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

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

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

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

674
#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