Coverage Report

Created: 2025-08-08 06:53

/src/proftpd/modules/mod_log.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5
 * Copyright (c) 2001-2025 The ProFTPD Project team
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20
 *
21
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
22
 * and other respective copyright holders give permission to link this program
23
 * with OpenSSL, and distribute the resulting executable, without including
24
 * the source code for OpenSSL in the source distribution.
25
 */
26
27
/* Flexible logging module for proftpd */
28
29
#include "conf.h"
30
#include "privs.h"
31
#include "logfmt.h"
32
#include "jot.h"
33
34
0
#define MOD_LOG_VERSION       "mod_log/1.0"
35
36
module log_module;
37
38
/* Max path length plus 128 bytes for additional info. */
39
#define EXTENDED_LOG_BUFFER_SIZE    (PR_TUNABLE_PATH_MAX + 128)
40
41
0
#define EXTENDED_LOG_MODE     0644
42
0
#define EXTENDED_LOG_FORMAT_DEFAULT   "default"
43
44
typedef struct logformat_struc  logformat_t;
45
typedef struct logfile_struc  logfile_t;
46
47
struct logformat_struc {
48
  logformat_t *next, *prev;
49
50
  char *lf_fmt_name;
51
  unsigned char *lf_format;
52
};
53
54
struct logfile_struc {
55
  logfile_t   *next, *prev;
56
57
  char      *lf_filename;
58
  int     lf_fd;
59
  int     lf_syslog_level;
60
61
  logformat_t   *lf_format;
62
  pr_jot_filters_t  *lf_jot_filters;
63
64
  /* Pointer to the "owning" configuration */
65
  config_rec    *lf_conf;
66
};
67
68
/* Value for lf_fd signalling that data should be logged via syslog, rather
69
 * than written to a file.
70
 */
71
0
#define EXTENDED_LOG_SYSLOG -4
72
73
static pool *log_pool = NULL;
74
static logformat_t *formats = NULL;
75
static xaset_t *format_set = NULL;
76
static logfile_t *logs = NULL;
77
static xaset_t *log_set = NULL;
78
79
static const char *trace_channel = "extlog";
80
81
/* format string args:
82
   %A     - Anonymous username (password given)
83
   %a     - Remote client IP address
84
   %b     - Bytes sent for request
85
   %{basename}    - Basename of path
86
   %c     - Class
87
   %D     - full directory path
88
   %d     - directory (for client)
89
   %E     - End-of-session reason
90
   %{FOOBAR}e   - Contents of environment variable FOOBAR
91
   %F     - Transfer path (filename for client)
92
   %f     - Filename
93
   %g     - Local user's primary group name
94
   %H                   - Local IP address of server handling session
95
   %h     - Remote client DNS name
96
   %I                   - Total number of "raw" bytes read in from network
97
   %J                   - Request (command) arguments (file.txt, etc)
98
   %L                   - Local IP address contacted by client
99
   %l     - Remote logname (from identd)
100
   %m     - Request (command) method (RETR, etc)
101
   %O                   - Total number of "raw" bytes written out to network
102
   %P                   - Process ID of child serving request
103
   %p     - Port of server serving request
104
   %R                   - Response time for command/request, in milliseconds
105
   %r     - Full request (command)
106
   %s     - Response code (status)
107
   %S                   - Response string
108
   %T     - Time taken to transfer file, in seconds
109
   %t     - Time
110
   %{format}t   - Formatted time (strftime(3) format)
111
   %U                   - Original username sent by client
112
   %u     - Local user
113
   %V                   - DNS name of server serving request
114
   %v     - ServerName of server serving request
115
   %w                   - RNFR path ("whence" a rename comes, i.e. the source)
116
   %{epoch}             - Unix epoch (seconds since Jan 1 1970)
117
   %{file-modified}     - Indicates whether a file is being modified
118
                          (i.e. already exists) or not.
119
   %{file-offset}       - Contains the offset at which the file is read/written
120
   %{file-size}         - Contains the file size at the end of the transfer
121
   %{iso8601}           - ISO-8601 timestamp: YYYY-MM-dd HH:mm:ss,SSS
122
                            for example: "1999-11-27 15:49:37,459"
123
   %{microsecs}         - 6 digits of microseconds of current time
124
   %{millisecs}         - 3 digits of milliseconds of current time
125
   %{protocol}          - Current protocol (e.g. "ftp", "sftp", etc)
126
   %{uid}               - UID of logged-in user
127
   %{gid}               - Primary GID of logged-in user
128
   %{transfer-failure}  - reason, or "-"
129
   %{transfer-millisecs}- Time taken to transfer file, in milliseconds
130
   %{transfer-status}   - "success", "failed", "cancelled", "timeout", or "-"
131
   %{transfer-type}     - "binary" or "ASCII"
132
   %{version}           - ProFTPD version
133
*/
134
135
/* Necessary prototypes */
136
static int log_sess_init(void);
137
static void log_xfer_stalled_ev(const void *, void *);
138
139
static void parse_logformat(const char *directive, char *fmt_name,
140
0
    char *fmt_text) {
141
0
  int res;
142
0
  pool *tmp_pool;
143
0
  pr_jot_ctx_t *jot_ctx;
144
0
  pr_jot_parsed_t *jot_parsed;
145
0
  unsigned char format_buf[4096] = {'\0'};
146
0
  size_t fmt_len;
147
0
  logformat_t *lf;
148
149
  /* This function can cause potential problems.  Custom LogFormats
150
   * might overrun the format buffer.  Fixing this problem involves a
151
   * rewrite of most of this module.  This will happen post 1.2.0.
152
   */
153
154
0
  tmp_pool = make_sub_pool(log_pool);
155
0
  jot_ctx = pcalloc(tmp_pool, sizeof(pr_jot_ctx_t));
156
0
  jot_parsed = pcalloc(tmp_pool, sizeof(pr_jot_parsed_t));
157
0
  jot_parsed->bufsz = jot_parsed->buflen = sizeof(format_buf);
158
0
  jot_parsed->ptr = jot_parsed->buf = format_buf;
159
160
0
  jot_ctx->log = jot_parsed;
161
162
0
  res = pr_jot_parse_logfmt(tmp_pool, fmt_text, jot_ctx, pr_jot_parse_on_meta,
163
0
    pr_jot_parse_on_unknown, pr_jot_parse_on_other, 0);
164
0
  if (res < 0) {
165
0
    pr_log_pri(PR_LOG_NOTICE, MOD_LOG_VERSION
166
0
      ": error parsing LogFormat '%s': %s", fmt_text, strerror(errno));
167
168
0
    destroy_pool(tmp_pool);
169
0
    return;
170
0
  }
171
172
0
  fmt_len = jot_parsed->bufsz - jot_parsed->buflen;
173
174
0
  lf = (logformat_t *) pcalloc(log_pool, sizeof(logformat_t));
175
0
  lf->lf_fmt_name = pstrdup(log_pool, fmt_name);
176
0
  lf->lf_format = palloc(log_pool, fmt_len + 1);
177
0
  memcpy(lf->lf_format, format_buf, fmt_len);
178
0
  lf->lf_format[fmt_len] = '\0';
179
180
0
  if (format_set == NULL) {
181
0
    format_set = xaset_create(log_pool, NULL);
182
0
  }
183
184
0
  xaset_insert_end(format_set, (xasetmember_t *) lf);
185
0
  formats = (logformat_t *) format_set->xas_list;
186
187
0
  if (directive != NULL) {
188
0
    config_rec *c;
189
0
    char *ptr;
190
191
    /* Store the parsed format in the config tree as well, for use by other
192
     * logging-related modules.
193
     */
194
0
    c = add_config_param(directive, 2, NULL, NULL);
195
0
    c->argv[0] = pstrdup(c->pool, fmt_name);
196
0
    c->argv[1] = palloc(c->pool, fmt_len + 1);
197
198
0
    ptr = c->argv[1];
199
0
    memcpy(ptr, format_buf, fmt_len);
200
0
    ptr[fmt_len] = '\0';
201
0
  }
202
203
0
  destroy_pool(tmp_pool);
204
0
}
205
206
/* Syntax: LogFormat name "format string" */
207
0
MODRET set_logformat(cmd_rec *cmd) {
208
0
  CHECK_ARGS(cmd, 2);
209
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_GLOBAL);
210
211
0
  if (strlen(cmd->argv[1]) == 0) {
212
0
    CONF_ERROR(cmd, "missing required name parameter");
213
0
  }
