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

proftpd / proftpd / 14562012250

20 Apr 2025 06:03PM UTC coverage: 92.667% (-0.4%) from 93.03%
14562012250

push

github

Castaglia
Fix comment typo/thinko.

47172 of 50905 relevant lines covered (92.67%)

219.52 hits per line

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

95.68
/src/cmd.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2009-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 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
#include "conf.h"
26

27
/* This struct and the list of such structs are used to try to reduce
28
 * the use of the following idiom to identify which command a given
29
 * cmd_rec is:
30
 *
31
 *  if (strcmp(cmd->argv[0], C_USER) == 0)
32
 *
33
 * Rather than using strcmp(3) so freely, try to reduce the command to
34
 * a fixed ID (an index into the struct list); this ID can then be compared
35
 * rather than using strcmp(3).  For commands not in the list, strcmp(3)
36
 * can always be used as a fallback.
37
 *
38
 * A future improvement would be to sort the entries in the table so that
39
 * the most common commands appear earlier in the table, and make the
40
 * linear scan even shorter.  But I'd need to collect better metrics in
41
 * order to do that.
42
 */
43

44
struct cmd_entry {
45
  const char *cmd_name;
46
  size_t cmd_namelen;
47
};
48

49
static struct cmd_entry cmd_ids[] = {
50
  { " ",        1 },        /* Index 0 is intentionally filled with a sentinel */
51
  { C_USER,        4 },        /* PR_CMD_USER_ID (1) */
52
  { C_PASS,        4 },        /* PR_CMD_PASS_ID (2) */
53
  { C_ACCT,        4 },        /* PR_CMD_ACCT_ID (3) */
54
  { C_CWD,        3 },        /* PR_CMD_CWD_ID (4) */
55
  { C_XCWD,        4 },        /* PR_CMD_XCWD_ID (5) */
56
  { C_CDUP,        4 },        /* PR_CMD_CDUP_ID (6) */
57
  { C_XCUP,        4 },        /* PR_CMD_XCUP_ID (7) */
58
  { C_SMNT,        4 },        /* PR_CMD_SMNT_ID (8) */
59
  { C_REIN,        4 },        /* PR_CMD_REIN_ID (9) */
60
  { C_QUIT,        4 },        /* PR_CMD_QUIT_ID (10) */
61
  { C_PORT,        4 },        /* PR_CMD_PORT_ID (11) */
62
  { C_EPRT,        4 },        /* PR_CMD_EPRT_ID (12) */
63
  { C_PASV,        4 },        /* PR_CMD_PASV_ID (13) */
64
  { C_EPSV,        4 },        /* PR_CMD_EPSV_ID (14) */
65
  { C_TYPE,        4 },        /* PR_CMD_TYPE_ID (15) */
66
  { C_STRU,        4 },        /* PR_CMD_STRU_ID (16) */
67
  { C_MODE,        4 },        /* PR_CMD_MODE_ID (17) */
68
  { C_RETR,        4 },        /* PR_CMD_RETR_ID (18) */
69
  { C_STOR,        4 },        /* PR_CMD_STOR_ID (19) */
70
  { C_STOU,        4 },        /* PR_CMD_STOU_ID (20) */
71
  { C_APPE,        4 },        /* PR_CMD_APPE_ID (21) */
72
  { C_ALLO,        4 },        /* PR_CMD_ALLO_ID (22) */
73
  { C_REST,        4 },        /* PR_CMD_REST_ID (23) */
74
  { C_RNFR,        4 },        /* PR_CMD_RNFR_ID (24) */
75
  { C_RNTO,        4 },        /* PR_CMD_RNTO_ID (25) */
76
  { C_ABOR,        4 },        /* PR_CMD_ABOR_ID (26) */
77
  { C_DELE,        4 },        /* PR_CMD_DELE_ID (27) */
78
  { C_MDTM,        4 },        /* PR_CMD_MDTM_ID (28) */
79
  { C_RMD,        3 },        /* PR_CMD_RMD_ID (29) */
80
  { C_XRMD,        4 },        /* PR_CMD_XRMD_ID (30) */
81
  { C_MKD,        3 },        /* PR_CMD_MKD_ID (31) */
82
  { C_MLSD,        4 },        /* PR_CMD_MLSD_ID (32) */
83
  { C_MLST,        4 },        /* PR_CMD_MLST_ID (33) */
84
  { C_XMKD,        4 },        /* PR_CMD_XMKD_ID (34) */
85
  { C_PWD,        3 },        /* PR_CMD_PWD_ID (35) */
86
  { C_XPWD,        4 },        /* PR_CMD_XPWD_ID (36) */
87
  { C_SIZE,        4 },        /* PR_CMD_SIZE_ID (37) */
88
  { C_LIST,        4 },        /* PR_CMD_LIST_ID (38) */
89
  { C_NLST,        4 },        /* PR_CMD_NLST_ID (39) */
90
  { C_SITE,        4 },        /* PR_CMD_SITE_ID (40) */
91
  { C_SYST,        4 },        /* PR_CMD_SYST_ID (41) */
92
  { C_STAT,        4 },        /* PR_CMD_STAT_ID (42) */
93
  { C_HELP,        4 },        /* PR_CMD_HELP_ID (43) */
94
  { C_NOOP,        4 },        /* PR_CMD_NOOP_ID (44) */
95
  { C_FEAT,        4 },        /* PR_CMD_FEAT_ID (45) */
96
  { C_OPTS,        4 },        /* PR_CMD_OPTS_ID (46) */
97
  { C_LANG,        4 },        /* PR_CMD_LANG_ID (47) */
98
  { C_ADAT,        4 },        /* PR_CMD_ADAT_ID (48) */
99
  { C_AUTH,        4 },        /* PR_CMD_AUTH_ID (49) */
100
  { C_CCC,        3 },        /* PR_CMD_CCC_ID (50) */
101
  { C_CONF,        4 },        /* PR_CMD_CONF_ID (51) */
102
  { C_ENC,        3 },        /* PR_CMD_ENC_ID (52) */
103
  { C_MIC,        3 },        /* PR_CMD_MIC_ID (53) */
104
  { C_PBSZ,        4 },        /* PR_CMD_PBSZ_ID (54) */
105
  { C_PROT,        4 },        /* PR_CMD_PROT_ID (55) */
106
  { C_MFF,        3 },        /* PR_CMD_MFF_ID (56) */
107
  { C_MFMT,        4 },        /* PR_CMD_MFMT_ID (57) */
108
  { C_HOST,        4 },        /* PR_CMD_HOST_ID (58) */
109
  { C_CLNT,        4 },        /* PR_CMD_CLNT_ID (59) */
110
  { C_RANG,        4 },        /* PR_CMD_RANG_ID (60) */
111
  { C_CSID,        4 },        /* PR_CMD_CSID_ID (61) */
112

113
  { NULL,        0 }
114
};
115

