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