214
215
0
  parse_logformat(cmd->argv[0], cmd->argv[1], cmd->argv[2]);
216
0
  return PR_HANDLED(cmd);
217
0
}
218
219
/* usage: LogOptions opt1 ... */
220
0
MODRET set_logoptions(cmd_rec *cmd) {
221
0
  register unsigned int i;
222
0
  int ctx;
223
0
  unsigned long log_opts = PR_LOG_OPT_DEFAULT;
224
0
  config_rec *c;
225
226
0
  if (cmd->argc < 2) {
227
0
    CONF_ERROR(cmd, "wrong number of parameters");
228
0
  }
229
230
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_GLOBAL|CONF_VIRTUAL);
231
232
0
  for (i = 1; i < cmd->argc; i++) {
233
0
    char action, *opt;
234
235
0
    opt = cmd->argv[i];
236
0
    action = *opt;
237
238
0
    if (action != '+' &&
239
0
        action != '-') {
240
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "bad LogOption: '", opt, "'",
241
0
        NULL));
242
0
    }
243
244
0
    opt++;
245
246
0
    if (strcasecmp(opt, "Timestamp") == 0) {
247
0
      switch (action) {
248
0
        case '-':
249
0
          log_opts &= ~PR_LOG_OPT_USE_TIMESTAMP;
250
0
          break;
251
252
0
        case '+':
253
0
          log_opts |= PR_LOG_OPT_USE_TIMESTAMP;
254
0
          break;
255
0
      }
256
257
0
    } else if (strcasecmp(opt, "Hostname") == 0) {
258
0
      switch (action) {
259
0
        case '-':
260
0
          log_opts &= ~PR_LOG_OPT_USE_HOSTNAME;
261
0
          break;
262
263
0
        case '+':
264
0
          log_opts |= PR_LOG_OPT_USE_HOSTNAME;
265
0
          break;
266
0
      }
267
268
0
    } else if (strcasecmp(opt, "VirtualHost") == 0) {
269
0
      switch (action) {
270
0
        case '-':
271
0
          log_opts &= ~PR_LOG_OPT_USE_VHOST;
272
0
          break;
273
274
0
        case '+':
275
0
          log_opts |= PR_LOG_OPT_USE_VHOST;
276
0
          break;
277
0
      }
278
279
0
    } else if (strcasecmp(opt, "RoleBasedProcessLabels") == 0) {
280
0
      switch (action) {
281
0
        case '-':
282
0
          log_opts &= ~PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS;
283
0
          break;
284
285
0
        case '+':
286
0
          log_opts |= PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS;
287
0
          break;
288
0
      }
289
290
0
    } else {
291
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown LogOption: '",
292
0
        opt, "'", NULL));
293
0
    }
294
0
  }
295
296
0
  c = add_config_param(cmd->argv[0], 1, NULL);
297
0
  c->argv[0] = palloc(c->pool, sizeof(unsigned long));
298
0
  *((unsigned long *) c->argv[0]) = log_opts;
299
300
0
  ctx = (cmd->config && cmd->config->config_type != CONF_PARAM ?
301
0
    cmd->config->config_type : cmd->server->config_type ?
302
0
    cmd->server->config_type : CONF_ROOT);
303
304
0
  if (ctx == CONF_ROOT) {
305
    /* If we're the "server config" context, set the LogOptions here,
306
     * too.  This will apply these LogOptions to the daemon process.
307
     */
308
0
    if (pr_log_set_options(log_opts) < 0) {
309
0
      pr_log_debug(DEBUG6, "%s: error setting LogOptions (%lu): %s",
310
0
        (char *) cmd->argv[0], log_opts, strerror(errno));
311
0
    }
312
0
  }
313
314
0
  return PR_HANDLED(cmd);
315
0
}
316
317
/* Syntax: ExtendedLog file [<cmd-classes> [<name>]] */
318
0
MODRET set_extendedlog(cmd_rec *cmd) {
319
0
  config_rec *c = NULL;
320
0
  int argc;
321
0
  char *path;
322
323
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
324
325
0
  argc = cmd->argc;
326
327
0
  if (argc < 2) {
328
0
    CONF_ERROR(cmd, "Syntax: ExtendedLog file [<cmd-classes> [<name>]]");
329
0
  }
330
331
0
  c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
332
333
0
  path = cmd->argv[1];
334
0
  if (strncasecmp(path, "syslog:", 7) == 0) {
335
0
    char *ptr;
336
337
0
    ptr = strchr(path, ':');
338
339
0
    if (pr_log_str2sysloglevel(++ptr) < 0) {
340
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown syslog level: '",
341
0
        ptr, "'", NULL));
342
0
    }
343
344
0
    c->argv[0] = pstrdup(log_pool, path);
345
346
0
  } else if (path[0] != '/') {
347
0
    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "relative paths not allowed: '",
348
0
      path, "'", NULL));
349
350
0
  } else {
351
0
    c->argv[0] = pstrdup(log_pool, path);
352
0
  }
353
354
0
  if (argc > 2) {
355
0
    pr_jot_filters_t *jot_filters;
356
0
    char *ptr, *rules, *cmd_sifts = NULL;
357
358
0
    rules = cmd->argv[2];
359
360
    /* Check for command sifting rules in the provided classes. */
361
0
    ptr = strchr(rules, '+');
362
0
    if (ptr != NULL) {
363
0
      cmd_sifts = ptr + 1;
364
0
      *ptr = '\0';
365
0
    }
366
367
0
    jot_filters = pr_jot_filters_create(c->pool, rules,
368
0
      PR_JOT_FILTER_TYPE_CLASSES, 0);
369
0
    if (jot_filters == NULL) {
370
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "invalid log class in '", rules,
371
0
        "': ", strerror(errno), NULL));
372
0
    }
373
374
0
    if (cmd_sifts != NULL) {
375
0
      int res;
376
377
0
      res = pr_jot_filters_parse_sifts(c->pool, jot_filters, cmd_sifts, 0);
378
0
      if (res < 0) {
379
0
        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error parsing commands in '",
380
0
          cmd_sifts, "': ", strerror(errno), NULL));