116
/* Due to potential XSS issues (see Bug#4143), we want to explicitly
117
 * check for commands from other text-based protocols (e.g. HTTP and SMTP);
118
 * if we see these, we want to close the connection with extreme prejudice.
119
 */
120

121
static struct cmd_entry http_ids[] = {
122
  { " ",        1 },    /* Index 0 is intentionally filled with a sentinel */
123
  { "CONNECT",        7 },
124
  { "DELETE",        6 },
125
  { "GET",        3 },
126
  { "HEAD",        4 },
127
  { "OPTIONS",        7 },
128
  { "PATCH",        5 },
129
  { "POST",        4 },
130
  { "PUT",        3 },
131

132
  { NULL,        0 }
133
};
134

135
static struct cmd_entry smtp_ids[] = {
136
  { " ",        1 },    /* Index 0 is intentionally filled with a sentinel */
137
  { "DATA",        4 },
138
  { "EHLO",        4 },
139
  { "HELO",        4 },
140
  { "MAIL",        4 },
141
  { "RCPT",        4 },
142
  { "RSET",        4 },
143
  { "VRFY",        4 },
144

145
  { NULL,        0 }
146
};
147

148
static const char *trace_channel = "command";
149

150
cmd_rec *pr_cmd_alloc(pool *p, unsigned int argc, ...) {
213✔
151
  pool *newpool = NULL;
213✔
152
  cmd_rec *cmd = NULL;
213✔
153
  int *xerrno = NULL;
213✔
154
  va_list args;
155

156
  if (p == NULL) {
213✔
157
    errno = EINVAL;
1✔
158
    return NULL;
1✔
159
  }
160

161
  newpool = make_sub_pool(p);
212✔
162
  pr_pool_tag(newpool, "cmd_rec pool");
212✔
163

164
  cmd = pcalloc(newpool, sizeof(cmd_rec));
212✔
165
  cmd->argc = argc;
212✔
166
  cmd->stash_index = -1;
212✔
167
  cmd->stash_hash = 0;
212✔
168
  cmd->pool = newpool;
212✔
169
  cmd->tmp_pool = make_sub_pool(cmd->pool);
212✔
170
  pr_pool_tag(cmd->tmp_pool, "cmd_rec tmp pool");
212✔
171

172
  if (argc > 0) {
212✔
173
    register unsigned int i = 0;
210✔
174

175
    cmd->argv = pcalloc(cmd->pool, sizeof(void *) * (argc + 1));
210✔
176
    va_start(args, argc);
210✔
177

178
    for (i = 0; i < argc; i++) {
451✔
179
      cmd->argv[i] = va_arg(args, void *);
241✔
180
    }
181

182
    va_end(args);
210✔
183
    cmd->argv[argc] = NULL;
210✔
184

185
    pr_pool_tag(cmd->pool, cmd->argv[0]);
210✔
186
  }
187

188
  /* This table will not contain that many entries, so a low number
189
   * of chains should suffice.
190
   */
191
  cmd->notes = pr_table_nalloc(cmd->pool, 0, 8);
212✔
192

193
  /* Initialize the "errno" note to be zero, so that it is always present. */
194
  xerrno = palloc(cmd->pool, sizeof(int));
212✔
195
  *xerrno = 0;
212✔
196
  (void) pr_table_add(cmd->notes, "errno", xerrno, sizeof(int));
212✔
197

198
  return cmd;
212✔
199
}
200

