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