Coverage Report

Created: 2026-09-03 06:37

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