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