Coverage Report

Created: 2026-05-30 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/modules/mod_facts.c
Line
Count
Source
1
/*
2
 * ProFTPD: mod_facts -- a module for handling "facts" [RFC3659]
3
 * Copyright (c) 2007-2026 The ProFTPD Project
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
24
#include "conf.h"
25
#include "privs.h"
26
#include "error.h"
27
28
0
#define MOD_FACTS_VERSION   "mod_facts/0.7"
29
30
#if PROFTPD_VERSION_NUMBER < 0x0001030101
31
# error "ProFTPD 1.3.1rc1 or later required"
32
#endif
33
34
module facts_module;
35
36
static unsigned long facts_opts = 0;
37
0
#define FACTS_OPT_SHOW_MODIFY   0x00001
38
0
#define FACTS_OPT_SHOW_PERM   0x00002
39
0
#define FACTS_OPT_SHOW_SIZE   0x00004
40
0
#define FACTS_OPT_SHOW_TYPE   0x00008
41
0
#define FACTS_OPT_SHOW_UNIQUE   0x00010
42
0
#define FACTS_OPT_SHOW_UNIX_GROUP 0x00020
43
0
#define FACTS_OPT_SHOW_UNIX_MODE  0x00040
44
0
#define FACTS_OPT_SHOW_UNIX_OWNER 0x00080
45
0
#define FACTS_OPT_SHOW_MEDIA_TYPE 0x00100
46
0
#define FACTS_OPT_SHOW_UNIX_OWNER_NAME  0x00200
47
0
#define FACTS_OPT_SHOW_UNIX_GROUP_NAME  0x00400
48
49
/* By default, we show most of the facts; see FactsDefault. */
50
#define FACTS_OPT_SHOW_DEFAULT \
51
0
  FACTS_OPT_SHOW_MODIFY|\
52
0
  FACTS_OPT_SHOW_PERM|\
53
0
  FACTS_OPT_SHOW_SIZE|\
54
0
  FACTS_OPT_SHOW_TYPE|\
55
0
  FACTS_OPT_SHOW_UNIQUE|\
56
0
  FACTS_OPT_SHOW_UNIX_GROUP|\
57
0
  FACTS_OPT_SHOW_UNIX_GROUP_NAME|\
58
0
  FACTS_OPT_SHOW_UNIX_MODE|\
59
0
  FACTS_OPT_SHOW_UNIX_OWNER|\
60
0
  FACTS_OPT_SHOW_UNIX_OWNER_NAME
61
62
static unsigned long facts_mlinfo_opts = 0;
63
0
#define FACTS_MLINFO_FL_SHOW_SYMLINKS     0x00001
64
0
#define FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK   0x00002
65
0
#define FACTS_MLINFO_FL_NO_CDIR       0x00004
66
0
#define FACTS_MLINFO_FL_APPEND_CRLF     0x00008
67
0
#define FACTS_MLINFO_FL_ADJUSTED_SYMLINKS   0x00010
68
0
#define FACTS_MLINFO_FL_NO_NAMES      0x00020
69
70
struct mlinfo {
71
  pool *pool;
72
  struct stat st;
73
  struct tm *tm;
74
  const char *user;
75
  const char *group;
76
  const char *type;
77
  const char *perm;
78
  const char *path;
79
  const char *real_path;
80
};
81
82
/* Necessary prototypes */
83
static int facts_mlinfobuf_flush(void);
84
static int facts_sess_init(void);
85
86
/* Support functions
87
 */
88
89
0
static int facts_get_show_opt(const char *fact) {
90
0
  if (strcasecmp(fact, "modify") == 0) {
91
0
    return FACTS_OPT_SHOW_MODIFY;
92
0
  }
93
94
0
  if (strcasecmp(fact, "perm") == 0) {
95
0
    return FACTS_OPT_SHOW_PERM;
96
0
  }
97
98
0
  if (strcasecmp(fact, "size") == 0) {
99
0
    return FACTS_OPT_SHOW_SIZE;
100
0
  }
101
102
0
  if (strcasecmp(fact, "type") == 0) {
103
0
    return FACTS_OPT_SHOW_TYPE;
104
0
  }
105
106
0
  if (strcasecmp(fact, "unique") == 0) {
107
0
    return FACTS_OPT_SHOW_UNIQUE;
108
0
  }
109
110
0
  if (strcasecmp(fact, "UNIX.group") == 0) {
111
0
    return FACTS_OPT_SHOW_UNIX_GROUP;
112
0
  }
113
114
0
  if (strcasecmp(fact, "UNIX.groupname") == 0) {
115
0
    return FACTS_OPT_SHOW_UNIX_GROUP_NAME;
116
0
  }
117
118
0
  if (strcasecmp(fact, "UNIX.mode") == 0) {
119
0
    return FACTS_OPT_SHOW_UNIX_MODE;
120
0
  }
121
122
0
  if (strcasecmp(fact, "UNIX.owner") == 0) {
123
0
    return FACTS_OPT_SHOW_UNIX_OWNER;
124
0
  }
125
126
0
  if (strcasecmp(fact, "UNIX.ownername") == 0) {
127
0
    return FACTS_OPT_SHOW_UNIX_OWNER_NAME;
128
0
  }
129
130
0
  if (strcasecmp(fact, "media-type") == 0) {
131
0
    return FACTS_OPT_SHOW_MEDIA_TYPE;
132
0
  }
133
134
0
  return -1;
135
0
}
136
137
0
static int facts_filters_allow_path(cmd_rec *cmd, const char *path) {
138
0
#ifdef PR_USE_REGEX
139
0
  pr_regex_t *pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE);
140
0
  if (pre != NULL &&
141
0
      pr_regexp_exec(pre, path, 0, NULL, 0, 0, 0) != 0) {
142
0
    pr_log_pri(PR_LOG_NOTICE, MOD_FACTS_VERSION
143
0
      ": %s denied by PathAllowFilter on '%s'", (char *) cmd->argv[0],
144
0
      cmd->arg);
145
0
    return -1;
146
0
  }
147
148
0
  pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE);
149
0
  if (pre != NULL &&
150
0
      pr_regexp_exec(pre, path, 0, NULL, 0, 0, 0) == 0) {
151
0
    pr_log_pri(PR_LOG_NOTICE, MOD_FACTS_VERSION
152
0
      ": %s denied by PathDenyFilter on '%s'", (char *) cmd->argv[0], cmd->arg);
153
0
    return -1;
154
0
  }
155
0
#endif
156
157
0
  return 0;