381
0
      }
382
0
    }
383
384
0
    c->argv[1] = jot_filters;
385
0
  }
386
387
0
  if (argc > 3) {
388
0
    c->argv[2] = pstrdup(log_pool, cmd->argv[3]);
389
0
  }
390
391
0
  if (pr_module_exists("mod_ifsession.c")) {
392
    /* These are needed in case this directive is used with mod_ifsession
393
     * configuration.
394
     */
395
0
    c->flags |= CF_MULTI;
396
0
  }
397
398
0
  return PR_HANDLED(cmd);
399
0
}
400
401
/* Syntax: AllowLogSymlinks <on|off> */
402
0
MODRET set_allowlogsymlinks(cmd_rec *cmd) {
403
0
  int allow_log_symlinks = -1;
404
0
  config_rec *c = NULL;
405
406
0
  CHECK_ARGS(cmd, 1);
407
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
408
409
0
  allow_log_symlinks = get_boolean(cmd, 1);
410
0
  if (allow_log_symlinks == -1) {
411
0
    CONF_ERROR(cmd, "expected Boolean parameter");
412
0
  }
413
414
0
  c = add_config_param(cmd->argv[0], 1, NULL);
415
0
  c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
416
0
  *((unsigned char *) c->argv[0]) = allow_log_symlinks;
417
418
0
  return PR_HANDLED(cmd);
419
0
}
420
421
/* Syntax: ServerLog <filename> */
422
0
MODRET set_serverlog(cmd_rec *cmd) {
423
0
  CHECK_ARGS(cmd, 1);
424
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
425
426
0
  add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
427
428
0
  return PR_HANDLED(cmd);
429
0
}
430
431
/* Syntax: SystemLog <filename> */
432
0
MODRET set_systemlog(cmd_rec *cmd) {
433
0
  CHECK_ARGS(cmd, 1);
434
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_GLOBAL);
435
436
0
  (void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
437
0
  return PR_HANDLED(cmd);
438
0
}
439
440
0
static struct tm *get_gmtoff(pool *p, int *tz) {
441
0
  time_t now;
442
0
  struct tm *gmt, *tm = NULL;
443
444
  /* Note that the ordering of the calls to gmtime(3) and pr_localtime()
445
   * here are IMPORTANT; gmtime(3) MUST be called first.  Otherwise,
446
   * the TZ environment variable may not be honored as one would expect;
447
   * see:
448
   *  https://forums.proftpd.org/smf/index.php/topic,11971.0.html
449
   */
450
0
  time(&now);
451
452
0
#if defined(HAVE_GMTIME_R)
453
0
  gmt = gmtime_r(&now, pcalloc(p, sizeof(struct tm)));
454
#else
455
  gmt = gmtime(&now);
456
#endif /* HAVE_GMTIME_R */
457
0
  if (gmt != NULL) {
458
0
    tm = pr_localtime(p, &now);
459
0
    if (tm != NULL) {
460
0
      int days, hours, minutes;
461
462
0
      days = tm->tm_yday - gmt->tm_yday;
463
0
      hours = ((days < -1 ? 24 : 1 < days ? -24 : days * 24)
464
0
              + tm->tm_hour - gmt->tm_hour);
465
0
      minutes = hours * 60 + tm->tm_min - gmt->tm_min;
466
0
      *tz = minutes;
467
0
    }
468
0
  }
469
470
0
  return tm;
471
0
}
472
473
/* Note: maybe the pr_buffer_t should be made to look like this? */
474
struct extlog_buffer {
475
  char *ptr, *buf;
476
  size_t bufsz, buflen;
477
};
478
479
static void extlog_buffer_append(struct extlog_buffer *log, const char *text,
480
0
    size_t text_len) {
481
0
  if (text == NULL ||
482
0
      text_len == 0) {
483
0
    return;
484
0
  }
485
486
0
  if (text_len > log->buflen) {
487
0
    text_len = log->buflen;
488
0
  }
489
490
0
  pr_trace_msg(trace_channel, 19, "appending text '%.*s' (%lu) to buffer",
491
0
    (int) text_len, text, (unsigned long) text_len);
492
0
  memcpy(log->buf, text, text_len);
493
0
  log->buf += text_len;
494
0
  log->buflen -= text_len;
495
0
}
496
497
static int resolve_on_meta(pool *p, pr_jot_ctx_t *jot_ctx,
498
0
    unsigned char logfmt_id, const char *jot_hint, const void *val) {
499
0
  struct extlog_buffer *log;
500
501
0
  log = jot_ctx->log;
502
0
  if (log->buflen > 0) {
503
0
    const char *text = NULL;
504
0
    size_t text_len = 0;
505
0
    char buf[1024];
506
507
0
    switch (logfmt_id) {
508
0
      case LOGFMT_META_MICROSECS: {
509
0
        unsigned long num;
510
511
0
        num = *((double *) val);
512
0
        text_len = pr_snprintf(buf, sizeof(buf)-1, "%06lu", num);
513
0
        buf[text_len] = '\0';
514
0
        text = buf;
515
0
        break;
516
0
      }
517
518
0
      case LOGFMT_META_MILLISECS: {
519
0
        unsigned long num;
520
521
0
        num = *((double *) val);
522
0
        text_len = pr_snprintf(buf, sizeof(buf)-1, "%03lu", num);
523
0
        buf[text_len] = '\0';
524
0
        text = buf;
525
0
        break;
526
0
      }
527
528
0
      case LOGFMT_META_LOCAL_PORT:
529
0
      case LOGFMT_META_REMOTE_PORT:
530
0
      case LOGFMT_META_RESPONSE_CODE:
531
0
      case LOGFMT_META_XFER_PORT: {
532
0
        int num;
533
534
0
        num = *((double *) val);
535
0
        text_len = pr_snprintf(buf, sizeof(buf)-1, "%d", num);
536
0
        buf[text_len] = '\0';
537
0
        text = buf;
538
0
        break;
539
0
      }
540
541
0
      case LOGFMT_META_UID: {
542
0
        uid_t uid;
543
544
0
        uid = *((double *) val);
545
0
        text = pr_uid2str(p, uid);
546
0
        break;
547
0
      }
548
549
0
      case LOGFMT_META_GID: {
550
0
        gid_t gid;
551
552
0
        gid = *((double *) val);
553
0
        text = pr_gid2str(p, gid);
554
0
        break;
555
0
      }
556
557
0
      case LOGFMT_META_BYTES_SENT:
558
0
      case LOGFMT_META_FILE_OFFSET:
559
0
      case LOGFMT_META_FILE_SIZE:
560
0
      case LOGFMT_META_RAW_BYTES_IN:
561
0
      case LOGFMT_META_RAW_BYTES_OUT:
562
0
      case LOGFMT_META_RESPONSE_MS:
563
0
      case LOGFMT_META_XFER_MS: {
564
0
        off_t num;
565
566
0
        num = *((double *) val);
567
0
        text_len = pr_snprintf(buf, sizeof(buf)-1, "%" PR_LU, (pr_off_t) num);
568
0
        buf[text_len] = '\0';
569
0
        text = buf;
570
0
        break;
571
0
      }
572
573
0
      case LOGFMT_META_EPOCH:
574
0
      case LOGFMT_META_PID: {
575
0
        unsigned long num;
576
577
0
        num = *((double *) val);
578
0
        text_len = pr_snprintf(buf, sizeof(buf)-1, "%lu", num);
579
0
        buf[text_len] = '\0';
580
0
        text = buf;
581
0
        break;
582
0
      }
583
584
0
      case LOGFMT_META_FILE_MODIFIED: {
585
0
        int truth;
586
587
0
        truth = *((int *) val);
588
0
        text = truth ? "true" : "false";
589
0
        break;
590
0
      }
591
592
0
      case LOGFMT_META_SECONDS: {
593
0
        float num;
594
595
0
        num = *((double *) val);
596
0
        text_len = pr_snprintf(buf, sizeof(buf)-1, "%0.3f", num);
597
0
        buf[text_len] = '\0';
598
0
        text = buf;
599
0
        break;
600
0
      }
601
602
      /* mod_log has a different implementation of META_TIME than the Jot
603
       * API.  Thus we do it ourselves here.
604
       */
605
0
      case LOGFMT_META_TIME: {
606
0
        char sign, *time_fmt = "[%d/%b/%Y:%H:%M:%S ";
607
0
        struct tm t;
608
0
        int internal_fmt = TRUE, with_tz = FALSE;
609
610
0
        if (jot_hint != NULL) {
611
0
          time_fmt = (char *) jot_hint;
612
0
          internal_fmt = FALSE;
613
0
        }
614
615
0
        t = *get_gmtoff(p, &with_tz);
616
0
        sign = (with_tz < 0 ? '-' : '+');
617
0
        if (with_tz < 0) {
618
0
          with_tz = -with_tz;
619
0
        }
620
621
0
        if (time_fmt != NULL) {
622
0
          memset(buf, '\0', sizeof(buf));
623
0
          text_len = strftime(buf, sizeof(buf) - 1, time_fmt, &t);
624
0
          if (internal_fmt == TRUE) {
625
0
            if (text_len < sizeof(buf)) {
626
0
              text_len += pr_snprintf(buf + text_len,
627
0
                sizeof(buf) - text_len - 1, "%c%.2d%.2d]", sign,
628
0
                (with_tz / 60), (with_tz % 60));
629
0
            }
630
0
          }
631
632
0
          text = buf;
633
0
        }
634
635
0
        break;
636
0
      }
637
638
0
      case LOGFMT_META_ANON_PASS:
639
0
      case LOGFMT_META_BASENAME:
640
0
      case LOGFMT_META_CLASS:
641
0
      case LOGFMT_META_CMD_PARAMS:
642
0
      case LOGFMT_META_COMMAND:
643
0
      case LOGFMT_META_DIR_NAME:
644
0
      case LOGFMT_META_DIR_PATH:
645
0
      case LOGFMT_META_ENV_VAR:
646
0
      case LOGFMT_META_EOS_REASON:
647
0
      case LOGFMT_META_FILENAME:
648
0
      case LOGFMT_META_GROUP:
649
0
      case LOGFMT_META_IDENT_USER:
650
0
      case LOGFMT_META_ISO8601:
651
0
      case LOGFMT_META_LOCAL_FQDN:
652
0
      case LOGFMT_META_LOCAL_IP:
653
0
      case LOGFMT_META_LOCAL_NAME:
654
0
      case LOGFMT_META_METHOD:
655
0
      case LOGFMT_META_NOTE_VAR:
656
0
      case LOGFMT_META_ORIGINAL_USER:
657
0
      case LOGFMT_META_PROTOCOL:
658
0
      case LOGFMT_META_REMOTE_HOST:
659
0
      case LOGFMT_META_REMOTE_IP:
660
0
      case LOGFMT_META_RENAME_FROM:
661
0
      case LOGFMT_META_RESPONSE_STR:
662
0
      case LOGFMT_META_USER:
663
0
      case LOGFMT_META_VERSION:
664
0
      case LOGFMT_META_VHOST_IP:
665
0
      case LOGFMT_META_XFER_FAILURE:
666
0
      case LOGFMT_META_XFER_PATH:
667
0
      case LOGFMT_META_XFER_SPEED:
668
0
      case LOGFMT_META_XFER_STATUS:
669
0
      case LOGFMT_META_XFER_TYPE:
670
0
      default:
671
0
        text = val;
672
0
        break;
673
0
    }
674
675
0
    if (text != NULL &&
676
0
        text_len == 0) {
677
0
      text_len = strlen(text);
678
0
    }
679
680
0
    extlog_buffer_append(log, text, text_len);
681
0
  }
682
683
0
  return 0;
684
0
}
685
686
static int resolve_on_default(pool *p, pr_jot_ctx_t *jot_ctx,
687
0
    unsigned char logfmt_id) {
688
0
  struct extlog_buffer *log;
689
690
0
  log = jot_ctx->log;
691
0
  if (log->buflen > 0) {
692
0
    const char *text = NULL;
693
0
    size_t text_len = 0;
694
695
0
    switch (logfmt_id) {
696
0
      case LOGFMT_META_ANON_PASS:
697
0
      case LOGFMT_META_IDENT_USER:
698
0
        text = "UNKNOWN";
699
0
        text_len = strlen(text);
700
0
        break;
701
702
0
      case LOGFMT_META_BASENAME:
703
0
      case LOGFMT_META_BYTES_SENT:
704
0
      case LOGFMT_META_CLASS:
705
0
      case LOGFMT_META_FILENAME:
706
0
      case LOGFMT_META_FILE_OFFSET:
707
0
      case LOGFMT_META_FILE_SIZE:
708
0
      case LOGFMT_META_GROUP:
709
0
      case LOGFMT_META_ORIGINAL_USER:
710
0
      case LOGFMT_META_RENAME_FROM:
711
0
      case LOGFMT_META_RESPONSE_CODE:
712
0
      case LOGFMT_META_RESPONSE_MS:
713
0
      case LOGFMT_META_RESPONSE_STR:
714
0
      case LOGFMT_META_SECONDS:
715
0
      case LOGFMT_META_USER:
716
0
      case LOGFMT_META_XFER_FAILURE:
717
0
      case LOGFMT_META_XFER_MS:
718
0
      case LOGFMT_META_XFER_PATH:
719
0
      case LOGFMT_META_XFER_PORT:
720
0
      case LOGFMT_META_XFER_SPEED:
721
0
      case LOGFMT_META_XFER_STATUS:
722
0
      case LOGFMT_META_XFER_TYPE:
723
0
        text = "-";
724
0
        text_len = 1;
725
0
        break;
726
727
      /* These explicitly do NOT have default values. */
728
0
      case LOGFMT_META_CMD_PARAMS:
729
0
      case LOGFMT_META_COMMAND:
730
0
      case LOGFMT_META_DIR_NAME:
731
0
      case LOGFMT_META_DIR_PATH:
732
0
      case LOGFMT_META_ENV_VAR:
733
0
      case LOGFMT_META_EOS_REASON:
734
0
      case LOGFMT_META_NOTE_VAR:
735
0
      case LOGFMT_META_METHOD:
736
0
      default:
737
0
        break;
738
0
    }
739
740
0
    extlog_buffer_append(log, text, text_len);
741
0
  }
742
743
0
  return 0;
744
0
}
745
746
static int resolve_on_other(pool *p, pr_jot_ctx_t *jot_ctx,
747
0
    unsigned char *text, size_t text_len) {
748
0
  struct extlog_buffer *log;
749
750
0
  log = jot_ctx->log;
751
0
  if (log->buflen > 0) {
752
0
    pr_trace_msg(trace_channel, 19, "appending text '%.*s' (%lu) to buffer",
753
0
      (int) text_len, text, (unsigned long) text_len);
754
0
    memcpy(log->buf, text, text_len);
755
0
    log->buf += text_len;
756
0
    log->buflen -= text_len;
757
0
  }
758
759
0
  return 0;
760
0
}
761
762
/* from src/log.c */
763
extern int syslog_sockfd;
764
765
0
static void log_event(cmd_rec *cmd, logfile_t *lf) {
766
0
  int res;
767
0
  unsigned char *f = NULL;
768
0
  char logbuf[EXTENDED_LOG_BUFFER_SIZE] = {'\0'};
769
0
  logformat_t *fmt = NULL;
770
0
  size_t logbuflen;
771
0
  pool *tmp_pool;
772
0
  pr_jot_ctx_t *jot_ctx;
773
0
  struct extlog_buffer *log;
774
775
0
  fmt = lf->lf_format;
776
0
  f = fmt->lf_format;
777
778
0
  tmp_pool = make_sub_pool(cmd->tmp_pool);
779
0
  jot_ctx = pcalloc(tmp_pool, sizeof(pr_jot_ctx_t));
780
0
  log = pcalloc(tmp_pool, sizeof(struct extlog_buffer));
781
0
  log->bufsz = log->buflen = sizeof(logbuf) - 1;
782
0
  log->ptr = log->buf = logbuf;
783
784
0
  jot_ctx->log = log;
785
786
0
  res = pr_jot_resolve_logfmt(tmp_pool, cmd, lf->lf_jot_filters, f, jot_ctx,
787
0
    resolve_on_meta, resolve_on_default, resolve_on_other);
788
0
  if (res < 0) {
789
    /* EPERM indicates that the event was filtered, thus is not necessarily
790
     * an unexpected condition.
791
     */
792
0
    if (errno != EPERM) {
793
0
      pr_log_pri(PR_LOG_NOTICE, MOD_LOG_VERSION
794
0
        ": error formatting ExtendedLog message: %s", strerror(errno));
795
0
    }
796
797
0
    destroy_pool(tmp_pool);
798
0
    return;
799
0
  }
800
801
0
  extlog_buffer_append(log, "\n", 1);
802
0
  logbuflen = (log->bufsz - log->buflen);
803
804
0
  if (lf->lf_fd != EXTENDED_LOG_SYSLOG) {
805
0
    pr_log_event_generate(PR_LOG_TYPE_EXTLOG, lf->lf_fd, -1, logbuf, logbuflen);
806
807
    /* What about short writes? */
808
0
    if (write(lf->lf_fd, logbuf, logbuflen) < 0) {
809
0
      pr_log_pri(PR_LOG_ALERT, "error: cannot write ExtendedLog '%s': %s",
810
0
        lf->lf_filename, strerror(errno));
811
0
    }
812
813
0
  } else {
814
0
    pr_log_event_generate(PR_LOG_TYPE_EXTLOG, syslog_sockfd,
815
0
      lf->lf_syslog_level, logbuf, logbuflen);
816
0
    pr_syslog(syslog_sockfd, lf->lf_syslog_level, "%s", logbuf);
817
0
  }
818
819
0
  destroy_pool(tmp_pool);
820
0
}
821
822
0
MODRET log_any(cmd_rec *cmd) {
823
0
  logfile_t *lf = NULL;
824
825
  /* If not in anon mode, only handle logs for main servers */
826
0
  for (lf = logs; lf; lf = lf->next) {
827
0
    pr_signals_handle();
828
829
    /* Skip any unopened files (obviously); make sure that special fd
830
     * for syslog is NOT skipped, though.
831
     */
832
0
    if (lf->lf_fd < 0 &&
833
0
        lf->lf_fd != EXTENDED_LOG_SYSLOG) {
834
0
      continue;
835
0
    }
836
837
    /* If this is not an <Anonymous> section, and this IS an <Anonymous>
838
     * ExtendedLog, skip it.
839
     */
840
0
    if (session.anon_config == NULL &&
841
0
        lf->lf_conf != NULL &&
842
0
        lf->lf_conf->config_type == CONF_ANON) {
843
0
      continue;
844
0
    }
845
846
0
    log_event(cmd, lf);
847
0
  }
848
849
0
  return PR_DECLINED(cmd);
850
0
}
851
852
/* Event handlers
853
 */
