Coverage Report

Created: 2026-05-14 06:38

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