158
0
}
159
160
0
#define FACTS_SECS_PER_MIN  (60)
161
0
#define FACTS_SECS_PER_HOUR (60 * FACTS_SECS_PER_MIN)
162
0
#define FACTS_SECS_PER_DAY  (24 * FACTS_SECS_PER_HOUR)
163
0
#define FACTS_EPOCH_YEAR  1970
164
165
/* How many days come before each month (0-12).  */
166
static const unsigned short int facts_ydays_for_mon[2][13] = {
167
  /* Normal years.  */
168
  { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
169
170
  /* Leap years.  */
171
  { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
172
};
173
174
0
static unsigned long facts_secs_per_min(unsigned int min) {
175
0
  unsigned long nsecs;
176
177
0
  nsecs = (min * FACTS_SECS_PER_MIN);
178
0
  return nsecs;
179
0
}
180
181
0
static unsigned long facts_secs_per_hour(unsigned int hour) {
182
0
  unsigned long nsecs;
183
184
0
  nsecs = (hour * FACTS_SECS_PER_HOUR);
185
0
  return nsecs;
186
0
}
187
188
0
static unsigned long facts_secs_per_day(unsigned long ndays) {
189
0
  unsigned long nsecs;
190
191
0
  nsecs = (ndays * FACTS_SECS_PER_DAY);
192
0
  return nsecs;
193
0
}
194
195
/* Every 4th year is a leap year, except for every 100th year, but including
196
 * every 400th year.
197
 */
198
0
static int facts_leap_year(unsigned int year) {
199
0
  int leap_year = 0;
200
201
0
  if ((year % 4) == 0) {
202
0
    leap_year = 1;
203
204
0
    if ((year % 100) == 0) {
205
0
      leap_year = 0;
206
207
0
      if ((year % 400) == 0) {
208
0
        leap_year = 1;
209
0
      }
210
0
    }
211
0
  }
212
213
0
  return leap_year;
214
0
}
215
216
0
static unsigned long facts_secs_per_mon(unsigned int mon, unsigned int year) {
217
0
  int leap_year;
218
0
  static unsigned int ndays;
219
0
  static unsigned long nsecs;
220
221
0
  leap_year = facts_leap_year(year);
222
0
  ndays = facts_ydays_for_mon[leap_year][mon-1];
223
224
0
  nsecs = facts_secs_per_day(ndays);
225
0
  return nsecs;
226
0
}
227
228
0
static unsigned long facts_secs_per_year(unsigned int year) {
229
0
  unsigned long ndays, nsecs;
230
231
0
  ndays = (year - FACTS_EPOCH_YEAR) * 365;
232
233
  /* Compute the number of leap days between 1970 and the given year
234
   * (exclusive).  There is a leap day every 4th year...
235
   */
236
0
  ndays += (((year - 1) / 4) - (FACTS_EPOCH_YEAR / 4));
237
238
  /* ...except every 100th year...*/
239
0
  ndays -= (((year - 1) / 100) - (FACTS_EPOCH_YEAR / 100));
240
241
  /* ...but still every 400th year. */
242
0
  ndays += (((year - 1) / 400) - (FACTS_EPOCH_YEAR / 400));
243
244
0
  nsecs = facts_secs_per_day(ndays);
245
0
  return nsecs;
246
0
}
247
248
static time_t facts_mktime(unsigned int year, unsigned int month,
249
0
    unsigned int mday, unsigned int hour, unsigned int min, unsigned int sec) {
250
0
  time_t res;
251
252
  /* Rather than using the system mktime(3) function (which requires external
253
   * files such as /etc/localtime and the timezone definition files, depending
254
   * on the TZ environment value setting), we use a custom mktime collection
255
   * of functions.
256
   *
257
   * Fortunately, our homegrown collection of time conversion functions
258
   * ONLY needs to generate GMT seconds here, and so we don't have to worry
259
   * about DST, timezones, etc (Bug#3790).
260
   */
261
262
0
  res = facts_secs_per_year(year) +
263
0
        facts_secs_per_mon(month, year) +
264
265
        /* Subtract one day to make the mday zero-based. */
266
0
        facts_secs_per_day(mday - 1) +
267
268
0
        facts_secs_per_hour(hour) +
269
0
        facts_secs_per_min(min) +
270
0
        sec;
271
272
0
  return res;
273
0
}
274
275
0
static const char *facts_mime_type(struct mlinfo *info) {
276
0
  cmdtable *cmdtab;
277
0
  cmd_rec *cmd;
278
0
  modret_t *res;
279
280
0
  cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "mime_type", NULL, NULL, NULL);
281
0
  if (cmdtab == NULL) {
282
0
    errno = EPERM;
283
0
    return NULL;
284
0
  }
285
286
0
  cmd = pr_cmd_alloc(info->pool, 1, info->real_path);
287
0
  res = pr_module_call(cmdtab->m, cmdtab->handler, cmd);
288
0
  if (MODRET_ISHANDLED(res) &&
289
0
      MODRET_HASDATA(res)) {
290
0
    return res->data;
291
0
  }
292
293
0
  errno = ENOENT;
294
0
  return NULL;
295
0
}
296
297
static size_t facts_mlinfo_fmt(struct mlinfo *info, char *buf, size_t bufsz,
298
0
    int flags) {
299
0
  int len;
300
0
  char *ptr;
301
0
  size_t buflen = 0;
302
303
0
  memset(buf, '\0', bufsz);
304
305
0
  ptr = buf;
306
307
0
  if (facts_opts & FACTS_OPT_SHOW_MODIFY) {
308
0
    if (info->tm != NULL) {
309
0
      len = pr_snprintf(ptr, bufsz, "modify=%04d%02d%02d%02d%02d%02d;",
310
0
        info->tm->tm_year+1900, info->tm->tm_mon+1, info->tm->tm_mday,
311
0
        info->tm->tm_hour, info->tm->tm_min, info->tm->tm_sec);
312
313
0
    } else {
314
0
      len = 0;
315
0
    }
316
317
0
    buflen += len;
318
0
    ptr = buf + buflen;
319
0
  }
320
321
0
  if (facts_opts & FACTS_OPT_SHOW_PERM) {
322
0
    len = pr_snprintf(ptr, bufsz - buflen, "perm=%s;", info->perm);
323
0
    buflen += len;
324
0
    ptr = buf + buflen;
325
0
  }
326
327
0
  if (!S_ISDIR(info->st.st_mode) &&
328
0
      (facts_opts & FACTS_OPT_SHOW_SIZE)) {
329
0
    len = pr_snprintf(ptr, bufsz - buflen, "size=%" PR_LU ";",
330
0
      (pr_off_t) info->st.st_size);
331
0
    buflen += len;
332
0
    ptr = buf + buflen;
333
0
  }
334
335
0
  if (facts_opts & FACTS_OPT_SHOW_TYPE) {
336
0
    len = pr_snprintf(ptr, bufsz - buflen, "type=%s;", info->type);
337
0
    buflen += len;
338
0
    ptr = buf + buflen;
339
0
  }
340
341
0
  if (facts_opts & FACTS_OPT_SHOW_UNIQUE) {
342
0
    len = pr_snprintf(ptr, bufsz - buflen, "unique=%lXU%lX;",
343
0
      (unsigned long) info->st.st_dev, (unsigned long) info->st.st_ino);
344
0
    buflen += len;
345
0
    ptr = buf + buflen;
346
0
  }
347
348
0
  if (facts_opts & FACTS_OPT_SHOW_UNIX_GROUP) {
349
0
    len = pr_snprintf(ptr, bufsz - buflen, "UNIX.group=%s;",
350
0
      pr_gid2str(NULL, info->st.st_gid));
351
0
    buflen += len;
352
0
    ptr = buf + buflen;
353
0
  }
354
355
0
  if (!(facts_mlinfo_opts & FACTS_MLINFO_FL_NO_NAMES)) {
356
0
    if (facts_opts & FACTS_OPT_SHOW_UNIX_GROUP_NAME) {
357
0
      const char *group;
358
359
      /* In order to be compliant with RFC 3659, Section 7.4, we must ensure
360
       * that the group name NOT contain the space character.
361
       */
362
0
      group = sreplace(info->pool, info->group, " ", "_", NULL);
363
364
0
      len = pr_snprintf(ptr, bufsz - buflen, "UNIX.groupname=%s;", group);
365
0
      buflen += len;
366
0
      ptr = buf + buflen;
367
0
    }
368
0
  }
369
370
0
  if (facts_opts & FACTS_OPT_SHOW_UNIX_MODE) {
371
0
    len = pr_snprintf(ptr, bufsz - buflen, "UNIX.mode=0%o;",
372
0
      (unsigned int) info->st.st_mode & 07777);
373
0
    buflen += len;
374
0
    ptr = buf + buflen;
375
0
  }
376
377
0
  if (facts_opts & FACTS_OPT_SHOW_UNIX_OWNER) {
378
0
    len = pr_snprintf(ptr, bufsz - buflen, "UNIX.owner=%s;",
379
0
      pr_uid2str(NULL, info->st.st_uid));
380
0
    buflen += len;
381
0
    ptr = buf + buflen;
382
0
  }
383
384
0
  if (!(facts_mlinfo_opts & FACTS_MLINFO_FL_NO_NAMES)) {
385
0
    if (facts_opts & FACTS_OPT_SHOW_UNIX_OWNER_NAME) {
386
0
      const char *user;
387
388
      /* In order to be compliant with RFC 3659, Section 7.4, we must ensure
389
       * that the user name NOT contain the space character.
390
       */
391
0
      user = sreplace(info->pool, info->user, " ", "_", NULL);
392
393
0
      len = pr_snprintf(ptr, bufsz - buflen, "UNIX.ownername=%s;", user);
394
0
      buflen += len;
395
0
      ptr = buf + buflen;
396
0
    }
397
0
  }
398
399
0
  if (facts_opts & FACTS_OPT_SHOW_MEDIA_TYPE) {
400
0
    const char *mime_type;
401
402
0
    mime_type = facts_mime_type(info);
403
0
    if (mime_type != NULL) {
404
0
      len = pr_snprintf(ptr, bufsz - buflen, "media-type=%s;",
405
0
        mime_type);
406
0
      buflen += len;
407
0
      ptr = buf + buflen;
408
0
    }
409
0
  }
410
411
0
  if (flags & FACTS_MLINFO_FL_APPEND_CRLF) {
412
0
    len = pr_snprintf(ptr, bufsz - buflen, " %s\r\n", info->path);
413
414
0
  } else {
415
0
    len = pr_snprintf(ptr, bufsz - buflen, " %s", info->path);
416
0
  }
417
418
0
  buf[bufsz-1] = '\0';
419
0
  buflen += len;
420
421
0
  return buflen;
422
0
}
423
424
/* This buffer is used by the MLSD handler, to buffer up the output lines.
425
 * When all the lines have been added, or when the buffer is full, it will
426
 * flushed out.
427
 *
428
 * This handling is different from the MLST handler's use of
429
 * facts_mlinfo_add() because MLST gets to send its line back on the control
430
 * channel, whereas MLSD's output is sent via a data transfer, much like
431
 * LIST or NLST.
432
 */
433
static pool *mlinfo_pool = NULL;
434
static char *mlinfo_buf = NULL, *mlinfo_bufptr = NULL;
435
static size_t mlinfo_bufsz = 0;
436
static size_t mlinfo_buflen = 0;
437
438
0
static void facts_mlinfobuf_init(void) {
439
0
  if (mlinfo_buf == NULL) {
440
0
    mlinfo_bufsz = pr_config_get_server_xfer_bufsz(PR_NETIO_IO_WR);
441
442
0
    if (mlinfo_pool != NULL) {
443
0
      destroy_pool(mlinfo_pool);
444
0
    }
445
446
0
    mlinfo_pool = make_sub_pool(session.pool);
447
0
    pr_pool_tag(mlinfo_pool, "Facts MLSD Buffer Pool");
448
449
0
    mlinfo_buf = palloc(mlinfo_pool, mlinfo_bufsz);
450
0
    pr_trace_msg("data", 8, "allocated facts buffer of %lu bytes",
451
0
      (unsigned long) mlinfo_bufsz);
452
0
  }
453
454
0
  memset(mlinfo_buf, '\0', mlinfo_bufsz);
455
0
  mlinfo_bufptr = mlinfo_buf;
456
0
  mlinfo_buflen = 0;
457
0
}
458
459
0
static int facts_mlinfobuf_add(struct mlinfo *info, int flags) {
460
0
  char buf[PR_TUNABLE_BUFFER_SIZE];
461
0
  size_t buflen;
462
463
0
  buflen = facts_mlinfo_fmt(info, buf, sizeof(buf), flags);
464
465
  /* If this buffer will exceed the capacity of mlinfo_buf, then flush
466
   * mlinfo_buf.
467
   */
468
0
  if (buflen >= (mlinfo_bufsz - mlinfo_buflen)) {
469
470
    /* This can fail, if the client closes its end abruptly. */
471
0
    if (facts_mlinfobuf_flush() < 0) {
472
0
      return -1;
473
0
    }
474
0
  }
475
476
0
  sstrcat(mlinfo_bufptr, buf, mlinfo_bufsz - mlinfo_buflen);
477
0
  mlinfo_bufptr += buflen;
478
0
  mlinfo_buflen += buflen;
479
480
0
  return 0;
481
0
}
482
483
0
static int facts_mlinfobuf_flush(void) {
484
0
  int res = 0, xerrno = 0;
485
486
0
  if (mlinfo_buflen > 0) {
487
    /* Make sure the ASCII flags are cleared from the session flags,
488
     * so that the pr_data_xfer() function does not try to perform
489
     * ASCII translation on this data.
490
     */
491
0
    session.sf_flags &= ~SF_ASCII_OVERRIDE;
492
493
0
    res = pr_data_xfer(mlinfo_buf, mlinfo_buflen);
494
0
    xerrno = errno;
495
496
0
    if (res < 0 &&
497
0
        xerrno != 0) {
498
0
      pr_log_debug(DEBUG3, MOD_FACTS_VERSION
499
0
        ": error transferring data: [%d] %s", errno, strerror(xerrno));
500
0
    }
501
502
0
    session.sf_flags |= SF_ASCII_OVERRIDE;
503
0
  }
504
505
0
  facts_mlinfobuf_init();
506
507
0
  errno = xerrno;
508
0
  return res;
509
0
}
510
511
static int facts_mlinfo_get(struct mlinfo *info, const char *path,
512
    const char *dent_name, int flags, const char *user, uid_t uid,
513
0
    const char *group, gid_t gid, const mode_t *mode) {
514
0
  char *perm = "";
515
0
  int res;
516
517
0
  pr_fs_clear_cache2(path);
518
0
  res = pr_fsio_lstat(path, &(info->st));
519
0
  if (res < 0) {
520
0
    int xerrno = errno;
521
522
0
    pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": error lstat'ing '%s': %s",
523
0
      path, strerror(xerrno));
524
525
0
    errno = xerrno;
526
0
    return -1;
527
0
  }
528
529
0
  if (user != NULL) {
530
0
    info->user = pstrdup(info->pool, user);
531
532
0
  } else {
533
0
    info->user = session.user;
534
0
  }
535
536
0
  if (uid != (uid_t) -1) {
537
0
    info->st.st_uid = uid;
538
0
  }
539
540
0
  if (group != NULL) {
541
0
    info->group = pstrdup(info->pool, group);
542
543
0
  } else {
544
0
    info->group = session.group;
545
0
  }
546
547
0
  if (gid != (gid_t) -1) {
548
0
    info->st.st_gid = gid;
549
0
  }
550
551
0
  info->tm = pr_gmtime(info->pool, &(info->st.st_mtime));
552
553
0
  if (!S_ISDIR(info->st.st_mode)) {
554
0
#ifdef S_ISLNK
555
0
    if (S_ISLNK(info->st.st_mode)) {
556
0
      struct stat target_st;
557
0
      const char *dst_path;
558
0
      int len = 0;
559
560
      /* Now we need to use stat(2) on the path (versus lstat(2)) to get the
561
       * info for the target, and copy its st_dev and st_ino values to our
562
       * stat in order to ensure that the unique fact values are the same.
563
       *
564
       * If we are chrooted, however, then the stat(2) on the symlink will
565
       * almost certainly fail, especially if the destination path is an
566
       * absolute path.
567
       */
568
569
0
      if (flags & FACTS_MLINFO_FL_ADJUSTED_SYMLINKS) {
570
0
        char *link_path;
571
0
        size_t link_pathsz;
572
573
0
        link_pathsz = PR_TUNABLE_PATH_MAX;
574
0
        link_path = pcalloc(info->pool, link_pathsz);
575
0
        len = dir_readlink(info->pool, path, link_path, link_pathsz-1,
576
0
          PR_DIR_READLINK_FL_HANDLE_REL_PATH);
577
0
        if (len > 0 &&
578
0
            (size_t) len < link_pathsz) {
579
0
          char *best_path;
580
581
0
          best_path = dir_best_path(info->pool, link_path);
582
0
          if (best_path != NULL) {
583
0
            dst_path = best_path;
584
585
0
          } else {
586
0
            dst_path = link_path;
587
0
          }
588
589
0
        } else {
590
0
          dst_path = path;
591
0
        }
592
593
0
        pr_fs_clear_cache2(dst_path);
594
0
        res = pr_fsio_stat(dst_path, &target_st);
595
596
0
      } else {
597
0
        dst_path = path;
598
0
        res = pr_fsio_stat(dst_path, &target_st);
599
0
      }
600
601
0
      if (res < 0) {
602
0
        int xerrno = errno;
603
604
0
        pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": error stat'ing '%s': %s",
605
0
          dst_path, strerror(xerrno));
606
607
0
        errno = xerrno;
608
0
        return -1;
609
0
      }
610
611
0
      info->st.st_dev = target_st.st_dev;
612
0
      info->st.st_ino = target_st.st_ino;
613
614
0
      if (flags & FACTS_MLINFO_FL_SHOW_SYMLINKS) {
615
616
        /* Do we use the proper RFC 3659 syntax (i.e. following the BNF rules
617
         * of RFC 3659), which would be:
618
         *
619
         *   type=OS.unix=symlink
620
         *
621
         * See:
622
         *   http://www.rfc-editor.org/errata_search.php?rfc=3659
623
         *
624
         * and search for "OS.unix=slink".
625
         *
626
         * Or do we use the syntax in the _examples_ presented in RFC 3659,
627
         * which is what clients such as FileZilla expect:
628
         *
629
         *   type=OS.unix=slink:<target>
630
         *
631
         * See:
632
         *   http://trac.filezilla-project.org/ticket/4490
633
         */
634
635
0
        if (flags & FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK) {
636
0
          char target[PR_TUNABLE_PATH_MAX+1];
637
0
          int targetlen;
638
639
0
          if (flags & FACTS_MLINFO_FL_ADJUSTED_SYMLINKS) {
640
0
            sstrncpy(target, dst_path, sizeof(target)-1);
641
0
            targetlen = len;
642
643
0
          } else {
644
0
            targetlen = pr_fsio_readlink(path, target, sizeof(target)-1);
645
0
          }
646
647
0
          if (targetlen < 0) {
648
0
            int xerrno = errno;
649
650
0
            pr_log_debug(DEBUG4, MOD_FACTS_VERSION
651
0
              ": error reading symlink '%s': %s", path, strerror(xerrno));
652
653
0
            errno = xerrno;
654
0
            return -1;
655
0
          }
656
657
0
          if ((size_t) targetlen >= sizeof(target)-1) {
658
0
            targetlen = sizeof(target)-1;
659
0
          }
660
661
0
          target[targetlen] = '\0';
662
663
0
          info->type = pstrcat(info->pool, "OS.unix=slink:",
664
0
            dir_best_path(info->pool, target), NULL);
665
666
0
        } else {
667
          /* Use the proper syntax.  Too bad for the not-really-compliant
668
           * FileZilla.
669
           */
670
0
          info->type = "OS.unix=symlink";
671
0
        }
672
673
0
      } else {
674
0
        if (S_ISDIR(target_st.st_mode)) {
675
0
          info->type = "dir";
676
677
0
        } else {
678
0
          info->type = "file";
679
0
        }
680
0
      }
681
682
0
    } else {
683
0
      info->type = "file";
684
0
    }
685
#else
686
    info->type = "file";
687
#endif
688
689
0
    if (pr_fsio_access(path, R_OK, session.uid, session.gid,
690
0
        session.gids) == 0) {
691
692
      /* XXX Need to come up with a good way of determining whether 'd'
693
       * should be listed.  For example, if the parent directory does not
694
       * allow write privs to the current user/group, then the file cannot
695
       * be deleted.
696
       */
697
698
0
      perm = pstrcat(info->pool, perm, "adfr", NULL);
699
700
0
    } else {
701
0
      perm = pstrcat(info->pool, perm, "dfr", NULL);
702
0
    }
703
704
0
    if (pr_fsio_access(path, W_OK, session.uid, session.gid,
705
0
        session.gids) == 0) {
706
0
      perm = pstrcat(info->pool, perm, "w", NULL);
707
0
    }
708
709
0
  } else {
710
0
    info->type = "dir";
711
712
0
    if (!(flags & FACTS_MLINFO_FL_NO_CDIR)) {
713
0
      if (dent_name[0] == '.') {
714
0
        if (dent_name[1] == '\0') {
715
0
          info->type = "cdir";
716
0
        }
717
718
0
        if (strlen(dent_name) >= 2) {
719
0
          if (dent_name[1] == '.' &&
720
0
              dent_name[2] == '\0') {
721
0
            info->type = "pdir";
722
0
          }
723
0
        }
724
0
      }
725
0
    }
726
727
0
    if (pr_fsio_access(path, R_OK, session.uid, session.gid,
728
0
        session.gids) == 0) {
729
0
      perm = pstrcat(info->pool, perm, "fl", NULL);
730
0
    }
731
732
0
    if (pr_fsio_access(path, W_OK, session.uid, session.gid,
733
0
        session.gids) == 0) {
734
0
      perm = pstrcat(info->pool, perm, "cdmp", NULL);
735
0
    }
736
737
0
    if (pr_fsio_access(path, X_OK, session.uid, session.gid,
738
0
        session.gids) == 0) {
739
0
      perm = pstrcat(info->pool, perm, "e", NULL);
740
0
    }
741
0
  }