854
855
0
static void log_exit_ev(const void *event_data, void *user_data) {
856
0
  pool *tmp_pool;
857
0
  cmd_rec *cmd;
858
0
  int responses_blocked;
859
860
0
  tmp_pool = make_sub_pool(session.pool);
861
0
  cmd = pr_cmd_alloc(tmp_pool, 1, pstrdup(tmp_pool, "EXIT"));
862
0
  cmd->cmd_class |= CL_DISCONNECT;
863
864
0
  responses_blocked = pr_response_blocked();
865
0
  if (responses_blocked == FALSE) {
866
0
    (void) pr_response_block(TRUE);
867
0
  }
868
869
0
  (void) pr_cmd_dispatch_phase(cmd, LOG_CMD,
870
0
    PR_CMD_DISPATCH_FL_CLEAR_RESPONSE);
871
872
0
  pr_response_block(responses_blocked);
873
0
  destroy_pool(tmp_pool);
874
0
}
875
876
0
static void log_postparse_ev(const void *event_data, void *user_data) {
877
0
  config_rec *c;
878
879
0
  c = find_config(main_server->conf, CONF_PARAM, "SystemLog", FALSE);
880
0
  if (c != NULL) {
881
0
    char *path;
882
883
0
    path = c->argv[0];
884
0
    log_closesyslog();
885
886
0
    if (strcasecmp(path, "none") != 0) {
887
0
      int res, xerrno;
888
889
0
      path = dir_canonical_path(main_server->pool, path);
890
891
0
      pr_signals_block();
892
0
      PRIVS_ROOT
893
0
      res = log_opensyslog(path);
894
0
      xerrno = errno;
895
0
      PRIVS_RELINQUISH
896
0
      pr_signals_unblock();
897
898
0
      if (res < 0) {
899
0
        if (res == PR_LOG_WRITABLE_DIR) {
900
0
          pr_log_pri(PR_LOG_ERR,
901
0
            "unable to open SystemLog '%s': %s is a world-writable directory",
902
0
            path, path);
903
904
0
        } else if (res == PR_LOG_SYMLINK) {
905
0
          pr_log_pri(PR_LOG_ERR,
906
0
            "unable to open SystemLog '%s': %s is a symbolic link", path, path);
907
908
0
        } else {
909
0
          if (xerrno != ENXIO) {
910
0
            pr_log_pri(PR_LOG_ERR,
911
0
              "unable to open SystemLog '%s': %s", path, strerror(xerrno));
912
913
0
          } else {
914
0
            pr_log_pri(PR_LOG_ERR,
915
0
              "unable to open SystemLog '%s': "
916
0
              "FIFO reader process must be running first", path);
917
0
          }
918
0
        }
919
920
0
        exit(1);
921
0
      }
922
923
0
    } else {
924
0
      log_discard();
925
0
    }
926
0
  }
927
0
}
928
929
0
static void log_restart_ev(const void *event_data, void *user_data) {
930
0
  destroy_pool(log_pool);
931
932
0
  formats = NULL;
933
0
  format_set = NULL;
934
0
  logs = NULL;
935
0
  log_set = NULL;
936
937
0
  log_pool = make_sub_pool(permanent_pool);
938
0
  pr_pool_tag(log_pool, "mod_log pool");
939
940
0
  parse_logformat(NULL, "", "%h %l %u %t \"%r\" %s %b");
941
0
}
942
943
0
static void log_sess_reinit_ev(const void *event_data, void *user_data) {
944
0
  int res;
945
0
  logfile_t *lf = NULL;
946
947
  /* A HOST command changed the main_server pointer, reinitialize ourselves. */
948
949
0
  pr_event_unregister(&log_module, "core.exit", log_exit_ev);
950
0
  pr_event_unregister(&log_module, "core.session-reinit", log_sess_reinit_ev);
951
0
  pr_event_unregister(&log_module, "core.timeout-stalled", log_xfer_stalled_ev);
952
953
  /* XXX If ServerLog configured, close/reopen syslog? */
954
955
  /* Close all ExtendedLog files, to prevent duplicate fds. */
956
0
  for (lf = logs; lf; lf = lf->next) {
957
0
    if (lf->lf_fd > -1) {
958
      /* No need to close the special EXTENDED_LOG_SYSLOG (i.e. fake) fd. */
959
0
      if (lf->lf_fd != EXTENDED_LOG_SYSLOG) {
960
0
        (void) close(lf->lf_fd);
961
0
      }
962
963
0
      lf->lf_fd = -1;
964
0
    }
965
0
  }
966
967
  /* Restore original LogOptions settings. */
968
0
  (void) pr_log_set_options(PR_LOG_OPT_DEFAULT);
969
970
0
  res = log_sess_init();
971
0
  if (res < 0) {
972
0
    pr_session_disconnect(&log_module,
973
0
      PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
974
0
  }
975
0
}
976
977
0
static void log_xfer_stalled_ev(const void *event_data, void *user_data) {
978
0
  if (session.curr_cmd_rec != NULL) {
979
    /* Automatically dispatch the current command, at the LOG_CMD_ERR phase,
980
     * so that the ExtendedLog entry for the command gets written out.  This
981
     * should handle any LIST/MLSD/NLST commands as well (Bug#3696).
982
     */
983
0
    (void) log_any(session.curr_cmd_rec);
984
0
  }
985
0
}
986
987
/* Initialization handlers
988
 */
989
990
0
static int log_init(void) {
991
0
  log_pool = make_sub_pool(permanent_pool);
992
0
  pr_pool_tag(log_pool, "mod_log pool");
993
994
  /* Add the "default" extendedlog format */
995
0
  parse_logformat(NULL, "", "%h %l %u %t \"%r\" %s %b");
996
997
0
  pr_event_register(&log_module, "core.postparse", log_postparse_ev, NULL);
998
0
  pr_event_register(&log_module, "core.restart", log_restart_ev, NULL);
999
1000
0
  return 0;
1001
0
}
1002
1003
0
static void find_extendedlogs(void) {
1004
0
  config_rec *c;
1005
0
  char *logfname, *logfmt_name = NULL;
1006
0
  logformat_t *logfmt;
1007
0
  logfile_t *extlog = NULL;
1008
0
  unsigned long config_flags = (PR_CONFIG_FIND_FL_SKIP_DIR|PR_CONFIG_FIND_FL_SKIP_LIMIT|PR_CONFIG_FIND_FL_SKIP_DYNDIR);
1009
1010
  /* We DO actually want the recursion here.  The reason is that we want
1011
   * to find ALL_ ExtendedLog directives in the configuration, including
1012
   * those in <Anonymous> sections.  We have the ability to use root privs
1013
   * now, to make sure these files can be opened, but after the user has
1014
   * authenticated (and we know for sure whether they're anonymous or not),
1015
   * root privs may be permanently revoked.
1016
   *
1017
   * We mitigate the cost of the recursive search (especially for configs
1018
   * with thousands of <Directory>/<Limit> sections) by specifying the
1019
   * find_config() flags to skip those sections; we are only interested
1020
   * in the top-level (CONF_ROOT, CONF_VIRTUAL) and <Anonymous> sections.
1021
   */
1022
1023
0
  c = find_config2(main_server->conf, CONF_PARAM, "ExtendedLog", TRUE,
1024
0
    config_flags);
1025
0
  while (c != NULL) {
1026
0
    pr_jot_filters_t *jot_filters = NULL;
1027
1028
0
    pr_signals_handle();
1029
1030
0
    logfname = c->argv[0];
1031
0
    logfmt_name = NULL;
1032
1033
0
    if (c->argc > 1) {
1034
0
      jot_filters = c->argv[1];
1035
1036
0
      if (c->argc > 2) {
1037
0
        if (c->argv[2] != NULL) {
1038
0
          logfmt_name = c->argv[2];
1039
0
        }
1040
0
      }
1041
0
    }
1042
1043
    /* No logging for this round.  If, however, this was found in an
1044
     * <Anonymous> section, add a logfile entry for it anyway; the anonymous
1045
     * directive might be trying to override a higher-level config; see
1046
     * Bug#1908.
1047
     */
1048
0
    if (c->parent != NULL &&
1049
0
        c->parent->config_type != CONF_ANON) {
1050
0
      goto loop_extendedlogs;
1051
0
    }
1052
1053
0
    if (logfmt_name != NULL) {
1054
      /* Search for the format name */
1055
0
      for (logfmt = formats; logfmt; logfmt = logfmt->next) {
1056
0
        if (strcmp(logfmt->lf_fmt_name, logfmt_name) == 0) {
1057
0
          break;
1058
0
        }
1059
0
      }
1060
1061
0
      if (logfmt == NULL) {
1062
0
        if (strcasecmp(logfmt_name, EXTENDED_LOG_FORMAT_DEFAULT) == 0) {
1063
          /* Try again, this time looking for the default LogFormat
1064
           * name, which is registered using a name of "".
1065
           */
1066
0
          for (logfmt = formats; logfmt; logfmt = logfmt->next) {
1067
0
            if (strcmp(logfmt->lf_fmt_name, "") == 0) {
1068
0
              break;
1069
0
            }
1070
0
          }
1071
0
        }
1072
0
      }
1073
1074
0
      if (logfmt == NULL) {
1075
0
        pr_log_pri(PR_LOG_NOTICE,
1076
0
          "ExtendedLog '%s' uses unknown format name '%s'", logfname,
1077
0
          logfmt_name);
1078
0
        goto loop_extendedlogs;
1079
0
      }
1080
1081
0
    } else {
1082
0
      logfmt = formats;
1083
0
    }
1084
1085
0
    extlog = (logfile_t *) pcalloc(session.pool, sizeof(logfile_t));
1086
1087
0
    extlog->lf_filename = pstrdup(session.pool, logfname);
1088
0
    extlog->lf_fd = -1;
1089
0
    extlog->lf_syslog_level = -1;
1090
0
    extlog->lf_jot_filters = jot_filters;
1091
0
    extlog->lf_format = logfmt;
1092
0
    extlog->lf_conf = c->parent;
1093
0
    if (log_set == NULL) {
1094
0
      log_set = xaset_create(session.pool, NULL);
1095
0
    }
1096
1097
0
    xaset_insert(log_set, (xasetmember_t *) extlog);
1098
0
    logs = (logfile_t *) log_set->xas_list;
1099
1100
0
loop_extendedlogs:
1101
0
    c = find_config_next2(c, c->next, CONF_PARAM, "ExtendedLog", TRUE,
1102
0
      config_flags);
1103
0
  }
1104
0
}
1105
1106
0
MODRET log_pre_dele(cmd_rec *cmd) {
1107
0
  char *path;
1108
1109
0
  jot_set_deleted_filesz(0);
1110
1111
0
  path = dir_canonical_path(cmd->tmp_pool,
1112
0
    pr_fs_decode_path(cmd->tmp_pool, cmd->arg));
1113
0
  if (path != NULL) {
1114
0
    struct stat st;
1115
1116
    /* Briefly cache the size of the file being deleted, so that it can be
1117
     * logged properly using %b.
1118
     */
1119
0
    pr_fs_clear_cache2(path);
1120
0
    if (pr_fsio_stat(path, &st) == 0) {
1121
0
      jot_set_deleted_filesz(st.st_size);
1122
0
    }
1123
0
  }
1124
1125
0
  return PR_DECLINED(cmd);
1126
0
}
1127
1128
0
MODRET log_post_pass(cmd_rec *cmd) {
1129
0
  logfile_t *lf;
1130
1131
  /* Authentication is complete, if we aren't in anon-mode, close
1132
   * all extendedlogs opened inside <Anonymous> blocks.
1133
   */
1134
0
  if (!session.anon_config) {
1135
0
    for (lf = logs; lf; lf = lf->next) {
1136
0
      if (lf->lf_fd != -1 &&
1137
0
          lf->lf_fd != EXTENDED_LOG_SYSLOG &&
1138
0
          lf->lf_conf &&
1139
0
          lf->lf_conf->config_type == CONF_ANON) {
1140
0
        pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s' (fd %d)",
1141
0
          lf->lf_filename, lf->lf_fd);
1142
0
        (void) close(lf->lf_fd);
1143
0
        lf->lf_fd = -1;
1144
0
      }
1145
0
    }
1146
1147
0
  } else {
1148
    /* Close all logs which were opened inside a _different_ anonymous
1149
     * context.
1150
     */
1151
0
    for (lf = logs; lf; lf = lf->next) {
1152
0
      if (lf->lf_fd != -1 &&
1153
0
          lf->lf_fd != EXTENDED_LOG_SYSLOG &&
1154
0
          lf->lf_conf &&
1155
0
          lf->lf_conf != session.anon_config) {
1156
0
        pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s' (fd %d)",
1157
0
          lf->lf_filename, lf->lf_fd);
1158
0
        (void) close(lf->lf_fd);
1159
0
        lf->lf_fd = -1;
1160
0
      }
1161
0
    }
1162
1163
    /* If any ExtendedLogs set inside our context match an outer log,
1164
     * close the outer (this allows overriding inside <Anonymous>).
1165
     */
1166
0
    for (lf = logs; lf; lf = lf->next) {
1167
0
      if (lf->lf_conf &&
1168
0
          lf->lf_conf == session.anon_config) {
1169
        /* This should "override" any lower-level extendedlog with the
1170
         * same filename.
1171
         */
1172
0
        logfile_t *lfi = NULL;
1173
1174
0
        for (lfi = logs; lfi; lfi = lfi->next) {
1175
0
          if (lfi->lf_fd != -1 &&
1176
0
              lfi->lf_fd != EXTENDED_LOG_SYSLOG &&
1177
0
              !lfi->lf_conf &&
1178
0
              strcmp(lfi->lf_filename, lf->lf_filename) == 0) {
1179
0
            pr_log_debug(DEBUG7, "mod_log: closing ExtendedLog '%s' (fd %d)",
1180
0
              lf->lf_filename, lfi->lf_fd);
1181
0
            (void) close(lfi->lf_fd);
1182
0
            lfi->lf_fd = -1;
1183
0
          }
1184
0
        }
1185
1186
        /* Go ahead and close the log if it's CL_NONE */
1187
0
        if (lf->lf_fd != -1 &&
1188
0
            lf->lf_fd != EXTENDED_LOG_SYSLOG &&
1189
0
            pr_jot_filters_include_classes(lf->lf_jot_filters, CL_NONE) == TRUE) {
1190
0
          (void) close(lf->lf_fd);
1191
0
          lf->lf_fd = -1;
1192
0
        }
1193
0
      }
1194
0
    }
1195
0
  }
