/************************************************************************
*
* "minirsyslogd.c" by <mikael.olsson@clavister.com>
*
* Simplistic, fast and secure (through lack of bloat) remote syslog
* receiver suitable for hardened log receiver hosts and/or central log
* receivers that receive several gigabytes of logs each day.
*
*
* TERMS OF USE ("no terms")
*
* minirsyslogd is released into the public domain, and, as such, is
* provided "AS IS". Do with it what you wish. I'll happily consider
* adding relevant improvements to it, but be warned: I won't personally
* host anything called 'minirsyslogd' that grows to include a full-sized
* ruleset, etc...
*
*
* VERSION HISTORY
*
* v1.02: 2003-10-30
* Initial public release. Prior to this, it has been in operation
* in various locations for over a year.
*
* There never was a version 1.0. Paul Robertson added his two cents.
*
*
* BASIC IDEA OF OPERATION
*
* - Receive inbound UDP syslog packets on a port of the user's choice.
* (Do NOT deal with local syslog sockets! This is remote only.)
*
* - Store in a structured way according to sender IP, e.g.:
* ./192.168.0.123/192.168.0.123-2002102407
* (The timestamp is YYYYMMDDHH, or YYYYMMDD with --split day)
*
* - Open files always close at the turn of the hour.
*
* - Do NOT create directories automatically.
* The existance/nonexistance of a destination directory is the
* Access Control List of the receiver.
*
* - If the open file table is full, a random file will be closed
* to allow a new one to be opened. This is better than it sounds;
* definitely a LOT better than "closing the oldest one".
*
* - Once an hour: Output a list IP addresses denied access.
* The size of this list is limited to 10 entries.
*
* - Do nothing else. KISS - Keep It Simple Stupid.
* Also, keep it fast. The current implementation was about
* 20 times faster than syslog-ng last time I measured.
*
*
* FUTURE IMPROVEMENTS
*
* - Smarter error reporting for fopen() failures (did it fail because the
* destination directory didn't exist? any other reason?)
*
************************************************************************/
/************************************************************************
*
* Tweakables..
*
************************************************************************/
#define MAX_DESTS 1000 // Maximum number of concurrently open files
// (Further limited by what your OS supports)
#define DEST_HASH_BITS 12 // 1<<bits must be >= MAX_DESTS*3
// 1<<12 = 4096 .. 1<<13 = 8192 .. 1<<14 = 16384
// 1<<15 = 32768 .. 1<<16 = 65536 .. 1<<17 = 131072
#define MAX_FAILREPORTS 10 // Maximum number of "failed to open" reports
// per hour
/************************************************************************
*
* Includes and typedefs
*
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h> // varargs.h instead?
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#define VERSION_STRING "1.02"
typedef char *LPSTR;
typedef const char *LPCSTR;
typedef unsigned int DWORD;
typedef short unsigned int WORD;
typedef int BOOL;
typedef void * PVOID;
typedef unsigned char BYTE, *PBYTE;
#define __far
#ifndef FALSE
#define FALSE (0)
#define TRUE (!FALSE)
#endif
#define stricmp strcasecmp
#define mymin(a,b) ((a)<(b) ? (a) : (b))
/************************************************************************
*
* CONSTANTS
*
************************************************************************/
// Syslog facilities / severities
enum {
FACILITY_KERNEL = 0<<3,
FACILITY_USER = 1<<3,
FACILITY_MAIL = 2<<3,
FACILITY_SYSDAEMON = 3<<3,
FACILITY_AUTH1 = 4<<3,
FACILITY_SYSLOGD = 5<<3,
FACILITY_LPR = 6<<3,
FACILITY_NEWS = 7<<3,
FACILITY_UUCP = 8<<3,
FACILITY_CLOCK1 = 9<<3,
FACILITY_AUTH2 = 10<<3,
FACILITY_FTP = 11<<3,
FACILITY_NTP = 12<<3,
FACILITY_LOGAUDIT = 13<<3,
FACILITY_LOGALERT = 14<<3,
FACILITY_CLOCK2 = 15<<3,
FACILITY_LOCAL0 = 16<<3,
FACILITY_LOCAL1 = 17<<3,
FACILITY_LOCAL2 = 18<<3,
FACILITY_LOCAL3 = 19<<3,
FACILITY_LOCAL4 = 20<<3,
FACILITY_LOCAL5 = 21<<3,
FACILITY_LOCAL6 = 22<<3,
FACILITY_LOCAL7 = 23<<3,
NUM_FACILITIES = 24,
SEVERITY_EMERG = 0,
SEVERITY_ALERT = 1,
SEVERITY_CRIT = 2,
SEVERITY_ERROR = 3,
SEVERITY_WARN = 4,
SEVERITY_NOTICE = 5,
SEVERITY_INFO = 6,
SEVERITY_DEBUG = 7
};
// Receive modes
enum
{
RECVMODE_TRUNCATE,
RECVMODE_FLAT,
RECVMODE_SPLIT, // Default
RECVMODE_FORENSIC,
RECVMODE_FORENSICRAW
};
// Log file splitting modes
enum
{
SPLITMODE_HOUR, // Default
SPLITMODE_DAY
};
/***************************************************************************
*
* Type definitions and macros
*
***************************************************************************/
// Basic IPv4 address representation -- struct inaddr never looks the same
typedef union tagIP4ADDR
{
DWORD dwIP4;
BYTE achIP4[4];
} IP4ADDR, *PIP4ADDR;
// Description of an (open) log output destination
typedef struct tagDEST
{
FILE *fp;
long lFileSize;
IP4ADDR IPAddr;
char szIPStr[20];
char *vbuf;
} DEST, *PDEST;
// Failure report for a single IP address (with occurence counter)
typedef struct tagFAILREPORT
{
IP4ADDR IPAddr;
int nOccurences;
} FAILREPORT, *PFAILREPORT;
// Information about current time
typedef struct tagTIMEINFO
{
struct timeval tv;
int nUTCOffs;
char szTimestamp[64]; // Time like "2002-01-05T12:34:56%06u+01:00" or "Jan 5 12:34:56" (if "--oldtimestamp")
char szNameTimestamp_Day[20]; // Time like "20020105"
char szNameTimestamp[20]; // Time like "2002010512" or "20020105" (if "--split day")
} TIMEINFO, *PTIMEINFO;
typedef const TIMEINFO *LPCTIMEINFO;
/************************************************************************
*
* Global variables
*
************************************************************************/
PDEST g_apDests[MAX_DESTS];
int g_nDests;
PDEST g_apUnusedDests[MAX_DESTS];
int g_nUnusedDests;
PDEST g_apDestHash[1<<DEST_HASH_BITS];
int g_nRecvMode = RECVMODE_SPLIT;
LPCSTR g_pszProgname = "minirsyslogd";
// These two get reset every hour
DWORD g_dwBytesReceived=0; // Counts up to 1MB
DWORD g_dwMegaBytesReceived=0; // Counts the rest
FILE *g_fpDaemonLog = NULL;
FAILREPORT g_aFailReports[MAX_FAILREPORTS];
int g_nFailReports=0;
int g_nWildcardFails = 0; // Failures not accounted for in the g_aFailReports array
BOOL g_bExitNow = FALSE; // Set to TRUE by SIGINT and SIGTERM
BOOL g_bCloseAllFilesNow = FALSE; // Set to TRUE by SIGHUP
TIMEINFO g_tiNow; // Lots of places need the current time
/************************************************************************
*
* Global variables -- settings parsed from cmdline
*
************************************************************************/
BOOL g_bVerbose = FALSE;
int g_nBufSize=16384;
int g_nPort=514;
int g_nMaxDests=50;
int g_nMaxOpensPerSec=200;
long g_lMaxFileSize=2000000000; // Don't exceed 2GB file size limit
BOOL g_bDaemon = FALSE;
int g_nSplitMode = SPLITMODE_HOUR;
BOOL g_bOldTimestamps = FALSE; // Use old-style syslog timestamps rather than RFC3339
mode_t g_nUmask = 0077;
LPCSTR g_pszRootDir = ".";
/************************************************************************
*
* Function : mystrlcpy
*
* Purpose : strncpy with guaranteed NUL termination
*
* Params : [O] char *dest
* [I] const char *src
* [I] size_t n
*
* Returns : -
*
************************************************************************/
void mystrlcpy(char *dest, const char *src, size_t n)
{
strncpy(dest, src, n);
dest[n-1]='\0';
}
/************************************************************************
*
* Function : GetTimestamp
*
* Purpose : Return log record timestamp string like
* "2002-01-05T12:34:56.456789+01:00" or
* "Jan 5 12:34:56" (if g_bOldTimestamps)
*
* Note : Uses internal static buffer. Next call overwrites.
*
* Params : [I] LPCTIMEINFO pti
*
* Returns : LPCSTR -- pointer to usable timestamp string
* in static buffer
*
************************************************************************/
LPCSTR GetTimestamp(LPCTIMEINFO pti)
{
static char achBuf[64];
if(g_bOldTimestamps)
mystrlcpy(achBuf, pti->szTimestamp, sizeof(achBuf));
else
// szTimestamp will contain a "%06u" where the microsecond value goes
snprintf(achBuf, sizeof(achBuf), pti->szTimestamp, pti->tv.tv_usec);
return achBuf;
}
/************************************************************************
*
* Function : dlog
*
* Purpose : Output a message to the local daemon log
* Accepts printf-style input
*
* If the daemon log is not open: dump on stderr
*
* If g_bVerbose, messages will always be copied to stdout
*
* Params : [I] int nWhere -- 0: only daemon log
* 1: daemon log + stdout
* 2: daemon log + stderr
* [I] LPCSTR pszFmt -- printf-style format string
* [I] ... -- args
*
* Returns : -
*
************************************************************************/
void dlog(int nWhere, LPCSTR pszFmt, ...)
{
va_list args;
FILE *fp;
// If the local daemon log file isn't open, dump the message on stderr
fp=g_fpDaemonLog;
if(!g_fpDaemonLog)
fp = stderr;
fprintf(fp, "%s ", GetTimestamp(&g_tiNow));
va_start(args, pszFmt);
vfprintf(fp, pszFmt, args);
va_end(args);
fprintf(fp, "\n");
fflush(fp);
// If we're verbose, or nWhere>0: output on stdout too
if( g_bVerbose || nWhere>0 )
{
if(nWhere>=2 && fp!=stderr)
fp=stderr;
else
fp=stdout;
fprintf(fp, "%s ", g_pszProgname);
va_start(args, pszFmt);
vfprintf(fp, pszFmt, args);
va_end(args);
fprintf(fp,"\n");
fflush(fp);
}
}
/************************************************************************
*
* Function : bomb / bomberrno
*
* Purpose : - Print error message
* (bomberrno: append error description)
* - exit(1)
*
* Params : [I] LPCSTR str
*
* Returns : -
*
************************************************************************/
void bomb(LPCSTR pszMsg)
{
dlog(2, "fatal: %s", pszMsg);
exit(1);
}
void bomberrno(LPCSTR pszMsg)
{
dlog(2, "fatal: %s: %s", pszMsg, strerror(errno));
exit(1);
}
/************************************************************************
*
* Function : myrand
*
* Purpose : Low-cost PRNG which behaves "well" for these purposes.
* Don't you DARE think about using it for anything
* security critical - it is very predictable if the
* outputs are actually revealed.
*
* Returns : DWORD -- 32-bit pseudorandom number
* The best entropy is returned in the low 16 bits.
*
************************************************************************/
DWORD myrand(void)
{
static DWORD dwLCPRNG=0xdeadbeef;
static DWORD dwCntr=0;
// Simple LCPRNG, period 2^31-1
dwLCPRNG = dwLCPRNG * 1103515245 + 12345;
if(dwLCPRNG >= 0x7fffffff)
(dwLCPRNG) -= 0x7fffffff;
if(dwLCPRNG >= 0x7fffffff)
(dwLCPRNG) -= 0x7fffffff;
// Increment counter by the decimal part of the golden ratio, period 2^32
dwCntr+=0x9e3779b9;
// Return wordswap(dwLCPRNG) + dwCntr + g_dwBytesReceived.
// The total period of dwLCPRNG and dwCntr combined is about 2^63.
// The addition of the received byte count adds "real" entropy, making
// the period "indefinitely" long.
// The high/low words of dwLCPRNG are swapped since the low bits of LCPRNGs
// exhibit disturbing statistical correlations. This instead puts the "bad
// entropy" in the middle of the returned DWORD.
return (dwLCPRNG>>16) + (dwLCPRNG<<16) + dwCntr + g_dwBytesReceived;
}
/************************************************************************
*
* Function : GenerateTimeInfo
*
* Purpose : Fill out a TIMEINFO struct:
* - tv
* - nUTCOffs
* - szTimestamp -- used in log records, either RFC3339 or syslog std
* - szNameTimestamp_Day -- YYYYMMDD
* - szNameTimestamp -- YYYYMMDDHH, or YYYYMMDD (with "--split day")
*
* szTimestamp is special. If RFC3339 timestamps are used,
* it will contain a "%06u" format string so that it can
* be passed to a printf-style formatter that inserts the
* the microsecond value.
*
* Note : Never returns failure; calls bomberrno() on failure
*
* Uses the existing contents of the timeinfo struct
* to determine whether or not we have passed into a
* new second. If not, only tv.tv_usec is updated, and
* the function returns FALSE.
*
* Called by: main loop, at startup and every second
*
* Params : [I/O] PTIMEINFO pti
* [O] struct tm *ptmOut -- copy of local time (optional)
*
* Returns : If we have passed into a new second: TRUE, and
* will have updated all members of the struct
* If we're in the same second: FALSE, and
* only tv.tv_usec updated
*
************************************************************************/
BOOL GenerateTimeInfo(PTIMEINFO pti, struct tm *ptmOut)
{
static LPCSTR apszMonths[]={"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
int nUTCOffs_Abs;
struct tm *ptmTmp, tmLocal, tmUTC;
struct timeval tv;
time_t t;
////////////////////////////////////////////////////////////
// Get current time
if(gettimeofday(&tv, NULL)<0)
bomberrno("gettimeofday");
if(tv.tv_usec>=1000000) // Yes, this actually happens on some OSes
{
tv.tv_usec-=1000000;
tv.tv_sec+=1;
}
// Are we still in the same second?
if(tv.tv_sec == pti->tv.tv_sec)
{
pti->tv.tv_usec = tv.tv_usec;
return FALSE; // "same second"
}
////////////////////////////////////////////////////////////
// New second: regenerate everything
pti->tv = tv;
// Get time components
t=(time_t)pti->tv.tv_sec;
if(!( ptmTmp = localtime(&t) ))
bomberrno("localtime");
tmLocal = *ptmTmp;
if(!( ptmTmp = gmtime(&t) ))
bomberrno("gmtime");
tmUTC = *ptmTmp;
// Hand out tmLocal?
if(ptmOut)
*ptmOut = tmLocal;
// Compute UTC offset. (No, there's no other portable way of getting hold of it.)
pti->nUTCOffs= ( (tmLocal.tm_hour - tmUTC.tm_hour) * 60 ) + tmLocal.tm_min - tmUTC.tm_min;
if(tmLocal.tm_year > tmUTC.tm_year)
pti->nUTCOffs += 24*60;
else if(tmLocal.tm_year < tmUTC.tm_year)
pti->nUTCOffs -= 24*60;
else if(tmLocal.tm_yday > tmUTC.tm_yday)
pti->nUTCOffs += 24*60;
else if(tmLocal.tm_yday < tmUTC.tm_yday)
pti->nUTCOffs -= 24*60;
if((nUTCOffs_Abs = pti->nUTCOffs)<0)
nUTCOffs_Abs = 0 - nUTCOffs_Abs;
////////////////////////////////////////////////////////////
// Format szTimestamp (used in log records):
if(g_bOldTimestamps)
// as "Jun 02 12:34:56" (syslog standard format)
snprintf(pti->szTimestamp, sizeof(pti->szTimestamp), "%s %2u %02u:%02u:%02u",
apszMonths[tmLocal.tm_mon], tmLocal.tm_mday, tmLocal.tm_hour, tmLocal.tm_min, tmLocal.tm_sec
);
else
// or "2002-06-02T12:34:56.%06u+02:00" (RFC3339) -- note the "%06u"
snprintf(pti->szTimestamp, sizeof(pti->szTimestamp), "%04u-%02u-%02uT%02u:%02u:%02u.%%06u%c%02u:%02u",
tmLocal.tm_year+1900, tmLocal.tm_mon+1, tmLocal.tm_mday,
tmLocal.tm_hour, tmLocal.tm_min, tmLocal.tm_sec,
pti->nUTCOffs>=0 ? '+' : '-',
nUTCOffs_Abs/60,
nUTCOffs_Abs%60
);
////////////////////////////////////////////////////////////
// Format g_szNameTimestamp_Day as "20020602" (used by daemon's own logs)
snprintf(pti->szNameTimestamp_Day, sizeof(pti->szNameTimestamp_Day), "%04u%02u%02u",
tmLocal.tm_year+1900, tmLocal.tm_mon+1, tmLocal.tm_mday
);
////////////////////////////////////////////////////////////
// Format g_szNameTimestamp (log file naming)
if(g_nSplitMode == SPLITMODE_DAY)
// as "20020602" (with --split day)
mystrlcpy(pti->szNameTimestamp, pti->szNameTimestamp_Day, sizeof(pti->szNameTimestamp));
else
// as "2002060212" (default)
snprintf(pti->szNameTimestamp, sizeof(pti->szNameTimestamp), "%04u%02u%02u%02u",
tmLocal.tm_year+1900, tmLocal.tm_mon+1, tmLocal.tm_mday, tmLocal.tm_hour
);
return TRUE; // "we hit a new second"
}
/************************************************************************
*
* Function : OpenDaemonLog
*
* Purpose : Open the local daemon log file g_fpDaemonLog:
* "minirsyslogd-YYYYMMDD"
*
* Note : Never returns failure; calls bomberrno() on failure
*
* Assumes : That g_fpDaemonLog is already closed
*
* Called by: main loop, at startup and every hour
*
* Returns : -
* Will have set g_fpDaemonLog
*
************************************************************************/
void OpenDaemonLog(void)
{
char szTmp[1024];
snprintf(szTmp, sizeof(szTmp), "minirsyslogd-%s", g_tiNow.szNameTimestamp_Day);
g_fpDaemonLog = fopen(szTmp, "at");
if(!g_fpDaemonLog)
bomberrno("could not open local daemon log file");
}
/************************************************************************
*
* Function : CloseDaemonLog
*
* Purpose : Close the local daemon log file (if it is open)
*
* Called by: main loop, at shutdown and every hour
*
* Returns : -
* g_fpDaemonLog will be NULL
*
************************************************************************/
void CloseDaemonLog(void)
{
if(g_fpDaemonLog)
fclose(g_fpDaemonLog);
g_fpDaemonLog=NULL;
}
/************************************************************************
*
* Function : DestHash_HashFunc
*
* Purpose : Generate hash value for given IP4ADDR
*
* Note : This hash would be at the mercy of attackers if it wasn't
* for the fact that the administrator gets to decide the IPs,
* not the attacker. Hah.
*
* Called by: DestHash_Add, DestHash_Remove
*
* Params : [I] PIP4ADDR pIPAddr
*
* Returns : DWORD
*
************************************************************************/
DWORD DestHash_HashFunc(PIP4ADDR pIPAddr)
{
DWORD dw;
dw = (pIPAddr->achIP4[0]<<24) |
(pIPAddr->achIP4[1]<<16) |
(pIPAddr->achIP4[2]<<8) |
(pIPAddr->achIP4[3]);
dw *= 0x9e3779b9;
dw >>= 32-DEST_HASH_BITS;
return dw;
}
/************************************************************************
*
* Function : DestHash_Add
*
* Purpose : Add given pDest to g_apDestHash
*
* Called by: Dest_Init
*
* Params : [I] PDEST pDest
*
* Returns : -
*
************************************************************************/
void DestHash_Add(PDEST pDest)
{
DWORD dwHash, dwCnt;
// This is a simple skip hash. Instead of making arbitrarily deep buckets,
// we just try the next bucket if the right one is taken.
//
// The hash MUST have at least as many buckets as items, and preferably
// three times as many for it to work any where near well.
dwHash = DestHash_HashFunc(&pDest->IPAddr);
// Find the first empty slot at or after the hash value
for( dwCnt=1<<DEST_HASH_BITS ; g_apDestHash[dwHash] ; dwCnt--)
{
if(!dwCnt)
bomb("INTERNAL BROKENNESS: DestHash_Add: Hash is FULL?!");
if(g_apDestHash[dwHash]==pDest)
bomb("INTERNAL BROKENNESS: DestHash_Add: pDest was already in the hash?!");
dwHash = (dwHash+1) & ((1<<DEST_HASH_BITS)-1);
}
g_apDestHash[dwHash] = pDest;
}
/************************************************************************
*
* Function : DestHash_Remove
*
* Purpose : Remove given pDest from g_apDestHash
*
* Called by: Dest_Destroy
*
* Params : [I] PDEST pDest
*
* Returns : -
*
************************************************************************/
void DestHash_Remove(PDEST pDest)
{
DWORD dwHash, dwCnt;
dwHash = DestHash_HashFunc(&pDest->IPAddr);
// Find the given pDest at or after the hash value
for( dwCnt=1<<DEST_HASH_BITS ; g_apDestHash[dwHash]!=pDest ; dwCnt--)
{
if(!dwCnt)
bomb("INTERNAL BROKENNESS: DestHash_Remove: Couldn't find pDest in the hash?!");
dwHash = (dwHash+1) & ((1<<DEST_HASH_BITS)-1);
}
g_apDestHash[dwHash] = NULL;
}
/************************************************************************
*
* Function : Dest_Init
*
* Purpose : Initialize a DEST struct:
* - Copy in destination information (IP address)
* - Open output file
* - Allocate (bigger?) stream buffer
*
* Calls : DestHash_Add()
*
* Assumes : That g_tiNow contains valid time data
*
* Params : [O] PDEST pDest
* [I] PIP4ADDR pip
*
* Returns : BOOL - FALSE for failure (failed to open output file)
*
************************************************************************/
BOOL Dest_Init(PDEST pDest, PIP4ADDR pip)
{
char szTmp[1024];
memset(pDest, 0, sizeof(DEST));
// Copy in given parameters
pDest->IPAddr = *pip;
// Add to hash
DestHash_Add(pDest);
// Set up string representation of IP address (for speed, later on)
snprintf(pDest->szIPStr, sizeof(pDest->szIPStr), "%u.%u.%u.%u",
pDest->IPAddr.achIP4[0], pDest->IPAddr.achIP4[1], pDest->IPAddr.achIP4[2], pDest->IPAddr.achIP4[3]
);
// Open output file
snprintf(szTmp, sizeof(szTmp), "%s/%s-%s",
pDest->szIPStr, pDest->szIPStr, g_tiNow.szNameTimestamp
);
if(g_bVerbose) printf("OPEN %s ", szTmp);
if(!( pDest->fp = fopen(szTmp, "at") ))
{
// Open failed -- we keep the destination around with a null FP though, to cache the "failed" indiciation
// 3TODO: Make this smarter -- WHY did it fail?
if(g_bVerbose) printf("FAILED!\n");
return FALSE;
}
if(g_bVerbose) printf("OK\n");
// Get current file length (we need to track it so that we never write
// files that exceed the 2GB limit -- it causes glibc to terminate the
// application!)
if( (pDest->lFileSize = ftell(pDest->fp)) < 0)
{
dlog(0, "warning: could not get length of %s: %s", szTmp, strerror(errno));
fclose(pDest->fp);
pDest->fp=NULL;
return FALSE;
}
// Set buffer size (do nothing if our malloc fails; the buffer allocated by
// libc gets used). I originally had a smarter algorithm that malloced and
// switched to the larger buffer only if it was actually necessary (lots of
// writes per second), but many versions of glibc hates that. Duh.
if((pDest->vbuf = malloc(g_nBufSize)))
setvbuf(pDest->fp, pDest->vbuf, _IOFBF, g_nBufSize);
return TRUE;
}
/************************************************************************
*
* Function : Dest_Output
*
* Purpose : Format pszFmt+varargs and output to the given pDest
*
* Also keep track of file lengths so that they aren't
* exceeded. If this happens, we dlog() the event and output
* a "Maximum file length exceeded" line in the output file
* and refuse to write more data to the file.
*
* Note : No extra formatting. Data is written exactly as given.
* Don't do multiple calls for a single entry; you don't
* want the size limit to trigger in the middle of an entry!
*
* Called by: main loop
*
* Params : [I] PDEST pDest - an open DEST struct
* [I] LPCSTR pszFmt - printf style format
* [I] ...
*
* Returns : -
*
************************************************************************/
void Dest_Output(PDEST pDest, LPCSTR pszFmt, ...)
{
va_list args;
long lWritten;
// Bail early if the file is already too big
if(pDest->lFileSize >= g_lMaxFileSize)
return;
// Format and output
va_start(args, pszFmt);
lWritten=vfprintf(pDest->fp, pszFmt, args);
va_end(args);
// Track file size changes
if(lWritten<0)
; // 4TODO: What to do here? Close the output file? Log an error? Keep ignoring it?
else if(lWritten>=65600)
; // 5TODO: "Should never happen" ...?
else
pDest->lFileSize += lWritten;
// Emit warnings if this write exceeded the max file size
if(pDest->lFileSize >= g_lMaxFileSize)
{
fprintf(pDest->fp, "%s minirsyslogd <%u>Maximum file length (%u) exceeded for %s\n",
GetTimestamp(&g_tiNow), FACILITY_SYSLOGD | SEVERITY_WARN, (DWORD)g_lMaxFileSize, pDest->szIPStr);
dlog(0, "drop: maximum file length (%u) exceeded for %s", (DWORD)g_lMaxFileSize, pDest->szIPStr);
}
}
/************************************************************************
*
* Function : Dest_Destroy
*
* Purpose : Destroy a DEST struct. Free associated resources.
*
* Calls : DestHash_Remove()
*
* Params : PDEST pDest
*
* Returns : -
*
************************************************************************/
void Dest_Destroy(PDEST pDest)
{
DestHash_Remove(pDest);
if(pDest->fp)
fclose(pDest->fp);
pDest->fp=NULL;
if(pDest->vbuf)
free(pDest->vbuf);
pDest->vbuf=NULL;
}
/************************************************************************
*
* Function : Dest_DestroyAll
*
* Purpose : Call Dest_DestroyAll for ALL open dests
*
* Calls : Dest_Destroy()
*
* Called by: main
*
* Returns : -
*
************************************************************************/
void Dest_DestroyAll(void)
{
while(g_nDests>0)
{
g_nDests--;
if(g_bVerbose) printf("CLOSE %s\n", g_apDests[g_nDests]->szIPStr);
Dest_Destroy(g_apDests[g_nDests]);
g_apUnusedDests[g_nUnusedDests++] = g_apDests[g_nDests];
}
}
/************************************************************************
*
* Function : AddFailReport
*
* Purpose : Add a failure report to the g_aFailReports array
* or: increment counter of an entry already in the array
* or: increment g_nWildcardFails if there's no room
*
* Params : [I] PIP4ADDR pIPAddr
*
* Returns : -
*
************************************************************************/
void AddFailReport(PIP4ADDR pIPAddr)
{
int n;
// See if this IP is already in the list of failure reports
for(n=g_nFailReports-1 ; n>=0 ; n--)
if(g_aFailReports[n].IPAddr.dwIP4 == pIPAddr->dwIP4)
{
if(g_aFailReports[n].nOccurences < 0x7fffffff)
g_aFailReports[n].nOccurences++; // Just increment the "occurences" counter
return;
}
// Not in the list of failure reports: add it if there's room
if(g_nFailReports<MAX_FAILREPORTS)
{
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]);
g_aFailReports[g_nFailReports].IPAddr = *pIPAddr;
g_aFailReports[g_nFailReports].nOccurences = 1;
g_nFailReports++;
}
else if(g_nWildcardFails < 0x7fffffff)
g_nWildcardFails++;
}
/************************************************************************
*
* Function : OutputFailReports
*
* Purpose : Output the list of "failed to open" occurences
* (IP addresses denied access)
*
* Clear the status and prepare for another period
*
* Assumes : That g_tiNow contains valid time data
*
* Returns : -
*
************************************************************************/
void OutputFailReports(void)
{
int n;
if(g_nFailReports<=0)
goto justreset;
// Print detailed list
for(n=0;n<g_nFailReports;n++)
{
IP4ADDR ip = g_aFailReports[n].IPAddr;
dlog(0, "drop: failed %u.%u.%u.%u %i times",
ip.achIP4[0], ip.achIP4[1], ip.achIP4[2], ip.achIP4[3],
g_aFailReports[n].nOccurences
);
}
// Print wildcard occurences (stuff that didn't fit in the list)
if(g_nWildcardFails)
{
dlog(0, "drop: failed * %i times",
g_nWildcardFails
);
}
// Reset everything
justreset:
g_nFailReports=0;
g_nWildcardFails=0;
}
/************************************************************************
*
* Function : usage
*
* Purpose : Display program usage guide and, optionally, an error
* message. If an error message is displayed, everything
* goes out through stderr, otherwise stdout.
*
* Exits the program with a status of 1 or 0 depending
* on if an error message was displayed.
*
* Calls : exit()
*
* Params : [I] LPCSTR pszMsg
*
* Returns : int (ignored; the function never returns)
*
************************************************************************/
int usage(LPCSTR pszMsg)
{
FILE *fp;
if(pszMsg && !*pszMsg)
pszMsg = NULL;
if(pszMsg)
fp = stderr;
else
fp = stdout;
if(pszMsg)
fprintf(fp, "%s: %s\n\n", g_pszProgname, pszMsg);
fprintf(fp,
"%s " VERSION_STRING " options:\n"
" --daemon run in background, as a daemon\n"
" --rootdir <path> where to store the logs (default: .)\n"
" --maxopen <num> max open files (default: 50, limit: %u)\n"
" --port <num> port number to use (default: 514)\n"
" --recvmode <mode> see 'receive modes' below (default: split)\n"
" --split <mode> see 'splitting modes' below (default: hour)\n"
" --oldtimestamp use old-style syslog timestamps rather than rfc3339\n"
" --pidfile <filename> where to output the pid of minirsyslogd\n"
" (default: /var/run/minirsyslogd[-<port>].pid)\n"
" --umask <mask> umask to use when creating log files (default: 077)\n"
" --maxopenspersec <num> max file open attempts/s (default: 200)\n"
" --maxfilesize <mbytes> maximum output file size in megabytes (default: 2000)\n"
" --bufsize <bytes> output file buffer size (default: 16384)\n"
" --verbose verbose output (to stdout)\n"
"\n"
"Log file splitting modes:\n"
" hour - split log files each hour (default)\n"
" day - split log files each day\n"
"\n"
"Receive modes:\n"
" truncate - truncate the event at the first LF\n"
" flat - convert all LFs to spaces\n"
" split - split data on LFs and store as multiple events (default)\n"
" forensic - emit a byte count, followed by data (possibly with LFs)\n"
" forensicraw - -\"-, but without stripping control codes\n"
"\n"
" In all modes except forensicraw, ASCII codes 1-31 and 128-159\n"
" are converted to spaces (32). NUL characters are always\n"
" converted to spaces.\n"
,
g_pszProgname,
(int)mymin(MAX_DESTS, sysconf(_SC_OPEN_MAX)-5)
);
exit(pszMsg ? 1 : 0);
return 0; // Keep compilers happy
}
/************************************************************************
*
* Function : sighandler
*
* Purpose : For SIGTERM and SIGINT: Set g_bExitNow and set these signal
* back to default behavior.
* For SIGHUP: Set g_bCloseAllFilesNow
* For others: no-op. None others are expected.
*
* Params : [I] int sig
*
* Returns : -
*
************************************************************************/
void sighandler(int sig)
{
if(sig==SIGTERM || sig==SIGINT)
{
if(g_bVerbose) printf("sighandler: got SIGTERM/SIGINT\n");
g_bExitNow=TRUE;
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
}
else if(sig==SIGHUP)
{
if(g_bVerbose) printf("sighandler: got SIGHUP\n");
g_bCloseAllFilesNow=TRUE;
}
else if(g_bVerbose)
printf("sighandler: got signal %u ?!?!?!\n", sig);
}
/************************************************************************
*
* Function : WritePIDFile
*
* Purpose : Write the current PID (or not) to the given file
*
* Called by: main()
*
* Params : [I] BOOL bWritePID -- TRUE: write the PID
* FALSE: just wipe the contents of the file
*
* Returns : -
*
************************************************************************/
void WritePIDFile(BOOL bWritePID, LPCSTR pszPIDFile)
{
char szPIDFileTmp[1024];
FILE *fp;
mode_t nPreviousUmask;
// Change umask: causes pidfile to be -rw-------
nPreviousUmask = umask(077);
// Figure out name of pid file if not given
if(!pszPIDFile)
{
if(g_nPort==514)
snprintf(szPIDFileTmp, sizeof(szPIDFileTmp), "/var/run/minirsyslogd.pid");
else
snprintf(szPIDFileTmp, sizeof(szPIDFileTmp), "/var/run/minirsyslogd-%u.pid", g_nPort);
pszPIDFile=szPIDFileTmp;
}
if(g_bVerbose)
{
if(bWritePID)
printf("Writing '%u' to pidfile '%s'...\n", (int)getpid(), pszPIDFile);
else
printf("Wiping pidfile '%s'...\n", pszPIDFile);
}
// Open, write and close
if(!(fp=fopen(pszPIDFile, "wt")))
dlog(2, "warning: failed to write to pidfile '%s'", pszPIDFile);
else
{
if(bWritePID)
fprintf(fp, "%u\n", getpid());
fclose(fp);
}
// Restore umask
umask(nPreviousUmask);
}
/************************************************************************
*
* Function : main
*
* Purpose : - Parse arguments
*
* Params : [I] int argc
* [I] char *argv[]
*
* Returns : int
*
************************************************************************/
int main(int argc, char *argv[])
{
int sock;
int n, nArg;
char szTmp[1024];
struct sockaddr_in Sin;
size_t cbSin;
struct tm tmNow, tmPrev;
PDEST pDest;
int nOpensThisSec=0; // Number of file open attempts this second
int nOpensThisHour=0; // Number of file open attempts this hour
LPCSTR pszPIDFile=NULL;
int nSecondsSinceCloseAll=0; // Seconds since the turn of the hour, or since SIGHUP
BOOL bFirstLoop=TRUE;
g_pszProgname = argv[0];
g_bExitNow = FALSE;
g_bCloseAllFilesNow = FALSE;
g_tiNow.tv.tv_sec = 0; // Make sure GenerateTimeInfo() generates EVERYTHING this time
GenerateTimeInfo(&g_tiNow, &tmNow); // Set up initial timestamps for startup messages
signal(SIGTERM, &sighandler);
signal(SIGINT, &sighandler);
signal(SIGHUP, &sighandler);
////////////////////////////////////////////////////////////
// Parse arguments
for(nArg=1;nArg<argc;nArg++)
{
if(!stricmp(argv[nArg], "--help"))
usage(NULL);
else if(!stricmp(argv[nArg], "--port"))
{
if(++nArg >= argc)
usage("missing argument to '--port'");
g_nPort = atoi(argv[nArg]);
if(g_nPort<1 || g_nPort>65535)
usage("bad argument to '--port': must be 1..65535");
}
else if(!stricmp(argv[nArg], "--maxopen"))
{
if(++nArg >= argc)
usage("missing argument to '--maxopen'");
g_nMaxDests = atoi(argv[nArg]);
if(g_nMaxDests<5 || g_nMaxDests>MAX_DESTS || g_nMaxDests>sysconf(_SC_OPEN_MAX)-5)
{
snprintf(szTmp, sizeof(szTmp), "bad argument to '--maxopen': must be 5..%i", (int)mymin(MAX_DESTS,sysconf(_SC_OPEN_MAX)-5) );
usage(szTmp);
}
}
else if(!stricmp(argv[nArg], "--maxopenspersec"))
{
if(++nArg >= argc)
usage("missing argument to '--maxopenspersec'");
g_nMaxOpensPerSec = atoi(argv[nArg]);
if(g_nMaxOpensPerSec<5 || g_nMaxOpensPerSec>MAX_DESTS*1000)
{
snprintf(szTmp, sizeof(szTmp), "bad argument to '--maxopenspersec': must be 5..%i", MAX_DESTS*1000);
usage(szTmp);
}
}
else if(!stricmp(argv[nArg], "--bufsize"))
{
if(++nArg >= argc)
usage("missing argument to '--bufsize'");
g_nBufSize = atoi(argv[nArg]);
if(g_nBufSize<1024 || g_nBufSize>131072)
usage("bad argument to '--bufsize': must be 1024..131072, preferably a power of 2");
}
else if(!stricmp(argv[nArg], "--maxfilesize"))
{
int n;
if(++nArg >= argc)
usage("missing argument to '--maxfilesize'");
n = atoi(argv[nArg]);
if(n<1 || n>2000)
usage("bad argument to '--maxfilesize': must be 1..2000 (megabytes)");
g_lMaxFileSize = (long)n * 1000000;
}
else if(!stricmp(argv[nArg], "--recvmode"))
{
if(++nArg >= argc)
usage("missing argument to '--recvmode'");
if(!stricmp(argv[nArg], "truncate"))
g_nRecvMode = RECVMODE_TRUNCATE;
else if(!stricmp(argv[nArg], "flat"))
g_nRecvMode = RECVMODE_FLAT;
else if(!stricmp(argv[nArg], "split"))
g_nRecvMode = RECVMODE_SPLIT;
else if(!stricmp(argv[nArg], "forensic"))
g_nRecvMode = RECVMODE_FORENSIC;
else if(!stricmp(argv[nArg], "forensicraw"))
g_nRecvMode = RECVMODE_FORENSICRAW;
else
usage("bad argument to '--recvmode'");
}
else if(!stricmp(argv[nArg], "--daemon"))
{
g_bDaemon=TRUE;
}
else if(!stricmp(argv[nArg], "--splitday")) // Deprecated
{
g_nSplitMode = SPLITMODE_DAY;
dlog(2, "warning: '--splitday' is deprecated. Use '--split day' instead.");
}
else if(!stricmp(argv[nArg], "--split"))
{
if(++nArg >= argc)
usage("missing argument to '--split'");
if(!stricmp(argv[nArg], "day"))
g_nSplitMode = SPLITMODE_DAY;
else if(!stricmp(argv[nArg], "hour"))
g_nSplitMode = SPLITMODE_HOUR;
else
usage("bad argument to '--split'");
}
else if(!stricmp(argv[nArg], "--oldtimestamp"))
{
g_bOldTimestamps=TRUE;
}
else if(!stricmp(argv[nArg], "--umask"))
{
DWORD dw=0;
LPCSTR psz;
if(++nArg >= argc)
usage("missing argument to '--umask'");
psz = argv[nArg];
for(;*psz;psz++)
if(*psz>='0' && *psz<='7')
dw=(dw<<3) | (DWORD)( *psz - '0' );
else
usage("bad argument to --umask. expected octal number.");
if(dw>=0100)
usage("bad argument to --umask. expected octal number between 000 and 077.");
g_nUmask = (mode_t)dw;
printf("umask: %08x\n", dw);
}
else if(!stricmp(argv[nArg], "--pidfile"))
{
if(++nArg >= argc)
usage("missing argument to '--pidfile'");
pszPIDFile = argv[nArg];
}
else if(!stricmp(argv[nArg], "--rootdir"))
{
if(++nArg >= argc)
usage("missing argument to '--rootdir'");
g_pszRootDir = argv[nArg];
}
else if(!stricmp(argv[nArg], "--verbose"))
{
g_bVerbose=TRUE;
}
else
{
snprintf(szTmp, sizeof(szTmp), "unrecognized option '%s'", argv[nArg]);
szTmp[sizeof(szTmp)-1]='\0';
usage(szTmp);
}
} // END for(nArg=1;nArg<argc;nArg++)
////////////////////////////////////////////////////////////
// Fix umask and current directory
umask(g_nUmask);
if(chdir(g_pszRootDir)!=0)
{
snprintf(szTmp, sizeof(szTmp), "chdir \"%s\"", g_pszRootDir);
bomberrno(szTmp);
}
////////////////////////////////////////////////////////////
// Tell people that we're starting up
OpenDaemonLog(); // Open local daemon log
dlog(1, "startup: version=\"" VERSION_STRING "\" pid=%u uid=%u gid=%u euid=%u egid=%u",
getpid(), getuid(), getgid(), geteuid(), getegid());
if(!getcwd(szTmp, sizeof(szTmp)))
bomb("getcwd");
dlog(1, "settings: rootdir=\"%s\" maxopen=%u port=%u maxopenspersec=%u split=%s recvmode=%s",
szTmp, g_nMaxDests, g_nPort, g_nMaxOpensPerSec,
g_nSplitMode == SPLITMODE_DAY ? "day" :
"hour",
g_nRecvMode == RECVMODE_TRUNCATE ? "truncate" :
g_nRecvMode == RECVMODE_FLAT ? "flat" :
g_nRecvMode == RECVMODE_FORENSIC ? "forensic" :
g_nRecvMode == RECVMODE_FORENSICRAW ? "forensicraw" :
"split"
);
/////////////////////////////////////////////////////////
// Set up DEST structs
for(n=0;n<g_nMaxDests;n++)
{
if(!(pDest = malloc(sizeof(DEST))))
bomb("out of memory mallocing destination descriptors");
g_apUnusedDests[n]=pDest;
}
g_nUnusedDests=n;
g_nDests=0;
memset(g_apDestHash, 0, sizeof(g_apDestHash));
////////////////////////////////////////////////////////////
// Daemonize if requested
if(g_bDaemon && !g_bVerbose)
{
int i;
dlog(1, "startup: backgrounding (daemonizing)");
// Fork once so that the new process can call setsid()
switch(fork())
{
case -1:
bomberrno("fork 1");
case 0:
// I'm the child.
break;
default:
exit(0);
}
// Call setsid() to start a new process group
if(!setsid())
bomberrno("setsid");
// Fork again so the child isn't the process group leader
switch(fork())
{
case -1:
bomberrno("fork 2");
case 0:
// I'm the grandchild.
break;
default:
exit(0);
}
// Close all open files
CloseDaemonLog();
for(i=0;i<sysconf(_SC_OPEN_MAX);i++)
close(i);
// Reopen stdin/stderr/stdout to point at "/dev/null"
if(open("/dev/null", O_RDWR, 666)!=0) // The first opened file _SHOULD_ be 0!
exit(2); // And no stderr to report to, either. *sigh*
dup2(0,1);
dup2(0,2);
// Reopen the local daemon log
OpenDaemonLog();
}
////////////////////////////////////////////////////////
// Bind listening socket
if(g_bVerbose) printf("Binding port %u/udp\n", g_nPort);
sock=socket(AF_INET, SOCK_DGRAM, 0);
if(sock==-1)
bomberrno("socket");
memset(&Sin, 0, sizeof(Sin));
Sin.sin_port=htons(g_nPort);
Sin.sin_family=AF_INET;
if(bind(sock, (struct sockaddr*)&Sin, sizeof(Sin))!=0)
bomberrno("bind");
////////////////////////////////////////////////////////////
// Main loop
WritePIDFile(TRUE, pszPIDFile);
dlog(1, "startup: minirsyslogd initialized. listening on %u/udp", g_nPort);
bFirstLoop = TRUE;
while(!g_bExitNow)
{
char achBuf[8193];
int nLen;
IP4ADDR ip;
char *pch;
fd_set fds;
struct timeval tmo={0,250000};
////////////////////////////////////////////////////////////
// Receive inbound packet
#ifndef SPAMRECEIVE
cbSin=sizeof(Sin);
FD_ZERO(&fds);
FD_SET(sock, &fds);
if(select(sock+1, &fds, NULL, NULL, &tmo)>0)
{
nLen=recvfrom(sock, achBuf, sizeof(achBuf)-1, 0, (struct sockaddr*)&Sin, &cbSin);
if(nLen<0)
nLen=0;
memcpy(&ip, &Sin.sin_addr, sizeof(ip));
}
else
nLen=0;
#else
if(1)
{
static int nSpams=0;
strcpy(achBuf, "<123>");
for(nLen=5;nLen<100;nLen++)
achBuf[nLen]='a';
ip.achIP4[0]=ip.achIP4[1]=ip.achIP4[2]=1;
ip.achIP4[3]=(BYTE)rand();
if(++nSpams>=2000000)
g_bExitNow = TRUE;
}
#endif
g_dwBytesReceived+=(DWORD)nLen;
while(g_dwBytesReceived>=1000000)
{
g_dwBytesReceived-=1000000;
g_dwMegaBytesReceived++;
}
////////////////////////////////////////////////////////////
// Per-second processing
if(GenerateTimeInfo(&g_tiNow, &tmNow) || bFirstLoop )
{
// New second!
// See if we've passed into a new hour (test all variables to catch system date changes!)
if(tmNow.tm_hour != tmPrev.tm_hour ||
tmNow.tm_mday != tmPrev.tm_mday ||
tmNow.tm_mon != tmPrev.tm_mon ||
tmNow.tm_year != tmPrev.tm_year ||
bFirstLoop )
{
// Open new local daemon log file
CloseDaemonLog();
OpenDaemonLog();
// Forcibly close all dests
Dest_DestroyAll();
nSecondsSinceCloseAll=0;
// Output and reset statistics for current hour
if(!bFirstLoop)
dlog(0, "statistics: opens=%u recvd=%u%06u",
nOpensThisHour, g_dwMegaBytesReceived, g_dwBytesReceived
);
nOpensThisHour=0;
g_dwMegaBytesReceived = g_dwBytesReceived = 0;
// Report open failures (IPs denied access)
OutputFailReports(); // Also clears all counters / state
}
// Whine if the maxopenspersec count has been exceeded
if( g_nMaxOpensPerSec<1 )
; // noop
else if( nOpensThisSec <= g_nMaxOpensPerSec )
; // ok
else if(nSecondsSinceCloseAll<2 && nOpensThisSec <= g_nMaxDests)
; // allow more open attempts the first 2 seconds after having closed all open files
else if(nSecondsSinceCloseAll<2 && g_nMaxDests > g_nMaxOpensPerSec )
{
dlog(0, "drops: ignored %u file open attempts. maxopen (%u) exceeded during a single second.",
nOpensThisSec - g_nMaxDests,
g_nMaxDests
);
}
else
{
dlog(0, "drops: ignored %u file open attempts. maxopenspersec (%u) exceeded.",
nOpensThisSec - g_nMaxOpensPerSec,
g_nMaxOpensPerSec
);
}
nSecondsSinceCloseAll++;
// Reset stuff that needs to be reset
nOpensThisSec = 0;
tmPrev = tmNow;
} // END if(GenerateTimeInfo(&g_tiNow, &tmNow))
bFirstLoop=FALSE;
////////////////////////////////////////////////////////////
// SIGHUP -> g_bCloseAllFilesNow ?
if(g_bCloseAllFilesNow)
{
g_bCloseAllFilesNow = FALSE;
dlog(0, "signal: got SIGHUP - flushing and closing all open files");
Dest_DestroyAll();
nSecondsSinceCloseAll=0;
CloseDaemonLog();
OpenDaemonLog();
dlog(0, "signal: back from SIGHUP - all files including local daemon log were closed");
}
////////////////////////////////////////////////////////////
// Now, did we actually get a packet above?
if(nLen<1)
continue; // Nope. Go back to idling.
////////////////////////////////////////////////////////////
// Get or set up a destination to store the data to
// Find out if we already have the destination (use hash lookup)
pDest=NULL;
if(1)
{
DWORD dwHash, dwCnt;
dwHash = DestHash_HashFunc(&ip);
for(dwCnt=1<<DEST_HASH_BITS ; dwCnt>0 ; dwCnt--)
{
if(!g_apDestHash[dwHash])
break;
if(ip.dwIP4 == g_apDestHash[dwHash]->IPAddr.dwIP4)
{
pDest = g_apDestHash[dwHash];
break;
}
dwHash = (dwHash+1) & ((1<<DEST_HASH_BITS)-1);
}
}
// WE DO NOT HAVE THIS DESTINATION OPEN: Set up a new destination
if(!pDest)
{
// Apply maxopenspersec limit
nOpensThisSec++;
if( g_nMaxOpensPerSec<1 )
; // noop
else if( nOpensThisSec <= g_nMaxOpensPerSec )
; // ok
else if(nSecondsSinceCloseAll<2 && nOpensThisSec <= g_nMaxDests)
; // allow more open attempts the first 2 seconds after having closed all open files
else
continue; // This error is reported at the turn of the second
// Statistics...
if(nOpensThisHour<0x7fffffff)
nOpensThisHour++;
// Get unused dest struct or free a random one (which is better than the least recently used one)
if(g_nUnusedDests<1)
{
int i, nDest;
if(g_nDests<1)
bomb("Internal brokenness: no used and no unused destinations?!?!?!");
// We try up to three times to find a dest without an open file.
for(i=0;i<3;i++)
{
nDest = myrand() % g_nDests;
pDest = g_apDests[nDest];
if(!pDest->fp)
break;
}
if(g_bVerbose && pDest->fp) printf("FORCECLOSE %s\n", pDest->szIPStr);
Dest_Destroy(pDest);
}
else
{
pDest = g_apUnusedDests[--g_nUnusedDests];
g_apDests[g_nDests++]=pDest;
}
// Initialize destination (open output file)
if(!Dest_Init(pDest, &ip))
{
AddFailReport(&ip);
continue;
}
} // END if(!pDest)
// See if this destination was a cached "couldn't open the file" indicator
if(!pDest->fp)
{
AddFailReport(&pDest->IPAddr);
// Note that this means that we get one fail report _per_ _packet_, even
// if we're in "multiple events per packet" mode.
continue;
}
////////////////////////////////////////////////////////////
// Actual packet processing...
// Compute a length that we can actually trust, and terminate buffer
if(nLen<1)
continue;
if(nLen>sizeof(achBuf)-1)
nLen = sizeof(achBuf)-1;
achBuf[nLen]='\0';
// Act according to receive mode
switch(g_nRecvMode)
{
// TRUNCATE -- convert ctl to space, stop at first LF (or EOL)
case RECVMODE_TRUNCATE:
{
char* pchBufEnd = achBuf+nLen;
for(pch=achBuf ; pch<pchBufEnd && *pch!='\n' ; pch++)
if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
*pch=' ';
*pch='\0';
Dest_Output(pDest, "%s %s %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, achBuf);
break;
}
// FLAT -- convert all ctl (incl. LF) to space
case RECVMODE_FLAT:
{
char* pchBufEnd = achBuf+nLen;
for(pch=achBuf ; pch<pchBufEnd ; pch++)
if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
*pch=' ';
Dest_Output(pDest, "%s %s %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, achBuf);
break;
}
// FORENSIC -- emit byte count, convert ctl to space, leave LFs alone
case RECVMODE_FORENSIC:
{
char* pchBufEnd = achBuf+nLen;
for(pch=achBuf ; pch<pchBufEnd ; pch++)
if( *pch=='\n' )
; // leave LF alone
else if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
*pch=' ';
Dest_Output(pDest, "%s %s %u %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, nLen, achBuf);
break;
}
// FORENSICRAW -- emit byte count, output received data as-is (except convert NUL to space)
case RECVMODE_FORENSICRAW:
{
char* pchBufEnd = achBuf+nLen;
for(pch=achBuf ; pch<pchBufEnd ; pch++)
if( *pch=='\0' )
*pch=' ';
Dest_Output(pDest, "%s %s %u %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, nLen, achBuf);
break;
}
// SPLIT (default) -- convert ctl to space, split events on LF
default:
case RECVMODE_SPLIT:
{
char* pchBufEnd = achBuf+nLen;
char *pchLineBegin = achBuf;
pch=achBuf;
while(TRUE)
{
if(*pch=='\n' || pch>=pchBufEnd)
{
*(pch++)='\0';
Dest_Output(pDest, "%s %s %s\n", GetTimestamp(&g_tiNow), pDest->szIPStr, pchLineBegin);
pchLineBegin=pch;
if(pch>=pchBufEnd)
break;
continue;
}
if( (*pch>=0x00 && *pch<=0x1f) || (*(PBYTE)pch>=0x80 && *(PBYTE)pch<=0x9f) )
*pch=' ';
pch++;
}
break;
}
} // END switch(g_nRecvMode)
} // END neverending loop
////////////////////////////////////////////////////////////
// Output some final statistics
dlog(0, "statistics: opens=%u recvd=%u%06u",
nOpensThisHour, g_dwMegaBytesReceived, g_dwBytesReceived
);
nOpensThisHour=0;
g_dwMegaBytesReceived = g_dwBytesReceived = 0;
OutputFailReports();
////////////////////////////////////////////////////////////
// Clean up and exit
dlog(1, "shutdown: version=\"" VERSION_STRING "\" pid=%u uid=%u gid=%u euid=%u egid=%u",
getpid(), getuid(), getgid(), geteuid(), getegid());
Dest_DestroyAll();
WritePIDFile(FALSE, pszPIDFile); // Clear the PID file
CloseDaemonLog();
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1