742
743
0
  info->perm = perm;
744
745
0
  if (mode != NULL) {
746
    /* We cheat here by simply overwriting the entire st.st_mode value with
747
     * the DirFakeMode.  This works because later operations on this data
748
     * don't pay attention to the file type.
749
     */
750
0
    info->st.st_mode = *mode;
751
0
  }
752
753
0
  info->real_path = pstrdup(info->pool, path);
754
0
  return 0;
755
0
}
756
757
0
static void facts_mlinfo_add(struct mlinfo *info, int flags) {
758
0
  char buf[PR_TUNABLE_BUFFER_SIZE];
759
760
0
  (void) facts_mlinfo_fmt(info, buf, sizeof(buf), flags);
761
762
  /* The trailing CRLF will be added by pr_response_add(). */
763
0
  pr_response_add(R_DUP, "%s", buf);
764
0
}
765
766
0
static void facts_mlst_feat_add(pool *p) {
767
0
  char *feat_str = "";
768
769
0
  feat_str = pstrcat(p, feat_str, "modify", NULL);
770
0
  if (facts_opts & FACTS_OPT_SHOW_MODIFY) {
771
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
772
773
0
  } else {
774
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
775
0
  }
776
777
0
  feat_str = pstrcat(p, feat_str, "perm", NULL);
778
0
  if (facts_opts & FACTS_OPT_SHOW_PERM) {
779
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
780
781
0
  } else {
782
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
783
0
  }
784
785
0
  feat_str = pstrcat(p, feat_str, "size", NULL);
786
0
  if (facts_opts & FACTS_OPT_SHOW_SIZE) {
787
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
788
789
0
  } else {
790
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
791
0
  }
792
793
0
  feat_str = pstrcat(p, feat_str, "type", NULL);
794
0
  if (facts_opts & FACTS_OPT_SHOW_TYPE) {
795
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
796
797
0
  } else {
798
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
799
0
  }
800
801
0
  feat_str = pstrcat(p, feat_str, "unique", NULL);
802
0
  if (facts_opts & FACTS_OPT_SHOW_UNIQUE) {
803
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
804
805
0
  } else {
806
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
807
0
  }
808
809
0
  feat_str = pstrcat(p, feat_str, "UNIX.group", NULL);
810
0
  if (facts_opts & FACTS_OPT_SHOW_UNIX_GROUP) {
811
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
812
813
0
  } else {
814
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
815
0
  }
816
817
0
  if (!(facts_mlinfo_opts & FACTS_MLINFO_FL_NO_NAMES)) {
818
0
    feat_str = pstrcat(p, feat_str, "UNIX.groupname", NULL);
819
0
    if (facts_opts & FACTS_OPT_SHOW_UNIX_GROUP_NAME) {
820
0
      feat_str = pstrcat(p, feat_str, "*;", NULL);
821
822
0
    } else {
823
0
      feat_str = pstrcat(p, feat_str, ";", NULL);
824
0
    }
825
0
  }
826
827
0
  feat_str = pstrcat(p, feat_str, "UNIX.mode", NULL);
828
0
  if (facts_opts & FACTS_OPT_SHOW_UNIX_MODE) {
829
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
830
831
0
  } else {
832
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
833
0
  }
834
835
0
  feat_str = pstrcat(p, feat_str, "UNIX.owner", NULL);
836
0
  if (facts_opts & FACTS_OPT_SHOW_UNIX_OWNER) {
837
0
    feat_str = pstrcat(p, feat_str, "*;", NULL);
838
839
0
  } else {
840
0
    feat_str = pstrcat(p, feat_str, ";", NULL);
841
0
  }
842
843
0
  if (!(facts_mlinfo_opts & FACTS_MLINFO_FL_NO_NAMES)) {
844
0
    feat_str = pstrcat(p, feat_str, "UNIX.ownername", NULL);
845
0
    if (facts_opts & FACTS_OPT_SHOW_UNIX_OWNER_NAME) {
846
0
      feat_str = pstrcat(p, feat_str, "*;", NULL);
847
848
0
    } else {
849
0
      feat_str = pstrcat(p, feat_str, ";", NULL);
850
0
    }
851
0
  }
852
853
  /* Note: we only show the 'media-type' fact IFF mod_mime is present AND
854
   * is enabled via MIMEEngine.
855
   */
856
0
  if (pr_module_exists("mod_mime.c") == TRUE &&
857
0
      (facts_opts & FACTS_OPT_SHOW_MEDIA_TYPE)) {
858
0
    feat_str = pstrcat(p, feat_str, "media-type*;", NULL);
859
0
  }
860
861
0
  feat_str = pstrcat(p, "MLST ", feat_str, NULL);
862
0
  pr_feat_add(feat_str);
863
0
}
864
865
0
static void facts_mlst_feat_remove(void) {
866
0
  const char *feat, *mlst_feat = NULL;
867
868
0
  feat = pr_feat_get();
869
0
  while (feat) {
870
0
    pr_signals_handle();
871
872
0
    if (strncmp(feat, C_MLST, 4) == 0) {
873
0
      mlst_feat = feat;
874
0
      break;
875
0
    }
876
877
0
    feat = pr_feat_get_next();
878
0
  }
879
880
0
  if (mlst_feat != NULL) {
881
0
    pr_feat_remove(mlst_feat);
882
0
  }
883
0
}
884
885
0
static int facts_modify_mtime(pool *p, const char *path, char *timestamp) {
886
0
  char c, *ptr;
887
0
  int year, month, day, hour, min, sec;
888
0
  struct timeval tvs[2];
889
0
  int res;
890
891
0
  (void) p;
892
893
0
  ptr = timestamp;
894
0
  c = timestamp[4];
895
0
  timestamp[4] = '\0';
896
0
  year = atoi(ptr);
897
0
  timestamp[4] = c;
898
899
0
  if (year < FACTS_EPOCH_YEAR) {
900
0
    pr_log_debug(DEBUG8, MOD_FACTS_VERSION
901
0
      ": bad year value (%d) in timestamp '%s'", year, timestamp);
902
0
    errno = EINVAL;
903
0
    return -1;
904
0
  }
905
906
0
  ptr = &(timestamp[4]);
907
0
  c = timestamp[6];
908
0
  timestamp[6] = '\0';
909
0
  month = atoi(ptr);
910
0
  timestamp[6] = c;
911
912
0
  if (month < 1 ||
913
0
      month > 12) {
914
0
    pr_log_debug(DEBUG8, MOD_FACTS_VERSION
915
0
      ": bad number of months (%d) in timestamp '%s'", month, timestamp);
916
0
    errno = EINVAL;
917
0
    return -1;
918
0
  }
919
920
0
  ptr = &(timestamp[6]);
921
0
  c = timestamp[8];
922
0
  timestamp[8] = '\0';
923
0
  day = atoi(ptr);
924
0
  timestamp[8] = c;
925
926
0
  if (day < 1 ||
927
0
      day > 31) {
928
0
    pr_log_debug(DEBUG8, MOD_FACTS_VERSION
929
0
      ": bad number of days (%d) in timestamp '%s'", day, timestamp);
930
0
    errno = EINVAL;
931
0
    return -1;
932
0
  }
933
934
0
  ptr = &(timestamp[8]);
935
0
  c = timestamp[10];
936
0
  timestamp[10] = '\0';
937
0
  hour = atoi(ptr);
938
0
  timestamp[10] = c;
939
940
0
  if (hour < 0 ||
941
0
      hour > 24) {
942
0
    pr_log_debug(DEBUG8, MOD_FACTS_VERSION
943
0
      ": bad number of hours (%d) in timestamp '%s'", hour, timestamp);
944
0
    errno = EINVAL;
945
0
    return -1;
946
0
  }
947
948
0
  ptr = &(timestamp[10]);
949
0
  c = timestamp[12];
950
0
  timestamp[12] = '\0';
951
0
  min = atoi(ptr);
952
0
  timestamp[12] = c;
953
954
0
  if (min < 0 ||
955
0
      min > 60) {
956
0
    pr_log_debug(DEBUG8, MOD_FACTS_VERSION
957
0
      ": bad number of minutes (%d) in timestamp '%s'", min, timestamp);
958
0
    errno = EINVAL;
959
0
    return -1;
960
0
  }
961
962
0
  ptr = &(timestamp[12]);
963
0
  sec = atoi(ptr);
964
965
0
  if (sec < 0 ||
966
0
      sec > 61) {
967
0
    pr_log_debug(DEBUG8, MOD_FACTS_VERSION
968
0
      ": bad number of seconds (%d) in timestamp '%s'", sec, timestamp);
969
0
    errno = EINVAL;
970
0
    return -1;
971
0
  }
972
973
0
  tvs[0].tv_usec = tvs[1].tv_usec = 0;
974
0
  tvs[0].tv_sec = tvs[1].tv_sec = facts_mktime(year, month, day, hour, min,
975
0
    sec);
976
977
0
  res = pr_fsio_utimes_with_root(path, tvs);
978
0
  if (res < 0) {
979
0
    int xerrno = errno;
980
981
0
    pr_log_debug(DEBUG2, MOD_FACTS_VERSION
982
0
      ": error modifying fact for '%s': %s", path, strerror(xerrno));
983
0
    errno = xerrno;
984
0
    return -1;
985
0
  }
986
987
0
  return 0;
988
0
}
989
990
static int facts_modify_unix_group(pool *p, const char *path,
991
0
    const char *group) {
992
0
  int res, xerrno = 0;
993
0
  gid_t gid;
994
0
  char *ptr = NULL;
995
0
  pr_error_t *err = NULL;
996
997
0
  gid = strtoul(group, &ptr, 10);
998
0
  if (ptr &&
999
0
      *ptr) {
1000
    /* Try to lookup the GID using the value as a name. */
1001
0
    gid = pr_auth_name2gid(p, group);
1002
0
    if (gid == (gid_t) -1) {
1003
0
      pr_log_debug(DEBUG7, MOD_FACTS_VERSION ": no such group '%s'", group);
1004
0
      errno = EINVAL;
1005
0
      return -1;
1006
0
    }
1007
0
  }
1008
1009
0
  res = pr_fsio_chown_with_error(p, path, (uid_t) -1, gid, &err);
1010
0
  xerrno = errno;
1011
1012
0
  if (res < 0) {
1013
0
    pr_error_set_where(err, &facts_module, __FILE__, __LINE__ - 4);
1014
0
    pr_error_set_why(err, pstrcat(p, "modify UNIX.group fact for '", path,
1015
0
      "'", NULL));
1016
1017
0
    if (err != NULL) {
1018
0
      pr_log_debug(DEBUG5, MOD_FACTS_VERSION ": %s", pr_error_strerror(err, 0));
1019
0
      pr_error_destroy(err);
1020
0
      err = NULL;
1021
1022
0
    } else {
1023
0
      pr_log_debug(DEBUG5, MOD_FACTS_VERSION
1024
0
        ": error modifying UNIX.group fact for '%s': %s", path,
1025
0
        strerror(xerrno));
1026
0
    }
1027
1028
0
    errno = xerrno;
1029
0
    return -1;
1030
0
  }
1031
1032
0
  return 0;
1033
0
}
1034
1035
0
static int facts_modify_unix_mode(pool *p, const char *path, char *mode_str) {
1036
0
  int res, xerrno = 0;
1037
0
  mode_t mode;
1038
0
  char *ptr = NULL;
1039
0
  pr_error_t *err = NULL;
1040
1041
0
  mode = strtoul(mode_str, &ptr, 8);
1042
0
  if (ptr &&
1043
0
      *ptr) {
1044
0
    pr_log_debug(DEBUG3, MOD_FACTS_VERSION
1045
0
      ": UNIX.mode fact '%s' is not an octal number", mode_str);
1046
0
    errno = EINVAL;
1047
0
    return -1;
1048
0
  }
1049
1050
0
  res = pr_fsio_chmod_with_error(p, path, mode, &err);
1051
0
  xerrno = errno;
1052
1053
0
  if (res < 0) {
1054
0
    pr_error_set_where(err, &facts_module, __FILE__, __LINE__ - 4);
1055
0
    pr_error_set_why(err, pstrcat(p, "modify UNIX.mode fact for '", path, "'",
1056
0
      NULL));
1057
1058
0
    if (err != NULL) {
1059
0
      pr_log_debug(DEBUG5, MOD_FACTS_VERSION ": %s", pr_error_strerror(err, 0));
1060
0
      pr_error_destroy(err);
1061
0
      err = NULL;
1062
1063
0
    } else {
1064
0
      pr_log_debug(DEBUG5, MOD_FACTS_VERSION
1065
0
        ": error modifying UNIX.mode fact for '%s': %s", path,
1066
0
        strerror(xerrno));
1067
0
    }
1068
1069
0
    errno = xerrno;
1070
0
    return -1;
1071
0
  }
1072
1073
0
  return 0;
1074
0
}
1075
1076
/* Command handlers
1077
 */