201
int pr_cmd_clear_cache(cmd_rec *cmd) {
2✔
202
  if (cmd == NULL) {
2✔
203
    errno = EINVAL;
1✔
204
    return -1;
1✔
205
  }
206

207
  /* Clear the strings that have been cached for this command in the
208
   * notes table.
209
   */
210

211
  (void) pr_table_remove(cmd->notes, "displayable-str", NULL);
1✔
212
  (void) pr_cmd_set_errno(cmd, 0);
1✔
213

214
  return 0;
1✔
215
}
216

217
int pr_cmd_cmp(cmd_rec *cmd, int cmd_id) {
1,378✔
218
  if (cmd == NULL ||
2,756✔
219
      cmd_id <= 0) {
1,378✔
220
    errno = EINVAL;
4✔
221
    return -1;
4✔
222
  }
223

224
  if (cmd->argc == 0 ||
2,746✔
225
      cmd->argv == NULL) {
1,372✔
226
    return 1;
227
  }
228

229
  /* The cmd ID is unknown; look it up. */
230
  if (cmd->cmd_id == 0) {
1,372✔
231
    cmd->cmd_id = pr_cmd_get_id(cmd->argv[0]);
154✔
232
  }
233

234
  /* The cmd ID is known to be unknown. */
235
  if (cmd->cmd_id < 0) {
1,372✔
236
    return 1;
237
  }
238

239
  if (cmd->cmd_id == cmd_id) {
984✔
240
    return 0;
241
  }
242

243
  return cmd->cmd_id < cmd_id ? -1 : 1;
854✔
244
}
245

246
int pr_cmd_get_errno(cmd_rec *cmd) {
4✔
247
  void *v;
248
  int *xerrno;
249

250
  if (cmd == NULL) {
4✔
251
    errno = EINVAL;
1✔
252
    return -1;
1✔
253
  }
254

255
  v = (void *) pr_table_get(cmd->notes, "errno", NULL);
3✔
256
  if (v == NULL) {
3✔
257
    errno = ENOENT;
1✔
258
    return -1;
1✔
259
  }
260

261
  xerrno = v;
2✔
262
  return *xerrno;
2✔
263
}
264

265
int pr_cmd_set_errno(cmd_rec *cmd, int xerrno) {
3✔
266
  void *v;
267

268
  if (cmd == NULL ||
5✔
269
      cmd->notes == NULL) {
2✔
270
    errno = EINVAL;
1✔
271
    return -1;
1✔
272
  }
273

274
  v = (void *) pr_table_get(cmd->notes, "errno", NULL);
2✔
275
  if (v == NULL) {
2✔
276
    errno = ENOENT;
×
277
    return -1;
×
278
  }
279

280
  *((int *) v) = xerrno;
2✔
281
  return 0;
2✔
282
}
283

284
int pr_cmd_set_name(cmd_rec *cmd, const char *cmd_name) {
3✔
285
  if (cmd == NULL ||
6✔
286
      cmd_name == NULL) {
3✔
287
    errno = EINVAL;
2✔
288
    return -1;
2✔
289
  }
290

291
  cmd->argv[0] = (char *) cmd_name;
1✔
292
  cmd->cmd_id = pr_cmd_get_id(cmd->argv[0]);
1✔
293

294
  return 0;
1✔
295
}
296

