1 /************************************************************************
2 *
3 * "minirsyslogd.c" by <mikael.olsson@clavister.com>
4 *
5 * Simplistic, fast and secure (through lack of bloat) remote syslog
6 * receiver suitable for hardened log receiver hosts and/or central log
7 * receivers that receive several gigabytes of logs each day.
8 *
9 *
10 * TERMS OF USE ("no terms")
11 *
12 * minirsyslogd is released into the public domain, and, as such, is
13 * provided "AS IS". Do with it what you wish. I'll happily consider
14 * adding relevant improvements to it, but be warned: I won't personally
15 * host anything called 'minirsyslogd' that grows to include a full-sized
16 * ruleset, etc...
17 *
18 *
19 * VERSION HISTORY
20 *
21 * v1.02: 2003-10-30
22 * Initial public release. Prior to this, it has been in operation
23 * in various locations for over a year.
24 *
25 * There never was a version 1.0. Paul Robertson added his two cents.
26 *
27 *
28 * BASIC IDEA OF OPERATION
29 *
30 * - Receive inbound UDP syslog packets on a port of the user's choice.
31 * (Do NOT deal with local syslog sockets! This is remote only.)
32 *
33 * - Store in a structured way according to sender IP, e.g.:
34 * ./192.168.0.123/192.168.0.123-2002102407
35 * (The timestamp is YYYYMMDDHH, or YYYYMMDD with --split day)
36 *
37 * - Open files always close at the turn of the hour.
38 *
39 * - Do NOT create directories automatically.
40 * The existance/nonexistance of a destination directory is the
41 * Access Control List of the receiver.
42 *
43 * - If the open file table is full, a random file will be closed
44 * to allow a new one to be opened. This is better than it sounds;
45 * definitely a LOT better than "closing the oldest one".
46 *
47 * - Once an hour: Output a list IP addresses denied access.
48 * The size of this list is limited to 10 entries.
49 *
50 * - Do nothing else. KISS - Keep It Simple Stupid.
51 * Also, keep it fast. The current implementation was about
52 * 20 times faster than syslog-ng last time I measured.
53 *
54 *
55 * FUTURE IMPROVEMENTS
56 *
57 * - Smarter error reporting for fopen() failures (did it fail because the
58 * destination directory didn't exist? any other reason?)
59 *
60 ************************************************************************/
61
62
63
64 /************************************************************************
65 *
66 * Tweakables..
67 *
68 ************************************************************************/
69
70
71 #define MAX_DESTS 1000 // Maximum number of concurrently open files
72 // (Further limited by what your OS supports)
73
74 #define DEST_HASH_BITS 12 // 1<<bits must be >= MAX_DESTS*3
75 // 1<<12 = 4096 .. 1<<13 = 8192 .. 1<<14 = 16384
76 // 1<<15 = 32768 .. 1<<16 = 65536 .. 1<<17 = 131072
77
78 #define MAX_FAILREPORTS 10 // Maximum number of "failed to open" reports
79 // per hour
80
81
82
83 /************************************************************************
84 *
85 * Includes and typedefs
86 *
87 ************************************************************************/
88
89 #include <stdio.h>
90 #include <stdlib.h>
91 #include <stdarg.h> // varargs.h instead?
92 #include <string.h>
93 #include <sys/time.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <unistd.h>
97 #include <sys/socket.h>
98 #include <errno.h>
99 #include <netdb.h>
100 #include <netinet/in.h>
101 #include <unistd.h>
102 #include <time.h>
103 #include <signal.h>
104 #include <fcntl.h>
105
106 #define VERSION_STRING "1.02"
107
108 typedef char *LPSTR;
109 typedef const char *LPCSTR;
110 typedef unsigned int DWORD;
111 typedef short unsigned int WORD;
112 typedef int BOOL;
113 typedef void * PVOID;
114 typedef unsigned char BYTE, *PBYTE;
115 #define __far
116
117 #ifndef FALSE
118 #define FALSE (0)
119 #define TRUE (!FALSE)
120 #endif
121
122 #define stricmp strcasecmp
123
124 #define mymin(a,b) ((a)<(b) ? (a) : (b))
125
126 /************************************************************************
127 *
128 * CONSTANTS
129 *
130 ************************************************************************/
131
132 // Syslog facilities / severities
133 enum {
134 FACILITY_KERNEL = 0<<3,
135 FACILITY_USER = 1<<3,
136 FACILITY_MAIL = 2<<3,
137 FACILITY_SYSDAEMON = 3<<3,
138 FACILITY_AUTH1 = 4<<3,
139 FACILITY_SYSLOGD = 5<<3,
140 FACILITY_LPR = 6<<3,
141 FACILITY_NEWS = 7<<3,
142 FACILITY_UUCP = 8<<3,
143 FACILITY_CLOCK1 = 9<<3,
144 FACILITY_AUTH2 = 10<<3,
145 FACILITY_FTP = 11<<3,
146 FACILITY_NTP = 12<<3,
147 FACILITY_LOGAUDIT = 13<<3,
148 FACILITY_LOGALERT = 14<<3,
149 FACILITY_CLOCK2 = 15<<3,
150 FACILITY_LOCAL0 = 16<<3,
151 FACILITY_LOCAL1 = 17<<3,
152 FACILITY_LOCAL2 = 18<<3,
153 FACILITY_LOCAL3 = 19<<3,
154 FACILITY_LOCAL4 = 20<<3,
155 FACILITY_LOCAL5 = 21<<3,
156 FACILITY_LOCAL6 = 22<<3,
157 FACILITY_LOCAL7 = 23<<3,
158
159 NUM_FACILITIES = 24,
160
161 SEVERITY_EMERG = 0,
162 SEVERITY_ALERT = 1,
163 SEVERITY_CRIT = 2,
164 SEVERITY_ERROR = 3,
165 SEVERITY_WARN = 4,
166 SEVERITY_NOTICE = 5,
167 SEVERITY_INFO = 6,
168 SEVERITY_DEBUG = 7
169 };
170
171
172 // Receive modes
173 enum
174 {
175 RECVMODE_TRUNCATE,
176 RECVMODE_FLAT,
177 RECVMODE_SPLIT, // Default
178 RECVMODE_FORENSIC,
179 RECVMODE_FORENSICRAW
180 };
181
182
183 // Log file splitting modes
184 enum
185 {
186 SPLITMODE_HOUR, // Default
187 SPLITMODE_DAY
188 };
189
190
191
192 /***************************************************************************
193 *
194 * Type definitions and macros
195 *
196 ***************************************************************************/
197
198
199 // Basic IPv4 address representation -- struct inaddr never looks the same
200 typedef union tagIP4ADDR
201 {
202 DWORD dwIP4;
203 BYTE achIP4[4];
204 } IP4ADDR, *PIP4ADDR;
205
206
207 // Description of an (open) log output destination
208 typedef struct tagDEST
209 {
210 FILE *fp;
211 long lFileSize;
212 IP4ADDR IPAddr;
213 char szIPStr[20];
214 char *vbuf;
215 } DEST, *PDEST;
216
217
218 // Failure report for a single IP address (with occurence counter)
219 typedef struct tagFAILREPORT
220 {
221 IP4ADDR IPAddr;
222 int nOccurences;
223 } FAILREPORT, *PFAILREPORT;
224
225 // Information about current time
226 typedef struct tagTIMEINFO
227 {
228 struct timeval tv;
229 int nUTCOffs;
230 char szTimestamp[64]; // Time like "2002-01-05T12:34:56%06u+01:00" or "Jan 5 12:34:56" (if "--oldtimestamp")
231 char szNameTimestamp_Day[20]; // Time like "20020105"
232 char szNameTimestamp[20]; // Time like "2002010512" or "20020105" (if "--split day")
233 } TIMEINFO, *PTIMEINFO;
234 typedef const TIMEINFO *LPCTIMEINFO;
235
236
237
238 /************************************************************************
239 *
240 * Global variables
241 *
242 ************************************************************************/
243
244 PDEST g_apDests[MAX_DESTS];
245 int g_nDests;
246 PDEST g_apUnusedDests[MAX_DESTS];
247 int g_nUnusedDests;
248
249 PDEST g_apDestHash[1<<DEST_HASH_BITS];
250
251
252 int g_nRecvMode = RECVMODE_SPLIT;
253
254 LPCSTR g_pszProgname = "minirsyslogd";
255
256 // These two get reset every hour
257 DWORD g_dwBytesReceived=0; // Counts up to 1MB
258 DWORD g_dwMegaBytesReceived=0; // Counts the rest
259
260 FILE *g_fpDaemonLog = NULL;
261
262
263 FAILREPORT g_aFailReports[MAX_FAILREPORTS];
264 int g_nFailReports=0;
265 int g_nWildcardFails = 0; // Failures not accounted for in the g_aFailReports array
266
267 BOOL g_bExitNow = FALSE; // Set to TRUE by SIGINT and SIGTERM
268 BOOL g_bCloseAllFilesNow = FALSE; // Set to TRUE by SIGHUP
269
270 TIMEINFO g_tiNow; // Lots of places need the current time
271
272
273
274 /************************************************************************
275 *
276 * Global variables -- settings parsed from cmdline
277 *
278 ************************************************************************/
279
280 BOOL g_bVerbose = FALSE;
281 int g_nBufSize=16384;
282 int g_nPort=514;
283 int g_nMaxDests=50;
284 int g_nMaxOpensPerSec=200;
285 long g_lMaxFileSize=2000000000; // Don't exceed 2GB file size limit
286 BOOL g_bDaemon = FALSE;
287 int g_nSplitMode = SPLITMODE_HOUR;
288 BOOL g_bOldTimestamps = FALSE; // Use old-style syslog timestamps rather than RFC3339
289 mode_t g_nUmask = 0077;
290 LPCSTR g_pszRootDir = ".";
291
292
293
294 /************************************************************************
295 *
296 * Function : mystrlcpy
297 *
298 * Purpose : strncpy with guaranteed NUL termination
299 *
300 * Params : [O] char *dest
301 * [I] const char *src
302 * [I] size_t n
303 *
304 * Returns : -
305 *
306 ************************************************************************/
307
mystrlcpy(char * dest,const char * src,size_t n)308 void mystrlcpy(char *dest, const char *src, size_t n)
309 {
310 strncpy(dest, src, n);
311 dest[n-1]='\0';
312 }
313
314
315 /************************************************************************
316 *
317 * Function : GetTimestamp
318 *
319 * Purpose : Return log record timestamp string like
320 * "2002-01-05T12:34:56.456789+01:00" or
321 * "Jan 5 12:34:56" (if g_bOldTimestamps)
322 *
323 * Note : Uses internal static buffer. Next call overwrites.
324 *
325 * Params : [I] LPCTIMEINFO pti
326 *
327 * Returns : LPCSTR -- pointer to usable timestamp string
328 * in static buffer
329 *
330 ************************************************************************/
331
GetTimestamp(LPCTIMEINFO pti)332 LPCSTR GetTimestamp(LPCTIMEINFO pti)
333 {
334 static char achBuf[64];
335
336 if(g_bOldTimestamps)
337 mystrlcpy(achBuf, pti->szTimestamp, sizeof(achBuf));
338 else
339 // szTimestamp will contain a "%06u" where the microsecond value goes
340 snprintf(achBuf, sizeof(achBuf), pti->szTimestamp, pti->tv.tv_usec);
341
342 return achBuf;
343 }
344
345
346 /************************************************************************
347 *
348 * Function : dlog
349 *
350 * Purpose : Output a message to the local daemon log
351 * Accepts printf-style input
352 *
353 * If the daemon log is not open: dump on stderr
354 *
355 * If g_bVerbose, messages will always be copied to stdout
356 *
357 * Params : [I] int nWhere -- 0: only daemon log
358 * 1: daemon log + stdout
359 * 2: daemon log + stderr
360 * [I] LPCSTR pszFmt -- printf-style format string
361 * [I] ... -- args
362 *
363 * Returns : -
364 *
365 ************************************************************************/
366
dlog(int nWhere,LPCSTR pszFmt,...)367 void dlog(int nWhere, LPCSTR pszFmt, ...)
368 {
369 va_list args;
370 FILE *fp;
371
372 // If the local daemon log file isn't open, dump the message on stderr
373 fp=g_fpDaemonLog;
374 if(!g_fpDaemonLog)
375 fp = stderr;
376
377 fprintf(fp, "%s ", GetTimestamp(&g_tiNow));
378
379 va_start(args, pszFmt);
380 vfprintf(fp, pszFmt, args);
381 va_end(args);
382
383 fprintf(fp, "\n");
384
385 fflush(fp);
386
387 // If we're verbose, or nWhere>0: output on stdout too
388 if( g_bVerbose || nWhere>0 )
389 {
390 if(nWhere>=2 && fp!=stderr)
391 fp=stderr;
392 else
393 fp=stdout;
394 fprintf(fp, "%s ", g_pszProgname);
395 va_start(args, pszFmt);
396 vfprintf(fp, pszFmt, args);
397 va_end(args);
398 fprintf(fp,"\n");
399 fflush(fp);
400 }
401 }
402
403
404
405 /************************************************************************
406 *
407 * Function : bomb / bomberrno
408 *
409 * Purpose : - Print error message
410 * (bomberrno: append error description)
411 * - exit(1)
412 *
413 * Params : [I] LPCSTR str
414 *
415 * Returns : -
416 *
417 ************************************************************************/
418
bomb(LPCSTR pszMsg)419 void bomb(LPCSTR pszMsg)
420 {
421 dlog(2, "fatal: %s", pszMsg);
422 exit(1);
423 }
424
bomberrno(LPCSTR pszMsg)425 void bomberrno(LPCSTR pszMsg)
426 {
427 dlog(2, "fatal: %s: %s", pszMsg, strerror(errno));
428 exit(1);
429 }
430
431
432
433
434
435
436 /************************************************************************
437 *
438 * Function : myrand
439 *
440 * Purpose : Low-cost PRNG which behaves "well" for these purposes.
441 * Don't you DARE think about using it for anything
442 * security critical - it is very predictable if the
443 * outputs are actually revealed.
444 *
445 * Returns : DWORD -- 32-bit pseudorandom number
446 * The best entropy is returned in the low 16 bits.
447 *
448 ************************************************************************/
449
myrand(void)450 DWORD myrand(void)
451 {
452 static DWORD dwLCPRNG=0xdeadbeef;
453 static DWORD dwCntr=0;
454
455 // Simple LCPRNG, period 2^31-1
456 dwLCPRNG = dwLCPRNG * 1103515245 + 12345;
457 if(dwLCPRNG >= 0x7fffffff)
458 (dwLCPRNG) -= 0x7fffffff;
459 if(dwLCPRNG >= 0x7fffffff)
460 (dwLCPRNG) -= 0x7fffffff;
461
462 // Increment counter by the decimal part of the golden ratio, period 2^32
463 dwCntr+=0x9e3779b9;
464
465 // Return wordswap(dwLCPRNG) + dwCntr + g_dwBytesReceived.
466
467 // The total period of dwLCPRNG and dwCntr combined is about 2^63.
468 // The addition of the received byte count adds "real" entropy, making
469 // the period "indefinitely" long.
470
471 // The high/low words of dwLCPRNG are swapped since the low bits of LCPRNGs
472 // exhibit disturbing statistical correlations. This instead puts the "bad
473 // entropy" in the middle of the returned DWORD.
474 return (dwLCPRNG>>16) + (dwLCPRNG<<16) + dwCntr + g_dwBytesReceived;
475 }
476
477
478
479 /************************************************************************
480 *
481 * Function : GenerateTimeInfo
482 *
483 * Purpose : Fill out a TIMEINFO struct:
484 * - tv
485 * - nUTCOffs
486 * - szTimestamp -- used in log records, either RFC3339 or syslog std
487 * - szNameTimestamp_Day -- YYYYMMDD
488 * - szNameTimestamp -- YYYYMMDDHH, or YYYYMMDD (with "--split day")
489 *
490 * szTimestamp is special. If RFC3339 timestamps are used,
491 * it will contain a "%06u" format string so that it can
492 * be passed to a printf-style formatter that inserts the
493 * the microsecond value.
494 *
495 * Note : Never returns failure; calls bomberrno() on failure
496 *
497 * Uses the existing contents of the timeinfo struct
498 * to determine whether or not we have passed into a
499 * new second. If not, only tv.tv_usec is updated, and
500 * the function returns FALSE.
501 *
502 * Called by: main loop, at startup and every second
503 *
504 * Params : [I/O] PTIMEINFO pti
505 * [O] struct tm *ptmOut -- copy of local time (optional)
506 *
507 * Returns : If we have passed into a new second: TRUE, and
508 * will have updated all members of the struct
509 * If we're in the same second: FALSE, and
510 * only tv.tv_usec updated
511 *
512 ************************************************************************/
513
GenerateTimeInfo(PTIMEINFO pti,struct tm * ptmOut)514 BOOL GenerateTimeInfo(PTIMEINFO pti, struct tm *ptmOut)
515 {
516 static LPCSTR apszMonths[]={"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
517 int nUTCOffs_Abs;
518 struct tm *ptmTmp, tmLocal, tmUTC;
519 struct timeval tv;
520 time_t t;
521
522 ////////////////////////////////////////////////////////////
523 // Get current time
524
525 if(gettimeofday(&tv, NULL)<0)
526 bomberrno("gettimeofday");
527
528 if(tv.tv_usec>=1000000) // Yes, this actually happens on some OSes
529 {
530 tv.tv_usec-=1000000;
531 tv.tv_sec+=1;
532 }
533
534 // Are we still in the same second?
535 if(tv.tv_sec == pti->tv.tv_sec)
536 {
537 pti->tv.tv_usec = tv.tv_usec;
538 return FALSE; // "same second"
539 }
540
541
542 ////////////////////////////////////////////////////////////
543 // New second: regenerate everything
544
545 pti->tv = tv;
546
547 // Get time components
548 t=(time_t)pti->tv.tv_sec;
549 if(!( ptmTmp = localtime(&t) ))
550 bomberrno("localtime");
551 tmLocal = *ptmTmp;
552
553 if(!( ptmTmp = gmtime(&t) ))
554 bomberrno("gmtime");
555 tmUTC = *ptmTmp;
556
557 // Hand out tmLocal?
558 if(ptmOut)
559 *ptmOut = tmLocal;
560
561 // Compute UTC offset. (No, there's no other portable way of getting hold of it.)
562 pti->nUTCOffs= ( (tmLocal.tm_hour - tmUTC.tm_hour) * 60 ) + tmLocal.tm_min - tmUTC.tm_min;
563
564 if(tmLocal.tm_year > tmUTC.tm_year)
565 pti->nUTCOffs += 24*60;
566 else if(tmLocal.tm_year < tmUTC.tm_year)
567 pti->nUTCOffs -= 24*60;
568 else if(tmLocal.tm_yday > tmUTC.tm_yday)
569 pti->nUTCOffs += 24*60;
570 else if(tmLocal.tm_yday < tmUTC.tm_yday)
571 pti->nUTCOffs -= 24*60;
572
573 if((nUTCOffs_Abs = pti->nUTCOffs)<0)
574 nUTCOffs_Abs = 0 - nUTCOffs_Abs;
575
576
577 ////////////////////////////////////////////////////////////
578 // Format szTimestamp (used in log records):
579
580 if(g_bOldTimestamps)
581 // as "Jun 02 12:34:56" (syslog standard format)
582 snprintf(pti->szTimestamp, sizeof(pti->szTimestamp), "%s %2u %02u:%02u:%02u",
583 apszMonths[tmLocal.tm_mon], tmLocal.tm_mday, tmLocal.tm_hour, tmLocal.tm_min, tmLocal.tm_sec
584 );
585 else
586 // or "2002-06-02T12:34:56.%06u+02:00" (RFC3339) -- note the "%06u"
587 snprintf(pti->szTimestamp, sizeof(pti->szTimestamp), "%04u-%02u-%02uT%02u:%02u:%02u.%%06u%c%02u:%02u",
588 tmLocal.tm_year+1900, tmLocal.tm_mon+1, tmLocal.tm_mday,
589 tmLocal.tm_hour, tmLocal.tm_min, tmLocal.tm_sec,
590 pti->nUTCOffs>=0 ? '+' : '-',
591 nUTCOffs_Abs/60,
592 nUTCOffs_Abs%60
593 );
594
595
596 ////////////////////////////////////////////////////////////
597 // Format g_szNameTimestamp_Day as "20020602" (used by daemon's own logs)
598
599 snprintf(pti->szNameTimestamp_Day, sizeof(pti->szNameTimestamp_Day), "%04u%02u%02u",
600 tmLocal.tm_year+1900, tmLocal.tm_mon+1, tmLocal.tm_mday
601 );
602
603
604 ////////////////////////////////////////////////////////////
605 // Format g_szNameTimestamp (log file naming)
606
607 if(g_nSplitMode == SPLITMODE_DAY)
608 // as "20020602" (with --split day)
609 mystrlcpy(pti->szNameTimestamp, pti->szNameTimestamp_Day, sizeof(pti->szNameTimestamp));
610 else
611 // as "2002060212" (default)
612 snprintf(pti->szNameTimestamp, sizeof(pti->szNameTimestamp), "%04u%02u%02u%02u",
613 tmLocal.tm_year+1900, tmLocal.tm_mon+1, tmLocal.tm_mday, tmLocal.tm_hour
614 );
615
616 return TRUE; // "we hit a new second"
617 }
618
619
620
621
622
623
624 /************************************************************************
625 *
626 * Function : OpenDaemonLog
627 *
628 * Purpose : Open the local daemon log file g_fpDaemonLog:
629 * "minirsyslogd-YYYYMMDD"
630 *
631 * Note : Never returns failure; calls bomberrno() on failure
632 *
633 * Assumes : That g_fpDaemonLog is already closed
634 *
635 * Called by: main loop, at startup and every hour
636 *
637 * Returns : -
638 * Will have set g_fpDaemonLog
639 *
640 ************************************************************************/
641
OpenDaemonLog(void)642 void OpenDaemonLog(void)
643 {
644 char szTmp[1024];
645
646 snprintf(szTmp, sizeof(szTmp), "minirsyslogd-%s", g_tiNow.szNameTimestamp_Day);
647 g_fpDaemonLog = fopen(szTmp, "at");
648 if(!g_fpDaemonLog)
649 bomberrno("could not open local daemon log file");
650 }
651
652
653
654 /************************************************************************
655 *
656 * Function : CloseDaemonLog
657 *
658 * Purpose : Close the local daemon log file (if it is open)
659 *
660 * Called by: main loop, at shutdown and every hour
661 *
662 * Returns : -
663 * g_fpDaemonLog will be NULL
664 *
665 ************************************************************************/
666
CloseDaemonLog(void)667 void CloseDaemonLog(void)
668 {
669 if(g_fpDaemonLog)
670 fclose(g_fpDaemonLog);
671 g_fpDaemonLog=NULL;
672 }
673
674
675
676
677
678 /************************************************************************
679 *
680 * Function : DestHash_HashFunc
681 *
682 * Purpose : Generate hash value for given IP4ADDR
683 *
684 * Note : This hash would be at the mercy of attackers if it wasn't
685 * for the fact that the administrator gets to decide the IPs,
686 * not the attacker. Hah.
687 *
688 * Called by: DestHash_Add, DestHash_Remove
689 *
690 * Params : [I] PIP4ADDR pIPAddr
691 *
692 * Returns : DWORD
693 *
694 ************************************************************************/
695
DestHash_HashFunc(PIP4ADDR pIPAddr)696 DWORD DestHash_HashFunc(PIP4ADDR pIPAddr)
697 {
698
699 DWORD dw;
700
701 dw = (pIPAddr->achIP4[0]<<24) |
702 (pIPAddr->achIP4[1]<<16) |
703 (pIPAddr->achIP4[2]<<8) |
704 (pIPAddr->achIP4[3]);
705
706 dw *= 0x9e3779b9;
707
708 dw >>= 32-DEST_HASH_BITS;
709
710 return dw;
711 }
712
713 /************************************************************************
714 *
715 * Function : DestHash_Add
716 *
717 * Purpose : Add given pDest to g_apDestHash
718 *
719 * Called by: Dest_Init
720 *
721 * Params : [I] PDEST pDest
722 *
723 * Returns : -
724 *
725 ************************************************************************/
726
DestHash_Add(PDEST pDest)727 void DestHash_Add(PDEST pDest)
728 {
729 DWORD dwHash, dwCnt;
730
731 // This is a simple skip hash. Instead of making arbitrarily deep buckets,
732 // we just try the next bucket if the right one is taken.
733 //
734 // The hash MUST have at least as many buckets as items, and preferably
735 // three times as many for it to work any where near well.
736
737 dwHash = DestHash_HashFunc(&pDest->IPAddr);
738 // Find the first empty slot at or after the hash value
739 for( dwCnt=1<<DEST_HASH_BITS ; g_apDestHash[dwHash] ; dwCnt--)
740 {
741 if(!dwCnt)
742 bomb("INTERNAL BROKENNESS: DestHash_Add: Hash is FULL?!");
743 if(g_apDestHash[dwHash]==pDest)
744 bomb("INTERNAL BROKENNESS: DestHash_Add: pDest was already in the hash?!");
745 dwHash = (dwHash+1) & ((1<<DEST_HASH_BITS)-1);
746 }
747
748 g_apDestHash[dwHash] = pDest;
749 }
750
751
752 /************************************************************************
753 *
754 * Function : DestHash_Remove
755 *
756 * Purpose : Remove given pDest from g_apDestHash
757 *
758 * Called by: Dest_Destroy
759 *
760 * Params : [I] PDEST pDest
761 *
762 * Returns : -
763 *
764 ************************************************************************/
765
DestHash_Remove(PDEST pDest)766 void DestHash_Remove(PDEST pDest)
767 {
768 DWORD dwHash, dwCnt;
769
770 dwHash = DestHash_HashFunc(&pDest->IPAddr);
771 // Find the given pDest at or after the hash value
772 for( dwCnt=1<<DEST_HASH_BITS ; g_apDestHash[dwHash]!=pDest ; dwCnt--)
773 {
774 if(!dwCnt)
775 bomb("INTERNAL BROKENNESS: DestHash_Remove: Couldn't find pDest in the hash?!");
776 dwHash = (dwHash+1) & ((1<<DEST_HASH_BITS)-1);
777 }
778
779 g_apDestHash[dwHash] = NULL;
780 }
781
782
783 /************************************************************************
784 *
785 * Function : Dest_Init
786 *
787 * Purpose : Initialize a DEST struct:
788 * - Copy in destination information (IP address)
789 * - Open output file
790 * - Allocate (bigger?) stream buffer
791 *
792 * Calls : DestHash_Add()
793 *
794 * Assumes : That g_tiNow contains valid time data
795 *
796 * Params : [O] PDEST pDest
797 * [I] PIP4ADDR pip
798 *
799 * Returns : BOOL - FALSE for failure (failed to open output file)
800 *
801 ************************************************************************/
802
Dest_Init(PDEST pDest,PIP4ADDR pip)803 BOOL Dest_Init(PDEST pDest, PIP4ADDR pip)
804 {
805 char szTmp[1024];
806
807 memset(pDest, 0, sizeof(DEST));
808
809 // Copy in given parameters
810 pDest->IPAddr = *pip;
811
812 // Add to hash
813 DestHash_Add(pDest);
814
815 // Set up string representation of IP address (for speed, later on)
816 snprintf(pDest->szIPStr, sizeof(pDest->szIPStr), "%u.%u.%u.%u",
817 pDest->IPAddr.achIP4[0], pDest->IPAddr.achIP4[1], pDest->IPAddr.achIP4[2], pDest->IPAddr.achIP4[3]
818 );
819
820 // Open output file
821 snprintf(szTmp, sizeof(szTmp), "%s/%s-%s",
822 pDest->szIPStr, pDest->szIPStr, g_tiNow.szNameTimestamp
823 );
824
825 if(g_bVerbose) printf("OPEN %s ", szTmp);
826
827 if(!( pDest->fp = fopen(szTmp, "at") ))
828 {
829 // Open failed -- we keep the destination around with a null FP though, to cache the "failed" indiciation
830 // 3TODO: Make this smarter -- WHY did it fail?
831 if(g_bVerbose) printf("FAILED!\n");
832 return FALSE;
833 }
834
835 if(g_bVerbose) printf("OK\n");
836
837 // Get current file length (we need to track it so that we never write
838 // files that exceed the 2GB limit -- it causes glibc to terminate the
839 // application!)
840 if( (pDest->lFileSize = ftell(pDest->fp)) < 0)
841 {
842 dlog(0, "warning: could not get length of %s: %s", szTmp, strerror(errno));
843 fclose(pDest->fp);
844 pDest->fp=NULL;
845 return FALSE;
846 }
847
848
849 // Set buffer size (do nothing if our malloc fails; the buffer allocated by
850 // libc gets used). I originally had a smarter algorithm that malloced and
851 // switched to the larger buffer only if it was actually necessary (lots of
852 // writes per second), but many versions of glibc hates that. Duh.
853 if((pDest->vbuf = malloc(g_nBufSize)))
854 setvbuf(pDest->fp, pDest->vbuf, _IOFBF, g_nBufSize);
855
856 return TRUE;
857 }
858
859
860
861 /************************************************************************
862 *
863 * Function : Dest_Output
864 *
865 * Purpose : Format pszFmt+varargs and output to the given pDest
866 *
867 * Also keep track of file lengths so that they aren't
868 * exceeded. If this happens, we dlog() the event and output
869 * a "Maximum file length exceeded" line in the output file
870 * and refuse to write more data to the file.
871 *
872 * Note : No extra formatting. Data is written exactly as given.
873 * Don't do multiple calls for a single entry; you don't
874 * want the size limit to trigger in the middle of an entry!
875 *
876 * Called by: main loop
877 *
878 * Params : [I] PDEST pDest - an open DEST struct
879 * [I] LPCSTR pszFmt - printf style format
880 * [I] ...
881 *
882 * Returns : -
883 *
884 ************************************************************************/
885
Dest_Output(PDEST pDest,LPCSTR pszFmt,...)886 void Dest_Output(PDEST pDest, LPCSTR pszFmt, ...)
887 {
888 va_list args;
889 long lWritten;
890
891 // Bail early if the file is already too big
892 if(pDest->lFileSize >= g_lMaxFileSize)
893 return;
894
895 // Format and output
896 va_start(args, pszFmt);
897 lWritten=vfprintf(pDest->fp, pszFmt, args);
898 va_end(args);
899
900 // Track file size changes
901 if(lWritten<0)
902 ; // 4TODO: What to do here? Close the output file? Log an error? Keep ignoring it?
903 else if(lWritten>=65600)
904 ; // 5TODO: "Should never happen" ...?
905 else
906 pDest->lFileSize += lWritten;
907
908 // Emit warnings if this write exceeded the max file size
909 if(pDest->lFileSize >= g_lMaxFileSize)
910 {
911 fprintf(pDest->fp, "%s minirsyslogd <%u>Maximum file length (%u) exceeded for %s\n",
912 GetTimestamp(&g_tiNow), FACILITY_SYSLOGD | SEVERITY_WARN, (DWORD)g_lMaxFileSize, pDest->szIPStr);
913 dlog(0, "drop: maximum file length (%u) exceeded for %s", (DWORD)g_lMaxFileSize, pDest->szIPStr);
914 }
915 }
916
917
918
919 /************************************************************************
920 *
921 * Function : Dest_Destroy
922 *
923 * Purpose : Destroy a DEST struct. Free associated resources.
924 *
925 * Calls : DestHash_Remove()
926 *
927 * Params : PDEST pDest
928 *
929 * Returns : -
930 *
931 ************************************************************************/
932
Dest_Destroy(PDEST pDest)933 void Dest_Destroy(PDEST pDest)
934 {
935 DestHash_Remove(pDest);
936
937 if(pDest->fp)
938 fclose(pDest->fp);
939 pDest->fp=NULL;
940
941 if(pDest->vbuf)
942 free(pDest->vbuf);
943 pDest->vbuf=NULL;
944 }
945
946
947
948 /************************************************************************
949 *
950 * Function : Dest_DestroyAll
951 *
952 * Purpose : Call Dest_DestroyAll for ALL open dests
953 *
954 * Calls : Dest_Destroy()
955 *
956 * Called by: main
957 *
958 * Returns : -
959 *
960 ************************************************************************/
961
Dest_DestroyAll(void)962 void Dest_DestroyAll(void)
963 {
964 while(g_nDests>0)
965 {
966 g_nDests--;
967 if(g_bVerbose) printf("CLOSE %s\n", g_apDests[g_nDests]->szIPStr);
968 Dest_Destroy(g_apDests[g_nDests]);
969 g_apUnusedDests[g_nUnusedDests++] = g_apDests[g_nDests];
970 }
971 }
972
973 /************************************************************************
974 *
975 * Function : AddFailReport
976 *
977 * Purpose : Add a failure report to the g_aFailReports array
978 * or: increment counter of an entry already in the array
979 * or: increment g_nWildcardFails if there's no room
980 *
981 * Params : [I] PIP4ADDR pIPAddr
982 *
983 * Returns : -
984 *
985 ************************************************************************/
986
AddFailReport(PIP4ADDR pIPAddr)987 void AddFailReport(PIP4ADDR pIPAddr)
988 {
989 int n;
990
991 // See if this IP is already in the list of failure reports
992 for(n=g_nFailReports-1 ; n>=0 ; n--)
993 if(g_aFailReports[n].IPAddr.dwIP4 == pIPAddr->dwIP4)
994 {
995 if(g_aFailReports[n].nOccurences < 0x7fffffff)
996 g_aFailReports[n].nOccurences++; // Just increment the "occurences" counter
997 return;
998 }
999
1000 // Not in the list of failure reports: add it if there's room
1001 if(g_nFailReports<MAX_FAILREPORTS)
1002 {
1003 if(g_bVerbose) printf("Adding new fail report for %u.%u.%u.%u\n", pIPAddr->achIP4[0], pIPAddr->achIP4[1], pIPAddr->achIP4[2], pIPAddr->achIP4[3]);
1004 g_aFailReports[g_nFailReports].IPAddr = *pIPAddr;
1005 g_aFailReports[g_nFailReports].nOccurences = 1;
1006 g_nFailReports++;
1007 }
1008 else if(g_nWildcardFails < 0x7fffffff)
1009 g_nWildcardFails++;
1010 }
1011
1012
1013
1014 /************************************************************************
1015 *
1016 * Function : OutputFailReports
1017 *
1018 * Purpose : Output the list of "failed to open" occurences
1019 * (IP addresses denied access)
1020 *
1021 * Clear the status and prepare for another period
1022 *
1023 * Assumes : That g_tiNow contains valid time data
1024 *
1025 * Returns : -
1026 *
1027 ************************************************************************/
1028
OutputFailReports(void)1029 void OutputFailReports(void)
1030 {
1031 int n;
1032
1033 if(g_nFailReports<=0)
1034 goto justreset;
1035
1036 // Print detailed list
1037 for(n=0;n<g_nFailReports;n++)
1038 {
1039 IP4ADDR ip = g_aFailReports[n].IPAddr;
1040 dlog(0, "drop: failed %u.%u.%u.%u %i times",
1041 ip.achIP4[0], ip.achIP4[1], ip.achIP4[2], ip.achIP4[3],
1042 g_aFailReports[n].nOccurences
1043 );
1044 }
1045
1046 // Print wildcard occurences (stuff that didn't fit in the list)
1047 if(g_nWildcardFails)
1048 {
1049 dlog(0, "drop: failed * %i times",
1050 g_nWildcardFails
1051 );
1052 }
1053
1054 // Reset everything
1055 justreset:
1056 g_nFailReports=0;
1057 g_nWildcardFails=0;
1058 }
1059
1060
1061
1062 /************************************************************************
1063 *
1064 * Function : usage
1065 *
1066 * Purpose : Display program usage guide and, optionally, an error
1067 * message. If an error message is displayed, everything
1068 * goes out through stderr, otherwise stdout.
1069 *
1070 * Exits the program with a status of 1 or 0 depending
1071 * on if an error message was displayed.
1072 *
1073 * Calls : exit()
1074 *
1075 * Params : [I] LPCSTR pszMsg
1076 *
1077 * Returns : int (ignored; the function never returns)
1078 *
1079 ************************************************************************/
1080
usage(LPCSTR pszMsg)1081 int usage(LPCSTR pszMsg)
1082 {
1083 FILE *fp;
1084
1085 if(pszMsg && !*pszMsg)
1086 pszMsg = NULL;
1087
1088 if(pszMsg)
1089 fp = stderr;
1090 else
1091 fp = stdout;
1092
1093 if(pszMsg)
1094 fprintf(fp, "%s: %s\n\n", g_pszProgname, pszMsg);
1095
1096 fprintf(fp,
1097 "%s " VERSION_STRING " options:\n"
1098 " --daemon run in background, as a daemon\n"
1099 " --rootdir <path> where to store the logs (default: .)\n"
1100 " --maxopen <num> max open files (default: 50, limit: %u)\n"
1101 " --port <num> port number to use (default: 514)\n"
1102 " --recvmode <mode> see 'receive modes' below (default: split)\n"
1103 " --split <mode> see 'splitting modes' below (default: hour)\n"
1104 " --oldtimestamp use old-style syslog timestamps rather than rfc3339\n"
1105 " --pidfile <filename> where to output the pid of minirsyslogd\n"
1106 " (default: /var/run/minirsyslogd[-<port>].pid)\n"
1107 " --umask <mask> umask to use when creating log files (default: 077)\n"
1108 " --maxopenspersec <num> max file open attempts/s (default: 200)\n"
1109 " --maxfilesize <mbytes> maximum output file size in megabytes (default: 2000)\n"
1110 " --bufsize <bytes> output file buffer size (default: 16384)\n"
1111 " --verbose verbose output (to stdout)\n"
1112 "\n"
1113 "Log file splitting modes:\n"
1114 " hour - split log files each hour (default)\n"
1115 " day - split log files each day\n"
1116 "\n"
1117 "Receive modes:\n"
1118 " truncate - truncate the event at the first LF\n"
1119 " flat - convert all LFs to spaces\n"
1120 " split - split data on LFs and store as multiple events (default)\n"
1121 " forensic - emit a byte count, followed by data (possibly with LFs)\n"
1122 " forensicraw - -\"-, but without stripping control codes\n"
1123 "\n"
1124 " In all modes except forensicraw, ASCII codes 1-31 and 128-159\n"
1125 " are converted to spaces (32). NUL characters are always\n"
1126 " converted to spaces.\n"
1127 ,
1128 g_pszProgname,
1129 (int)mymin(MAX_DESTS, sysconf(_SC_OPEN_MAX)-5)
1130 );
1131
1132 exit(pszMsg ? 1 : 0);
1133 return 0; // Keep compilers happy
1134 }
1135
1136
1137
1138
1139 /************************************************************************
1140 *
1141 * Function : sighandler
1142 *
1143 * Purpose : For SIGTERM and SIGINT: Set g_bExitNow and set these signal
1144 * back to default behavior.
1145 * For SIGHUP: Set g_bCloseAllFilesNow
1146 * For others: no-op. None others are expected.
1147 *
1148 * Params : [I] int sig
1149 *
1150 * Returns : -
1151 *
1152 ************************************************************************/
1153
sighandler(int sig)1154 void sighandler(int sig)
1155 {
1156 if(sig==SIGTERM || sig==SIGINT)
1157 {
1158 if(g_bVerbose) printf("sighandler: got SIGTERM/SIGINT\n");
1159 g_bExitNow=TRUE;
1160 signal(SIGTERM, SIG_DFL);
1161 signal(SIGINT, SIG_DFL);
1162 }
1163 else if(sig==SIGHUP)
1164 {
1165 if(g_bVerbose) printf("sighandler: got SIGHUP\n");
1166 g_bCloseAllFilesNow=TRUE;
1167 }
1168 else if(g_bVerbose)
1169 printf("sighandler: got signal %u ?!?!?!\n", sig);
1170 }
1171
1172
1173
1174
1175 /************************************************************************
1176 *
1177 * Function : WritePIDFile
1178 *
1179 * Purpose : Write the current PID (or not) to the given file
1180 *
1181 * Called by: main()
1182 *
1183 * Params : [I] BOOL bWritePID -- TRUE: write the PID
1184 * FALSE: just wipe the contents of the file
1185 *
1186 * Returns : -
1187 *
1188 ************************************************************************/
1189
WritePIDFile(BOOL bWritePID,LPCSTR pszPIDFile)1190 void WritePIDFile(BOOL bWritePID, LPCSTR pszPIDFile)
1191 {
1192 char szPIDFileTmp[1024];
1193 FILE *fp;
1194 mode_t nPreviousUmask;
1195
1196 // Change umask: causes pidfile to be -rw-------
1197 nPreviousUmask = umask(077);
1198
1199 // Figure out name of pid file if not given
1200 if(!pszPIDFile)
1201 {
1202 if(g_nPort==514)
1203 snprintf(szPIDFileTmp, sizeof(szPIDFileTmp), "/var/run/minirsyslogd.pid");
1204 else
1205 snprintf(szPIDFileTmp, sizeof(szPIDFileTmp), "/var/run/minirsyslogd-%u.pid", g_nPort);
1206 pszPIDFile=szPIDFileTmp;
1207 }
1208
1209 if(g_bVerbose)
1210 {
1211 if(bWritePID)
1212 printf("Writing '%u' to pidfile '%s'...\n", (int)getpid(), pszPIDFile);
1213 else
1214 printf("Wiping pidfile '%s'...\n", pszPIDFile);
1215 }
1216
1217 // Open, write and close
1218 if(!(fp=fopen(pszPIDFile, "wt")))
1219 dlog(2, "warning: failed to write to pidfile '%s'", pszPIDFile);
1220 else
1221 {
1222 if(bWritePID)
1223 fprintf(fp, "%u\n", getpid());
1224 fclose(fp);
1225 }
1226
1227 // Restore umask
1228 umask(nPreviousUmask);
1229 }
1230
1231
1232 /************************************************************************
1233 *
1234 * Function : main
1235 *
1236 * Purpose : - Parse arguments
1237 *
1238 * Params : [I] int argc
1239 * [I] char *argv[]
1240 *
1241 * Returns : int
1242 *
1243 ************************************************************************/
1244
main(int argc,char * argv[])1245 int main(int argc, char *argv[])
1246 {
1247 int sock;
1248 int n, nArg;
1249 char szTmp[1024];
1250 struct sockaddr_in Sin;
1251 size_t cbSin;
1252 struct tm tmNow, tmPrev;
1253 PDEST pDest;
1254 int nOpensThisSec=0; // Number of file open attempts this second
1255 int nOpensThisHour=0; // Number of file open attempts this hour
1256 LPCSTR pszPIDFile=NULL;
1257 int nSecondsSinceCloseAll=0; // Seconds since the turn of the hour, or since SIGHUP
1258 BOOL bFirstLoop=TRUE;
1259
1260 g_pszProgname = argv[0];
1261 g_bExitNow = FALSE;
1262 g_bCloseAllFilesNow = FALSE;
1263
1264 g_tiNow.tv.tv_sec = 0; // Make sure GenerateTimeInfo() generates EVERYTHING this time
1265 GenerateTimeInfo(&g_tiNow, &tmNow); // Set up initial timestamps for startup messages
1266
1267 signal(SIGTERM, &sighandler);
1268 signal(SIGINT, &sighandler);
1269 signal(SIGHUP, &sighandler);
1270
1271
1272 ////////////////////////////////////////////////////////////
1273 // Parse arguments
1274
1275 for(nArg=1;nArg<argc;nArg++)
1276 {
1277 if(!stricmp(argv[nArg], "--help"))
1278 usage(NULL);
1279 else if(!stricmp(argv[nArg], "--port"))
1280 {
1281 if(++nArg >= argc)
1282 usage("missing argument to '--port'");
1283 g_nPort = atoi(argv[nArg]);
1284 if(g_nPort<1 || g_nPort>65535)
1285 usage("bad argument to '--port': must be 1..65535");
1286 }
1287 else if(!stricmp(argv[nArg], "--maxopen"))
1288 {
1289 if(++nArg >= argc)
1290 usage("missing argument to '--maxopen'");
1291 g_nMaxDests = atoi(argv[nArg]);
1292
1293 if(g_nMaxDests<5 || g_nMaxDests>MAX_DESTS || g_nMaxDests>sysconf(_SC_OPEN_MAX)-5)
1294 {
1295 snprintf(szTmp, sizeof(szTmp), "bad argument to '--maxopen': must be 5..%i", (int)mymin(MAX_DESTS,sysconf(_SC_OPEN_MAX)-5) );
1296 usage(szTmp);
1297 }
1298 }
1299 else if(!stricmp(argv[nArg], "--maxopenspersec"))
1300 {
1301 if(++nArg >= argc)
1302 usage("missing argument to '--maxopenspersec'");
1303 g_nMaxOpensPerSec = atoi(argv[nArg]);
1304 if(g_nMaxOpensPerSec<5 || g_nMaxOpensPerSec>MAX_DESTS*1000)
1305 {
1306 snprintf(szTmp, sizeof(szTmp), "bad argument to '--maxopenspersec': must be 5..%i", MAX_DESTS*1000);
1307 usage(szTmp);
1308 }
1309 }
1310 else if(!stricmp(argv[nArg], "--bufsize"))
1311 {
1312 if(++nArg >= argc)
1313 usage("missing argument to '--bufsize'");
1314 g_nBufSize = atoi(argv[nArg]);
1315 if(g_nBufSize<1024 || g_nBufSize>131072)
1316 usage("bad argument to '--bufsize': must be 1024..131072, preferably a power of 2");
1317 }
1318 else if(!stricmp(argv[nArg], "--maxfilesize"))
1319 {
1320 int n;
1321 if(++nArg >= argc)
1322 usage("missing argument to '--maxfilesize'");
1323 n = atoi(argv[nArg]);
1324 if(n<1 || n>2000)
1325 usage("bad argument to '--maxfilesize': must be 1..2000 (megabytes)");
1326 g_lMaxFileSize = (long)n * 1000000;
1327 }
1328 else if(!stricmp(argv[nArg], "--recvmode"))
1329 {
1330 if(++nArg >= argc)
1331 usage("missing argument to '--recvmode'");
1332 if(!stricmp(argv[nArg], "truncate"))
1333 g_nRecvMode = RECVMODE_TRUNCATE;
1334 else if(!stricmp(argv[nArg], "flat"))
1335 g_nRecvMode = RECVMODE_FLAT;
1336 else if(!stricmp(argv[nArg], "split"))
1337 g_nRecvMode = RECVMODE_SPLIT;
1338 else if(!stricmp(argv[nArg], "forensic"))
1339 g_nRecvMode = RECVMODE_FORENSIC;
1340 else if(!stricmp(argv[nArg], "forensicraw"))
1341 g_nRecvMode = RECVMODE_FORENSICRAW;
1342 else
1343 usage("bad argument to '--recvmode'");
1344 }
1345 else if(!stricmp(argv[nArg], "--daemon"))
1346 {
1347 g_bDaemon=TRUE;
1348 }
1349 else if(!stricmp(argv[nArg], "--splitday")) // Deprecated
1350 {
1351 g_nSplitMode = SPLITMODE_DAY;
1352 dlog(2, "warning: '--splitday' is deprecated. Use '--split day' instead.");
1353 }
1354 else if(!stricmp(argv[nArg], "--split"))
1355 {
1356 if(++nArg >= argc)
1357 usage("missing argument to '--split'");
1358 if(!stricmp(argv[nArg], "day"))
1359 g_nSplitMode = SPLITMODE_DAY;
1360 else if(!stricmp(argv[nArg], "hour"))
1361 g_nSplitMode = SPLITMODE_HOUR;
1362 else
1363 usage("bad argument to '--split'");
1364 }
1365 else if(!stricmp(argv[nArg], "--oldtimestamp"))
1366 {
1367 g_bOldTimestamps=TRUE;
1368 }
1369 else if(!stricmp(argv[nArg], "--umask"))
1370 {
1371 DWORD dw=0;
1372 LPCSTR psz;
1373
1374 if(++nArg >= argc)
1375 usage("missing argument to '--umask'");
1376
1377 psz = argv[nArg];
1378 for(;*psz;psz++)
1379 if(*psz>='0' && *psz<='7')
1380 dw=(dw<<3) | (DWORD)( *psz - '0' );
1381 else
1382 usage("bad argument to --umask. expected octal number.");
1383
1384 if(dw>=0100)
1385 usage("bad argument to --umask. expected octal number between 000 and 077.");
1386
1387 g_nUmask = (mode_t)dw;
1388
1389 printf("umask: %08x\n", dw);
1390 }
1391 else if(!stricmp(argv[nArg], "--pidfile"))
1392 {
1393 if(++nArg >= argc)
1394 usage("missing argument to '--pidfile'");
1395 pszPIDFile = argv[nArg];
1396 }
1397 else if(!stricmp(argv[nArg], "--rootdir"))
1398 {
1399 if(++nArg >= argc)
1400 usage("missing argument to '--rootdir'");
1401 g_pszRootDir = argv[nArg];
1402 }
1403 else if(!stricmp(argv[nArg], "--verbose"))
1404 {
1405 g_bVerbose=TRUE;
1406 }
1407 else
1408 {
1409 snprintf(szTmp, sizeof(szTmp), "unrecognized option '%s'", argv[nArg]);
1410 szTmp[sizeof(szTmp)-1]='\0';
1411 usage(szTmp);
1412 }
1413
1414 } // END for(nArg=1;nArg<argc;nArg++)
1415
1416
1417 ////////////////////////////////////////////////////////////
1418 // Fix umask and current directory
1419
1420 umask(g_nUmask);
1421
1422 if(chdir(g_pszRootDir)!=0)
1423 {
1424 snprintf(szTmp, sizeof(szTmp), "chdir \"%s\"", g_pszRootDir);
1425 bomberrno(szTmp);
1426 }
1427
1428
1429 ////////////////////////////////////////////////////////////
1430 // Tell people that we're starting up
1431
1432 OpenDaemonLog(); // Open local daemon log
1433
1434 dlog(1, "startup: version=\"" VERSION_STRING "\" pid=%u uid=%u gid=%u euid=%u egid=%u",
1435 getpid(), getuid(), getgid(), geteuid(), getegid());
1436
1437 if(!getcwd(szTmp, sizeof(szTmp)))
1438 bomb("getcwd");
1439
1440 dlog(1, "settings: rootdir=\"%s\" maxopen=%u port=%u maxopenspersec=%u split=%s recvmode=%s",
1441 szTmp, g_nMaxDests, g_nPort, g_nMaxOpensPerSec,
1442 g_nSplitMode == SPLITMODE_DAY ? "day" :
1443 "hour",
1444 g_nRecvMode == RECVMODE_TRUNCATE ? "truncate" :
1445 g_nRecvMode == RECVMODE_FLAT ? "flat" :
1446 g_nRecvMode == RECVMODE_FORENSIC ? "forensic" :
1447 g_nRecvMode == RECVMODE_FORENSICRAW ? "forensicraw" :
1448 "split"
1449 );
1450
1451
1452
1453 /////////////////////////////////////////////////////////
1454 // Set up DEST structs
1455
1456 for(n=0;n<g_nMaxDests;n++)
1457 {
1458 if(!(pDest = malloc(sizeof(DEST))))
1459 bomb("out of memory mallocing destination descriptors");
1460 g_apUnusedDests[n]=pDest;
1461 }
1462 g_nUnusedDests=n;
1463 g_nDests=0;
1464 memset(g_apDestHash, 0, sizeof(g_apDestHash));
1465
1466
1467 ////////////////////////////////////////////////////////////
1468 // Daemonize if requested
1469
1470 if(g_bDaemon && !g_bVerbose)
1471 {
1472 int i;
1473
1474 dlog(1, "startup: backgrounding (daemonizing)");
1475
1476 // Fork once so that the new process can call setsid()
1477 switch(fork())
1478 {
1479 case -1:
1480 bomberrno("fork 1");
1481 case 0:
1482 // I'm the child.
1483 break;
1484 default:
1485 exit(0);
1486 }
1487
1488 // Call setsid() to start a new process group
1489 if(!setsid())
1490 bomberrno("setsid");
1491
1492 // Fork again so the child isn't the process group leader
1493 switch(fork())
1494 {
1495 case -1:
1496 bomberrno("fork 2");
1497 case 0:
1498 // I'm the grandchild.
1499 break;
1500 default:
1501 exit(0);
1502 }
1503
1504 // Close all open files
1505 CloseDaemonLog();
1506 for(i=0;i<sysconf(_SC_OPEN_MAX);i++)
1507 close(i);
1508
1509 // Reopen stdin/stderr/stdout to point at "/dev/null"
1510 if(open("/dev/null", O_RDWR, 666)!=0) // The first opened file _SHOULD_ be 0!
1511 exit(2); // And no stderr to report to, either. *sigh*
1512 dup2(0,1);
1513 dup2(0,2);
1514
1515 // Reopen the local daemon log
1516 OpenDaemonLog();
1517 }
1518
1519
1520 ////////////////////////////////////////////////////////
1521 // Bind listening socket
1522
1523 if(g_bVerbose) printf("Binding port %u/udp\n", g_nPort);
1524 sock=socket(AF_INET, SOCK_DGRAM, 0);
1525 if(sock==-1)
1526 bomberrno("socket");
1527
1528 memset(&Sin, 0, sizeof(Sin));
1529 Sin.sin_port=htons(g_nPort);
1530 Sin.sin_family=AF_INET;
1531 if(bind(sock, (struct sockaddr*)&Sin, sizeof(Sin))!=0)
1532 bomberrno("bind");
1533
1534
1535 ////////////////////////////////////////////////////////////
1536 // Main loop
1537
1538 WritePIDFile(TRUE, pszPIDFile);
1539
1540 dlog(1, "startup: minirsyslogd initialized. listening on %u/udp", g_nPort);
1541 bFirstLoop = TRUE;
1542
1543 while(!g_bExitNow)
1544 {
1545 char achBuf[8193];
1546 int nLen;
1547 IP4ADDR ip;
1548 char *pch;
1549 fd_set fds;
1550 struct timeval tmo={0,250000};
1551
1552 ////////////////////////////////////////////////////////////
1553 // Receive inbound packet
1554
1555 #ifndef SPAMRECEIVE
1556 cbSin=sizeof(Sin);
1557 FD_ZERO(&fds);
1558 FD_SET(sock, &fds);
1559 if(select(sock+1, &fds, NULL, NULL, &tmo)>0)
1560 {
1561 nLen=recvfrom(sock, achBuf, sizeof(achBuf)-1, 0, (struct sockaddr*)&Sin, &cbSin);
1562 if(nLen<0)
1563 nLen=0;
1564 memcpy(&ip, &Sin.sin_addr, sizeof(ip));
1565 }
1566 else
1567 nLen=0;
1568 #else
1569 if(1)
1570 {
1571 static int nSpams=0;
1572 strcpy(achBuf, "<123>");
1573 for(nLen=5;nLen<100;nLen++)
1574 achBuf[nLen]='a';
1575 ip.achIP4[0]=ip.achIP4[1]=ip.achIP4[2]=1;
1576 ip.achIP4[3]=(BYTE)rand();
1577 if(++nSpams>=2000000)
1578 g_bExitNow = TRUE;
1579 }
1580 #endif
1581 g_dwBytesReceived+=(DWORD)nLen;
1582 while(g_dwBytesReceived>=1000000)
1583 {
1584 g_dwBytesReceived-=1000000;
1585 g_dwMegaBytesReceived++;
1586 }
1587
1588 ////////////////////////////////////////////////////////////
1589 // Per-second processing
1590
1591 if(GenerateTimeInfo(&g_tiNow, &tmNow) || bFirstLoop )
1592 {
1593 // New second!
1594
1595 // See if we've passed into a new hour (test all variables to catch system date changes!)
1596 if(tmNow.tm_hour != tmPrev.tm_hour ||
1597 tmNow.tm_mday != tmPrev.tm_mday ||
1598 tmNow.tm_mon != tmPrev.tm_mon ||
1599 tmNow.tm_year != tmPrev.tm_year ||
1600 bFirstLoop )
1601 {
1602 // Open new local daemon log file
1603 CloseDaemonLog();
1604 OpenDaemonLog();
1605
1606 // Forcibly close all dests
1607 Dest_DestroyAll();
1608 nSecondsSinceCloseAll=0;
1609
1610 // Output and reset statistics for current hour
1611 if(!bFirstLoop)
1612 dlog(0, "statistics: opens=%u recvd=%u%06u",
1613 nOpensThisHour, g_dwMegaBytesReceived, g_dwBytesReceived
1614 );
1615 nOpensThisHour=0;
1616 g_dwMegaBytesReceived = g_dwBytesReceived = 0;
1617
1618 // Report open failures (IPs denied access)
1619 OutputFailReports(); // Also clears all counters / state
1620 }
1621
1622 // Whine if the maxopenspersec count has been exceeded
1623 if( g_nMaxOpensPerSec<1 )
1624 ; // noop
1625 else if( nOpensThisSec <= g_nMaxOpensPerSec )
1626 ; // ok
1627 else if(nSecondsSinceCloseAll<2 && nOpensThisSec <= g_nMaxDests)
1628 ; // allow more open attempts the first 2 seconds after having closed all open files
1629 else if(nSecondsSinceCloseAll<2 && g_nMaxDests > g_nMaxOpensPerSec )
1630 {
1631 dlog(0, "drops: ignored %u file open attempts. maxopen (%u) exceeded during a single second.",
1632 nOpensThisSec - g_nMaxDests,
1633 g_nMaxDests
1634 );
1635 }
1636 else
1637 {
1638 dlog(0, "drops: ignored %u file open attempts. maxopenspersec (%u) exceeded.",
1639 nOpensThisSec - g_nMaxOpensPerSec,
1640 g_nMaxOpensPerSec
1641 );
1642 }
1643 nSecondsSinceCloseAll++;
1644
1645 // Reset stuff that needs to be reset
1646 nOpensThisSec = 0;
1647 tmPrev = tmNow;
1648
1649 } // END if(GenerateTimeInfo(&g_tiNow, &tmNow))
1650
1651 bFirstLoop=FALSE;
1652
1653 ////////////////////////////////////////////////////////////
1654 // SIGHUP -> g_bCloseAllFilesNow ?
1655
1656 if(g_bCloseAllFilesNow)
1657 {
1658 g_bCloseAllFilesNow = FALSE;
1659
1660 dlog(0, "signal: got SIGHUP - flushing and closing all open files");
1661
1662 Dest_DestroyAll();
1663 nSecondsSinceCloseAll=0;
1664
1665 CloseDaemonLog();
1666 OpenDaemonLog();
1667
1668 dlog(0, "signal: back from SIGHUP - all files including local daemon log were closed");
1669 }
1670
1671
1672 ////////////////////////////////////////////////////////////
1673 // Now, did we actually get a packet above?
1674
1675 if(nLen<1)
1676 continue; // Nope. Go back to idling.
1677
1678
1679 ////////////////////////////////////////////////////////////
1680 // Get or set up a destination to store the data to
1681
1682 // Find out if we already have the destination (use hash lookup)
1683 pDest=NULL;
1684 if(1)
1685 {
1686 DWORD dwHash, dwCnt;
1687 dwHash = DestHash_HashFunc(&ip);
1688 for(dwCnt=1<<DEST_HASH_BITS ; dwCnt>0 ; dwCnt--)
1689 {
1690 if(!g_apDestHash[dwHash])
1691 break;
1692 if(ip.dwIP4 == g_apDestHash[dwHash]->IPAddr.dwIP4)
1693 {
1694 pDest = g_apDestHash[dwHash];
1695 break;
1696 }
1697 dwHash = (dwHash+1) & ((1<<DEST_HASH_BITS)-1);
1698 }
1699
1700 }
1701
1702 // WE DO NOT HAVE THIS DESTINATION OPEN: Set up a new destination
1703 if(!pDest)
1704 {
1705 // Apply maxopenspersec limit
1706 nOpensThisSec++;
1707 if( g_nMaxOpensPerSec<1 )
1708 ; // noop
1709 else if( nOpensThisSec <= g_nMaxOpensPerSec )
1710 ; // ok
1711 else if(nSecondsSinceCloseAll<2 && nOpensThisSec <= g_nMaxDests)
1712 ; // allow more open attempts the first 2 seconds after having closed all open files
1713 else
1714 continue; // This error is reported at the turn of the second
1715
1716 // Statistics...
1717 if(nOpensThisHour<0x7fffffff)
1718 nOpensThisHour++;
1719
1720 // Get unused dest struct or free a random one (which is better than the least recently used one)
1721 if(g_nUnusedDests<1)
1722 {
1723 int i, nDest;
1724 if(g_nDests<1)
1725 bomb("Internal brokenness: no used and no unused destinations?!?!?!");
1726 // We try up to three times to find a dest without an open file.
1727 for(i=0;i<3;i++)
1728 {
1729 nDest = myrand() % g_nDests;
1730 pDest = g_apDests[nDest];
1731 if(!pDest->fp)
1732 break;
1733 }
1734 if(g_bVerbose && pDest->fp) printf("FORCECLOSE %s\n", pDest->szIPStr);
1735 Dest_Destroy(pDest);
1736 }
1737 else
1738 {
1739 pDest = g_apUnusedDests[--g_nUnusedDests];
1740 g_apDests[g_nDests++]=pDest;
1741 }
1742
1743 // Initialize destination (open output file)
1744 if(!Dest_Init(pDest, &ip))
1745 {
1746 AddFailReport(&ip);
1747 continue;
1748 }
1749
1750 } // END if(!pDest)
1751
1752
1753 // See if this destination was a cached "couldn't open the file" indicator
1754 if(!pDest->fp)
1755 {
1756 AddFailReport(&pDest->IPAddr);
1757 // Note that this means that we get one fail report _per_ _packet_, even
1758 // if we're in "multiple events per packet" mode.
1759 continue;
1760 }
1761
1762
1763 ////////////////////////////////////////////////////////////
1764 // Actual packet processing...
1765
1766 // Compute a length that we can actually trust, and terminate buffer
1767 if(nLen<1)
1768 continue;
1769 if(nLen>sizeof(achBuf)-1)
1770 nLen = sizeof(achBuf)-1;
1771 achBuf[nLen]='\0';
1772
1773 // Act according to receive mode
1774 switch(g_nRecvMode)
1775 {
1776 // TRUNCATE -- convert ctl to space, stop at first LF (or EOL)
1777 case RECVMODE_TRUNCATE:
1778 {
1779 char* pchBufEnd = achBuf+nLen;
1780 for(pch=achBuf ; pch<pchBufEnd && *pch!='\n' ; pch++)
1781 if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
1782 *pch=' ';
1783 *pch='\0';
1784 Dest_Output(pDest, "%s %s %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, achBuf);
1785 break;
1786 }
1787
1788 // FLAT -- convert all ctl (incl. LF) to space
1789 case RECVMODE_FLAT:
1790 {
1791 char* pchBufEnd = achBuf+nLen;
1792 for(pch=achBuf ; pch<pchBufEnd ; pch++)
1793 if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
1794 *pch=' ';
1795 Dest_Output(pDest, "%s %s %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, achBuf);
1796 break;
1797 }
1798
1799 // FORENSIC -- emit byte count, convert ctl to space, leave LFs alone
1800 case RECVMODE_FORENSIC:
1801 {
1802 char* pchBufEnd = achBuf+nLen;
1803 for(pch=achBuf ; pch<pchBufEnd ; pch++)
1804 if( *pch=='\n' )
1805 ; // leave LF alone
1806 else if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
1807 *pch=' ';
1808 Dest_Output(pDest, "%s %s %u %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, nLen, achBuf);
1809 break;
1810 }
1811
1812 // FORENSICRAW -- emit byte count, output received data as-is (except convert NUL to space)
1813 case RECVMODE_FORENSICRAW:
1814 {
1815 char* pchBufEnd = achBuf+nLen;
1816 for(pch=achBuf ; pch<pchBufEnd ; pch++)
1817 if( *pch=='\0' )
1818 *pch=' ';
1819 Dest_Output(pDest, "%s %s %u %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, nLen, achBuf);
1820 break;
1821 }
1822
1823 // SPLIT (default) -- convert ctl to space, split events on LF
1824 default:
1825 case RECVMODE_SPLIT:
1826 {
1827 char* pchBufEnd = achBuf+nLen;
1828 char *pchLineBegin = achBuf;
1829 pch=achBuf;
1830
1831 while(TRUE)
1832 {
1833 if(*pch=='\n' || pch>=pchBufEnd)
1834 {
1835 *(pch++)='\0';
1836 Dest_Output(pDest, "%s %s %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, pchLineBegin);
1837 pchLineBegin=pch;
1838 if(pch>=pchBufEnd)
1839 break;
1840 continue;
1841 }
1842
1843 if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
1844 *pch=' ';
1845 pch++;
1846 }
1847 break;
1848 }
1849
1850 } // END switch(g_nRecvMode)
1851
1852
1853 } // END neverending loop
1854
1855
1856 ////////////////////////////////////////////////////////////
1857 // Output some final statistics
1858
1859 dlog(0, "statistics: opens=%u recvd=%u%06u",
1860 nOpensThisHour, g_dwMegaBytesReceived, g_dwBytesReceived
1861 );
1862 nOpensThisHour=0;
1863 g_dwMegaBytesReceived = g_dwBytesReceived = 0;
1864
1865 OutputFailReports();
1866
1867
1868 ////////////////////////////////////////////////////////////
1869 // Clean up and exit
1870
1871 dlog(1, "shutdown: version=\"" VERSION_STRING "\" pid=%u uid=%u gid=%u euid=%u egid=%u",
1872 getpid(), getuid(), getgid(), geteuid(), getegid());
1873
1874 Dest_DestroyAll();
1875
1876 WritePIDFile(FALSE, pszPIDFile); // Clear the PID file
1877
1878 CloseDaemonLog();
1879
1880 return 0;
1881 }
1882
1883