1078
1079
0
MODRET facts_mff(cmd_rec *cmd) {
1080
0
  const char *path, *canon_path, *decoded_path;
1081
0
  char *facts, *ptr;
1082
1083
0
  if (cmd->argc < 3) {
1084
0
    pr_response_add_err(R_501, _("Invalid number of parameters"));
1085
1086
0
    pr_cmd_set_errno(cmd, EINVAL);
1087
0
    errno = EINVAL;
1088
0
    return PR_ERROR(cmd);
1089
0
  }
1090
1091
0
  facts = cmd->argv[1];
1092
1093
  /* The path can contain spaces.  Thus we need to use cmd->arg, not cmd->argv,
1094
   * to find the path.  But cmd->arg contains the facts as well.  Thus we
1095
   * find the FIRST space in cmd->arg; the path is everything past that space.
1096
   */
1097
0
  ptr = strchr(cmd->arg, ' ');
1098
0
  if (ptr == NULL) {
1099
0
    pr_response_add_err(R_501, _("Invalid command syntax"));
1100
1101
0
    pr_cmd_set_errno(cmd, EINVAL);
1102
0
    errno = EINVAL;
1103
0
    return PR_ERROR(cmd);
1104
0
  }
1105
1106
0
  path = pstrdup(cmd->tmp_pool, ptr + 1);
1107
1108
0
  decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
1109
0
    FSIO_DECODE_FL_TELL_ERRORS);
1110
0
  if (decoded_path == NULL) {
1111
0
    int xerrno = errno;
1112
1113
0
    pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
1114
0
      strerror(xerrno));
1115
0
    pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
1116
0
      path);
1117
1118
0
    pr_cmd_set_errno(cmd, xerrno);
1119
0
    errno = xerrno;
1120
0
    return PR_ERROR(cmd);
1121
0
  }
1122
1123
0
  canon_path = dir_canonical_path(cmd->tmp_pool, decoded_path);
1124
0
  if (canon_path == NULL) {
1125
0
    int xerrno = EINVAL;
1126
1127
0
    pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1128
1129
0
    pr_cmd_set_errno(cmd, xerrno);
1130
0
    errno = xerrno;
1131
0
    return PR_ERROR(cmd);
1132
0
  }
1133
1134
0
  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, canon_path, NULL)) {
1135
0
    pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
1136
0
      (char *) cmd->argv[0]);
1137
0
    pr_response_add_err(R_550, _("Unable to handle command"));
1138
1139
0
    pr_cmd_set_errno(cmd, EPERM);
1140
0
    errno = EPERM;
1141
0
    return PR_ERROR(cmd);
1142
0
  }
1143
1144
0
  if (facts_filters_allow_path(cmd, decoded_path) < 0) {
1145
0
    int xerrno = EACCES;
1146
1147
0
    pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno));
1148
1149
0
    pr_cmd_set_errno(cmd, xerrno);
1150
0
    errno = xerrno;
1151
0
    return PR_ERROR(cmd);
1152
0
  }
