From f619d9ef14be7cf5bf3865f6b5cad758d5c1f863 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Fri, 10 Jul 2009 12:59:27 +0200 Subject: done implementing omudpspoof This is now a "good" rsyslog output module, working as usual. I've also sorted out some reentrency issues. Code is clean and ready to be tested in practice ;) --- plugins/omudpspoof/omudpspoof.c | 291 ++++++++++++++-------------------------- 1 file changed, 98 insertions(+), 193 deletions(-) (limited to 'plugins/omudpspoof') diff --git a/plugins/omudpspoof/omudpspoof.c b/plugins/omudpspoof/omudpspoof.c index 5e599d92..8eb63c73 100644 --- a/plugins/omudpspoof/omudpspoof.c +++ b/plugins/omudpspoof/omudpspoof.c @@ -2,26 +2,7 @@ * * This is a udp-based output module that support spoofing. * - * NOTE: read comments in module-template.h to understand how this file - * works! - * - * -------------------------------------------------------------------------------- - * - * USAGE NOTES: - * To use it create a template that puts the hostname-ip ahead of what you want to - * send, similar to - * - * $template TraditionalFwdFormat,"%fromhost-ip% <%pri%>%timegenerated% %HOSTNAME% - * %syslogtag%%msg%\n" - * - * *.* @10.0.0.100;TraditionalFwdFormat - * - * The one problem right now is that any logs sent from the local box will go out - * with a source IP of 127.0.0.1 - * - * -------------------------------------------------------------------------------- - * - * Note: this file builds on UDP spoofing code contributed by + * This file builds on UDP spoofing code contributed by * David Lang . I then created a "real" rsyslog module * out of that code and omfwd. I decided to make it a separate module because * omfwd already mixes up too many things (TCP & UDP & a differnt modes, @@ -29,6 +10,19 @@ * spoofing to it. And, looking at the requirements, there is little in * common between omfwd and this module. * + * Note: I have briefly checked libnet source code and I somewhat have the feeling + * that under some circumstances we may get into trouble with the lib. For + * example, it registers an atexit() handler, which should not play nicely + * with our dynamically loaded modules. Anyhow, I refrain from looking deeper + * at libnet code, especially as testing does not show any real issues. If some + * occur, it may be easier to modify libnet for dynamic load environments than + * using a work-around (as a side not, libnet looks somewhat unmaintained, the CVS + * I can see on sourceforge dates has no updates done less than 7 years ago). + * On the other hand, it looks like libnet is thread safe (at least is appropriately + * compiled, which I hope the standard packages are). So I do not guard calls to + * it with my own mutex calls. + * rgerhards, 2009-07-10 + * * Copyright 2009 David Lang (spoofing code) * Copyright 2009 Rainer Gerhards and Adiscon GmbH. * @@ -96,29 +90,37 @@ DEFobjCurrIf(glbl) DEFobjCurrIf(net) typedef struct _instanceData { - char *host; - char *port; + uchar *host; + uchar *port; int *pSockArray; /* sockets to use for UDP */ - int bIsAddrResolved; /* is hostname address resolved? 0 - no, 1 - yes */ int compressionLevel; /* 0 - no compression, else level for zlib */ struct addrinfo *f_addr; + u_short sourcePort; + u_short sourcePortStart; /* for sorce port iteration */ + u_short sourcePortEnd; } instanceData; +#define DFLT_SOURCE_PORT_START 32000 +#define DFLT_SOURCE_PORT_END 42000 + /* config data */ static uchar *pszTplName = NULL; /* name of the default template to use */ static uchar *pszSourceNameTemplate = NULL; /* name of the template containing the spoofing address */ +static uchar *pszTargetHost = NULL; +static uchar *pszTargetPort = NULL; +static int iCompressionLevel = 0; /* zlib compressionlevel, the usual values */ +static int iSourcePortStart = DFLT_SOURCE_PORT_START; +static int iSourcePortEnd = DFLT_SOURCE_PORT_END; /* add some variables needed for libnet */ libnet_t *libnet_handle; -libnet_ptag_t ip, ipo; -libnet_ptag_t udp; char errbuf[LIBNET_ERRBUF_SIZE]; -u_short source_port=32000; /* forward definitions */ static rsRetVal doTryResume(instanceData *pData); + /* Close the UDP sockets. * rgerhards, 2009-05-29 */ @@ -133,41 +135,23 @@ closeUDPSockets(instanceData *pData) freeaddrinfo(pData->f_addr); pData->f_addr = NULL; } -pData->bIsAddrResolved = 0; // TODO: remove this variable altogether RETiRet; } -/* get the syslog forward port from selector_t. The passed in - * struct must be one that is setup for forwarding. - * rgerhards, 2007-06-28 +/* get the syslog forward port * We may change the implementation to try to lookup the port * if it is unspecified. So far, we use the IANA default auf 514. + * rgerhards, 2007-06-28 */ -static char *getFwdPt(instanceData *pData) +static inline uchar *getFwdPt(instanceData *pData) { - assert(pData != NULL); - if(pData->port == NULL) - return("514"); - else - return(pData->port); + return (pData->port == NULL) ? UCHAR_CONSTANT("514") : pData->port; } + BEGINcreateInstance CODESTARTcreateInstance - /* Initialize the libnet library. Root priviledges are required. - * this initializes a IPv4 socket to use for forging UDP packets. - */ - libnet_handle = libnet_init( - LIBNET_RAW4, /* injection type */ - NULL, /* network interface */ - errbuf); /* errbuf */ - - if (libnet_handle == NULL) { - fprintf(stderr, "libnet_init() failed: %s\n", errbuf); - exit(EXIT_FAILURE); - } - ENDcreateInstance @@ -184,14 +168,12 @@ CODESTARTfreeInstance closeUDPSockets(pData); free(pData->port); free(pData->host); - /* destroy the libnet state needed for forged UDP sources */ - libnet_destroy(libnet_handle); ENDfreeInstance BEGINdbgPrintInstInfo CODESTARTdbgPrintInstInfo - dbgprintf("%s", pData->host); + DBGPRINTF("%s", pData->host); ENDdbgPrintInstInfo @@ -206,29 +188,27 @@ static rsRetVal UDPSend(instanceData *pData, uchar *pszSourcename, char *msg, si int j, build_ip; u_char opt[20]; struct sockaddr_in *tempaddr,source_ip; + libnet_ptag_t ip, ipo; + libnet_ptag_t udp; DEFiRet; -RUNLOG_VAR("%s", pszSourcename); if(pData->pSockArray == NULL) { CHKiRet(doTryResume(pData)); } ip = ipo = udp = 0; - if(source_port++ >= (u_short)42000){ - source_port = 32000; + if(pData->sourcePort++ >= pData->sourcePortEnd){ + pData->sourcePort = pData->sourcePortStart; } -int iii; - //iii = inet_pton(AF_INET, (char*)pszSourcename, &(source_ip.sin_addr)); - iii = inet_pton(AF_INET, "172.19.12.3", &(source_ip.sin_addr)); -RUNLOG_VAR("%d", iii); + inet_pton(AF_INET, (char*)pszSourcename, &(source_ip.sin_addr)); bSendSuccess = FALSE; for (r = pData->f_addr; r; r = r->ai_next) { tempaddr = (struct sockaddr_in *)r->ai_addr; libnet_clear_packet(libnet_handle); udp = libnet_build_udp( - source_port, /* source port */ + pData->sourcePort, /* source port */ tempaddr->sin_port, /* destination port */ LIBNET_UDP_H + len, /* packet length */ 0, /* checksum */ @@ -237,7 +217,7 @@ RUNLOG_VAR("%d", iii); libnet_handle, /* libnet handle */ udp); /* libnet id */ if (udp == -1) { - dbgprintf("Can't build UDP header: %s\n", libnet_geterror(libnet_handle)); + DBGPRINTF("Can't build UDP header: %s\n", libnet_geterror(libnet_handle)); } build_ip = 0; @@ -247,7 +227,7 @@ RUNLOG_VAR("%d", iii); } ipo = libnet_build_ipv4_options(opt, 20, libnet_handle, ipo); if (ipo == -1) { - dbgprintf("Can't build IP options: %s\n", libnet_geterror(libnet_handle)); + DBGPRINTF("Can't build IP options: %s\n", libnet_geterror(libnet_handle)); } ip = libnet_build_ipv4( LIBNET_IPV4_H + 20 + len + LIBNET_UDP_H, /* length */ @@ -264,13 +244,13 @@ RUNLOG_VAR("%d", iii); libnet_handle, /* libnet handle */ ip); /* libnet id */ if (ip == -1) { - dbgprintf("Can't build IP header: %s\n", libnet_geterror(libnet_handle)); + DBGPRINTF("Can't build IP header: %s\n", libnet_geterror(libnet_handle)); } /* Write it to the wire. */ lsent = libnet_write(libnet_handle); if (lsent == -1) { - dbgprintf("Write error: %s\n", libnet_geterror(libnet_handle)); + DBGPRINTF("Write error: %s\n", libnet_geterror(libnet_handle)); } else { bSendSuccess = TRUE; break; @@ -278,7 +258,7 @@ RUNLOG_VAR("%d", iii); } /* finished looping */ if (bSendSuccess == FALSE) { - dbgprintf("error forwarding via udp, suspending\n"); + DBGPRINTF("error forwarding via udp, suspending\n"); iRet = RS_RET_SUSPENDED; } @@ -301,20 +281,19 @@ static rsRetVal doTryResume(instanceData *pData) FINALIZE; /* The remote address is not yet known and needs to be obtained */ - dbgprintf(" %s\n", pData->host); + DBGPRINTF(" %s\n", pData->host); memset(&hints, 0, sizeof(hints)); /* port must be numeric, because config file syntax requires this */ hints.ai_flags = AI_NUMERICSERV; hints.ai_family = glbl.GetDefPFFamily(); hints.ai_socktype = SOCK_DGRAM; - if((iErr = (getaddrinfo(pData->host, getFwdPt(pData), &hints, &res))) != 0) { - dbgprintf("could not get addrinfo for hostname '%s':'%s': %d%s\n", + if((iErr = (getaddrinfo((char*)pData->host, (char*)getFwdPt(pData), &hints, &res))) != 0) { + DBGPRINTF("could not get addrinfo for hostname '%s':'%s': %d%s\n", pData->host, getFwdPt(pData), iErr, gai_strerror(iErr)); ABORT_FINALIZE(RS_RET_SUSPENDED); } - dbgprintf("%s found, resuming.\n", pData->host); + DBGPRINTF("%s found, resuming.\n", pData->host); pData->f_addr = res; - pData->bIsAddrResolved = 1; pData->pSockArray = net.create_udp_socket((uchar*)pData->host, NULL, 0); finalize_it: @@ -344,7 +323,7 @@ CODESTARTdoAction iMaxLine = glbl.GetMaxLine(); - dbgprintf(" %s:%s/udpspoofs\n", pData->host, getFwdPt(pData)); + DBGPRINTF(" %s:%s/udpspoofs\n", pData->host, getFwdPt(pData)); psz = (char*) ppString[0]; l = strlen((char*) psz); @@ -362,16 +341,16 @@ CODESTARTdoAction */ if(pData->compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) { Bytef *out; - uLongf destLen = sizeof(out) / sizeof(Bytef); + uLongf destLen = iMaxLine + iMaxLine/100 +12; /* recommended value from zlib doc */ uLong srcLen = l; int ret; /* TODO: optimize malloc sequence? -- rgerhards, 2008-09-02 */ - CHKmalloc(out = (Bytef*) malloc(iMaxLine + iMaxLine/100 + 12)); + CHKmalloc(out = (Bytef*) malloc(destLen)); out[0] = 'z'; out[1] = '\0'; ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz, srcLen, pData->compressionLevel); - dbgprintf("Compressing message, length was %d now %d, return state %d.\n", + DBGPRINTF("Compressing message, length was %d now %d, return state %d.\n", l, (int) destLen, ret); if(ret != Z_OK) { /* if we fail, we complain, but only in debug mode @@ -381,10 +360,10 @@ CODESTARTdoAction * best course of action. * rgerhards, 2006-11-30 */ - dbgprintf("Compression failed, sending uncompressed message\n"); + DBGPRINTF("Compression failed, sending uncompressed message\n"); } else if(destLen+1 < l) { /* only use compression if there is a gain in using it! */ - dbgprintf("there is gain in compression, so we do it\n"); + DBGPRINTF("there is gain in compression, so we do it\n"); psz = (char*) out; l = destLen + 1; /* take care for the "z" at message start! */ } @@ -399,10 +378,6 @@ ENDdoAction BEGINparseSelectorAct - uchar *q; - int i; - int bErr; - struct addrinfo; CODESTARTparseSelectorAct CODE_STD_STRING_REQUESTparseSelectorAct(2) /* first check if this config line is actually for us */ @@ -415,124 +390,30 @@ CODE_STD_STRING_REQUESTparseSelectorAct(2) CHKiRet(createInstance(&pData)); if(pszSourceNameTemplate == NULL) { - errmsg.LogError(0, NO_ERRCODE, "No $ActionOMUDPSpoofSourceNameTemplate given, can not continue"); + errmsg.LogError(0, NO_ERRCODE, "No $ActionOMUDPSpoofSourceNameTemplate given, can not continue with this action."); ABORT_FINALIZE(RS_RET_NO_SRCNAME_TPL); } - /* we are now after the protocol indicator. Now check if we should - * use compression. We begin to use a new option format for this: - * @(option,option)host:port - * The first option defined is "z[0..9]" where the digit indicates - * the compression level. If it is not given, 9 (best compression) is - * assumed. An example action statement might be: - * @@(z5,o)127.0.0.1:1400 - * Which means send via TCP with medium (5) compresion (z) to the local - * host on port 1400. The '0' option means that octet-couting (as in - * IETF I-D syslog-transport-tls) is to be used for framing (this option - * applies to TCP-based syslog only and is ignored when specified with UDP). - * That is not yet implemented. - * rgerhards, 2006-12-07 - * In order to support IPv6 addresses, we must introduce an extension to - * the hostname. If it is in square brackets, whatever is in them is treated as - * the hostname - without any exceptions ;) -- rgerhards, 2008-08-05 - */ - if(*p == '(') { - /* at this position, it *must* be an option indicator */ - do { - ++p; /* eat '(' or ',' (depending on when called) */ - /* check options */ - if(*p == 'z') { /* compression */ -# ifdef USE_NETZIP - ++p; /* eat */ - if(isdigit((int) *p)) { - int iLevel; - iLevel = *p - '0'; - ++p; /* eat */ - pData->compressionLevel = iLevel; - } else { - errmsg.LogError(0, NO_ERRCODE, "Invalid compression level '%c' specified in " - "forwardig action - NOT turning on compression.", - *p); - } -# else - errmsg.LogError(0, NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " - "with compression support - request ignored."); -# endif /* #ifdef USE_NETZIP */ - } else { /* invalid option! Just skip it... */ - errmsg.LogError(0, NO_ERRCODE, "Invalid option %c in forwarding action - ignoring.", *p); - ++p; /* eat invalid option */ - } - /* the option processing is done. We now do a generic skip - * to either the next option or the end of the option - * block. - */ - while(*p && *p != ')' && *p != ',') - ++p; /* just skip it */ - } while(*p && *p == ','); /* Attention: do.. while() */ - if(*p == ')') - ++p; /* eat terminator, on to next */ - else - /* we probably have end of string - leave it for the rest - * of the code to handle it (but warn the user) - */ - errmsg.LogError(0, NO_ERRCODE, "Option block not terminated in forwarding action."); + if(pszTargetHost == NULL) { + errmsg.LogError(0, NO_ERRCODE, "No $ActionOMUDPSpoofTargetHost given, can not continue with this action."); + ABORT_FINALIZE(RS_RET_HOST_NOT_SPECIFIED); } - /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0') - * now skip to port and then template name. rgerhards 2005-07-06 - */ - if(*p == '[') { /* everything is hostname upto ']' */ - ++p; /* skip '[' */ - for(q = p ; *p && *p != ']' ; ++p) - /* JUST SKIP */; - if(*p == ']') { - *p = '\0'; /* trick to obtain hostname (later)! */ - ++p; /* eat it */ - } - } else { /* traditional view of hostname */ - for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p) - /* JUST SKIP */; - } - - pData->port = NULL; - if(*p == ':') { /* process port */ - uchar * tmp; - - *p = '\0'; /* trick to obtain hostname (later)! */ - tmp = ++p; - for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i) - /* SKIP AND COUNT */; - pData->port = malloc(i + 1); - if(pData->port == NULL) { - errmsg.LogError(0, NO_ERRCODE, "Could not get memory to store syslog forwarding port, " - "using default port, results may not be what you intend\n"); - /* we leave f_forw.port set to NULL, this is then handled by getFwdPt(). */ - } else { - memcpy(pData->port, tmp, i); - *(pData->port + i) = '\0'; - } - } - - /* now skip to template */ - bErr = 0; - while(*p && *p != ';' && *p != '#' && !isspace((int) *p)) - ++p; /*JUST SKIP*/ - - /* TODO: make this if go away! */ - if(*p == ';' || *p == '#' || isspace(*p)) { - uchar cTmp = *p; - *p = '\0'; /* trick to obtain hostname (later)! */ - CHKmalloc(pData->host = strdup((char*) q)); - *p = cTmp; - } else { - CHKmalloc(pData->host = strdup((char*) q)); - } + /* fill instance properties */ + CHKmalloc(pData->host = ustrdup(pszTargetHost)); + if(pszTargetPort == NULL) + pData->port = NULL; + else + CHKmalloc(pData->port = ustrdup(pszTargetPort)); + CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pszSourceNameTemplate), OMSR_NO_RQD_TPL_OPTS)); + pData->compressionLevel = iCompressionLevel; + pData->sourcePort = pData->sourcePortStart = iSourcePortStart; + pData->sourcePortEnd = iSourcePortEnd; /* process template */ CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName)); - CHKiRet(OMSRsetEntry(*ppOMSR, 1, ustrdup(pszSourceNameTemplate), OMSR_NO_RQD_TPL_OPTS)); CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct @@ -543,20 +424,23 @@ ENDparseSelectorAct static void freeConfigVars(void) { - if(pszTplName != NULL) { - free(pszTplName); - pszTplName = NULL; - } + free(pszTplName); + pszTplName = NULL; + free(pszTargetHost); + pszTargetHost = NULL; + free(pszTargetPort); + pszTargetPort = NULL; } BEGINmodExit CODESTARTmodExit + /* destroy the libnet state needed for forged UDP sources */ + libnet_destroy(libnet_handle); /* release what we no longer need */ objRelease(errmsg, CORE_COMPONENT); objRelease(glbl, CORE_COMPONENT); objRelease(net, LM_NET_FILENAME); - freeConfigVars(); ENDmodExit @@ -574,6 +458,9 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a { freeConfigVars(); /* we now must reset all non-string values */ + iCompressionLevel = 0; + iSourcePortStart = DFLT_SOURCE_PORT_START; + iSourcePortEnd = DFLT_SOURCE_PORT_END; return RS_RET_OK; } @@ -586,8 +473,26 @@ CODEmodInit_QueryRegCFSLineHdlr CHKiRet(objUse(errmsg, CORE_COMPONENT)); CHKiRet(objUse(net,LM_NET_FILENAME)); + /* Initialize the libnet library. Root priviledges are required. + * this initializes a IPv4 socket to use for forging UDP packets. + */ + libnet_handle = libnet_init( + LIBNET_RAW4, /* injection type */ + NULL, /* network interface */ + errbuf); /* errbuf */ + + if(libnet_handle == NULL) { + errmsg.LogError(0, NO_ERRCODE, "Error initializing libnet, can not continue "); + ABORT_FINALIZE(RS_RET_ERR_LIBNET_INIT); + } + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofdefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofsourcenametemplate", 0, eCmdHdlrGetWord, NULL, &pszSourceNameTemplate, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspooftargethost", 0, eCmdHdlrGetWord, NULL, &pszTargetHost, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspooftargetport", 0, eCmdHdlrGetWord, NULL, &pszTargetPort, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofsourceportstart", 0, eCmdHdlrInt, NULL, &iSourcePortStart, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpspoofsourceportend", 0, eCmdHdlrInt, NULL, &iSourcePortEnd, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"actionomudpcompressionlevel", 0, eCmdHdlrInt, NULL, &iCompressionLevel, NULL)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit -- cgit