297
int pr_cmd_strcmp(cmd_rec *cmd, const char *cmd_name) {
5✔
298
  int cmd_id;
299
  size_t cmd_namelen;
300

301
  if (cmd == NULL ||
10✔
302
      cmd_name == NULL) {
5✔
303
    errno = EINVAL;
1✔
304
    return -1;
1✔
305
  }
306

307
  if (cmd->argc == 0 ||
7✔
308
      cmd->argv == NULL) {
3✔
309
    return 1;
310
  }
311

312
  /* The cmd ID is unknown; look it up. */
313
  if (cmd->cmd_id == 0) {
3✔
314
    cmd->cmd_id = pr_cmd_get_id(cmd->argv[0]);
3✔
315
  }
316

317
  if (cmd->cmd_id > 0) {
3✔
318
    int res;
319

320
    cmd_id = pr_cmd_get_id(cmd_name);
3✔
321

322
    res = pr_cmd_cmp(cmd, cmd_id);
3✔
323
    if (res == 0) {
3✔
324
      return 0;
325
    }
326

327
    return strncasecmp(cmd_name, cmd->argv[0],
2✔
328
      cmd_ids[cmd->cmd_id].cmd_namelen + 1);
2✔
329
  }
330

331
  cmd_namelen = strlen(cmd_name);
×
332
  return strncmp(cmd->argv[0], cmd_name, cmd_namelen + 1);
×
333
}
334

335
const char *pr_cmd_get_displayable_str(cmd_rec *cmd, size_t *str_len) {
10✔
336
  const char *res;
337
  unsigned int argc;
338
  void **argv;
339
  pool *p;
340

341
  if (cmd == NULL) {
10✔
342
    errno = EINVAL;
1✔
343
    return NULL;
1✔
344
  }
345

346
  res = pr_table_get(cmd->notes, "displayable-str", NULL);
9✔
347
  if (res != NULL) {
9✔
348
    if (str_len != NULL) {
1✔
349
      *str_len = strlen(res);
×
350
    }
351

352
    return res;
353
  }
354

355
  argc = cmd->argc;
8✔
356
  argv = cmd->argv;
8✔
357
  p = cmd->pool;
8✔
358

359
  res = "";
8✔
360

361
  /* Check for "sensitive" commands. */
362
  if (pr_cmd_cmp(cmd, PR_CMD_PASS_ID) == 0 ||
15✔
363
      pr_cmd_cmp(cmd, PR_CMD_ADAT_ID) == 0) {
7✔
364
    argc = 2;
2✔
365
    argv[1] = "(hidden)";
2✔
366
  }
367

368
  if (argc > 0) {
6✔
369
    register unsigned int i;
370

371
    res = pstrcat(p, res, pr_fs_decode_path(p, argv[0]), NULL);
7✔
372

373
    for (i = 1; i < argc; i++) {
13✔
374
      res = pstrcat(p, res, " ", pr_fs_decode_path(p, argv[i]), NULL);
6✔
375
    }
376
  }
377

378
  if (pr_table_add(cmd->notes, pstrdup(cmd->pool, "displayable-str"),
8✔
379
      pstrdup(cmd->pool, res), 0) < 0) {
8✔
380
    if (errno != EEXIST) {
×
381
      pr_trace_msg(trace_channel, 4,
×
382
        "error setting 'displayable-str' command note: %s", strerror(errno));
383
    }
384
  }
385

386
  if (str_len != NULL) {
8✔
387
    *str_len = strlen(res);
2✔
388
  }
389

390
  return res;
391
}
392