1153
1154
0
  ptr = strchr(facts, ';');
1155
0
  if (ptr == NULL) {
1156
0
    int xerrno = EINVAL;
1157
1158
0
    pr_response_add_err(R_550, "%s: %s", facts, strerror(xerrno));
1159
1160
0
    pr_cmd_set_errno(cmd, xerrno);
1161
0
    errno = xerrno;
1162
0
    return PR_ERROR(cmd);
1163
0
  }
1164
1165
0
  while (ptr) {
1166
0
    pr_signals_handle();
1167
1168
0
    *ptr = '\0';
1169
1170
0
    if (strncasecmp(facts, "modify", 6) == 0) {
1171
      /* Equivalent to SITE UTIME, or MFMT */
1172
1173
0
      char *timestamp, *ptr2;
1174
1175
0
      ptr2 = strchr(facts, '=');
1176
0
      if (ptr2 == NULL) {
1177
0
        int xerrno = EINVAL;
1178
1179
0
        pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[1],
1180
0
          strerror(xerrno));
1181
1182
0
        pr_cmd_set_errno(cmd, xerrno);
1183
0
        errno = xerrno;
1184
0
        return PR_ERROR(cmd);
1185
0
      }
1186
1187
0
      timestamp = ptr2 + 1;
1188
1189
0
      if (strlen(timestamp) < 14) {
1190
0
        int xerrno = EINVAL;
1191
1192
0
        pr_response_add_err(R_501, "%s: %s", timestamp, strerror(xerrno));
1193
1194
0
        pr_cmd_set_errno(cmd, xerrno);
1195
0
        errno = xerrno;
1196
0
        return PR_ERROR(cmd);
1197
0
      }
1198
1199
0
      ptr2 = strchr(timestamp, '.');
1200
0
      if (ptr2) {
1201
0
        pr_log_debug(DEBUG7, MOD_FACTS_VERSION
1202
0
          ": %s: ignoring unsupported timestamp precision in '%s'",
1203
0
          (char *) cmd->argv[0], timestamp);
1204
0
        *ptr2 = '\0';
1205
0
      }
1206
1207
0
      if (facts_modify_mtime(cmd->tmp_pool, decoded_path, timestamp) < 0) {
1208
0
        int xerrno = errno;
1209
1210
0
        pr_response_add_err(xerrno == ENOENT ? R_550 : R_501, "%s: %s", path,
1211
0
          strerror(xerrno));
1212
1213
0
        pr_cmd_set_errno(cmd, xerrno);
1214
0
        errno = xerrno;
1215
0
        return PR_ERROR(cmd);
1216
0
      }
1217
1218
0
    } else if (strncasecmp(facts, "UNIX.group", 10) == 0) {
1219
      /* Equivalent to SITE CHGRP */
1220
1221
0
      char *group, *ptr2;
1222
1223
0
      ptr2 = strchr(facts, '=');
1224
0
      if (ptr2 == NULL) {
1225
0
        int xerrno = EINVAL;
1226
1227
0
        *ptr = ';';
1228
0
        pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[1],
1229
0
          strerror(xerrno));
1230
1231
0
        pr_cmd_set_errno(cmd, xerrno);
1232
0
        errno = xerrno;
1233
0
        return PR_ERROR(cmd);
1234
0
      }
1235
1236
0
      group = ptr2 + 1;
1237
1238
0
      if (facts_modify_unix_group(cmd->tmp_pool, decoded_path, group) < 0) {
1239
0
        int xerrno = errno;
1240
1241
0
        pr_response_add_err(xerrno == ENOENT ? R_550 : R_501, "%s: %s", path,
1242
0
          strerror(xerrno));
1243
1244
0
        pr_cmd_set_errno(cmd, xerrno);
1245
0
        errno = xerrno;
1246
0
        return PR_ERROR(cmd);
1247
0
      }
1248
1249
0
    } else if (strncasecmp(facts, "UNIX.mode", 9) == 0) {
1250
      /* Equivalent to SITE CHMOD */
1251
1252
0
      char *mode_str, *ptr2;
1253
1254
0
      ptr2 = strchr(facts, '=');
1255
0
      if (ptr2 == NULL) {
1256
0
        int xerrno = errno;
1257
1258
0
        *ptr = ';';
1259
0
        pr_response_add_err(R_501, "%s: %s", (char *) cmd->argv[1],
1260
0
          strerror(xerrno));
1261
1262
0
        pr_cmd_set_errno(cmd, xerrno);
1263
0
        errno = xerrno;
1264
0
        return PR_ERROR(cmd);
1265
0
      }
1266
1267
0
      mode_str = ptr2 + 1;
1268
1269
0
      if (facts_modify_unix_mode(cmd->tmp_pool, decoded_path, mode_str) < 0) {
1270
0
        int xerrno = errno;
1271
1272
0
        pr_response_add_err(xerrno == ENOENT ? R_550 : R_501, "%s: %s", path,
1273
0
          strerror(xerrno));
1274
1275
0
        pr_cmd_set_errno(cmd, xerrno);
1276
0
        errno = xerrno;
1277
0
        return PR_ERROR(cmd);
1278
0
      }
1279
1280
0
    } else {
1281
      /* Unlike the OPTS MLST handling, if MFF is sent with an unsupported
1282
       * fact, we get to return an error.
1283
       */
1284
0
      pr_log_debug(DEBUG5, MOD_FACTS_VERSION
1285
0
        ": %s: fact '%s' unsupported for modification, denying request",
1286
0
        (char *) cmd->argv[0], facts);
1287
0
      pr_response_add_err(R_504, _("Cannot modify fact '%s'"), facts);
1288
1289
0
      *ptr = ';';
1290
1291
0
      pr_cmd_set_errno(cmd, EPERM);
1292
0
      errno = EPERM;
1293
0
      return PR_ERROR(cmd);
1294
0
    }
1295
1296
0
    *ptr = ';';
1297
0
    facts = ptr + 1;
1298
0
    ptr = strchr(facts, ';');
1299
0
  }
1300
1301
  /* Due to Draft requirements/recommendations, the list of facts that
1302
   * were successfully modified are to be included in the response, for
1303
   * possible client parsing.  This means that the list is NOT localisable.
1304
   */
1305
0
  pr_response_add(R_213, "%s %s", (char *) cmd->argv[1], path);
1306
0
  return PR_HANDLED(cmd);
1307
0
}
1308
1309
0
MODRET facts_mfmt(cmd_rec *cmd) {
1310
0
  const char *path, *canon_path, *decoded_path;
1311
0
  char *timestamp, *ptr;
1312
0
  int res;
1313
1314
0
  if (cmd->argc < 3) {
1315
0
    pr_response_add_err(R_501, _("Invalid number of parameters"));
1316
1317
0
    pr_cmd_set_errno(cmd, EINVAL);
1318
0
    errno = EINVAL;
1319
0
    return PR_ERROR(cmd);
1320
0
  }
1321
1322
0
  timestamp = cmd->argv[1];
1323
1324
  /* The path can contain spaces.  Thus we need to use cmd->arg, not cmd->argv,
1325
   * to find the path.  But cmd->arg contains the facts as well.  Thus we
1326
   * find the FIRST space in cmd->arg; the path is everything past that space.
1327
   */
1328
0
  ptr = strchr(cmd->arg, ' ');
1329
0
  if (ptr == NULL) {
1330
0
    pr_response_add_err(R_501, _("Invalid command syntax"));
1331
1332
0
    pr_cmd_set_errno(cmd, EINVAL);
1333
0
    errno = EINVAL;
1334
0
    return PR_ERROR(cmd);
1335
0
  }
1336
1337
0
  path = pstrdup(cmd->tmp_pool, ptr + 1);
1338
1339
0
  decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
1340
0
    FSIO_DECODE_FL_TELL_ERRORS);
1341
0
  if (decoded_path == NULL) {
1342
0
    int xerrno = errno;
1343
1344
0
    pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
1345
0
      strerror(xerrno));
1346
0
    pr_response_add_err(R_550, _("%s: Illegal character sequence in filename"),
1347
0
      path);
1348
1349
0
    pr_cmd_set_errno(cmd, xerrno);
1350
0
    errno = xerrno;
1351
0
    return PR_ERROR(cmd);
1352
0
  }
1353
1354
0
  canon_path = dir_canonical_path(cmd->tmp_pool, decoded_path);
1355
0
  if (canon_path == NULL) {
1356
0
    int xerrno = EINVAL;
1357
1358
0
    pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(xerrno));
1359
1360
0
    pr_cmd_set_errno(cmd, xerrno);
1361
0
    errno = xerrno;
1362
0
    return PR_ERROR(cmd);
1363
0
  }
1364
1365
0
  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, canon_path, NULL)) {
1366
0
    pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
1367
0
      (char *) cmd->argv[0]);
1368
0
    pr_response_add_err(R_550, _("Unable to handle command"));
1369
1370
0
    pr_cmd_set_errno(cmd, EPERM);
1371
0
    errno = EPERM;
1372
0
    return PR_ERROR(cmd);
1373
0
  }
1374
1375
0
  if (facts_filters_allow_path(cmd, decoded_path) < 0) {
1376
0
    int xerrno = EACCES;
1377
1378
0
    pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno));
1379
1380
0
    pr_cmd_set_errno(cmd, xerrno);
1381
0
    errno = xerrno;
1382
0
    return PR_ERROR(cmd);
1383
0
  }
1384
1385
0
  if (strlen(timestamp) < 14) {
1386
0
    int xerrno = EINVAL;
1387
1388
0
    pr_response_add_err(R_501, "%s: %s", timestamp, strerror(xerrno));
1389
1390
0
    pr_cmd_set_errno(cmd, xerrno);
1391
0
    errno = xerrno;
1392
0
    return PR_ERROR(cmd);
1393
0
  }
1394
1395
0
  ptr = strchr(timestamp, '.');
1396
0
  if (ptr) {
1397
0
    pr_log_debug(DEBUG7, MOD_FACTS_VERSION
1398
0
      ": %s: ignoring unsupported timestamp precision in '%s'",
1399
0
      (char *) cmd->argv[0], timestamp);
1400
0
    *ptr = '\0';
1401
0
  }
1402
1403
0
  res = facts_modify_mtime(cmd->tmp_pool, decoded_path, timestamp);
1404
0
  if (res < 0) {
1405
0
    int xerrno = errno;
1406
1407
0
    if (ptr) {
1408
0
      *ptr = '.';
1409
0
    }
1410
1411
0
    pr_response_add_err(xerrno == ENOENT ? R_550 : R_501, "%s: %s", path,
1412
0
      strerror(xerrno));
1413
1414
0
    pr_cmd_set_errno(cmd, xerrno);
1415
0
    errno = xerrno;
1416
0
    return PR_ERROR(cmd);
1417
0
  }
1418
1419
  /* We need to capitalize the 'modify' fact name in the response, as
1420
   * per the Draft, so that clients can parse it to see the actual
1421
   * time used by the server; it is possible for the server to ignore some
1422
   * of the precision requested by the client.
1423
   *
1424
   * This same requirement means that the string is NOT localisable.
1425
   */
1426
0
  pr_response_add(R_213, "Modify=%s; %s", timestamp, path);
