Coverage Report

Created: 2026-08-13 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/modules/mod_procfs.c
Line
Count
Source
1
/*
2
 * ProFTPD: mod_procfs -- a module for hiding the /proc filesystem
3
 * Copyright (c) 2026 TJ Saunders
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, TJ Saunders and other respective copyright holders
19
 * give permission to link this program with OpenSSL, and distribute the
20
 * resulting executable, without including the source code for OpenSSL in the
21
 * source distribution.
22
 *
23
 * This is mod_procfs contrib software for proftpd 1.3.x and above.
24
 * For more information contact TJ Saunders <tj@castaglia.org>.
25
 *
26
 * -----DO NOT CHANGE THE LINES BELOW-----
27
 */
28
29
#include "conf.h"
30
#include "privs.h"
31
32
#if !defined(HAVE_MNTENT_H)
33
/* Older ProFTPD versions did not check for the <mntent.h> header, so we
34
 * will use heuristics to guess whether it is present.  Some platforms,
35
 * such as Mac OSX, do not have this header.
36
 *
37
 * If/when I get access to BSD platforms, I can add heuristics to handle them
38
 * appropriately in the future.
39
 */
40
# if defined(__GLIBC__)
41
#   define HAVE_MNTENT_H 1
42
# elif defined(LINUX)
43
#   define HAVE_MNTENT_H 1
44
# endif /* LINUX */
45
#endif /* HAVE_MNTENT_H */
46
47
#if defined(HAVE_MNTENT_H)
48
# include <mntent.h>
49
#endif /* HAVE_MNTENT_H */
50
51
#if PROFTPD_VERSION_NUMBER < 0x0001030602
52
# error "ProFTPD 1.3.6rc2 or later required"
53
#endif
54
55
0
#define MOD_PROCFS_VERSION  "mod_procfs/0.4"
56
57
module procfs_module;
58
59
static int procfs_engine = FALSE;
60
static int procfs_logfd = -1;
61
static pool *procfs_pool = NULL;
62
63
static const char *trace_channel = "procfs";
64
65
struct procfs_mount {
66
  const char *path;
67
  size_t path_len;
68
  const char *type;
69
};
70
71
static array_header *procfs_mounts = NULL;
72
73
0
static int get_procfs_mounts(pool *p) {
74
0
#if defined(HAVE_MNTENT_H)
75
0
  FILE *mountf = NULL;
76
0
  struct mntent *mnt = NULL;
77
78
0
  mountf = setmntent("/etc/mtab", "r");
79
0
  if (mountf == NULL) {
80
0
    int xerrno = errno;
81
82
0
    pr_trace_msg(trace_channel, 1, "unable to read /etc/mtab: %s",
83
0
      strerror(xerrno));
84
0
    errno = xerrno;
85
0
    return -1;
86
0
  }
87
88
0
  mnt = getmntent(mountf);
89
0
  while (mnt != NULL) {
90
0
    struct procfs_mount *mount = NULL;
91
0
    size_t path_len = 0;
92
93
0
    pr_signals_handle();
94
95
0
    if (strcmp(mnt->mnt_type, "proc") == 0) {
96
0
      pr_log_debug(DEBUG0, MOD_PROCFS_VERSION
97
0
        ": discovered procfs mounted at '%s'", mnt->mnt_dir);
98
0
      mount = palloc(p, sizeof(struct procfs_mount));
99
0
      mount->type = pstrdup(p, "procfs");
100
101
0
    } else if (strcmp(mnt->mnt_type, "sysfs") == 0) {
102
0
      pr_log_debug(DEBUG0, MOD_PROCFS_VERSION
103
0
        ": discovered sysfs mounted at '%s'", mnt->mnt_dir);
104
0
      mount = palloc(p, sizeof(struct procfs_mount));
105
0
      mount->type = pstrdup(p, "sysfs");
106
0
    }
107
108
0
    if (mount != NULL) {
109
      /* If the mount point path does not end with a trailing slash, add it.
110
       * We use this property when checking paths that reference this mount
111
       * point.
112
       */
113
0
      path_len = strlen(mnt->mnt_dir);
114
0
      if (mnt->mnt_dir[path_len-1] != '/') {
115
0
        mount->path = pstrcat(p, mnt->mnt_dir, "/", NULL);
116
0
        mount->path_len = path_len + 1;
117
118
0
      } else {
119
0
        mount->path = pstrdup(p, mnt->mnt_dir);
120
0
        mount->path_len = path_len;
121
0
      }
122
123
0
      if (procfs_mounts == NULL) {
124
0
        procfs_mounts = make_array(p, 0, sizeof(struct procfs_mount *));
125
0
      }
126
127
0
      *((struct procfs_mount **) push_array(procfs_mounts)) = mount;
128
0
    }
129
130
0
    mnt = getmntent(mountf);
131
0
  }
132
133
0
  if (endmntent(mountf) != 1) {
134
0
    pr_trace_msg(trace_channel, 1, "error closing /etc/mtab: %s",
135
0
      strerror(errno));
136
0
  }
137
138
#else
139
  struct stat st;
140
141
  /* Check for the presence of the /proc filesystem on this host.  If it
142
   * is not present, then we need do nothing else.
143
   */
144
  if (lstat("/proc/", &st) == 0) {
145
    pr_log_debug(DEBUG10, MOD_PROCFS_VERSION ": found /proc/ filesystem");
146
147
    if (S_ISDIR(st.st_mode)) {
148
      struct procfs_mount *mount;
149
150
      mount = palloc(p, sizeof(struct procfs_mount));
151
      mount->path = pstrdup(p, "/proc/");
152
      mount->path_len = 6;
153
154
      procfs_mounts = make_array(p, 0, sizeof(struct procfs_mount *));
155
      *((struct procfs_mount **) push_array(procfs_mounts)) = mount;
156
157
    } else {
158
      pr_log_debug(DEBUG10, MOD_PROCFS_VERSION ": /proc/ is not a directory");
159
    }
160
  }
161
#endif /* HAVE_MNTENT_H */
162
163
0
  if (procfs_mounts != NULL) {
164
    /* Automatically enable ProcfsEngine on in such cases. */
165
0
    procfs_engine = TRUE;
166
167
0
  } else {
168
0
    pr_log_debug(DEBUG5, MOD_PROCFS_VERSION
169
0
      ": did not find /proc filesystem: %s", strerror(errno));
170
0
  }
171
172
0
  return 0;
173
0
}
174
175
0
static int is_blocked_path(pool *p, const char *path) {
176
0
  register unsigned int i;
177
0
  int res = FALSE;
178
0
  char *abs_path;
179
0
  size_t abs_pathlen;
180
0
  struct procfs_mount **mounts;
181
182
0
  abs_path = dir_abs_path(p, path, FALSE);
183
0
  abs_pathlen = strlen(abs_path);
184
185
0
  mounts = procfs_mounts->elts;
186
0
  for (i = 0; i < procfs_mounts->nelts; i++) {
187
0
    struct procfs_mount *mount;
188
189
0
    pr_signals_handle();
190
191
0
    mount = mounts[i];
192
193
0
    pr_trace_msg(trace_channel, 19,
194
0
      "checking path '%s' against %s mount '%s'", abs_path, mount->type,
195
0
      mount->path);
196
197
0
    if (abs_pathlen >= mount->path_len &&
198
0
        strncmp(abs_path, mount->path, mount->path_len) == 0) {
199
0
      res = TRUE;
200
201
0
    } else if (abs_pathlen == (mount->path_len - 1) &&
202
0
               strncmp(abs_path, mount->path, mount->path_len - 1) == 0) {
203
0
      res = TRUE;
204
0
    }
205
206
0
    if (res == TRUE) {
207
0
      break;
208
0
    }
209
0
  }
210
211
0
  return res;
212
0
}
213
214
/* Configuration handlers
215
 */
