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

proftpd / proftpd / 14526507026

17 Apr 2025 11:25PM UTC coverage: 93.03% (+0.4%) from 92.667%
14526507026

push

github

51358 of 55206 relevant lines covered (93.03%)

234.02 hits per line

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

86.81
/src/display.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2004-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 team and other respective
19
 * copyright holders give permission to link this program with OpenSSL, and
20
 * distribute the resulting executable, without including the source code for
21
 * OpenSSL in the source distribution.
22
 */
23

24
/* Display of files */
25

26
#include "conf.h"
27

28
#define DISPLAY_BUFFER_SIZE        32
29

30
static int first_msg_sent = FALSE;
31
static const char *first_msg = NULL;
32
static const char *prev_msg = NULL;
33

34
static const char *trace_channel = "display";
35

36
/* Note: The size provided by pr_fs_getsize2() is in KB, not bytes. */
37
static void format_size_str(char *buf, size_t buflen, off_t size) {
38
  char *units[] = {"K", "M", "G", "T", "P", "E", "Z", "Y"};
8✔
39
  unsigned int nunits = 8;
8✔
40
  register unsigned int i = 0;
8✔
41
  int res;
8✔
42

8✔
43
  /* Determine the appropriate units label to use. Do not exceed the max
44
   * possible unit support (yottabytes), by ensuring that i maxes out at
45
   * index 7 (of 8 possible units).
46
   */
47
  while (size > 1024 &&
48
         i < (nunits - 1)) {
24✔
49
    pr_signals_handle();
50

16✔
51
    size /= 1024;
52
    i++;
16✔
53
  }
16✔
54

55
  /* Now, prepare the buffer. */
56
  res = pr_snprintf(buf, buflen, "%.3" PR_LU "%sB", (pr_off_t) size, units[i]);
57
  if (res > 2) {
8✔
58
    /* Check for leading zeroes; it's an aethetic choice. */
8✔
59
    if (buf[0] == '0' && buf[1] != '.') {
60
      memmove(&buf[0], &buf[1], res-1);
8✔
61
      buf[res-1] = '\0';
8✔
62
    }
8✔
63
  }
64
}
65

8✔
66
static int display_add_line(pool *p, const char *resp_code,
67
    const char *resp_msg) {
24✔
68

69
  /* Handle the case where the data to Display might contain only one line. */
70

71
  if (first_msg_sent == FALSE &&
72
      first_msg == NULL) {
24✔
73
      first_msg = pstrdup(p, resp_msg);
12✔
74
    return 0;
6✔
75
  }
6✔
76

77
  if (first_msg != NULL) {
78
    pr_response_send_raw("%s-%s", resp_code, first_msg);
18✔
79
    first_msg = NULL;
6✔
80
    first_msg_sent = TRUE;
6✔
81

6✔
82
    prev_msg = pstrdup(p, resp_msg);
83
    return 0;
6✔
84
  }
6✔
85

86
  if (prev_msg != NULL) {
87
    pr_response_send_raw(" %s", prev_msg);
12✔
88
  }
12✔
89

90
  prev_msg = pstrdup(p, resp_msg);
91
  return 0;
12✔
92
}
12✔
93

94
static int display_flush_lines(pool *p, const char *resp_code, int flags) {
95
  if (first_msg != NULL) {
6✔
96
    if (session.auth_mech != NULL) {
6✔
97
      if (flags & PR_DISPLAY_FL_NO_EOM) {
×
98
        pr_response_send_raw("%s-%s", resp_code, first_msg);
×
99

×
100
      } else {
101
        pr_response_send_raw("%s %s", resp_code, first_msg);
102
      }
×
103

104
    } else {
105
      /* There is a special case if the client has not yet authenticated; it
106
       * means we are handling a DisplayConnect file.  The server will send
107
       * a banner as well, so we need to treat this is the start of a multiline
108
       * response.
109
       */
110
      pr_response_send_raw("%s-%s", resp_code, first_msg);
111
    }
×
112

113
  } else {
114
    if (prev_msg) {
115
      if (flags & PR_DISPLAY_FL_NO_EOM) {
6✔
116
        pr_response_send_raw(" %s", prev_msg);
6✔
117

3✔
118
      } else {
119
        pr_response_send_raw("%s %s", resp_code, prev_msg);
120
      }
3✔
121
    }
122
  }
123

124
  /* Reset state for the next set of lines. */
125
  first_msg_sent = FALSE;
126
  first_msg = NULL;
6✔
127
  prev_msg = NULL;
6✔
128

6✔
129
  return 0;
130
}
6✔
131