1196
1197
0
  return PR_DECLINED(cmd);
1198
0
}
1199
1200
/* Open all the log files */
1201
static int dispatched_connect = FALSE;
1202
1203
0
static int log_sess_init(void) {
1204
0
  config_rec *c;
1205
0
  char *serverlog_name = NULL;
1206
0
  logfile_t *lf = NULL;
1207
1208
0
  pr_event_register(&log_module, "core.session-reinit", log_sess_reinit_ev,
1209
0
    NULL);
1210
1211
0
  c = find_config(main_server->conf, CONF_PARAM, "LogOptions", FALSE);
1212
0
  if (c != NULL) {
1213
0
    unsigned long log_opts;
1214
1215
0
    log_opts = *((unsigned long *) c->argv[0]);
1216
0
    if (pr_log_set_options(log_opts) < 0) {
1217
0
      pr_log_debug(DEBUG6, "%s: error setting LogOptions (%lu): %s",
1218
0
        c->name, log_opts, strerror(errno));
1219
0
    }
1220
0
  }
1221
1222
  /* Open the ServerLog, if present. */
1223
0
  serverlog_name = get_param_ptr(main_server->conf, "ServerLog", FALSE);
1224
0
  if (serverlog_name != NULL) {
1225
0
    log_closesyslog();
1226
1227
0
    if (strncasecmp(serverlog_name, "none", 5) != 0) {
1228
0
      int res, xerrno;
1229
1230
0
      PRIVS_ROOT
1231
0
      res = log_opensyslog(serverlog_name);
1232
0
      xerrno = errno;
1233
0
      PRIVS_RELINQUISH
1234
1235
0
      if (res < 0) {
1236
0
        if (xerrno != ENXIO) {
1237
0
          pr_log_debug(DEBUG4, "unable to open ServerLog '%s': %s",
1238
0
            serverlog_name, strerror(xerrno));
1239
1240
0
        } else {
1241
0
          pr_log_debug(DEBUG4,
1242
0
            "unable to open ServerLog '%s': "
1243
0
            "FIFO reader process must be running first", serverlog_name);
1244
0
        }
1245
0
      }
1246
0
    }
1247
1248
0
  } else {
1249
0
    c = find_config(main_server->conf, CONF_PARAM, "SystemLog", FALSE);
1250
0
    if (c != NULL) {
1251
0
      char *path;
1252
1253
0
      path = c->argv[0];
1254
0
      log_closesyslog();
1255
1256
0
      if (strcasecmp(path, "none") != 0) {
1257
0
        int res, xerrno;
1258
1259
0
        path = dir_canonical_path(main_server->pool, path);
1260
1261
0
        pr_signals_block();
1262
0
        PRIVS_ROOT
1263
0
        res = log_opensyslog(path);
1264
0
        xerrno = errno;
1265
0
        PRIVS_RELINQUISH
1266
0
        pr_signals_unblock();
1267
1268
0
        if (res < 0) {
1269
0
          if (res == PR_LOG_WRITABLE_DIR) {
1270
0
            pr_log_pri(PR_LOG_ERR,
1271
0
              "unable to open SystemLog '%s': %s is a world-writable directory",
1272
0
              path, path);
1273
1274
0
          } else if (res == PR_LOG_SYMLINK) {
1275
0
            pr_log_pri(PR_LOG_ERR,
1276
0
              "unable to open SystemLog '%s': %s is a symbolic link", path,
1277
0
              path);
1278
1279
0
          } else {
1280
0
            if (xerrno != ENXIO) {
1281
0
              pr_log_pri(PR_LOG_ERR,
1282
0
                "unable to open SystemLog '%s': %s", path, strerror(xerrno));
1283
1284
0
            } else {
1285
0
              pr_log_pri(PR_LOG_ERR,
1286
0
                "unable to open SystemLog '%s': "
1287
0
                "FIFO reader process must be running first", path);
1288
0
            }
1289
0
          }
1290
0
        }
1291
1292
0
      } else {
1293
0
        log_discard();
1294
0
      }
1295
0
    }
1296
0
  }
1297
1298
  /* Open all the ExtendedLog files. */
1299
0
  find_extendedlogs();
1300
1301
0
  for (lf = logs; lf; lf = lf->next) {
1302
0
    if (lf->lf_fd == -1) {
1303
1304
      /* Is this ExtendedLog to be written to a file, or to syslog? */
1305
0
      if (strncasecmp(lf->lf_filename, "syslog:", 7) != 0) {
1306
0
        int res = 0, xerrno;
1307
1308
0
        pr_log_debug(DEBUG7, "mod_log: opening ExtendedLog '%s'",
1309
0
          lf->lf_filename);
1310
1311
0
        pr_signals_block();
1312
0
        PRIVS_ROOT
1313
0
        res = pr_log_openfile(lf->lf_filename, &(lf->lf_fd), EXTENDED_LOG_MODE);
1314
0
        xerrno = errno;
1315
0
        PRIVS_RELINQUISH
1316
0
        pr_signals_unblock();
1317
1318
0
        if (res < 0) {
1319
0
          if (res == -1) {
1320
0
            if (xerrno != ENXIO) {
1321
0
              pr_log_pri(PR_LOG_NOTICE, "unable to open ExtendedLog '%s': %s",
1322
0
                lf->lf_filename, strerror(xerrno));
1323
1324
0
            } else {
1325
0
              pr_log_pri(PR_LOG_NOTICE, "unable to open ExtendedLog '%s': "
1326
0
                "FIFO reader process must be running first", lf->lf_filename);
1327
0
            }
1328
1329
0
          } else if (res == PR_LOG_WRITABLE_DIR) {
1330
0
            pr_log_pri(PR_LOG_WARNING, "unable to open ExtendedLog '%s': "
1331
0
              "parent directory is world-writable", lf->lf_filename);
1332
1333
0
          } else if (res == PR_LOG_SYMLINK) {
1334
0
            pr_log_pri(PR_LOG_WARNING, "unable to open ExtendedLog '%s': "
1335
0
              "%s is a symbolic link", lf->lf_filename, lf->lf_filename);
1336
0
          }
1337
0
        }
1338
1339
0
      } else {
1340
0
        char *tmp = strchr(lf->lf_filename, ':');
1341
1342
0
        lf->lf_syslog_level = pr_log_str2sysloglevel(++tmp);
1343
0
        lf->lf_fd = EXTENDED_LOG_SYSLOG;
1344
0
      }
1345
0
    }
1346
0
  }
1347
1348
  /* Register event handlers for the session. */
1349
0
  pr_event_register(&log_module, "core.exit", log_exit_ev, NULL);
1350
0
  pr_event_register(&log_module, "core.timeout-stalled", log_xfer_stalled_ev,
1351
0
    NULL);
1352
1353
  /* Have we send our CONNECT event yet? */
1354
0
  if (dispatched_connect == FALSE) {
1355
0
    pool *tmp_pool;
1356
0
    cmd_rec *cmd;
1357
0
    int responses_blocked;
1358
1359
0
    tmp_pool = make_sub_pool(session.pool);
1360
0
    cmd = pr_cmd_alloc(tmp_pool, 1, pstrdup(tmp_pool, "CONNECT"));
1361
0
    cmd->cmd_class |= CL_CONNECT;
1362
1363
0
    responses_blocked = pr_response_blocked();
1364
0
    if (responses_blocked == FALSE) {
1365
0
      (void) pr_response_block(TRUE);
1366
0
    }
1367
1368
0
    (void) pr_cmd_dispatch_phase(cmd, LOG_CMD,
1369
0
      PR_CMD_DISPATCH_FL_CLEAR_RESPONSE);
1370
1371
0
    pr_response_block(responses_blocked);
1372
0
    destroy_pool(tmp_pool);
1373
0
    dispatched_connect = TRUE;
1374
0
  }
1375
1376
0
  return 0;
1377
0
}
1378
1379
/* Module API tables
1380
 */