216
217
/* usage: ProcfsEngine on|off */
218
0
MODRET set_procfsengine(cmd_rec *cmd) {
219
0
  int engine = -1;
220
0
  config_rec *c = NULL;
221
222
0
  CHECK_ARGS(cmd, 1); 
223
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
224
225
0
  engine = get_boolean(cmd, 1);
226
0
  if (engine == -1) {
227
0
    CONF_ERROR(cmd, "expected Boolean parameter");
228
0
  }
229
230
0
  c = add_config_param(cmd->argv[0], 1, NULL);
231
0
  c->argv[0] = palloc(c->pool, sizeof(int));
232
0
  *((int *) c->argv[0]) = engine;
233
234
0
  return PR_HANDLED(cmd);
235
0
}
236
237
/* usage: ProcfsLog path|"none" */ 
238
0
MODRET set_procfslog(cmd_rec *cmd) {
239
0
  CHECK_ARGS(cmd, 1); 
240
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
241
 
242
0
  if (pr_fs_valid_path(cmd->argv[1]) < 0) {
243
0
    CONF_ERROR(cmd, "must be an absolute path");
244
0
  }
245
 
246
0
  (void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
247
0
  return PR_HANDLED(cmd);
248
0
}
249
250
0
static const char *get_cmd_resp_code(cmd_rec *cmd) {
251
0
  const char *resp_code = R_550;
252
253
0
  if (pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
254
0
      pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
255
0
      pr_cmd_cmp(cmd, PR_CMD_STAT_ID) == 0) {
256
0
    resp_code = R_450;
257
0
  }
258
259
0
  return resp_code;
260
0
}
261
262
static modret_t *handle_path(cmd_rec *cmd, const char *cmd_name,
263
0
    const char *path) {
264
0
  pr_trace_msg(trace_channel, 19, "checking path '%s' for %s", path, cmd_name);
265
266
0
  if (is_blocked_path(cmd->tmp_pool, path) == TRUE) {
267
0
    const char *proto, *resp_code;
268
269
0
    proto = pr_session_get_protocol(0);
270
271
0
    (void) pr_log_writefile(procfs_logfd, MOD_PROCFS_VERSION,
272
0
      "%s %s denied by mod_procfs for user '%s', client IP %s, protocol %s",
273
0
      cmd_name, path, session.user,
274
0
      pr_netaddr_get_ipstr(session.c->remote_addr), proto);
275
0
    pr_log_pri(PR_LOG_NOTICE, "%s %s denied by mod_procfs", cmd_name, path);
276
277
    /* The response code to use depends on the command. */
278
0
    resp_code = get_cmd_resp_code(cmd);
279
0
    pr_response_add_err(resp_code, _("%s: %s"), path, strerror(ENOENT));
280
281
0
    pr_cmd_set_errno(cmd, ENOENT);
282
0
    errno = ENOENT;
283
0
    return PR_ERROR(cmd);
284
0
  }
285
286
0
  return PR_DECLINED(cmd);
287
0
}
288
289
/* Command handlers
290
 */
291
292
0
MODRET procfs_pre_mfmt(cmd_rec *cmd) {
293
0
  const char *path, *ptr;
294
295
0
  if (procfs_engine == FALSE) {
296
0
    return PR_DECLINED(cmd);
297
0
  }
298
299
0
  if (cmd->argc < 3) {
300
0
    return PR_DECLINED(cmd);
301
0
  }
302
303
  /* The path can contain spaces.  Thus we need to use cmd->arg, not cmd->argv,
304
   * to find the path.  But cmd->arg contains the facts as well.  Thus we
305
   * find the FIRST space in cmd->arg; the path is everything past that space.
306
   */
307
0
  ptr = strchr(cmd->arg, ' ');
308
0
  if (ptr == NULL) {
309
0
    return PR_DECLINED(cmd);
310
0
  }
311
312
0
  path = ptr + 1;
313
0
  return handle_path(cmd, cmd->argv[0], path);
314
0
}
315
316
0
MODRET procfs_pre_path(cmd_rec *cmd) {
317
0
  const char *path;
318
319
0
  if (procfs_engine == FALSE) {
320
0
    return PR_DECLINED(cmd);
321
0
  }
322
323
0
  if (cmd->argc < 2) {
324
0
    return PR_DECLINED(cmd);
325
0
  }
326
327
  /* TODO: Add similar decoding, handling of spaces as done by mod_core. */
328
329
0
  path = cmd->arg;
330
0
  return handle_path(cmd, cmd->argv[0], path);
331
0
}
332
333
0
MODRET procfs_pre_site(cmd_rec *cmd) {
334
0
  char *cmd_name, *path;
335
336
0
  if (procfs_engine == FALSE) {
337
0
    return PR_DECLINED(cmd);
338
0
  }
339
340
0
  if (cmd->argc < 3) {
341
0
    return PR_DECLINED(cmd);
342
0
  }
343
344
  /* These are the SITE commands we know about:
345
   *
346
   *  SITE CHGRP <group> <path>
347
   *  SITE CHMOD <mode> <path>
348
   *
349
   *  SITE MKDIR <path> (mod_site_misc)
350
   *  SITE RMDIR <path> (mod_site_misc)
351
   *  SITE SYMLINK <from> <to> (mod_site_misc)
352
   *  SITE UTIME <timestamp> <path> (mod_site_misc)
353
   *
354
   *  SITE CPFR <path> (mod_copy)
355
   *  SITE CPTO <path> (mod_copy)
356
   *  SITE COPY <from> <to> (mod_copy)
357
   */
358
359
0
  if (strcmp(cmd->argv[1], "CHGRP") == 0 ||
360
0
      strcmp(cmd->argv[1], "CHMOD") == 0 ||
361
0
      strcmp(cmd->argv[1], "UTIME") == 0) {
362
0
    register unsigned int i;
363
0
    char *arg = "";
364
365
0
    if (cmd->argc < 4) {
366
0
      return PR_DECLINED(cmd);
367
0
    }
368
369
    /* Construct the path by concatenating all of the parameter after the
370
     * operational data, separating them with spaces.
371
     */
372
373
0
    for (i = 3; i < cmd->argc; i++) {
374
0
      arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", cmd->argv[i], NULL);
375
0
    }
376
377
0
    cmd_name = pstrcat(cmd->tmp_pool, cmd->argv[0], " ", cmd->argv[1], NULL);
378
0
    path = arg;
379
0
    return handle_path(cmd, cmd_name, path);
380
381
0
  } else if (strcmp(cmd->argv[1], "CPFR") == 0 ||
382
0
             strcmp(cmd->argv[1], "CPTO") == 0 ||
383
0
             strcmp(cmd->argv[1], "MKDIR") == 0 ||
384
0
             strcmp(cmd->argv[1], "RMDIR") == 0) {
385
386
0
    cmd_name = pstrcat(cmd->tmp_pool, cmd->argv[0], " ", cmd->argv[1], NULL);
387
0
    path = cmd->argv[2];
388
0
    return handle_path(cmd, cmd_name, path);
389
390
0
  } else if (strcmp(cmd->argv[1], "COPY") == 0 ||
391
0
             strcmp(cmd->argv[1], "SYMLINK") == 0) {
392
0
    char *from, *to;
393
0
    modret_t *mr;
394
395
0
    if (cmd->argc < 4) {
396
0
      return PR_DECLINED(cmd);
397
0
    }
398
399
0
    cmd_name = pstrcat(cmd->tmp_pool, cmd->argv[0], " ", cmd->argv[1], NULL);
400
0
    from = cmd->argv[2];
401
0
    to = cmd->argv[3];
402
403
0
    mr = handle_path(cmd, cmd_name, from);
404
0
    if (MODRET_ISERROR(mr)) {
405
0
      return mr;
406
0
    }
407
408
0
    return handle_path(cmd, cmd_name, to);
409
410
0
  } else {
411
0
    pr_trace_msg(trace_channel, 7,
412
0
      "unknown/unsupported SITE '%s' command, ignoring", (char *) cmd->argv[1]);
413
0
  }
414
415
0
  return PR_DECLINED(cmd);
416
0
}
417
418
0
MODRET procfs_sftp_pre_path(cmd_rec *cmd) {
419
0
  const char *path, *proto;
420
421
0
  if (procfs_engine == FALSE) {
422
0
    return PR_DECLINED(cmd);
423
0
  }
424
425
0
  proto = pr_session_get_protocol(0);
426
0
  if (strcmp(proto, "sftp") != 0) {
427
0
    return PR_DECLINED(cmd);
428
0
  }
429
430
0
  path = cmd->argv[1];
431
0
  return handle_path(cmd, cmd->argv[0], path);
432
0
}
433
434
0
MODRET procfs_sftp_pre_hardlink(cmd_rec *cmd) {
435
0
  const char *src_path, *dst_path, *proto;
436
0
  char *ptr;
437
0
  modret_t *mr;
438
439
0
  if (procfs_engine == FALSE) {
440
0
    return PR_DECLINED(cmd);
441
0
  }
442
443
0
  proto = pr_session_get_protocol(0);
444
0
  if (strcmp(proto, "sftp") != 0) {
445
0
    return PR_DECLINED(cmd);
446
0
  }
447
448
  /* Unfortunately, mod_sftp currently does NOT break the two paths into
449
   * the cmd->argv array; it only populates cmd->arg.
450
   *
451
   * In the future, if/when mod_sftp behavior changes, we can look at the
452
   * cmd->argc to determine which style is being used by mod_sftp.
453
   */
454
0
  ptr = strchr(cmd->arg, ' ');
455
0
  if (ptr == NULL) {
456
0
    return PR_DECLINED(cmd);
457
0
  }
458
459
0
  src_path = pstrndup(cmd->tmp_pool, cmd->arg, ptr - cmd->arg);
460
0
  dst_path = pstrdup(cmd->tmp_pool, ptr + 1);
461
462
0
  mr = handle_path(cmd, cmd->argv[0], src_path);
463
0
  if (MODRET_ISERROR(mr)) {
464
0
    return mr;
465
0
  }
466
467
0
  return handle_path(cmd, cmd->argv[0], dst_path);
468
0
}
469
470
0
MODRET procfs_sftp_pre_symlink(cmd_rec *cmd) {
471
0
  const char *src_path, *dst_path, *proto;
472
0
  char *ptr;
473
0
  modret_t *mr;
474
475
0
  if (procfs_engine == FALSE) {
476
0
    return PR_DECLINED(cmd);
477
0
  }
478
479
0
  proto = pr_session_get_protocol(0);
480
0
  if (strcmp(proto, "sftp") != 0) {
481
0
    return PR_DECLINED(cmd);
482
0
  }
483
484
  /* Unfortunately, mod_sftp currently does NOT break the two paths into
485
   * the cmd->argv array; it only populates cmd->arg.
486
   *
487
   * In the future, if/when mod_sftp behavior changes, we can look at the
488
   * cmd->argc to determine which style is being used by mod_sftp.
489
   */
490
0
  ptr = strchr(cmd->arg, '\t');
491
0
  if (ptr == NULL) {
492
0
    return PR_DECLINED(cmd);
493
0
  }
494
495
0
  src_path = pstrndup(cmd->tmp_pool, cmd->arg, ptr - cmd->arg);
496
0
  dst_path = pstrdup(cmd->tmp_pool, ptr + 1);
497
498
0
  mr = handle_path(cmd, cmd->argv[0], src_path);
499
0
  if (MODRET_ISERROR(mr)) {
500
0
    return mr;
501
0
  }
502
503
0
  return handle_path(cmd, cmd->argv[0], dst_path);
504
0
}
505
506
0
MODRET procfs_post_pass(cmd_rec *cmd) {
507
0
  config_rec *c;
508
509
0
  if (procfs_mounts == NULL) {
510
0
    procfs_engine = FALSE;
511
0
    return PR_DECLINED(cmd);
512
0
  }
513
514
0
  c = find_config(main_server->conf, CONF_PARAM, "ProcfsEngine", FALSE);
515
0
  if (c != NULL) {
516
0
    procfs_engine = *((int *) c->argv[0]);
517
0
  }
518
519
0
  if (procfs_engine == FALSE) {
520
0
    return PR_DECLINED(cmd);
521
0
  }
522
523
  /* Check whether we are chrooted.  If so, then we need not do anything. */
524
0
  if (session.chroot_path != NULL) {
525
0
    if (strcmp(session.chroot_path, "/") != 0) {
526
0
      pr_trace_msg(trace_channel, 3,
527
0
        "session is chrooted to '%s', disabling mod_procfs",
528
0
        session.chroot_path);
529
0
      procfs_engine = FALSE;
530
0
    }
531
0
  }
532
533
0
  return PR_DECLINED(cmd);
534
0
}
535
536
/* Event Listeners
537
 */
538
539
#if defined(PR_SHARED_MODULE)
540
static void procfs_mod_unload_ev(const void *event_data, void *user_data) {
541
  if (strcmp("mod_procfs.c", (const char *) event_data) != 0) {
542
    return;
543
  }
544
545
  pr_event_unregister(&procfs_module, NULL, NULL);
546
547
  (void) close(procfs_logfd);
548
  procfs_logfd = -1;
549
550
  if (procfs_pool != NULL) {
551
    destroy_pool(procfs_pool);
552
    procfs_pool = NULL;
553
    procfs_mounts = NULL;
554
  }
555
556
  procfs_engine = FALSE;
557
}
558
#endif /* PR_SHARED_MODULE */
559
560
0
static void procfs_restart_ev(const void *event_data, void *user_data) {
561
0
  (void) close(procfs_logfd);
562
0
  procfs_logfd = -1;
563
564
0
  if (procfs_pool != NULL) {
565
0
    destroy_pool(procfs_pool);
566
0
  }
567
568
0
  procfs_pool = make_sub_pool(permanent_pool);
569
0
  pr_pool_tag(procfs_pool, MOD_PROCFS_VERSION);
570
0
}
571
572
0
static void procfs_shutdown_ev(const void *event_data, void *user_data) {
573
0
  (void) close(procfs_logfd);
574
0
  procfs_logfd = -1;
575
576
0
  if (procfs_pool != NULL) {
577
0
    destroy_pool(procfs_pool);
578
0
    procfs_pool = NULL;
579
0
    procfs_mounts = NULL;
580
0
  }
581
0
}
582
583
/* Initialization functions
584
 */
585
586
0
static int procfs_init(void) {
587
0
  if (procfs_pool != NULL) {
588
0
    destroy_pool(procfs_pool);
589
0
  }
590
591
0
  procfs_pool = make_sub_pool(permanent_pool);
592
0
  pr_pool_tag(procfs_pool, MOD_PROCFS_VERSION);
593
594
#if defined(PR_SHARED_MODULE)
595
  pr_event_register(&procfs_module, "core.module-unload", procfs_mod_unload_ev,
596
    NULL);
597
#endif /* PR_SHARED_MODULE */
598
0
  pr_event_register(&procfs_module, "core.restart", procfs_restart_ev, NULL);
599
0
  pr_event_register(&procfs_module, "core.shutdown", procfs_shutdown_ev, NULL);
600
601
0
  if (get_procfs_mounts(procfs_pool) < 0) {
602
0
    pr_trace_msg(trace_channel, 1, "unable to discover procfs mounts: %s",
603
0
      strerror(errno));
604
0
  }
605
606
0
  return 0;
607
0
}
608
609
0
static int procfs_sess_init(void) {
610
0
  config_rec *c;
611
0
  const char *path;
612
0
  int res, xerrno;
613
614
0
  c = find_config(main_server->conf, CONF_PARAM, "ProcfsLog", FALSE);
615
0
  if (c == NULL) {
616
0
    return 0;
617
0
  }
618
619
0
  path = c->argv[0];
620
0
  if (strcasecmp(path, "none") == 0) {
621
0
    return 0;
622
0
  }
623
624
0
  PRIVS_ROOT
625
0
  res = pr_log_openfile(path, &procfs_logfd, 0660);
626
0
  xerrno = errno;
627
0
  PRIVS_RELINQUISH
628
629
0
  switch (res) {
630
0
    case 0:
631
0
      break;
632
633
0
    case -1:
634
0
      pr_log_debug(DEBUG1, MOD_PROCFS_VERSION
635
0
        ": unable to open ProcfsLog '%s': %s", path, strerror(xerrno));
636
0
      break;
637
638
0
    case PR_LOG_SYMLINK:
639
0
      pr_log_debug(DEBUG1, MOD_PROCFS_VERSION
640
0
        ": unable to open ProcfsLog '%s': %s", path, "is a symlink");
641
0
      break;
642
643
0
    case PR_LOG_WRITABLE_DIR:
644
0
      pr_log_debug(DEBUG1, MOD_PROCFS_VERSION
645
0
        ": unable to open ProcfsLog '%s': %s", path,
646
0
        "parent directory is world-writable");
647
0
      break;
648
0
  }
649
650
0
  return 0;
651
0
}
652
653
/* Module API tables
654
 */
655
656
static conftable procfs_conftab[] = {
657
  { "ProcfsEngine", set_procfsengine, NULL },
658
  { "ProcfsLog",  set_procfslog,    NULL },
659
  { NULL }
660
};
661
662
static cmdtable procfs_cmdtab[] = {
663
664
  /* FTP */
665
  { PRE_CMD,    C_APPE, G_NONE, procfs_pre_path,  FALSE, FALSE },
666
  { PRE_CMD,    C_CWD,  G_NONE, procfs_pre_path,  FALSE, FALSE },
667
  { PRE_CMD,    C_XCWD, G_NONE, procfs_pre_path,  FALSE, FALSE },
668
  { PRE_CMD,    C_DELE, G_NONE, procfs_pre_path,  FALSE, FALSE },
669
  { PRE_CMD,    C_LIST, G_NONE, procfs_pre_path,  FALSE, FALSE },
670
  { PRE_CMD,    C_MDTM, G_NONE, procfs_pre_path,  FALSE, FALSE },
671
  { PRE_CMD,    C_MFF,  G_NONE, procfs_pre_mfmt,  FALSE, FALSE },
672
  { PRE_CMD,    C_MFMT, G_NONE, procfs_pre_mfmt,  FALSE, FALSE },
673
  { PRE_CMD,    C_MKD,  G_NONE, procfs_pre_path,  FALSE, FALSE },
674
  { PRE_CMD,    C_XMKD, G_NONE, procfs_pre_path,  FALSE, FALSE },
675
  { PRE_CMD,    C_MLSD, G_NONE, procfs_pre_path,  FALSE, FALSE },
676
  { PRE_CMD,    C_MLST, G_NONE, procfs_pre_path,  FALSE, FALSE },
677
  { PRE_CMD,    C_NLST, G_NONE, procfs_pre_path,  FALSE, FALSE },
678
  { PRE_CMD,    C_RETR, G_NONE, procfs_pre_path,  FALSE, FALSE },
679
  { PRE_CMD,    C_RMD,  G_NONE, procfs_pre_path,  FALSE, FALSE },
680
  { PRE_CMD,    C_XRMD, G_NONE, procfs_pre_path,  FALSE, FALSE },
681
  { PRE_CMD,    C_RNFR, G_NONE, procfs_pre_path,  FALSE, FALSE },
682
  { PRE_CMD,    C_RNTO, G_NONE, procfs_pre_path,  FALSE, FALSE },
683
  { PRE_CMD,    C_SITE, G_NONE, procfs_pre_site,  FALSE, FALSE },
684
  { PRE_CMD,    C_SIZE, G_NONE, procfs_pre_path,  FALSE, FALSE },
685
  { PRE_CMD,    C_STAT, G_NONE, procfs_pre_path,  FALSE, FALSE },
686
  { PRE_CMD,    C_STOR, G_NONE, procfs_pre_path,  FALSE, FALSE },
687
688
  /* mod_digest FTP commands */
689
  { PRE_CMD,  "HASH",   G_NONE, procfs_pre_path,  FALSE, FALSE },
690
  { PRE_CMD,  "MD5",    G_NONE, procfs_pre_path,  FALSE, FALSE },
691
  { PRE_CMD,  "XCRC",   G_NONE, procfs_pre_path,  FALSE, FALSE },
692
  { PRE_CMD,  "XMD5",   G_NONE, procfs_pre_path,  FALSE, FALSE },
693
  { PRE_CMD,  "XSHA",   G_NONE, procfs_pre_path,  FALSE, FALSE },
694
  { PRE_CMD,  "XSHA1",  G_NONE, procfs_pre_path,  FALSE, FALSE },
695
  { PRE_CMD,  "XSHA256",  G_NONE, procfs_pre_path,  FALSE, FALSE },
696
  { PRE_CMD,  "XSHA512",  G_NONE, procfs_pre_path,  FALSE, FALSE },
697
698
  /* SFTP */
699
  { PRE_CMD, "HARDLINK",  G_NONE, procfs_sftp_pre_hardlink, FALSE, FALSE },
700
  { PRE_CMD, "LINK",    G_NONE, procfs_sftp_pre_hardlink, FALSE, FALSE },
701
  { PRE_CMD, "LSTAT",   G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
702
  { PRE_CMD, "OPENDIR",   G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
703
  { PRE_CMD, "READLINK",  G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
704
  { PRE_CMD, "REALPATH",  G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
705
  { PRE_CMD, "SETSTAT",   G_NONE, procfs_sftp_pre_path, FALSE, FALSE },
706
  { PRE_CMD, "SYMLINK",   G_NONE, procfs_sftp_pre_symlink, FALSE, FALSE },
707
708
  { POST_CMD,   C_PASS, G_NONE, procfs_post_pass, FALSE, FALSE },
709
  { 0, NULL }
710
};
711
712
module procfs_module = {
713
  NULL, NULL,
714
715
  /* Module API version 2.0 */
716
  0x20,
717
718
  /* Module name */
719
  "procfs",
720
721
  /* Module configuration handler table */
722
  procfs_conftab,
723
724
  /* Module command handler table */
725
  procfs_cmdtab,
726
727
  /* Module authentication handler table */
728
  NULL,
729
730
  /* Module initialization function */
731
  procfs_init,
732
733
  /* Session initialization function */
734
  procfs_sess_init,
735
736
  /* Module version */
737
  MOD_PROCFS_VERSION
738
};