Coverage Report

Created: 2025-07-11 06:08

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