1427
1428
0
  if (ptr) {
1429
0
    *ptr = '.';
1430
0
  }
1431
1432
0
  return PR_HANDLED(cmd);
1433
0
}
1434
1435
0
MODRET facts_mlsd(cmd_rec *cmd) {
1436
0
  const char *path, *decoded_path, *best_path;
1437
0
  const char *fake_user = NULL, *fake_group = NULL;
1438
0
  config_rec *c;
1439
0
  uid_t fake_uid = -1;
1440
0
  gid_t fake_gid = -1;
1441
0
  const mode_t *fake_mode = NULL;
1442
0
  struct mlinfo info;
1443
0
  unsigned char *ptr;
1444
0
  int flags = 0, res, succeeded = TRUE;
1445
0
  DIR *dirh;
1446
0
  struct dirent *dent;
1447
1448
0
  if (cmd->argc != 1) {
1449
0
    path = pstrdup(cmd->tmp_pool, cmd->arg);
1450
1451
0
    decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
1452
0
      FSIO_DECODE_FL_TELL_ERRORS);
1453
0
    if (decoded_path == NULL) {
1454
0
      int xerrno = errno;
1455
1456
0
      pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
1457
0
        strerror(xerrno));
1458
0
      pr_response_add_err(R_550,
1459
0
        _("%s: Illegal character sequence in filename"), path);
1460
1461
0
      pr_cmd_set_errno(cmd, xerrno);
1462
0
      errno = xerrno;
1463
0
      return PR_ERROR(cmd);
1464
0
    }
1465
1466
0
  } else {
1467
0
    decoded_path = path = pr_fs_getcwd();
1468
0
  }
1469
1470
0
  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, (char *) decoded_path, NULL)) {
1471
0
    pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
1472
0
      (char *) cmd->argv[0]);
1473
0
    pr_response_add_err(R_550, _("Unable to handle command"));
1474
1475
0
    pr_cmd_set_errno(cmd, EPERM);
1476
0
    errno = EPERM;
1477
0
    return PR_ERROR(cmd);
1478
0
  }
1479
1480
  /* RFC3659 explicitly does NOT support glob characters.  So warn about
1481
   * this, but let the command continue as is.  We don't actually call
1482
   * glob(3) here, so no expansion will occur.
1483
   */
1484
0
  if (strpbrk(decoded_path, "{[*?") != NULL) {
1485
0
    pr_log_debug(DEBUG9, MOD_FACTS_VERSION ": glob characters in MLSD ('%s') "
1486
0
      "ignored", decoded_path);
1487
0
  }
1488
1489
  /* Make sure that the given path is actually a directory. */
1490
0
  if (pr_fsio_stat(decoded_path, &(info.st)) < 0) {
1491
0
    int xerrno = errno;
1492
1493
0
    pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": unable to stat '%s' (%s), "
1494
0
      "denying %s", decoded_path, strerror(xerrno), (char *) cmd->argv[0]);
1495
1496
0
    pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno));
1497
1498
0
    pr_cmd_set_errno(cmd, xerrno);
1499
0
    errno = xerrno;
1500
0
    return PR_ERROR(cmd);
1501
0
  }
1502
1503
0
  if (!S_ISDIR(info.st.st_mode)) {
1504
0
    pr_response_add_err(R_550, _("'%s' is not a directory"), path);
1505
1506
0
    pr_cmd_set_errno(cmd, EPERM);
1507
0
    errno = EPERM;
1508
0
    return PR_ERROR(cmd);
1509
0
  }
1510
1511
  /* Determine whether to display symlinks as such. */
1512
0
  ptr = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
1513
0
  if (ptr != NULL) {
1514
0
    if (*ptr == TRUE) {
1515
0
      flags |= FACTS_MLINFO_FL_SHOW_SYMLINKS;
1516
1517
0
      if (facts_mlinfo_opts & FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK) {
1518
0
        flags |= FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK;
1519
0
      }
1520
0
    }
1521
1522
0
  } else {
1523
    /* ShowSymlinks is documented as being 'on' by default. */
1524
0
    flags |= FACTS_MLINFO_FL_SHOW_SYMLINKS;
1525
1526
0
    if (facts_mlinfo_opts & FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK) {
1527
0
      flags |= FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK;
1528
0
    }
1529
0
  }
1530
1531
0
  best_path = dir_best_path(cmd->tmp_pool, decoded_path);
1532
1533
0
  fake_mode = get_param_ptr(get_dir_ctxt(cmd->tmp_pool, (char *) best_path),
1534
0
    "DirFakeMode", FALSE);
1535
1536
0
  c = find_config(get_dir_ctxt(cmd->tmp_pool, (char *) best_path), CONF_PARAM,
1537
0
    "DirFakeUser", FALSE);
1538
0
  if (c != NULL) {
1539
0
    if (c->argc > 0) {
1540
0
      fake_user = c->argv[0];
1541
0
      if (fake_user != NULL &&
1542
0
          strcmp(fake_user, "~") != 0) {
1543
0
        fake_uid = pr_auth_name2uid(cmd->tmp_pool, fake_user);
1544
1545
0
      } else {
1546
0
        fake_uid = session.uid;
1547
0
        fake_user = session.user;
1548
0
      }
1549
1550
0
    } else {
1551
      /* Handle the "DirFakeUser off" case (Bug#3715). */
1552
0
      fake_uid = (uid_t) -1;
1553
0
      fake_user = NULL;
1554
0
    }
1555
0
  }
1556
1557
0
  c = find_config(get_dir_ctxt(cmd->tmp_pool, (char *) best_path), CONF_PARAM,
1558
0
    "DirFakeGroup", FALSE);
1559
0
  if (c != NULL) {
1560
0
    if (c->argc > 0) {
1561
0
      fake_group = c->argv[0];
1562
0
      if (fake_group != NULL &&
1563
0
          strcmp(fake_group, "~") != 0) {
1564
0
        fake_gid = pr_auth_name2gid(cmd->tmp_pool, fake_group);
1565
1566
0
      } else {
1567
0
        fake_gid = session.gid;
1568
0
        fake_group = session.group;
1569
0
      }
1570
1571
0
    } else {
1572
      /* Handle the "DirFakeGroup off" case (Bug#3715). */
1573
0
      fake_gid = (gid_t) -1;
1574
0
      fake_group = NULL;
1575
0
    }
1576
0
  }
1577
1578
0
  dirh = pr_fsio_opendir(best_path);
1579
0
  if (dirh == NULL) {
1580
0
    int xerrno = errno;
1581
1582
0
    pr_trace_msg("fileperms", 1, "MLSD, user '%s' (UID %s, GID %s): "
1583
0
      "error opening directory '%s': %s", session.user,
1584
0
      pr_uid2str(cmd->tmp_pool, session.uid),
1585
0
      pr_gid2str(cmd->tmp_pool, session.gid),
1586
0
      best_path, strerror(xerrno));
1587
1588
0
    pr_response_add_err(R_550, "%s: %s", path, strerror(xerrno));
1589
1590
0
    pr_cmd_set_errno(cmd, xerrno);
1591
0
    errno = xerrno;
1592
0
    return PR_ERROR(cmd);
1593
0
  }
1594
1595
  /* Open data connection */
1596
0
  if (pr_data_open(NULL, C_MLSD, PR_NETIO_IO_WR, 0) < 0) {
1597
0
    int xerrno = errno;
1598
1599
0
    pr_fsio_closedir(dirh);
1600
1601
0
    pr_cmd_set_errno(cmd, xerrno);
1602
0
    errno = xerrno;
1603
0
    return PR_ERROR(cmd);
1604
0
  }
1605
0
  session.sf_flags |= SF_ASCII_OVERRIDE;
1606
1607
0
  facts_mlinfobuf_init();
1608
1609
0
  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
1610
0
    int hidden = FALSE, xerrno;
1611
0
    char *rel_path, *abs_path;
1612
1613
0
    pr_signals_handle();
1614
1615
0
    rel_path = pdircat(cmd->tmp_pool, best_path, dent->d_name, NULL);
1616
0
    res = dir_check(cmd->tmp_pool, cmd, cmd->group, rel_path, &hidden);
1617
0
    if (!res || hidden) {
1618
0
      continue;
1619
0
    }
1620
1621
    /* Check that the file can be listed. */
1622
0
    abs_path = dir_realpath(cmd->tmp_pool, rel_path);
1623
0
    if (abs_path != NULL) {
1624
0
      res = dir_check(cmd->tmp_pool, cmd, cmd->group, abs_path, &hidden);
1625
1626
0
    } else {
1627
0
      abs_path = dir_canonical_path(cmd->tmp_pool, rel_path);
1628
0
      if (abs_path == NULL) {
1629
0
        abs_path = rel_path;
1630
0
      }
1631
1632
0
      res = dir_check_canon(cmd->tmp_pool, cmd, cmd->group, abs_path, &hidden);
1633
0
    }
1634
1635
0
    if (!res || hidden) {
1636
0
      continue;
1637
0
    }
1638
1639
0
    memset(&info, 0, sizeof(struct mlinfo));
1640
1641
0
    info.pool = make_sub_pool(cmd->tmp_pool);
1642
0
    pr_pool_tag(info.pool, "MLSD facts pool");
1643
0
    if (facts_mlinfo_get(&info, rel_path, dent->d_name, flags,
1644
0
        fake_user, fake_uid, fake_group, fake_gid, fake_mode) < 0) {
1645
0
      continue;
1646
0
    }
1647
1648
    /* As per RFC3659, the directory being listed should not appear as a
1649
     * component in the paths of the directory contents.
1650
     */
1651
0
    info.path = pr_fs_encode_path(info.pool, dent->d_name);
1652
1653
0
    res = facts_mlinfobuf_add(&info, FACTS_MLINFO_FL_APPEND_CRLF);
1654
0
    xerrno = errno;
1655
1656
0
    destroy_pool(info.pool);
1657
0
    info.pool = NULL;
1658
1659
0
    if (XFER_ABORTED) {
1660
0
      pr_data_abort(0, FALSE);
1661
0
      succeeded = FALSE;
1662
0
      break;
1663
0
    }
1664
1665
0
    if (res < 0) {
1666
0
      pr_data_abort(xerrno, FALSE);
1667
0
      succeeded = FALSE;
1668
0
      break;
1669
0
    }
1670
0
  }
1671
1672
0
  pr_fsio_closedir(dirh);
1673
1674
0
  if (XFER_ABORTED) {
1675
0
    pr_data_close2();
1676
0
    return PR_ERROR(cmd);
1677
0
  }
1678
1679
0
  if (succeeded == FALSE) {
1680
0
    return PR_ERROR(cmd);
1681
0
  }
1682
1683
0
  res = facts_mlinfobuf_flush();
1684
0
  if (res < 0) {
1685
0
    int xerrno = errno;
1686
1687
0
    if (session.d != NULL &&
1688
0
        session.d->outstrm != NULL) {
1689
0
      xerrno = PR_NETIO_ERRNO(session.d->outstrm);
1690
0
    }
1691
1692
0
    pr_data_abort(xerrno, FALSE);
1693
0
    return PR_ERROR(cmd);
1694
0
  }
1695
1696
0
  pr_data_close2();
1697
0
  pr_response_add(R_226, _("Transfer complete"));
