1 /*
2 * mod_log_forensic - a buffering log module for aiding in server behavior
3 * forensic analysis
4 * Copyright (c) 2011-2017 TJ Saunders
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 *
20 * As a special exemption, TJ Saunders and other respective copyright holders
21 * give permission to link this program with OpenSSL, and distribute the
22 * resulting executable, without including the source code for OpenSSL in the
23 * source distribution.
24 */
25
26 #include "conf.h"
27 #include "privs.h"
28
29 #include <sys/uio.h>
30
31 #define MOD_LOG_FORENSIC_VERSION "mod_log_forensic/0.2"
32
33 /* Make sure the version of proftpd is as necessary. */
34 #if PROFTPD_VERSION_NUMBER < 0x0001030403
35 # error "ProFTPD 1.3.4rc3 or later required"
36 #endif
37
38 module log_forensic_module;
39
40 static pool *forensic_pool = NULL;
41 static int forensic_engine = FALSE;
42 static int forensic_logfd = -1;
43
44 /* Criteria for flushing out the "forensic" logs. */
45 #define FORENSIC_CRIT_FAILED_LOGIN 0x00001
46 #define FORENSIC_CRIT_MODULE_CONFIG 0x00002
47 #define FORENSIC_CRIT_UNTIMELY_DEATH 0x00004
48
49 #define FORENSIC_CRIT_DEFAULT \
50 (FORENSIC_CRIT_FAILED_LOGIN|FORENSIC_CRIT_UNTIMELY_DEATH)
51
52 static unsigned long forensic_criteria = FORENSIC_CRIT_DEFAULT;
53
54 /* Use a ring buffer for the cached/buffered log messages; the index pointing
55 * to where to stash the next message then moves around the ring.
56 *
57 * Overwritten messages will be allocated of a module-specific pool. To
58 * prevent this pool from growing unboundedly, we need to clear/destroy it
59 * periodically. But doing this without having to re-copy all of the
60 * buffered log lines could be expensive.
61 *
62 * Instead, what if we use subpools, for every 1/10th of the ring. When
63 * the last message for a subpool is purged/overwritten, that subpool can
64 * be destroyed without effecting any existing message in the ring.
65 */
66
67 #define FORENSIC_DEFAULT_NMSGS 1024
68
69 /* Regardless of the configured ForensicLogBufferSize, this defines the
70 * number of messages per sub-pool.
71 *
72 * Why 256 messages per sub-pool?
73 *
74 * 80 chars (avg) per message * 256 messages = 20 KB
75 *
76 * This means that a given sub-pool will hold roughly 20 KB. Which means
77 * that 20 KB + ring max size is the largest memory that mod_log_forensic
78 * should hold, before releasing a sub-pool back to the Pool API.
79 */
80
81 #define FORENSIC_DEFAULT_MSGS_PER_POOL 256
82 static unsigned int forensic_msgs_per_pool = FORENSIC_DEFAULT_MSGS_PER_POOL;
83
84 struct forensic_msg {
85 pool *fm_pool;
86 unsigned int fm_pool_msgno;
87
88 unsigned int fm_log_type;
89 int fm_log_level;
90 const char *fm_msg;
91 size_t fm_msglen;
92 };
93
94 static struct forensic_msg **forensic_msgs = NULL;
95 static unsigned int forensic_nmsgs = FORENSIC_DEFAULT_NMSGS;
96 static unsigned int forensic_msg_idx = 0;
97
98 static pool *forensic_subpool = NULL;
99 static unsigned int forensic_subpool_msgno = 1;
100
101 #define FORENSIC_MAX_LEVELS 50
102 static const char *forensic_log_levels[] = {
103 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
104 "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
105 "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
106 "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
107 "40", "41", "42", "43", "44", "45", "46", "47", "48", "49"
108 };
109
110 /* Necessary prototypes */
111 static int forensic_sess_init(void);
112
forensic_add_msg(unsigned int log_type,int log_level,const char * log_msg,size_t log_msglen)113 static void forensic_add_msg(unsigned int log_type, int log_level,
114 const char *log_msg, size_t log_msglen) {
115 struct forensic_msg *fm;
116 pool *sub_pool;
117 char *fm_msg;
118
119 /* Get the message that's currently in the ring where we want add our new
120 * one.
121 */
122 fm = forensic_msgs[forensic_msg_idx];
123 if (fm) {
124 /* If this message is the last one of it subpool, destroy that pool. */
125 if (fm->fm_pool_msgno == forensic_msgs_per_pool) {
126 destroy_pool(fm->fm_pool);
127 }
128
129 forensic_msgs[forensic_msg_idx] = NULL;
130 }
131
132 /* Add this message into the ring. */
133 sub_pool = pr_pool_create_sz(forensic_subpool, 128);
134 fm = pcalloc(sub_pool, sizeof(struct forensic_msg));
135 fm->fm_pool = sub_pool;
136 fm->fm_pool_msgno = forensic_subpool_msgno;
137 fm->fm_log_type = log_type;
138 fm->fm_log_level = log_level;
139
140 fm_msg = palloc(fm->fm_pool, log_msglen + 1);
141 memcpy(fm_msg, log_msg, log_msglen);
142 fm_msg[log_msglen] = '\0';
143
144 fm->fm_msg = fm_msg;
145 fm->fm_msglen = log_msglen;
146
147 forensic_msgs[forensic_msg_idx] = fm;
148
149 forensic_msg_idx += 1;
150 if (forensic_msg_idx == forensic_nmsgs) {
151 /* Wrap around */
152 forensic_msg_idx = 0;
153 }
154
155 if (forensic_subpool_msgno == forensic_msgs_per_pool) {
156 /* Time to create a new subpool */
157 forensic_subpool = pr_pool_create_sz(forensic_pool, 256);
158 forensic_subpool_msgno = 1;
159
160 } else {
161 forensic_subpool_msgno++;
162 }
163 }
164
forensic_get_begin_marker(unsigned int criterion,size_t * markerlen)165 static const char *forensic_get_begin_marker(unsigned int criterion,
166 size_t *markerlen) {
167 const char *marker = NULL;
168
169 switch (criterion) {
170 case FORENSIC_CRIT_FAILED_LOGIN:
171 marker = "-----BEGIN FAILED LOGIN FORENSICS-----\n";
172 break;
173
174 case FORENSIC_CRIT_MODULE_CONFIG:
175 marker = "-----BEGIN MODULE CONFIG FORENSICS-----\n";
176 break;
177
178 case FORENSIC_CRIT_UNTIMELY_DEATH:
179 marker = "-----BEGIN UNTIMELY DEATH FORENSICS-----\n";
180 break;
181 }
182
183 if (marker != NULL) {
184 *markerlen = strlen(marker);
185 }
186
187 return marker;
188 }
189
forensic_get_end_marker(unsigned int criterion,size_t * markerlen)190 static const char *forensic_get_end_marker(unsigned int criterion,
191 size_t *markerlen) {
192 const char *marker = NULL;
193
194 switch (criterion) {
195 case FORENSIC_CRIT_FAILED_LOGIN:
196 marker = "-----END FAILED LOGIN FORENSICS-----\n";
197 break;
198
199 case FORENSIC_CRIT_MODULE_CONFIG:
200 marker = "-----END MODULE CONFIG FORENSICS-----\n";
201 break;
202
203 case FORENSIC_CRIT_UNTIMELY_DEATH:
204 marker = "-----END UNTIMELY DEATH FORENSICS-----\n";
205 break;
206 }
207
208 if (marker != NULL) {
209 *markerlen = strlen(marker);
210 }
211
212 return marker;
213 }
214
215 /* Rather than deal with some homegrown itoa() sort of thing for converting
216 * the log level number to an easily printable string, I'm using the level
217 * as an index in a precomputed array of strings.
218 */
forensic_get_level_str(int log_level)219 static const char *forensic_get_level_str(int log_level) {
220 int i;
221
222 i = log_level;
223 if (i < 0) {
224 i = 0;
225 }
226
227 if (i >= FORENSIC_MAX_LEVELS) {
228 return "N";
229 }
230
231 return forensic_log_levels[i];
232 }
233
forensic_write_metadata(void)234 static void forensic_write_metadata(void) {
235 const char *client_ip, *server_ip, *proto, *unique_id;
236 int server_port;
237 char server_port_str[32], uid_str[32], gid_str[32], elapsed_str[64],
238 raw_bytes_in_str[64], raw_bytes_out_str[64],
239 total_bytes_in_str[64], total_bytes_out_str[64],
240 total_files_in_str[64], total_files_out_str[64];
241 size_t unique_idlen = 0;
242
243 /* 64 vectors is currently more than necessary, but it's better to have
244 * too many than too little.
245 */
246 struct iovec iov[64];
247 int niov = 0, res;
248 uint64_t now;
249 unsigned long elapsed_ms;
250
251 /* Write session metadata in key/value message headers:
252 *
253 * Client-Address:
254 * Server-Address:
255 * Elapsed:
256 * Protocol:
257 * User:
258 * UID:
259 * GID:
260 * [UNIQUE_ID:]
261 * Raw-Bytes-In:
262 * Raw-Bytes-Out:
263 * Total-Bytes-In:
264 * Total-Bytes-Out:
265 * Total-Files-In:
266 * Total-Files-Out:
267 */
268
269 client_ip = pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr());
270 server_ip = pr_netaddr_get_ipstr(pr_netaddr_get_sess_local_addr());
271 server_port = ntohs(pr_netaddr_get_port(pr_netaddr_get_sess_local_addr()));
272
273 /* Client address */
274 iov[niov].iov_base = "Client-Address: ";
275 iov[niov].iov_len = 16;
276 niov++;
277
278 iov[niov].iov_base = (void *) client_ip;
279 iov[niov].iov_len = strlen(client_ip);
280 niov++;
281
282 iov[niov].iov_base = "\n";
283 iov[niov].iov_len = 1;
284 niov++;
285
286 /* Server address */
287 iov[niov].iov_base = "Server-Address: ";
288 iov[niov].iov_len = 16;
289 niov++;
290
291 iov[niov].iov_base = (void *) server_ip;
292 iov[niov].iov_len = strlen(server_ip);
293 niov++;
294
295 memset(server_port_str, '\0', sizeof(server_port_str));
296 res = pr_snprintf(server_port_str, sizeof(server_port_str)-1, ":%d\n",
297 server_port);
298 iov[niov].iov_base = server_port_str;
299 iov[niov].iov_len = res;
300 niov++;
301
302 /* Elapsed (in ms) */
303 iov[niov].iov_base = "Elapsed: ";
304 iov[niov].iov_len = 9;
305 niov++;
306
307 pr_gettimeofday_millis(&now);
308 elapsed_ms = (unsigned long) (now - session.connect_time_ms);
309
310 memset(elapsed_str, '\0', sizeof(elapsed_str));
311 res = pr_snprintf(elapsed_str, sizeof(elapsed_str)-1, "%lu\n", elapsed_ms);
312 iov[niov].iov_base = (void *) elapsed_str;
313 iov[niov].iov_len = res;
314 niov++;
315
316 /* Protocol */
317 proto = pr_session_get_protocol(0);
318 iov[niov].iov_base = "Protocol: ";
319 iov[niov].iov_len = 10;
320 niov++;
321
322 iov[niov].iov_base = (char *) proto;
323 iov[niov].iov_len = strlen(proto);
324 niov++;
325
326 iov[niov].iov_base = "\n";
327 iov[niov].iov_len = 1;
328 niov++;
329
330 /* User */
331 if (session.user) {
332 iov[niov].iov_base = "User: ";
333 iov[niov].iov_len = 6;
334 niov++;
335
336 iov[niov].iov_base = (void *) session.user;
337 iov[niov].iov_len = strlen(session.user);
338 niov++;
339
340 iov[niov].iov_base = "\n";
341 iov[niov].iov_len = 1;
342 niov++;
343 }
344
345 /* UID */
346 iov[niov].iov_base = "UID: ";
347 iov[niov].iov_len = 5;
348 niov++;
349
350 memset(uid_str, '\0', sizeof(uid_str));
351 res = pr_snprintf(uid_str, sizeof(uid_str)-1, "%lu\n",
352 (unsigned long) geteuid());
353 iov[niov].iov_base = uid_str;
354 iov[niov].iov_len = res;
355 niov++;
356
357 /* GID */
358 iov[niov].iov_base = "GID: ";
359 iov[niov].iov_len = 5;
360 niov++;
361
362 memset(gid_str, '\0', sizeof(gid_str));
363 res = pr_snprintf(gid_str, sizeof(gid_str)-1, "%lu\n",
364 (unsigned long) getegid());
365 iov[niov].iov_base = gid_str;
366 iov[niov].iov_len = res;
367 niov++;
368
369 /* UNIQUE_ID (from mod_unique_id), if present. */
370 unique_id = pr_table_get(session.notes, "UNIQUE_ID", &unique_idlen);
371 if (unique_id != NULL) {
372 iov[niov].iov_base = "UNIQUE_ID: ";
373 iov[niov].iov_len = 11;
374 niov++;
375
376 iov[niov].iov_base = (char *) unique_id;
377 iov[niov].iov_len = unique_idlen;
378 niov++;
379
380 iov[niov].iov_base = "\n";
381 iov[niov].iov_len = 1;
382 niov++;
383 }
384
385 /* Raw bytes in */
386 iov[niov].iov_base = "Raw-Bytes-In: ";
387 iov[niov].iov_len = 14;
388 niov++;
389
390 if (session.total_raw_in == 0) {
391 iov[niov].iov_base = "0\n";
392 iov[niov].iov_len = 2;
393 niov++;
394
395 } else {
396 memset(raw_bytes_in_str, '\0', sizeof(raw_bytes_in_str));
397 res = pr_snprintf(raw_bytes_in_str, sizeof(raw_bytes_in_str)-1,
398 "%" PR_LU "\n", (pr_off_t) session.total_raw_in);
399 iov[niov].iov_base = raw_bytes_in_str;
400 iov[niov].iov_len = res;
401 niov++;
402 }
403
404 /* Raw bytes out */
405 iov[niov].iov_base = "Raw-Bytes-Out: ";
406 iov[niov].iov_len = 15;
407 niov++;
408
409 if (session.total_raw_out == 0) {
410 iov[niov].iov_base = "0\n";
411 iov[niov].iov_len = 2;
412 niov++;
413
414 } else {
415 memset(raw_bytes_out_str, '\0', sizeof(raw_bytes_out_str));
416 res = pr_snprintf(raw_bytes_out_str, sizeof(raw_bytes_out_str)-1,
417 "%" PR_LU "\n", (pr_off_t) session.total_raw_out);
418 iov[niov].iov_base = raw_bytes_out_str;
419 iov[niov].iov_len = res;
420 niov++;
421 }
422
423 /* Total bytes in */
424 iov[niov].iov_base = "Total-Bytes-In: ";
425 iov[niov].iov_len = 16;
426 niov++;
427
428 if (session.total_bytes_in == 0) {
429 iov[niov].iov_base = "0\n";
430 iov[niov].iov_len = 2;
431 niov++;
432
433 } else {
434 memset(total_bytes_in_str, '\0', sizeof(total_bytes_in_str));
435 res = pr_snprintf(total_bytes_in_str, sizeof(total_bytes_in_str)-1,
436 "%" PR_LU "\n", (pr_off_t) session.total_bytes_in);
437 iov[niov].iov_base = total_bytes_in_str;
438 iov[niov].iov_len = res;
439 niov++;
440 }
441
442 /* Total bytes out */
443 iov[niov].iov_base = "Total-Bytes-Out: ";
444 iov[niov].iov_len = 17;
445 niov++;
446
447 if (session.total_bytes_out == 0) {
448 iov[niov].iov_base = "0\n";
449 iov[niov].iov_len = 2;
450 niov++;
451
452 } else {
453 memset(total_bytes_out_str, '\0', sizeof(total_bytes_out_str));
454 res = pr_snprintf(total_bytes_out_str, sizeof(total_bytes_out_str)-1,
455 "%" PR_LU "\n", (pr_off_t) session.total_bytes_out);
456 iov[niov].iov_base = total_bytes_out_str;
457 iov[niov].iov_len = res;
458 niov++;
459 }
460
461 /* Total files in */
462 iov[niov].iov_base = "Total-Files-In: ";
463 iov[niov].iov_len = 16;
464 niov++;
465
466 if (session.total_files_in == 0) {
467 iov[niov].iov_base = "0\n";
468 iov[niov].iov_len = 2;
469 niov++;
470
471 } else {
472 memset(total_files_in_str, '\0', sizeof(total_files_in_str));
473 res = pr_snprintf(total_files_in_str, sizeof(total_files_in_str)-1,
474 "%u\n", session.total_files_in);
475 iov[niov].iov_base = total_files_in_str;
476 iov[niov].iov_len = res;
477 niov++;
478 }
479
480 /* Total files out */
481 iov[niov].iov_base = "Total-Files-Out: ";
482 iov[niov].iov_len = 17;
483 niov++;
484
485 if (session.total_files_out == 0) {
486 iov[niov].iov_base = "0\n";
487 iov[niov].iov_len = 2;
488 niov++;
489
490 } else {
491 memset(total_files_out_str, '\0', sizeof(total_files_out_str));
492 res = pr_snprintf(total_files_out_str, sizeof(total_files_out_str)-1,
493 "%u\n", session.total_files_out);
494 iov[niov].iov_base = total_files_out_str;
495 iov[niov].iov_len = res;
496 niov++;
497 }
498
499 iov[niov].iov_base = "\n";
500 iov[niov].iov_len = 1;
501 niov++;
502
503 res = writev(forensic_logfd, iov, niov);
504 }
505
forensic_write_msgs(unsigned int criterion)506 static void forensic_write_msgs(unsigned int criterion) {
507 register unsigned int i;
508 unsigned int start_idx, end_idx;
509 int res;
510 const char *crit_marker = NULL;
511 size_t crit_markerlen = 0;
512
513 /* XXX An interesting optimization would be to rework this code so that
514 * we used writev(2) to write out the buffer as quickly as possible,
515 * taking IOV_MAX into account.
516 */
517
518 crit_marker = forensic_get_begin_marker(criterion, &crit_markerlen);
519 if (crit_marker != NULL) {
520 res = write(forensic_logfd, crit_marker, crit_markerlen);
521 }
522
523 forensic_write_metadata();
524
525 /* The head of the log messages (i.e. the oldest message) is always where we
526 * want to place the newest message.
527 */
528 start_idx = forensic_msg_idx;
529 end_idx = forensic_msg_idx - 1;
530 if (forensic_msg_idx == 0) {
531 end_idx = forensic_nmsgs - 1;
532 }
533
534 i = start_idx;
535 while (i != end_idx) {
536 struct forensic_msg *fm;
537
538 pr_signals_handle();
539
540 fm = forensic_msgs[i];
541 if (fm != NULL) {
542 const char *level;
543 size_t level_len;
544
545 level = forensic_get_level_str(fm->fm_log_level);
546 level_len = strlen(level);
547
548 switch (fm->fm_log_type) {
549 case PR_LOG_TYPE_UNSPEC:
550 res = write(forensic_logfd, "[Unspec:", 8);
551 res = write(forensic_logfd, level, level_len);
552 res = write(forensic_logfd, "] ", 2);
553 break;
554
555 case PR_LOG_TYPE_XFERLOG:
556 res = write(forensic_logfd, "[TransferLog:", 13);
557 res = write(forensic_logfd, level, level_len);
558 res = write(forensic_logfd, "] ", 2);
559 break;
560
561 case PR_LOG_TYPE_SYSLOG: {
562 char pid_str[32];
563
564 res = write(forensic_logfd, "[syslog:", 8);
565 res = write(forensic_logfd, level, level_len);
566
567 /* syslogd normally adds the PID; we thus need to add the PID in
568 * here as well, to aid in the correlation of these log lines
569 * with other tools/diagnostics.
570 */
571 res = write(forensic_logfd, ", PID ", 6);
572
573 memset(pid_str, '\0', sizeof(pid_str));
574 res = pr_snprintf(pid_str, sizeof(pid_str)-1, "%lu",
575 (unsigned long) (session.pid ? session.pid : getpid()));
576 res = write(forensic_logfd, pid_str, res);
577
578 res = write(forensic_logfd, "] ", 2);
579 break;
580 }
581
582 case PR_LOG_TYPE_SYSTEMLOG:
583 res = write(forensic_logfd, "[SystemLog:", 11);
584 res = write(forensic_logfd, level, level_len);
585 res = write(forensic_logfd, "] ", 2);
586 break;
587
588 case PR_LOG_TYPE_EXTLOG:
589 res = write(forensic_logfd, "[ExtendedLog:", 13);
590 res = write(forensic_logfd, level, level_len);
591 res = write(forensic_logfd, "] ", 2);
592 break;
593
594 case PR_LOG_TYPE_TRACELOG:
595 res = write(forensic_logfd, "[TraceLog:", 10);
596 res = write(forensic_logfd, level, level_len);
597 res = write(forensic_logfd, "] ", 2);
598 break;
599 }
600
601 res = write(forensic_logfd, fm->fm_msg, fm->fm_msglen);
602 while (res < 0) {
603 if (errno == EINTR) {
604 pr_signals_handle();
605 res = write(forensic_logfd, fm->fm_msg, fm->fm_msglen);
606 continue;
607 }
608 }
609
610 /* syslog-type messages don't have a newline appended to them, since
611 * syslogd handles that. So we then need to add our own newline here.
612 */
613 if (fm->fm_log_type == PR_LOG_TYPE_SYSLOG) {
614 res = write(forensic_logfd, "\n", 1);
615 }
616
617 if (fm->fm_pool_msgno == forensic_msgs_per_pool) {
618 destroy_pool(fm->fm_pool);
619 }
620
621 forensic_msgs[i] = NULL;
622 }
623
624 i++;
625 if (i == forensic_nmsgs) {
626 /* Wrap around */
627 i = 0;
628 }
629 }
630
631 crit_marker = forensic_get_end_marker(criterion, &crit_markerlen);
632 if (crit_marker != NULL) {
633 res = write(forensic_logfd, crit_marker, crit_markerlen);
634 }
635 }
636
637 /* Configuration handlers
638 */
639
640 /* usage: ForensicLogBufferSize count */
set_forensiclogbuffersize(cmd_rec * cmd)641 MODRET set_forensiclogbuffersize(cmd_rec *cmd) {
642 config_rec *c;
643 unsigned int count;
644 char *ptr = NULL;
645
646 CHECK_ARGS(cmd, 1);
647 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
648
649 count = strtoul(cmd->argv[1], &ptr, 10);
650 if (ptr && *ptr) {
651 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "badly formatted number: ",
652 cmd->argv[1], NULL));
653 }
654
655 if (count == 0) {
656 CONF_ERROR(cmd, "size must be greater than zero");
657 }
658
659 c = add_config_param(cmd->argv[0], 1, NULL);
660 c->argv[0] = palloc(c->pool, sizeof(unsigned int));
661 *((unsigned int *) c->argv[0]) = count;
662
663 return PR_HANDLED(cmd);
664 }
665
666 /* usage: ForensicLogCapture type1 ... typeN */
set_forensiclogcapture(cmd_rec * cmd)667 MODRET set_forensiclogcapture(cmd_rec *cmd) {
668 config_rec *c;
669 int unspec_listen = FALSE;
670 int xferlog_listen = FALSE;
671 int syslog_listen = FALSE;
672 int systemlog_listen = FALSE;
673 int extlog_listen = FALSE;
674 int tracelog_listen = FALSE;
675 register unsigned int i;
676
677 if (cmd->argc-1 < 1) {
678 CONF_ERROR(cmd, "wrong number of parameters");
679 }
680
681 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
682
683 for (i = 1; i < cmd->argc; i++) {
684 if (strncasecmp(cmd->argv[i], "Unspec", 7) == 0 ||
685 strncasecmp(cmd->argv[i], "Unknown", 8) == 0) {
686 unspec_listen = TRUE;
687
688 } else if (strncasecmp(cmd->argv[i], "TransferLog", 12) == 0) {
689 xferlog_listen = TRUE;
690
691 } else if (strncasecmp(cmd->argv[i], "Syslog", 7) == 0) {
692 syslog_listen = TRUE;
693
694 } else if (strncasecmp(cmd->argv[i], "SystemLog", 10) == 0) {
695 systemlog_listen = TRUE;
696
697 } else if (strncasecmp(cmd->argv[i], "ExtendedLog", 12) == 0) {
698 extlog_listen = TRUE;
699
700 } else if (strncasecmp(cmd->argv[i], "TraceLog", 9) == 0) {
701 tracelog_listen = TRUE;
702
703 } else {
704 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown log type: ",
705 cmd->argv[i], NULL));
706 }
707 }
708
709 c = add_config_param(cmd->argv[0], 6, NULL, NULL, NULL, NULL, NULL, NULL);
710 c->argv[0] = palloc(c->pool, sizeof(int));
711 *((int *) c->argv[0]) = unspec_listen;
712 c->argv[1] = palloc(c->pool, sizeof(int));
713 *((int *) c->argv[1]) = xferlog_listen;
714 c->argv[2] = palloc(c->pool, sizeof(int));
715 *((int *) c->argv[2]) = syslog_listen;
716 c->argv[3] = palloc(c->pool, sizeof(int));
717 *((int *) c->argv[3]) = systemlog_listen;
718 c->argv[4] = palloc(c->pool, sizeof(int));
719 *((int *) c->argv[4]) = extlog_listen;
720 c->argv[5] = palloc(c->pool, sizeof(int));
721 *((int *) c->argv[5]) = tracelog_listen;
722
723 return PR_HANDLED(cmd);
724 }
725
726 /* usage: ForensicLogCriteria ... */
set_forensiclogcriteria(cmd_rec * cmd)727 MODRET set_forensiclogcriteria(cmd_rec *cmd) {
728 config_rec *c;
729 unsigned long criteria = 0UL;
730 register unsigned int i;
731
732 if (cmd->argc-1 < 1) {
733 CONF_ERROR(cmd, "wrong number of parameters");
734 }
735
736 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
737
738 /* Possible criteria:
739 *
740 * FailedLogin
741 * ModuleConfig
742 * UntimelyDeath
743 */
744
745 for (i = 1; i < cmd->argc; i++) {
746 if (strncasecmp(cmd->argv[i], "FailedLogin", 12) == 0) {
747 criteria |= FORENSIC_CRIT_FAILED_LOGIN;
748
749 } else if (strncasecmp(cmd->argv[i], "ModuleConfig", 13) == 0) {
750 criteria |= FORENSIC_CRIT_MODULE_CONFIG;
751
752 } else if (strncasecmp(cmd->argv[i], "UntimelyDeath", 14) == 0) {
753 criteria |= FORENSIC_CRIT_UNTIMELY_DEATH;
754
755 } else {
756 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown criterion: ",
757 cmd->argv[i], NULL));
758 }
759 }
760
761 c = add_config_param(cmd->argv[0], 1, NULL);
762 c->argv[0] = palloc(c->pool, sizeof(unsigned long));
763 *((unsigned long *) c->argv[0]) = criteria;
764
765 return PR_HANDLED(cmd);
766 }
767
768 /* usage: ForensicLogEngine on|off */
set_forensiclogengine(cmd_rec * cmd)769 MODRET set_forensiclogengine(cmd_rec *cmd) {
770 config_rec *c;
771 int bool;
772
773 CHECK_ARGS(cmd, 1);
774 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
775
776 bool = get_boolean(cmd, 1);
777 if (bool == -1) {
778 CONF_ERROR(cmd, "expected Boolean parameter");
779 }
780
781 c = add_config_param(cmd->argv[0], 1, NULL);
782 c->argv[0] = palloc(c->pool, sizeof(int));
783 *((int *) c->argv[0]) = bool;
784
785 return PR_HANDLED(cmd);
786 }
787
788 /* usage: ForensicLogFile path */
set_forensiclogfile(cmd_rec * cmd)789 MODRET set_forensiclogfile(cmd_rec *cmd) {
790 CHECK_ARGS(cmd, 1);
791 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
792
793 if (pr_fs_valid_path(cmd->argv[1]) < 0) {
794 CONF_ERROR(cmd, "must be an absolute path");
795 }
796
797 (void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
798 return PR_HANDLED(cmd);
799 }
800
801 /* Command handlers
802 */
803
forensic_pass_err(cmd_rec * cmd)804 MODRET forensic_pass_err(cmd_rec *cmd) {
805 if (!forensic_engine) {
806 return PR_DECLINED(cmd);
807 }
808
809 if (forensic_criteria & FORENSIC_CRIT_FAILED_LOGIN) {
810 forensic_write_msgs(FORENSIC_CRIT_FAILED_LOGIN);
811 }
812
813 return PR_DECLINED(cmd);
814 }
815
816 /* Event Listeners
817 */
818
forensic_exit_ev(const void * event_data,void * user_data)819 static void forensic_exit_ev(const void *event_data, void *user_data) {
820
821 switch (session.disconnect_reason) {
822 case PR_SESS_DISCONNECT_SIGNAL:
823 if (forensic_criteria & FORENSIC_CRIT_UNTIMELY_DEATH) {
824 forensic_write_msgs(FORENSIC_CRIT_UNTIMELY_DEATH);
825 }
826 break;
827
828 case PR_SESS_DISCONNECT_MODULE_ACL:
829 if (forensic_criteria & FORENSIC_CRIT_MODULE_CONFIG) {
830 forensic_write_msgs(FORENSIC_CRIT_MODULE_CONFIG);
831 }
832 break;
833 }
834
835 return;
836 }
837
forensic_log_ev(const void * event_data,void * user_data)838 static void forensic_log_ev(const void *event_data, void *user_data) {
839 const pr_log_event_t *le;
840
841 le = event_data;
842 forensic_add_msg(le->log_type, le->log_level, le->log_msg, le->log_msglen);
843 }
844
845 #if defined(PR_SHARED_MODULE)
forensic_mod_unload_ev(const void * event_data,void * user_data)846 static void forensic_mod_unload_ev(const void *event_data, void *user_data) {
847 if (strcmp("mod_log_forensic.c", (const char *) event_data) == 0) {
848 pr_event_unregister(&log_forensic_module, NULL, NULL);
849 }
850 }
851 #endif /* PR_SHARED_MODULE */
852
forensic_sess_reinit_ev(const void * event_data,void * user_data)853 static void forensic_sess_reinit_ev(const void *event_data, void *user_data) {
854 int res;
855
856 /* A HOST command changed the main_server pointer, reinitialize ourselves. */
857
858 pr_event_unregister(&log_forensic_module, "core.exit", forensic_exit_ev);
859 pr_event_unregister(&log_forensic_module, "core.log.unspec", forensic_log_ev);
860 pr_event_unregister(&log_forensic_module, "core.log.xferlog",
861 forensic_log_ev);
862 pr_event_unregister(&log_forensic_module, "core.log.syslog", forensic_log_ev);
863 pr_event_unregister(&log_forensic_module, "core.log.systemlog",
864 forensic_log_ev);
865 pr_event_unregister(&log_forensic_module, "core.log.extlog", forensic_log_ev);
866 pr_event_unregister(&log_forensic_module, "core.log.tracelog",
867 forensic_log_ev);
868 pr_event_unregister(&log_forensic_module, "core.session-reinit",
869 forensic_sess_reinit_ev);
870
871 forensic_engine = FALSE;
872 (void) close(forensic_logfd);
873 forensic_logfd = -1;
874 forensic_criteria = FORENSIC_CRIT_DEFAULT;
875 forensic_msgs = NULL;
876 forensic_nmsgs = FORENSIC_DEFAULT_NMSGS;
877 forensic_msg_idx = 0;
878
879 if (forensic_subpool != NULL) {
880 destroy_pool(forensic_subpool);
881 forensic_subpool = NULL;
882 }
883
884 forensic_subpool_msgno = 1;
885
886 res = forensic_sess_init();
887 if (res < 0) {
888 pr_session_disconnect(&log_forensic_module,
889 PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
890 }
891 }
892
893 /* Module Initialization
894 */
895
forensic_init(void)896 static int forensic_init(void) {
897 #if defined(PR_SHARED_MODULE)
898 pr_event_register(&log_forensic_module, "core.module-unload",
899 forensic_mod_unload_ev, NULL);
900 #endif /* PR_SHARED_MODULE */
901
902 return 0;
903 }
904
forensic_sess_init(void)905 static int forensic_sess_init(void) {
906 config_rec *c;
907 int unspec_listen = TRUE;
908 int xferlog_listen = TRUE;
909 int syslog_listen = TRUE;
910 int systemlog_listen = TRUE;
911 int extlog_listen = TRUE;
912 int tracelog_listen = TRUE;
913 int res, xerrno;
914
915 pr_event_register(&log_forensic_module, "core.session-reinit",
916 forensic_sess_reinit_ev, NULL);
917
918 /* Is this module enabled? */
919 c = find_config(main_server->conf, CONF_PARAM, "ForensicLogEngine", FALSE);
920 if (c != NULL) {
921 forensic_engine = *((int *) c->argv[0]);
922 }
923
924 if (forensic_engine != TRUE) {
925 return 0;
926 }
927
928 /* Do we have the required path for our logging? */
929 c = find_config(main_server->conf, CONF_PARAM, "ForensicLogFile", FALSE);
930 if (c == NULL) {
931 pr_log_debug(DEBUG1, MOD_LOG_FORENSIC_VERSION
932 ": missing required ForensicLogFile setting, disabling module");
933 forensic_engine = FALSE;
934 return 0;
935 }
936
937 pr_signals_block();
938 PRIVS_ROOT
939 res = pr_log_openfile((const char *) c->argv[0], &forensic_logfd, 0640);
940 xerrno = errno;
941 PRIVS_RELINQUISH
942 pr_signals_unblock();
943
944 if (res < 0) {
945 const char *path;
946
947 path = c->argv[0];
948
949 if (res == -1) {
950 pr_log_pri(PR_LOG_NOTICE, MOD_LOG_FORENSIC_VERSION
951 ": notice: unable to open ForensicLogFile '%s': %s", path,
952 strerror(xerrno));
953
954 } else if (res == PR_LOG_WRITABLE_DIR) {
955 pr_log_pri(PR_LOG_WARNING, MOD_LOG_FORENSIC_VERSION
956 ": notice: unable to open ForensicLogFile '%s': parent directory is "
957 "world-writable", path);
958
959 } else if (res == PR_LOG_SYMLINK) {
960 pr_log_pri(PR_LOG_WARNING, MOD_LOG_FORENSIC_VERSION
961 ": notice: unable to open ForensicLogFile '%s': "
962 "cannot log to a symlink", path);
963 }
964
965 pr_log_debug(DEBUG0, MOD_LOG_FORENSIC_VERSION
966 ": unable to ForensicLogFile '%s', disabling module", path);
967 forensic_engine = FALSE;
968 return 0;
969 }
970
971 /* Are there any log types for which we shouldn't be listening? */
972 c = find_config(main_server->conf, CONF_PARAM, "ForensicLogCapture", FALSE);
973 if (c) {
974 unspec_listen = *((int *) c->argv[0]);
975 xferlog_listen = *((int *) c->argv[1]);
976 syslog_listen = *((int *) c->argv[2]);
977 systemlog_listen = *((int *) c->argv[3]);
978 extlog_listen = *((int *) c->argv[4]);
979 tracelog_listen = *((int *) c->argv[5]);
980 }
981
982 /* What criteria are we to use for logging our captured log messages */
983 c = find_config(main_server->conf, CONF_PARAM, "ForensicLogCriteria", FALSE);
984 if (c) {
985 forensic_criteria = *((unsigned long *) c->argv[0]);
986 }
987
988 if (forensic_pool == NULL) {
989 forensic_pool = make_sub_pool(session.pool);
990 pr_pool_tag(forensic_pool, MOD_LOG_FORENSIC_VERSION);
991 }
992
993 c = find_config(main_server->conf, CONF_PARAM, "ForensicLogBufferSize",
994 FALSE);
995 if (c) {
996 forensic_nmsgs = *((unsigned int *) c->argv[0]);
997
998 if (forensic_nmsgs < forensic_msgs_per_pool) {
999 forensic_msgs_per_pool = forensic_nmsgs;
1000 }
1001 }
1002
1003 forensic_msgs = pcalloc(forensic_pool,
1004 sizeof(struct forensic_msg) * forensic_nmsgs);
1005 forensic_subpool = pr_pool_create_sz(forensic_pool, 256);
1006
1007 /* We register our event listeners as the last thing we do. */
1008
1009 if ((forensic_criteria & FORENSIC_CRIT_MODULE_CONFIG) ||
1010 (forensic_criteria & FORENSIC_CRIT_UNTIMELY_DEATH)) {
1011 pr_event_register(&log_forensic_module, "core.exit", forensic_exit_ev,
1012 NULL);
1013 }
1014
1015 if (unspec_listen) {
1016 pr_event_register(&log_forensic_module, "core.log.unspec", forensic_log_ev,
1017 NULL);
1018 }
1019
1020 if (xferlog_listen) {
1021 pr_event_register(&log_forensic_module, "core.log.xferlog", forensic_log_ev,
1022 NULL);
1023 }
1024
1025 if (syslog_listen) {
1026 pr_event_register(&log_forensic_module, "core.log.syslog", forensic_log_ev,
1027 NULL);
1028 }
1029
1030 if (systemlog_listen) {
1031 pr_event_register(&log_forensic_module, "core.log.systemlog",
1032 forensic_log_ev, NULL);
1033 }
1034
1035 if (extlog_listen) {
1036 pr_event_register(&log_forensic_module, "core.log.extlog", forensic_log_ev,
1037 NULL);
1038 }
1039
1040 if (tracelog_listen) {
1041 pr_event_register(&log_forensic_module, "core.log.tracelog",
1042 forensic_log_ev, NULL);
1043 }
1044
1045 return 0;
1046 }
1047
1048 /* Module API tables
1049 */
1050
1051 static conftable forensic_conftab[] = {
1052 { "ForensicLogBufferSize", set_forensiclogbuffersize, NULL },
1053 { "ForensicLogCapture", set_forensiclogcapture, NULL },
1054 { "ForensicLogCriteria", set_forensiclogcriteria, NULL },
1055 { "ForensicLogEngine", set_forensiclogengine, NULL },
1056 { "ForensicLogFile", set_forensiclogfile, NULL },
1057
1058 { NULL, NULL, NULL }
1059 };
1060
1061 static cmdtable forensic_cmdtab[] = {
1062 { LOG_CMD_ERR, C_PASS, G_NONE, forensic_pass_err, FALSE, FALSE },
1063
1064 { 0, NULL }
1065 };
1066
1067 module log_forensic_module = {
1068 /* Always NULL */
1069 NULL, NULL,
1070
1071 /* Module API version */
1072 0x20,
1073
1074 /* Module name */
1075 "log_forensic",
1076
1077 /* Module configuration directive table */
1078 forensic_conftab,
1079
1080 /* Module command handler table */
1081 forensic_cmdtab,
1082
1083 /* Module auth handler table */
1084 NULL,
1085
1086 /* Module initialization */
1087 forensic_init,
1088
1089 /* Session initialization */
1090 forensic_sess_init,
1091
1092 /* Module version */
1093 MOD_LOG_FORENSIC_VERSION
1094 };
1095
1096