1381
1382
static conftable log_conftab[] = {
1383
  { "AllowLogSymlinks", set_allowlogsymlinks,     NULL },
1384
  { "ExtendedLog",  set_extendedlog,      NULL },
1385
  { "LogFormat",  set_logformat,        NULL },
1386
  { "LogOptions", set_logoptions,       NULL },
1387
  { "ServerLog",  set_serverlog,        NULL },
1388
  { "SystemLog",  set_systemlog,        NULL },
1389
  { NULL,   NULL,         NULL }
1390
};
1391
1392
static cmdtable log_cmdtab[] = {
1393
  { PRE_CMD,    C_DELE, G_NONE, log_pre_dele, FALSE, FALSE },
1394
  { LOG_CMD,    C_ANY,  G_NONE, log_any,  FALSE, FALSE },
1395
  { LOG_CMD_ERR,  C_ANY,  G_NONE, log_any,  FALSE, FALSE },
1396
  { POST_CMD,   C_PASS, G_NONE, log_post_pass,  FALSE, FALSE },
1397
  { 0, NULL }
1398
};
1399
1400
module log_module = {
1401
  NULL, NULL,
1402
1403
  /* Module API version */
1404
  0x20,
1405
1406
  /* Module name */
1407
  "log",
1408
1409
  /* Module configuration handler table */
1410
  log_conftab,
1411
1412
  /* Module command handler table */
1413
  log_cmdtab,
1414
1415
  /* Module authentication handler table */
1416
  NULL,
1417
1418
  /* Module initialization */
1419
  log_init,
1420
1421
  /* Session initialization */
1422
  log_sess_init,
1423
1424
  /* Module version */
1425
  MOD_LOG_VERSION
1426
};