393
int pr_cmd_get_id(const char *cmd_name) {
284✔
394
  register unsigned int i;
395
  size_t cmd_namelen;
396
  char first_letter;
397

398
  if (cmd_name == NULL) {
284✔
399
    errno = EINVAL;
3✔
400
    return -1;
3✔
401
  }
402

403
  cmd_namelen = strlen(cmd_name);
281✔
404

405
  /* Take advantage of the fact that we know, a priori, that the shortest
406
   * command name in the list is 3 characters, and that the longest is 4
407
   * characters.  No need to scan the list if we know that the given name
408
   * is not within that length range.
409
   */
410
  if (cmd_namelen < PR_CMD_MIN_NAMELEN ||
281✔
411
      cmd_namelen > PR_CMD_MAX_NAMELEN) {
412
    errno = ENOENT;
10✔
413
    return -1;
10✔
414
  }
415

416
  first_letter = toupper((int) cmd_name[0]);
542✔
417

418
  for (i = 1; cmd_ids[i].cmd_name != NULL; i++) {
8,768✔
419
    if (cmd_ids[i].cmd_namelen != cmd_namelen) {
8,723✔
420
      continue;
3,146✔
421
    }
422

423
    if (cmd_ids[i].cmd_name[0] != first_letter) {
5,577✔
424
      continue;
4,946✔
425
    }
426

427
    if (strcasecmp(cmd_ids[i].cmd_name, cmd_name) == 0) {
631✔
428
      return i;
226✔
429
    }
430
  }
431

432
  errno = ENOENT;
45✔
433
  return -1;
45✔
434
}
435

436
static int is_known_cmd(struct cmd_entry *known_cmds, const char *cmd_name,
2✔
437
    size_t cmd_namelen) {
438
  register unsigned int i;
439
  int known = FALSE;
2✔
440

441
  for (i = 0; known_cmds[i].cmd_name != NULL; i++) {
10✔
442
    if (cmd_namelen == known_cmds[i].cmd_namelen) {
10✔
443
      if (strncmp(cmd_name, known_cmds[i].cmd_name, cmd_namelen + 1) == 0) {
6✔
444
        known = TRUE;
445
        break;
446
      }
447
    }
448
  }
449

450
  return known;
2✔
451
}
452

453
int pr_cmd_is_http(cmd_rec *cmd) {
4✔
454
  const char *cmd_name;
455
  size_t cmd_namelen;
456

457
  if (cmd == NULL) {
4✔
458
    errno = EINVAL;
1✔
459
    return -1;
1✔
460
  }
461

462
  cmd_name = cmd->argv[0];
3✔
463
  if (cmd_name == NULL) {
3✔
464
    errno = EINVAL;
1✔
465
    return -1;
1✔
466
  }
467

468
  if (cmd->cmd_id == 0) {
2✔
469
    cmd->cmd_id = pr_cmd_get_id(cmd_name);
2✔
470
  }
471

472
  if (cmd->cmd_id >= 0) {
2✔
473
    return FALSE;
474
  }
475

476
  cmd_namelen = strlen(cmd_name);
1✔
477
  return is_known_cmd(http_ids, cmd_name, cmd_namelen);
1✔
478
}
479

480
int pr_cmd_is_smtp(cmd_rec *cmd) {
4✔
481
  const char *cmd_name;
482
  size_t cmd_namelen;
483

484
  if (cmd == NULL) {
4✔
485
    errno = EINVAL;
1✔
486
    return -1;
1✔
487
  }
488

489
  cmd_name = cmd->argv[0];
3✔
490
  if (cmd_name == NULL) {
3✔
491
    errno = EINVAL;
1✔
492
    return -1;
1✔
493
  }
494

495
  if (cmd->cmd_id == 0) {
2✔
496
    cmd->cmd_id = pr_cmd_get_id(cmd_name);
2✔
497
  }
498

499
  if (cmd->cmd_id >= 0) {
2✔
500
    return FALSE;
501
  }
502

503
  cmd_namelen = strlen(cmd_name);
1✔
504
  return is_known_cmd(smtp_ids, cmd_name, cmd_namelen);
1✔
505
}
506

507
int pr_cmd_is_ssh2(cmd_rec *cmd) {
5✔
508
  const char *cmd_name;
509

510
  if (cmd == NULL) {
5✔
511
    errno = EINVAL;
1✔
512
    return -1;
1✔
513
  }
514

515
  cmd_name = cmd->argv[0];
4✔
516
  if (cmd_name == NULL) {
4✔
517
    errno = EINVAL;
1✔
518
    return -1;
1✔
519
  }
520

521
  if (cmd->cmd_id == 0) {
3✔
522
    cmd->cmd_id = pr_cmd_get_id(cmd_name);
3✔
523
  }
524

525
  if (cmd->cmd_id >= 0) {
3✔
526
    return FALSE;
527
  }
528

529
  if (strncmp(cmd_name, "SSH-2.0-", 8) == 0 ||
3✔
530
      strncmp(cmd_name, "SSH-1.99-", 9) == 0) {
1✔
531
    return TRUE;
532
  }
533

534
  return FALSE;
×
535
}
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