132
static int display_fh(pr_fh_t *fh, const char *fs, const char *resp_code,
133
    int flags) {
8✔
134
  struct stat st;
135
  char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
8✔
136
  int len, res;
8✔
137
  const unsigned int *current_clients = NULL;
8✔
138
  const unsigned int *max_clients = NULL;
8✔
139
  off_t fs_size = 0;
8✔
140
  pool *p;
8✔
141
  const void *v;
8✔
142
  xaset_t *s;
8✔
143
  config_rec *c = NULL;
8✔
144
  const char *mg_time, *outs = NULL, *rfc1413_ident = NULL, *user;
8✔
145
  const char *serverfqdn = main_server->ServerFQDN;
8✔
146
  char mg_size[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
147
  char mg_size_units[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
148
  char mg_max[DISPLAY_BUFFER_SIZE] = "unlimited";
8✔
149
  char total_files_in[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
150
  char total_files_out[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
151
  char total_files_xfer[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
152
  char mg_class_limit[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
153
  char mg_cur[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
154
  char mg_xfer_bytes[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
155
  char mg_cur_class[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
156
  char mg_xfer_units[DISPLAY_BUFFER_SIZE] = {'\0'};
8✔
157

8✔
158
  /* Stat the opened file to determine the optimal buffer size for IO. */
159
  memset(&st, 0, sizeof(st));
160
  if (pr_fsio_fstat(fh, &st) == 0) {
8✔
161
    fh->fh_iosz = st.st_blksize;
8✔
162
  }
8✔
163

164
  /* Note: The size provided by pr_fs_getsize() is in KB, not bytes. */
165
  res = pr_fs_fgetsize(fh->fh_fd, &fs_size);
166
  if (res < 0 &&
8✔
167
      errno != ENOSYS) {
8✔
168
    (void) pr_log_debug(DEBUG7, "error getting filesystem size for '%s': %s",
×
169
      fh->fh_path, strerror(errno));
×
170
    fs_size = 0;
171
  }
×
172

173
  pr_snprintf(mg_size, sizeof(mg_size), "%" PR_LU, (pr_off_t) fs_size);
174
  format_size_str(mg_size_units, sizeof(mg_size_units), fs_size);
8✔
175

8✔
176
  p = make_sub_pool(session.pool);
177
  pr_pool_tag(p, "Display Pool");
8✔
178

8✔
179
  s = (session.anon_config ? session.anon_config->subset : main_server->conf);
180

8✔
181
  mg_time = pr_strtime3(p, time(NULL), FALSE);
182

8✔
183
  max_clients = get_param_ptr(s, "MaxClients", FALSE);
184

8✔
185
  v = pr_table_get(session.notes, "client-count", NULL);
186
  if (v != NULL) {
8✔
187
    current_clients = v;
8✔
188
  }
×
189

190
  pr_snprintf(mg_cur, sizeof(mg_cur), "%u",
191
    current_clients ? *current_clients : 1);
8✔
192

193
  if (session.conn_class != NULL &&
194
      session.conn_class->cls_name != NULL) {
8✔
195
    const unsigned int *class_clients = NULL;
14✔
196
    config_rec *maxc = NULL;
7✔
197
    unsigned int maxclients = 0;
7✔
198

7✔
199
    v = pr_table_get(session.notes, "class-client-count", NULL);
200
    if (v != NULL) {
7✔
201
      class_clients = v;
7✔
202
    }
×
203

204
    pr_snprintf(mg_cur_class, sizeof(mg_cur_class), "%u",
205
      class_clients ? *class_clients : 0);
7✔
206

207
    /* For the %z variable, first we scan through the MaxClientsPerClass,
208
     * and use the first applicable one.  If none are found, look for
209
     * any MaxClients set.
210
     */
211

212
    maxc = find_config(main_server->conf, CONF_PARAM, "MaxClientsPerClass",
213
      FALSE);
7✔
214

215
    while (maxc != NULL) {
216
      pr_signals_handle();
7✔
217

×
218
      if (strcmp(maxc->argv[0], session.conn_class->cls_name) != 0) {
219
        maxc = find_config_next(maxc, maxc->next, CONF_PARAM,
×
220
          "MaxClientsPerClass", FALSE);
×
221
        continue;
222
      }
×
223

224
      maxclients = *((unsigned int *) maxc->argv[1]);
225
      break;
×
226
    }
×
227

228
    if (maxclients == 0) {
229
      maxc = find_config(main_server->conf, CONF_PARAM, "MaxClients", FALSE);
7✔
230
      if (maxc != NULL) {
7✔
231
        maxclients = *((unsigned int *) maxc->argv[0]);
7✔
232
      }
×
233
    }
234

235
    pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients);
236

7✔
237
  } else {
238
    pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u",
239
      max_clients ? *max_clients : 0);
1✔
240
    pr_snprintf(mg_cur_class, sizeof(mg_cur_class), "%u", 0);
241
  }
1✔
242

243
  pr_snprintf(mg_xfer_bytes, sizeof(mg_xfer_bytes), "%" PR_LU,
244
    (pr_off_t) session.total_bytes >> 10);
8✔
245
  pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "B",
8✔
246
    (pr_off_t) session.total_bytes);
8✔
247

8✔
248
  if (session.total_bytes >= 10240) {
249
    pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "kB",
8✔
250
      (pr_off_t) session.total_bytes >> 10);
×
251

×
252
  } else if ((session.total_bytes >> 10) >= 10240) {
253
    pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "MB",
254
      (pr_off_t) session.total_bytes >> 20);
255

256
  } else if ((session.total_bytes >> 20) >= 10240) {
257
    pr_snprintf(mg_xfer_units, sizeof(mg_xfer_units), "%" PR_LU "GB",
258
      (pr_off_t) session.total_bytes >> 30);
259
  }
260

261
  pr_snprintf(mg_max, sizeof(mg_max), "%u", max_clients ? *max_clients : 0);
262

8✔
263
  user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
264
  if (user == NULL) {
8✔
265
    user = "";
8✔
266
  }
8✔
267

268
  c = find_config(main_server->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
269
  if (c != NULL) {
8✔
270
    pr_netaddr_t *masq_addr = NULL;
8✔
271

×
272
    if (c->argv[0] != NULL) {
273
      masq_addr = c->argv[0];
×
274
    }
×
275

276
    if (masq_addr != NULL) {
277
      serverfqdn = pr_netaddr_get_dnsstr(masq_addr);
×
278
    }
×
279
  }
280

281
  /* "Stringify" the file number for this session. */
282
  pr_snprintf(total_files_in, sizeof(total_files_in), "%u",
283
    session.total_files_in);
8✔
284
  total_files_in[sizeof(total_files_in)-1] = '\0';
285

8✔
286
  pr_snprintf(total_files_out, sizeof(total_files_out), "%u",
287
    session.total_files_out);
8✔
288
  total_files_out[sizeof(total_files_out)-1] = '\0';
289

8✔
290
  pr_snprintf(total_files_xfer, sizeof(total_files_xfer), "%u",
291
    session.total_files_xfer);
8✔
292
  total_files_xfer[sizeof(total_files_xfer)-1] = '\0';
293

8✔
294
  rfc1413_ident = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL);
295
  if (rfc1413_ident == NULL) {
8✔
296
    rfc1413_ident = "UNKNOWN";
8✔
297
  }
8✔
298

299
  while (pr_fsio_gets(buf, sizeof(buf), fh) != NULL) {
300
    char *tmp;
40✔
301

32✔
302
    pr_signals_handle();
303

32✔
304
    buf[sizeof(buf)-1] = '\0';
305
    len = strlen(buf);
32✔
306

32✔
307
    while (len &&
308
           (buf[len-1] == '\r' || buf[len-1] == '\n')) {
64✔
309
      buf[len-1] = '\0';
64✔
310
      len--;
32✔
311
    }
32✔
312

313
    /* Check for any Variable-type strings. */
314
    tmp = strstr(buf, "%{");
315
    while (tmp) {
32✔
316
      char *key, *tmp2;
64✔
317
      const char *val;
32✔
318

32✔
319
      pr_signals_handle();
320

32✔
321
      tmp2 = strchr(tmp, '}');
322
      if (!tmp2) {
32✔
323
        tmp = strstr(tmp + 1, "%{");
32✔
324
        continue;
×
325
      }
×
326

327
      key = pstrndup(p, tmp, tmp2 - tmp + 1);
328

32✔
329
      /* There are a couple of special-case keys to watch for:
330
       *
331
       *   env:$var
332
       *   time:$fmt
333
       *
334
       * The Var API does not easily support returning values for keys
335
       * where part of the value depends on part of the key.  That's why
336
       * these keys are handled here, instead of in pr_var_get().
337
       */
338

339
      if (strncmp(key, "%{time:", 7) == 0) {
340
        char time_str[128], *fmt;
32✔
341
        time_t now;
8✔
342
        struct tm *tm;
8✔
343

8✔
344
        fmt = pstrndup(p, key + 7, strlen(key) - 8);
345

8✔
346
        time(&now);
347
        memset(time_str, 0, sizeof(time_str));
8✔
348

8✔
349
        tm = pr_localtime(p, &now);
350
        if (tm != NULL) {
8✔
351
          strftime(time_str, sizeof(time_str), fmt, tm);
8✔
352
        }
8✔
353

354
        val = pstrdup(p, time_str);
355

8✔
356
      } else if (strncmp(key, "%{env:", 6) == 0) {
357
        char *env_var;
24✔
358

16✔
359
        env_var = pstrndup(p, key + 6, strlen(key) - 7);
360
        val = pr_env_get(p, env_var);
16✔
361
        if (val == NULL) {
16✔
362
          pr_trace_msg("var", 4,
16✔
363
            "no value set for environment variable '%s', using \"(none)\"",
16✔
364
            env_var);
365
          val = "(none)";
366
        }
16✔
367

368
      } else {
369
        val = pr_var_get(key);
370
        if (val == NULL) {
8✔
371
          pr_trace_msg("var", 4,
8✔
372
            "no value set for name '%s' [%s], using \"(none)\"", key,
16✔
373
            strerror(errno));
374
          val = "(none)";
8✔
375
        }
8✔
376
      }
377

378
      outs = sreplace(p, buf, key, val, NULL);
379
      sstrncpy(buf, outs, sizeof(buf));
32✔
380

32✔
381
      tmp = strstr(outs, "%{");
382
    }
32✔
383

384
    outs = sreplace(p, buf,
385
      "%C", (session.cwd[0] ? session.cwd : "(none)"),
60✔
386
      "%E", main_server->ServerAdmin,
32✔
387
      "%F", mg_size,
388
      "%f", mg_size_units,
389
      "%i", total_files_in,
390
      "%K", mg_xfer_bytes,
391
      "%k", mg_xfer_units,
392
      "%L", serverfqdn,
393
      "%M", mg_max,
394
      "%N", mg_cur,
395
      "%o", total_files_out,
396
      "%R", (session.c && session.c->remote_name ?
397
        session.c->remote_name : "(unknown)"),
×
398
      "%T", mg_time,
399
      "%t", total_files_xfer,
400
      "%U", user,
401
      "%u", rfc1413_ident,
402
      "%V", main_server->ServerName,
403
      "%x", session.conn_class ? session.conn_class->cls_name : "(unknown)",
32✔
404
      "%y", mg_cur_class,
32✔
405
      "%z", mg_class_limit,
406
      NULL);
407

408
    sstrncpy(buf, outs, sizeof(buf));
409

32✔
410
    if (flags & PR_DISPLAY_FL_SEND_NOW) {
411
      /* Normally we use pr_response_add(), and let the response code
32✔
412
       * automatically handle all of the multiline response formatting.
413
       * However, some of the Display files are at times waiting for the
414
       * response chains to be flushed, which won't work (i.e. DisplayConnect
415
       * and DisplayQuit).
416
       */
417
      display_add_line(p, resp_code, outs);
418

24✔
419
    } else {
420
      pr_response_add(resp_code, "%s", outs);
421
    }
8✔
422
  }
423

424
  if (flags & PR_DISPLAY_FL_SEND_NOW) {
425
    display_flush_lines(p, resp_code, flags);
8✔
426
  }
6✔
427

428
  destroy_pool(p);
429
  return 0;
8✔
430
}
8✔
431

432
int pr_display_fh(pr_fh_t *fh, const char *fs, const char *resp_code,
433
    int flags) {
3✔
434
  if (fh == NULL ||
435
      resp_code == NULL) {
3✔
436
    errno = EINVAL;
3✔
437
    return -1;
2✔
438
  }
2✔
439

440
  return display_fh(fh, fs, resp_code, flags);
441
}
1✔
442

443
int pr_display_file(const char *path, const char *fs, const char *resp_code,
444
    int flags) {
11✔
445
  pr_fh_t *fh = NULL;
446
  int res, xerrno;
11✔
447
  struct stat st;
11✔
448

11✔
449
  if (path == NULL ||
450
      resp_code == NULL) {
11✔
451
    errno = EINVAL;
11✔
452
    return -1;
2✔
453
  }
2✔
454

455
  fh = pr_fsio_open_canon(path, O_RDONLY);
456
  xerrno = errno;
9✔
457

9✔
458
  if (fh == NULL) {
459
    pr_trace_msg(trace_channel, 4, "unable to open file '%s': %s",
9✔
460
      path, strerror(xerrno));
1✔
461

462
    errno = xerrno;
463
    return -1;
1✔
464
  }
1✔
465

466
  res = pr_fsio_fstat(fh, &st);
467
  xerrno = errno;
8✔
468

8✔
469
  if (res < 0) {
470
    pr_trace_msg(trace_channel, 4, "unable to stat file '%s': %s",
8✔
471
      path, strerror(xerrno));
×
472

473
    pr_fsio_close(fh);
474

×
475
    errno = xerrno;
476
    return -1;
×
477
  }
×
478

479
  if (S_ISDIR(st.st_mode)) {
480
    pr_fsio_close(fh);
8✔
481
    xerrno = EISDIR;
1✔
482

1✔
483
    pr_trace_msg(trace_channel, 4,
484
      "display file can not be a directory '%s': %s", path, strerror(xerrno));
1✔
485

486
    errno = xerrno;
487
    return -1;
1✔
488
  }
1✔
489

490
  res = display_fh(fh, fs, resp_code, flags);
491
  xerrno = errno;
7✔
492

7✔
493
  pr_fsio_close(fh);
494

7✔
495
  errno = xerrno;
496
  return res;
7✔
497
}
7✔
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