• 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-2024 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 team and other respective
20
 * copyright holders give permission to link this program with OpenSSL, and
21
 * distribute the resulting executable, without including the source code for
22
 * OpenSSL in the source distribution.
23
 */
24

25
/* Display of files */
26

27
#include "conf.h"
28

29
#define DISPLAY_BUFFER_SIZE        32
30

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

130
  return 0;
6✔
131
}
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

236
    pr_snprintf(mg_class_limit, sizeof(mg_class_limit), "%u", maxclients);
7✔
237

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

303
    pr_signals_handle();
32✔
304

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

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

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

320
      pr_signals_handle();
32✔
321

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

328
      key = pstrndup(p, tmp, tmp2 - tmp + 1);
32✔
329

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

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

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

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

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

355
        val = pstrdup(p, time_str);
8✔
356

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

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

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

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

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

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

409
    sstrncpy(buf, outs, sizeof(buf));
32✔
410

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

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

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

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

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

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

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

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

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

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

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

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

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

474
    pr_fsio_close(fh);
×
475

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

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

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

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

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

494
  pr_fsio_close(fh);
7✔
495

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