1698
0
  return PR_HANDLED(cmd);
1699
0
}
1700
1701
0
MODRET facts_mlsd_cleanup(cmd_rec *cmd) {
1702
0
  const char *proto;
1703
1704
0
  proto = pr_session_get_protocol(0);
1705
1706
  /* Ignore this for SFTP connections. */
1707
0
  if (strcmp(proto, "sftp") == 0) {
1708
0
    return PR_DECLINED(cmd);
1709
0
  }
1710
1711
0
  pr_data_clear_xfer_pool();
1712
0
  return PR_DECLINED(cmd);
1713
0
}
1714
1715
0
MODRET facts_mlst(cmd_rec *cmd) {
1716
0
  int flags = 0, hidden = FALSE;
1717
0
  config_rec *c;
1718
0
  uid_t fake_uid = -1;
1719
0
  gid_t fake_gid = -1;
1720
0
  const mode_t *fake_mode = NULL;
1721
0
  unsigned char *ptr;
1722
0
  const char *path, *decoded_path;
1723
0
  const char *fake_user = NULL, *fake_group = NULL;
1724
0
  struct mlinfo info;
1725
1726
0
  if (cmd->argc != 1) {
1727
0
    path = pstrdup(cmd->tmp_pool, cmd->arg);
1728
1729
0
    decoded_path = pr_fs_decode_path2(cmd->tmp_pool, path,
1730
0
      FSIO_DECODE_FL_TELL_ERRORS);
1731
0
    if (decoded_path == NULL) {
1732
0
      int xerrno = errno;
1733
1734
0
      pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", path,
1735
0
        strerror(xerrno));
1736
0
      pr_response_add_err(R_550,
1737
0
        _("%s: Illegal character sequence in filename"), path);
1738
1739
0
      pr_cmd_set_errno(cmd, xerrno);
1740
0
      errno = xerrno;
1741
0
      return PR_ERROR(cmd);
1742
0
    }
1743
1744
0
  } else {
1745
0
    decoded_path = path = pr_fs_getcwd();
1746
0
  }
1747
1748
0
  if (!dir_check(cmd->tmp_pool, cmd, cmd->group, (char *) decoded_path,
1749
0
      &hidden)) {
1750
0
    pr_log_debug(DEBUG4, MOD_FACTS_VERSION ": %s command denied by <Limit>",
1751
0
      (char *) cmd->argv[0]);
1752
0
    pr_response_add_err(R_550, _("Unable to handle command"));
1753
1754
0
    pr_cmd_set_errno(cmd, EPERM);
1755
0
    errno = EPERM;
1756
0
    return PR_ERROR(cmd);
1757
0
  }
1758
1759
0
  if (hidden) {
1760
    /* Simply send an empty list, much like we do for a STAT command for
1761
     * a hidden file.
1762
     */
1763
0
    pr_response_add(R_250, _("Start of list for %s"), path);
1764
0
    pr_response_add(R_250, _("End of list"));
1765
1766
0
    return PR_HANDLED(cmd);
1767
0
  }
1768
1769
  /* Determine whether to display symlinks as such. */
1770
0
  ptr = get_param_ptr(TOPLEVEL_CONF, "ShowSymlinks", FALSE);
1771
0
  if (ptr &&
1772
0
      *ptr == TRUE) {
1773
0
    flags |= FACTS_MLINFO_FL_SHOW_SYMLINKS;
1774
1775
0
    if (facts_mlinfo_opts & FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK) {
1776
0
      flags |= FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK;
1777
0
    }
1778
0
  }
1779
1780
0
  fake_mode = get_param_ptr(get_dir_ctxt(cmd->tmp_pool, (char *) decoded_path),
1781
0
    "DirFakeMode", FALSE);
1782
1783
0
  c = find_config(get_dir_ctxt(cmd->tmp_pool, (char *) decoded_path),
1784
0
    CONF_PARAM, "DirFakeUser", FALSE);
1785
0
  if (c != NULL) {
1786
0
    if (c->argc > 0) {
1787
0
      fake_user = c->argv[0];
1788
0
      if (fake_user != NULL &&
1789
0
          strcmp(fake_user, "~") != 0) {
1790
0
        fake_uid = pr_auth_name2uid(cmd->tmp_pool, fake_user);
1791
1792
0
      } else {
1793
0
        fake_uid = session.uid;
1794
0
        fake_user = session.user;
1795
0
      }
1796
1797
0
    } else {
1798
      /* Handle the "DirFakeUser off" case (Bug#3715). */
1799
0
      fake_uid = (uid_t) -1;
1800
0
      fake_user = NULL;
1801
0
    }
1802
0
  }
1803
1804
0
  c = find_config(get_dir_ctxt(cmd->tmp_pool, (char *) decoded_path),
1805
0
    CONF_PARAM, "DirFakeGroup", FALSE);
1806
0
  if (c != NULL) {
1807
0
    if (c->argc > 0) {
1808
0
      fake_group = c->argv[0];
1809
0
      if (fake_group != NULL &&
1810
0
          strcmp(fake_group, "~") != 0) {
1811
0
        fake_gid = pr_auth_name2gid(cmd->tmp_pool, fake_group);
1812
1813
0
      } else {
1814
0
        fake_gid = session.gid;
1815
0
        fake_group = session.group;
1816
0
      }
1817
1818
0
    } else {
1819
      /* Handle the "DirFakeGroup off" case (Bug#3715). */
1820
0
      fake_gid = (gid_t) -1;
1821
0
      fake_group = NULL;
1822
0
    }
1823
0
  }
1824
1825
0
  info.pool = cmd->tmp_pool;
1826
1827
  /* Since this is an MLST command, we are not listing the contents of
1828
   * of a directory, we're only showing the entry for a path, whether
1829
   * directory or not.  Thus the "cdir" type fact should not be used
1830
   * (Bug#4198).
1831
   */
1832
0
  flags |= FACTS_MLINFO_FL_NO_CDIR;
1833
1834
0
  pr_fs_clear_cache2(decoded_path);
1835
0
  if (facts_mlinfo_get(&info, decoded_path, decoded_path, flags,
1836
0
      fake_user, fake_uid, fake_group, fake_gid, fake_mode) < 0) {
1837
0
    pr_response_add_err(R_550, _("'%s' cannot be listed"), path);
1838
1839
0
    pr_cmd_set_errno(cmd, EPERM);
1840
0
    errno = EPERM;
1841
0
    return PR_ERROR(cmd);
1842
0
  }
1843
1844
  /* No need to re-encode the path here as UTF8, since 'path' is the
1845
   * original parameter as sent by the client.
1846
   *
1847
   * However, as per RFC3659 Section 7.3.1, since we advertise TVFS in our
1848
   * FEAT output, the path here should be the full path (as seen by the
1849
   * client).
1850
   */
1851
1852
  /* XXX What about chroots? */
1853
1854
0
  if (flags & FACTS_MLINFO_FL_SHOW_SYMLINKS) {
1855
0
    if (flags & FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK) {
1856
0
      info.path = dir_canonical_path(cmd->tmp_pool, path);
1857
1858
0
    } else {
1859
      /* If we are supposed to show symlinks, then use dir_best_path() to get
1860
       * the full path, including dereferencing the symlink.
1861
       */
1862
0
      info.path = dir_best_path(cmd->tmp_pool, path);
1863
0
    }
1864
1865
0
  } else {
1866
0
    info.path = dir_canonical_path(cmd->tmp_pool, path);
1867
0
  }
1868
1869
0
  pr_response_add(R_250, _("Start of list for %s"), path);
1870
0
  facts_mlinfo_add(&info, 0);
1871
0
  pr_response_add(R_250, _("End of list"));
1872
1873
0
  return PR_HANDLED(cmd);
1874
0
}
1875
1876
0
MODRET facts_opts_mlst(cmd_rec *cmd) {
1877
0
  register unsigned int i;
1878
0
  char *method, *facts, *ptr, *resp_str = "";
1879
1880
0
  method = pstrdup(cmd->tmp_pool, cmd->argv[0]);
1881
1882
  /* Convert underscores to spaces in the method name, for prettier logging. */
1883
0
  for (i = 0; method[i]; i++) {
1884
0
    if (method[i] == '_') {
1885
0
      method[i] = ' ';
1886
0
    }
1887
0
  }
1888
1889
0
  if (cmd->argc > 2) {
1890
0
    pr_response_add_err(R_501, _("'%s' not understood"), method);
1891
1892
0
    pr_cmd_set_errno(cmd, EINVAL);
1893
0
    errno = EINVAL;
1894
0
    return PR_ERROR(cmd);
1895
0
  }
1896
1897
0
  if (cmd->argc == 1) {
1898
0
    facts_opts = 0;
1899
1900
    /* Update MLST FEAT listing to match the showing of no facts. */
1901
0
    facts_mlst_feat_remove();
1902
0
    facts_mlst_feat_add(cmd->tmp_pool);
1903
1904
    /* This response is mandated by RFC3659, therefore it is not
1905
     * localisable.
1906
     */
1907
0
    pr_response_add(R_200, "%s", "MLST OPTS");
1908
0
    return PR_HANDLED(cmd);
1909
0
  }
1910
1911
  /* Do not show any facts by default at this point; the processing of the
1912
   * facts requested by the client will enable just the ones the client
1913
   * wishes to receive.
1914
   */
1915
0
  facts_opts = 0;
1916
0
  facts_mlst_feat_remove();
1917
1918
0
  facts = cmd->argv[1];
1919
0
  ptr = strchr(facts, ';');
1920
1921
0
  while (ptr != NULL) {
1922
0
    pr_signals_handle();
1923
1924
0
    *ptr = '\0';
1925
1926
0
    if (strcasecmp(facts, "modify") == 0) {
1927
0
      facts_opts |= facts_get_show_opt(facts);
1928
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "modify;", NULL);
1929
1930
0
    } else if (strcasecmp(facts, "perm") == 0) {
1931
0
      facts_opts |= facts_get_show_opt(facts);
1932
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "perm;", NULL);
1933
1934
0
    } else if (strcasecmp(facts, "size") == 0) {
1935
0
      facts_opts |= facts_get_show_opt(facts);
1936
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "size;", NULL);
1937
1938
0
    } else if (strcasecmp(facts, "type") == 0) {
1939
0
      facts_opts |= facts_get_show_opt(facts);
1940
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "type;", NULL);
1941
1942
0
    } else if (strcasecmp(facts, "unique") == 0) {
1943
0
      facts_opts |= facts_get_show_opt(facts);
1944
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "unique;", NULL);
1945
1946
0
    } else if (strcasecmp(facts, "UNIX.group") == 0) {
1947
0
      facts_opts |= facts_get_show_opt(facts);
1948
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "UNIX.group;", NULL);
1949
1950
0
    } else if (strcasecmp(facts, "UNIX.groupname") == 0) {
1951
0
      facts_opts |= facts_get_show_opt(facts);
1952
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "UNIX.groupname;", NULL);
1953
1954
0
    } else if (strcasecmp(facts, "UNIX.mode") == 0) {
1955
0
      facts_opts |= facts_get_show_opt(facts);
1956
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "UNIX.mode;", NULL);
1957
1958
0
    } else if (strcasecmp(facts, "UNIX.owner") == 0) {
1959
0
      facts_opts |= facts_get_show_opt(facts);
1960
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "UNIX.owner;", NULL);
1961
1962
0
    } else if (strcasecmp(facts, "UNIX.ownername") == 0) {
1963
0
      facts_opts |= facts_get_show_opt(facts);
1964
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "UNIX.ownername;", NULL);
1965
1966
0
    } else if (strcasecmp(facts, "media-type") == 0) {
1967
0
      facts_opts |= facts_get_show_opt(facts);
1968
0
      resp_str = pstrcat(cmd->tmp_pool, resp_str, "media-type;", NULL);
1969
1970
0
    } else {
1971
0
      pr_log_debug(DEBUG3, MOD_FACTS_VERSION
1972
0
        ": %s: client requested unsupported fact '%s'", method, facts);
1973
0
    }
1974
1975
0
    *ptr = ';';
1976
0
    facts = ptr + 1;
1977
0
    ptr = strchr(facts, ';');
1978
0
  }
1979
1980
0
  facts_mlst_feat_add(cmd->tmp_pool);
1981
1982
  /* This response is mandated by RFC3659, therefore it is not localisable. */
1983
0
  pr_response_add(R_200, "MLST OPTS %s", resp_str);
1984
0
  return PR_HANDLED(cmd);
1985
0
}
1986
1987
/* Configuration handlers
1988
 */
1989
1990
/* usage: FactsAdvertise on|off */
1991
0
MODRET set_factsadvertise(cmd_rec *cmd) {
1992
0
  int advertise_facts = -1;
1993
0
  config_rec *c = NULL;
1994
1995
0
  CHECK_ARGS(cmd, 1);
1996
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1997
1998
0
  advertise_facts = get_boolean(cmd, 1);
1999
0
  if (advertise_facts == -1) {
2000
0
    CONF_ERROR(cmd, "expected Boolean parameter");
2001
0
  }
2002
2003
0
  c = add_config_param(cmd->argv[0], 1, NULL);
2004
0
  c->argv[0] = pcalloc(c->pool, sizeof(int));
2005
0
  *((int *) c->argv[0]) = advertise_facts;
2006
2007
0
  return PR_HANDLED(cmd);
2008
0
}
2009
2010
/* usage: FactsDefault fact1 ... factN */
2011
0
MODRET set_factsdefault(cmd_rec *cmd) {
2012
0
  register unsigned int i;
2013
0
  config_rec *c;
2014
0
  unsigned long opts = 0UL;
2015
2016
0
  if (cmd->argc-1 == 0) {
2017
0
    CONF_ERROR(cmd, "wrong number of parameters");
2018
0
  }
2019
2020
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
2021
2022
0
  c = add_config_param(cmd->argv[0], 1, NULL);
2023
2024
0
  for (i = 1; i < cmd->argc; i++) {
2025
0
    int show_opt;
2026
2027
0
    show_opt = facts_get_show_opt(cmd->argv[i]);
2028
0
    if (show_opt < 0) {
2029
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown fact '",
2030
0
        cmd->argv[i], "'", NULL));
2031
0
    }
2032
2033
0
    opts |= show_opt;
2034
0
  }
2035
2036
0
  c->argv[0] = palloc(c->pool, sizeof(unsigned long));
2037
0
  *((unsigned long *) c->argv[0]) = opts;
2038
2039
0
  return PR_HANDLED(cmd);
2040
0
}
2041
2042
/* usage: FactsOptions opt1 ... optN */
2043
0
MODRET set_factsoptions(cmd_rec *cmd) {
2044
0
  register unsigned int i;
2045
0
  config_rec *c;
2046
0
  unsigned long opts = 0UL;
2047
2048
0
  if (cmd->argc-1 == 0) {
2049
0
    CONF_ERROR(cmd, "wrong number of parameters");
2050
0
  }
2051
2052
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
2053
2054
0
  c = add_config_param(cmd->argv[0], 1, NULL);
2055
2056
0
  for (i = 1; i < cmd->argc; i++) {
2057
0
    if (strcmp(cmd->argv[i], "UseSlink") == 0) {
2058
0
      opts |= FACTS_MLINFO_FL_SHOW_SYMLINKS_USE_SLINK;
2059
2060
0
    } else if (strcmp(cmd->argv[i], "AdjustedSymlinks") == 0) {
2061
0
      opts |= FACTS_MLINFO_FL_ADJUSTED_SYMLINKS;
2062
2063
0
    } else if (strcmp(cmd->argv[i], "NoAdjustedSymlinks") == 0) {
2064
      /* Ignore; retained for backward compatibility. */
2065
2066
0
    } else if (strcmp(cmd->argv[i], "NoNames") == 0) {
2067
0
      opts |= FACTS_MLINFO_FL_NO_NAMES;
2068
2069
0
    } else {
2070
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown FactsOption '",
2071
0
        cmd->argv[i], "'", NULL));
2072
0
    }
2073
0
  }
2074
2075
0
  c->argv[0] = palloc(c->pool, sizeof(unsigned long));
2076
0
  *((unsigned long *) c->argv[0]) = opts;
2077
2078
0
  if (pr_module_exists("mod_ifsession.c")) {
2079
    /* These are needed in case this directive is used with mod_ifsession
2080
     * configuration.
2081
     */
2082
0
    c->flags |= CF_MULTI;
2083
0
  }
2084
2085
0
  return PR_HANDLED(cmd);
2086
0
}
2087
2088
/* Event listeners
2089
 */
2090
2091
0
static void facts_sess_reinit_ev(const void *event_data, void *user_data) {
2092
0
  int res;
2093
2094
  /* A HOST command changed the main_server pointer, reinitialize ourselves. */
2095
2096
0
  pr_event_unregister(&facts_module, "core.session-reinit",
2097
0
    facts_sess_reinit_ev);
2098
2099
0
  facts_opts = 0;
2100
0
  facts_mlinfo_opts = 0;
2101
2102
0
  pr_feat_remove("MFF modify;UNIX.group;UNIX.mode;");
2103
0
  pr_feat_remove("MFMT");
2104
0
  pr_feat_remove("TVFS");
2105
0
  facts_mlst_feat_remove();
2106
2107
0
  res = facts_sess_init();
2108
0
  if (res < 0) {
2109
0
    pr_session_disconnect(&facts_module,
2110
0
      PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
2111
0
  }
2112
0
}
2113
2114
/* Initialization functions
2115
 */
2116
2117
0
static int facts_init(void) {
2118
0
  pr_help_add(C_MLSD, _("[<sp> pathname]"), TRUE);
2119
0
  pr_help_add(C_MLST, _("[<sp> pathname]"), TRUE);
2120
2121
0
  return 0;
2122
0
}
2123
2124
0
static int facts_sess_init(void) {
2125
0
  config_rec *c;
2126
0
  int advertise = TRUE;
2127
2128
0
  pr_event_register(&facts_module, "core.session-reinit",
2129
0
    facts_sess_reinit_ev, NULL);
2130
2131
0
  facts_opts = FACTS_OPT_SHOW_DEFAULT;
2132
2133
0
  c = find_config(main_server->conf, CONF_PARAM, "FactsAdvertise", FALSE);
2134
0
  if (c != NULL) {
2135
0
    advertise = *((int *) c->argv[0]);
2136
0
  }
2137
2138
0
  if (advertise == FALSE) {
2139
0
    return 0;
2140
0
  }
2141
2142
0
  c = find_config(main_server->conf, CONF_PARAM, "FactsDefault", FALSE);
2143
0
  if (c != NULL) {
2144
0
    facts_opts = *((unsigned long *) c->argv[0]);
2145
0
  }
2146
2147
0
  c = find_config(main_server->conf, CONF_PARAM, "FactsOptions", FALSE);
2148
0
  while (c != NULL) {
2149
0
    unsigned long opts;
2150
2151
0
    pr_signals_handle();
2152
2153
0
    opts = *((unsigned long *) c->argv[0]);
2154
0
    facts_mlinfo_opts |= opts;
2155
2156
0
    c = find_config_next(c, c->next, CONF_PARAM, "FactsOptions", FALSE);
2157
0
  }
2158
2159
0
  if (pr_module_exists("mod_mime.c") == TRUE) {
2160
    /* Check to see if MIMEEngine is enabled.  Yes, this is slightly
2161
     * naughty, looking at some other module's configuration directives,
2162
     * but for compliance with RFC 3659, specifically for implementing the
2163
     * "media-type" fact for MLSx commands, we need to do this.
2164
     */
2165
0
    c = find_config(main_server->conf, CONF_PARAM, "MIMEEngine", FALSE);
2166
0
    if (c != NULL) {
2167
0
      int engine;
2168
2169
0
      engine = *((int *) c->argv[0]);
2170
0
      if (engine == TRUE) {
2171
0
        facts_opts |= FACTS_OPT_SHOW_MEDIA_TYPE;
2172
0
      }
2173
0
    }
2174
0
  }
2175
2176
0
  pr_feat_add("MFF modify;UNIX.group;UNIX.mode;");
2177
0
  pr_feat_add("MFMT");
2178
0
  pr_feat_add("TVFS");
2179
2180
0
  facts_mlst_feat_add(session.pool);
2181
2182
0
  return 0;
2183
0
}
2184
2185
/* Module API tables
2186
 */
2187
2188
static conftable facts_conftab[] = {
2189
  { "FactsAdvertise", set_factsadvertise, NULL },
2190
  { "FactsDefault", set_factsdefault, NULL },
2191
  { "FactsOptions", set_factsoptions, NULL },
2192
  { NULL }
2193
};
2194
2195
static cmdtable facts_cmdtab[] = {
2196
  { CMD,  C_MFF,    G_WRITE,facts_mff,  TRUE, FALSE, CL_WRITE },
2197
  { CMD,  C_MFMT,   G_WRITE,facts_mfmt, TRUE, FALSE, CL_WRITE },
2198
  { CMD,  C_MLSD,   G_DIRS, facts_mlsd, TRUE, FALSE, CL_DIRS },
2199
  { LOG_CMD,  C_MLSD,   G_NONE, facts_mlsd_cleanup, FALSE, FALSE },
2200
  { LOG_CMD_ERR,C_MLSD,   G_NONE, facts_mlsd_cleanup, FALSE, FALSE },
2201
  { CMD,  C_MLST,   G_DIRS, facts_mlst, TRUE, FALSE, CL_DIRS },
2202
  { CMD,  C_OPTS "_MLST", G_NONE, facts_opts_mlst, FALSE, FALSE },
2203
  { 0, NULL }
2204
};
2205
2206
module facts_module = {
2207
  NULL, NULL,
2208
2209
  /* Module API version 2.0 */
2210
  0x20,
2211
2212
  /* Module name */
2213
  "facts",
2214
2215
  /* Module configuration handler table */
2216
  facts_conftab,
2217
2218
  /* Module command handler table */
2219
  facts_cmdtab,
2220
2221
  /* Module authentication handler table */
2222
  NULL,
2223
2224
  /* Module initialization function */
2225
  facts_init,
2226
2227
  /* Session initialization function */
2228
  facts_sess_init,
2229
2230
  /* Module version */
2231
  MOD_FACTS_VERSION
2232
};