diff options
Diffstat (limited to 'plugins')
54 files changed, 8891 insertions, 289 deletions
diff --git a/plugins/im3195/Makefile.am b/plugins/im3195/Makefile.am new file mode 100644 index 00000000..5af0b6f5 --- /dev/null +++ b/plugins/im3195/Makefile.am @@ -0,0 +1,8 @@ +pkglib_LTLIBRARIES = im3195.la + +im3195_la_SOURCES = im3195.c +im3195_la_CPPFLAGS = $(RSRT_CFLAGS) $(PTHREADS_CFLAGS) $(LIBLOGGING_CFLAGS) +im3195_la_LDFLAGS = -module -avoid-version +im3195_la_LIBADD = $(LIBLOGGING_LIBS) + +EXTRA_DIST = diff --git a/plugins/im3195/im3195.c b/plugins/im3195/im3195.c new file mode 100644 index 00000000..1c2502fe --- /dev/null +++ b/plugins/im3195/im3195.c @@ -0,0 +1,167 @@ +/** + * The rfc3195 input module. + * + * Please note that this file replaces the rfc3195d daemon that was + * also present in pre-v3 versions of rsyslog. + * + * WARNING: due to no demand at all for RFC3195, we have converted rfc3195d + * to this input module, but we have NOT conducted any testing. Also, + * the module does not yet properly handle the recovery case. If someone + * intends to put this module into production, good testing should be + * made and it also is a good idea to notify me that you intend to use + * it in production. In this case, I'll probably give the module another + * cleanup. I don't do this now because so far it looks just like a big + * waste of time. -- rgerhards, 2008-04-16 + * + * \author Rainer Gerhards <rgerhards@adiscon.com> + * + * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" + +#include <stdio.h> +#include <unistd.h> +#include <sys/errno.h> +#include <assert.h> +#include "rsyslog.h" +#include "dirty.h" +#include "liblogging/liblogging.h" +#include "liblogging/srAPI.h" +#include "liblogging/syslogmessage.h" +#include "module-template.h" +#include "cfsysline.h" +#include "errmsg.h" + +MODULE_TYPE_INPUT + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* configuration settings */ +static int listenPort = 601; + +/* we use a global API object below, because this listener is + * not very complex. As such, this hack should not harm anything. + * rgerhards, 2005-10-12 + */ +static srAPIObj* pAPI; + + +/* This method is called when a message has been fully received. + * It passes the received message to the rsyslog main message + * queue. Please note that this callback is synchronous, thus + * liblogging will be on hold until it returns. This is important + * to note because in an error case we might stay in this code + * for an extended amount of time. So far, we think this is the + * best solution, but real-world experience might tell us a + * different truth ;) + */ +void OnReceive(srAPIObj __attribute__((unused)) *pMyAPI, srSLMGObj* pSLMG) +{ + uchar *pszRawMsg; + uchar *fromHost = (uchar*) "[unset]"; /* TODO: get hostname */ + uchar *fromHostIP = (uchar*) "[unset]"; /* TODO: get hostname */ + + srSLMGGetRawMSG(pSLMG, &pszRawMsg); + + parseAndSubmitMessage(fromHost, fromHostIP, pszRawMsg, strlen((char*)pszRawMsg), + MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_FULL_DELAY, (uchar*)"im3195"); +} + + +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(!pThrd->bShallStop) { + /* now move the listener to running state. Control will only + * return after SIGUSR1. + */ + if((iRet = srAPIRunListener(pAPI)) != SR_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d running liblogging listener - im3195 is defunct", iRet); + FINALIZE; /* this causes im3195 to become defunct; TODO: recovery handling */ + } + } +finalize_it: +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + if((pAPI = srAPIInitLib()) == NULL) { + errmsg.LogError(0, NO_ERRCODE, "error initializing liblogging - im3195 is defunct"); + ABORT_FINALIZE(RS_RET_ERR); + } + + if((iRet = srAPISetOption(pAPI, srOPTION_BEEP_LISTENPORT, listenPort)) != SR_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d setting liblogging listen port - im3195 is defunct", iRet); + FINALIZE; + } + + if((iRet = srAPISetupListener(pAPI, OnReceive)) != SR_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d setting up liblogging listener - im3195 is defunct", iRet); + FINALIZE; + } + +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + dbgprintf("Shutting down rfc3195d. Be patient, this can take up to 30 seconds...\n"); + srAPIShutdownListener(pAPI); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + srAPIExitLib(pAPI); /* terminate liblogging */ + /* release objects we used */ + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + listenPort = 601; + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"input3195listenport", 0, eCmdHdlrInt, NULL, &listenPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imdiag/Makefile.am b/plugins/imdiag/Makefile.am new file mode 100644 index 00000000..33e86e93 --- /dev/null +++ b/plugins/imdiag/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imdiag.la + +imdiag_la_SOURCES = imdiag.c +imdiag_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imdiag_la_LDFLAGS = -module -avoid-version +imdiag_la_LIBADD = diff --git a/plugins/imdiag/imdiag.c b/plugins/imdiag/imdiag.c new file mode 100644 index 00000000..77e99236 --- /dev/null +++ b/plugins/imdiag/imdiag.c @@ -0,0 +1,200 @@ +#warning "imdiag is NOT supported in this version of rsyslog" +#if 0 +/* imdiag.c + * This is a diagnostics module, primarily meant for troubleshooting + * and information about the runtime state of rsyslog. It is implemented + * as an input plugin, because that interface best suits our needs + * and also enables us to inject test messages (something not yet + * implemented). + * + * File begun on 2008-07-25 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +//#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "netstrm.h" +#include "errmsg.h" + +MODULE_TYPE_INPUT + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(net) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(errmsg) + +/* Module static data */ +netstrms_t *pNS; /**< pointer to network stream subsystem */ +netstrm_t **ppLstn[10]; /**< our netstream listners */ +int iLstnMax = 0; /**< max nbr of listeners currently supported */ + + +/* config settings */ + + +/* add a listen socket to our listen socket array. This is a callback + * invoked from the netstrm class. -- rgerhards, 2008-04-23 + */ +static rsRetVal +addTcpLstn(void *pUsr, netstrm_t *pLstn) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pLstn, netstrm); + + if(iLstnMax >= sizeof(ppLstn)/sizeof(netstrm_t)) + ABORT_FINALIZE(RS_RET_MAX_LSTN_REACHED); + + ppLstn[pThis->iLstnMax] = pLstn; + ++iLstnMax; + +finalize_it: + RETiRet; +} + + +/* initialize network stream subsystem */ +static rsRetVal +initNetstrm(void) +{ + DEFiRet; + + /* prepare network stream subsystem */ + CHKiRet(netstrms.Construct(&pNS)); + CHKiRet(netstrms.SetDrvrMode(pNS, 0)); /* always plain text */ + //CHKiRet(netstrms.SetDrvrAuthMode(pThis->pNS, pThis->pszDrvrAuthMode)); + //CHKiRet(netstrms.SetDrvrPermPeers(pThis->pNS, pThis->pPermPeers)); + // TODO: set driver! + CHKiRet(netstrms.ConstructFinalize(pThis->pNS)); + + /* set up listeners */ + CHKiRet(netstrm.LstnInit(pNS, NULL, addTcpLstn, "127.0.0.1", "44514", 1)); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pThis->pNS != NULL) + netstrms.Destruct(&pThis->pNS); + } + RETiRet; +} + + +/* This function is called to gather input. In our case, it is a bit abused + * to drive the listener loop for the diagnostics code. + */ +BEGINrunInput +CODESTARTrunInput +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + iRet = initNetstrm(); +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + /* finally close our listen streams */ + for(i = 0 ; i < iLstnMax ; ++i) { + netstrm.Destruct(ppLstn + i); + } + + /* destruct netstream subsystem */ + netstrms.Destruct(pNS); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + return RS_RET_OK; +} + + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + +#if 0 + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverrun", 0, eCmdHdlrGetWord, + addTCPListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpmaxsessions", 0, eCmdHdlrInt, + NULL, &iTCPSessMax, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverstreamdrivermode", 0, + eCmdHdlrInt, NULL, &iStrmDrvrMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverstreamdriverauthmode", 0, + eCmdHdlrGetWord, NULL, &pszStrmDrvrAuthMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverstreamdriverpermittedpeer", 0, + eCmdHdlrGetWord, setPermittedPeer, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +#endif +ENDmodInit +#endif + + +/* vim:set ai: + */ diff --git a/plugins/imfile/Makefile.am b/plugins/imfile/Makefile.am new file mode 100644 index 00000000..551639ba --- /dev/null +++ b/plugins/imfile/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imfile.la + +imfile_la_SOURCES = imfile.c +imfile_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imfile_la_LDFLAGS = -module -avoid-version +imfile_la_LIBADD = diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c new file mode 100644 index 00000000..b0211bf6 --- /dev/null +++ b/plugins/imfile/imfile.c @@ -0,0 +1,534 @@ +/* imfile.c + * + * This is the input module for reading text file data. A text file is a + * non-binary file who's lines are delemited by the \n character. + * + * Work originally begun on 2008-02-01 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" /* this is for autotools and always must be the first include */ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> /* do NOT remove: will soon be done by the module generation macros */ +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#include "rsyslog.h" /* error codes etc... */ +#include "dirty.h" +#include "cfsysline.h" /* access to config file objects */ +#include "module-template.h" /* generic module interface code - very important, read it! */ +#include "srUtils.h" /* some utility functions */ +#include "msg.h" +#include "stream.h" +#include "errmsg.h" +#include "glbl.h" +#include "datetime.h" + +MODULE_TYPE_INPUT /* must be present for input modules, do not remove */ + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA /* must be present, starts static data */ +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(datetime) + +typedef struct fileInfo_s { + uchar *pszFileName; + uchar *pszTag; + uchar *pszStateFile; /* file in which state between runs is to be stored */ + int iFacility; + int iSeverity; + strm_t *pStrm; /* its stream (NULL if not assigned) */ +} fileInfo_t; + + +/* config variables */ +static uchar *pszFileName = NULL; +static uchar *pszFileTag = NULL; +static uchar *pszStateFile = NULL; +static int iPollInterval = 10; /* number of seconds to sleep when there was no file activity */ +static int iFacility = 128; /* local0 */ +static int iSeverity = 5; /* notice, as of rfc 3164 */ + +static int iFilPtr = 0; /* number of files to be monitored; pointer to next free spot during config */ +#define MAX_INPUT_FILES 100 +static fileInfo_t files[MAX_INPUT_FILES]; + + +/* enqueue the read file line as a message. The provided string is + * not freed - thuis must be done by the caller. + */ +static rsRetVal enqLine(fileInfo_t *pInfo, cstr_t *cstrLine) +{ + DEFiRet; + msg_t *pMsg; + + if(rsCStrLen(cstrLine) == 0) { + /* we do not process empty lines */ + FINALIZE; + } + + CHKiRet(msgConstruct(&pMsg)); + MsgSetFlowControlType(pMsg, eFLOWCTL_FULL_DELAY); + MsgSetInputName(pMsg, "imfile"); + MsgSetUxTradMsg(pMsg, (char*)rsCStrGetSzStr(cstrLine)); + MsgSetRawMsg(pMsg, (char*)rsCStrGetSzStr(cstrLine)); + MsgSetMSG(pMsg, (char*)rsCStrGetSzStr(cstrLine)); + MsgSetHOSTNAME(pMsg, (char*)glbl.GetLocalHostName()); + MsgSetTAG(pMsg, (char*)pInfo->pszTag); + pMsg->iFacility = LOG_FAC(pInfo->iFacility); + pMsg->iSeverity = LOG_PRI(pInfo->iSeverity); + pMsg->bParseHOSTNAME = 0; + CHKiRet(submitMsg(pMsg)); +finalize_it: + RETiRet; +} + + +/* try to open a file. This involves checking if there is a status file and, + * if so, reading it in. Processing continues from the last know location. + */ +static rsRetVal +openFile(fileInfo_t *pThis) +{ + DEFiRet; + strm_t *psSF = NULL; + uchar pszSFNam[MAXFNAME]; + size_t lenSFNam; + struct stat stat_buf; + + /* Construct file name */ + lenSFNam = snprintf((char*)pszSFNam, sizeof(pszSFNam) / sizeof(uchar), "%s/%s", + (char*) glbl.GetWorkDir(), (char*)pThis->pszStateFile); + + /* check if the file exists */ + if(stat((char*) pszSFNam, &stat_buf) == -1) { + if(errno == ENOENT) { + /* currently no object! dbgoprint((obj_t*) pThis, "clean startup, no .si file found\n"); */ + ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND); + } else { + /* currently no object! dbgoprint((obj_t*) pThis, "error %d trying to access .si file\n", errno); */ + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } + + /* If we reach this point, we have a .si file */ + + CHKiRet(strmConstruct(&psSF)); + CHKiRet(strmSettOperationsMode(psSF, STREAMMODE_READ)); + CHKiRet(strmSetsType(psSF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psSF, pszSFNam, lenSFNam)); + CHKiRet(strmConstructFinalize(psSF)); + + /* read back in the object */ + CHKiRet(obj.Deserialize(&pThis->pStrm, (uchar*) "strm", psSF, NULL, pThis)); + + CHKiRet(strmSeekCurrOffs(pThis->pStrm)); + + /* OK, we could successfully read the file, so we now can request that it be deleted. + * If we need it again, it will be written on the next shutdown. + */ + psSF->bDeleteOnClose = 1; + +finalize_it: + if(psSF != NULL) + strmDestruct(&psSF); + + if(iRet != RS_RET_OK) { + CHKiRet(strmConstruct(&pThis->pStrm)); + CHKiRet(strmSettOperationsMode(pThis->pStrm, STREAMMODE_READ)); + CHKiRet(strmSetsType(pThis->pStrm, STREAMTYPE_FILE_MONITOR)); + CHKiRet(strmSetFName(pThis->pStrm, pThis->pszFileName, strlen((char*) pThis->pszFileName))); + CHKiRet(strmConstructFinalize(pThis->pStrm)); + } + + RETiRet; +} + + +/* The following is a cancel cleanup handler for strmReadLine(). It is necessary in case + * strmReadLine() is cancelled while processing the stream. -- rgerhards, 2008-03-27 + */ +static void pollFileCancelCleanup(void *pArg) +{ + BEGINfunc; + cstr_t **ppCStr = (cstr_t**) pArg; + if(*ppCStr != NULL) + rsCStrDestruct(ppCStr); + ENDfunc; +} + + +/* poll a file, need to check file rollover etc. open file if not open */ +#pragma GCC diagnostic ignored "-Wempty-body" +static rsRetVal pollFile(fileInfo_t *pThis, int *pbHadFileData) +{ + cstr_t *pCStr = NULL; + DEFiRet; + + ASSERT(pbHadFileData != NULL); + + /* Note: we must do pthread_cleanup_push() immediately, because the POXIS macros + * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14 + */ + pthread_cleanup_push(pollFileCancelCleanup, &pCStr); + if(pThis->pStrm == NULL) { + CHKiRet(openFile(pThis)); /* open file */ + } + + /* loop below will be exited when strmReadLine() returns EOF */ + while(1) { + CHKiRet(strmReadLine(pThis->pStrm, &pCStr)); + *pbHadFileData = 1; /* this is just a flag, so set it and forget it */ + CHKiRet(enqLine(pThis, pCStr)); /* process line */ + rsCStrDestruct(&pCStr); /* discard string (must be done by us!) */ + } + +finalize_it: + /*EMPTY - just to keep the compiler happy, do NOT remove*/; + /* Note: the problem above is that pthread:cleanup_pop() is a macro which + * evaluates to something like "} while(0);". So the code would become + * "finalize_it: }", that is a label without a statement. The C standard does + * not permit this. So we add an empty statement "finalize_it: ; }" and + * everybody is happy. Note that without the ;, an error is reported only + * on some platforms/compiler versions. -- rgerhards, 2008-08-15 + */ + pthread_cleanup_pop(0); + + if(pCStr != NULL) { + rsCStrDestruct(&pCStr); + } + + RETiRet; +} +#pragma GCC diagnostic warning "-Wempty-body" + + +/* This function is the cancel cleanup handler. It is called when rsyslog decides the + * module must be stopped, what most probably happens during shutdown of rsyslogd. When + * this function is called, the runInput() function (below) is already terminated - somewhere + * in the middle of what it was doing. The cancel cleanup handler below should take + * care of any locked mutexes and such, things that really need to be cleaned up + * before processing continues. In general, many plugins do not need to provide + * any code at all here. + * + * IMPORTANT: the calling interface of this function can NOT be modified. It actually is + * called by pthreads. The provided argument is currently not being used. + */ +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ +static void +inputModuleCleanup(void __attribute__((unused)) *arg) +{ + BEGINfunc +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + + + /* so far not needed */ + + + +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + ENDfunc +} +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + +/* This function is called by the framework to gather the input. The module stays + * most of its lifetime inside this function. It MUST NEVER exit this function. Doing + * so would end module processing and rsyslog would NOT reschedule the module. If + * you exit from this function, you violate the interface specification! + * + * We go through all files and remember if at least one had data. If so, we do + * another run (until no data was present in any file). Then we sleep for + * PollInterval seconds and restart the whole process. This ensures that as + * long as there is some data present, it will be processed at the fastest + * possible pace - probably important for busy systmes. If we monitor just a + * single file, the algorithm is slightly modified. In that case, the sleep + * hapens immediately. The idea here is that if we have just one file, we + * returned from the file processer because that file had no additional data. + * So even if we found some lines, it is highly unlikely to find a new one + * just now. Trying it would result in a performance-costly additional try + * which in the very, very vast majority of cases will never find any new + * lines. + * On spamming the main queue: keep in mind that it will automatically rate-limit + * ourselfes if we begin to overrun it. So we really do not need to care here. + */ +#pragma GCC diagnostic ignored "-Wempty-body" +BEGINrunInput + int i; + int bHadFileData; /* were there at least one file with data during this run? */ +CODESTARTrunInput + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + pthread_cleanup_push(inputModuleCleanup, NULL); + while(1) { /* endless loop - do NOT break; out of it! */ + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + do { + bHadFileData = 0; + for(i = 0 ; i < iFilPtr ; ++i) { + pollFile(&files[i], &bHadFileData); + } + } while(iFilPtr > 1 && bHadFileData == 1); /* waring: do...while()! */ + + /* Note: the additional 10ns wait is vitally important. It guards rsyslog against totally + * hogging the CPU if the users selects a polling interval of 0 seconds. It doesn't hurt any + * other valid scenario. So do not remove. -- rgerhards, 2008-02-14 + */ + srSleep(iPollInterval, 10); + + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + } + /*NOTREACHED*/ + + pthread_cleanup_pop(0); /* just for completeness, but never called... */ + RETiRet; /* use it to make sure the housekeeping is done! */ +ENDrunInput +#pragma GCC diagnostic warning "-Wempty-body" + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + +/* The function is called by rsyslog before runInput() is called. It is a last chance + * to set up anything specific. Most importantly, it can be used to tell rsyslog if the + * input shall run or not. The idea is that if some config settings (or similiar things) + * are not OK, the input can tell rsyslog it will not execute. To do so, return + * RS_RET_NO_RUN or a specific error code. If RS_RET_OK is returned, rsyslog will + * proceed and call the runInput() entry point. + */ +BEGINwillRun +CODESTARTwillRun + if(iFilPtr == 0) { + errmsg.LogError(0, RS_RET_NO_RUN, "No files configured to be monitored"); + ABORT_FINALIZE(RS_RET_NO_RUN); + } + +finalize_it: +ENDwillRun + + + +/* This function persists information for a specific file being monitored. + * To do so, it simply persists the stream object. We do NOT abort on error + * iRet as that makes matters worse (at least we can try persisting the others...). + * rgerhards, 2008-02-13 + */ +static rsRetVal +persistStrmState(fileInfo_t *pInfo) +{ + DEFiRet; + strm_t *psSF = NULL; /* state file (stream) */ + + ASSERT(pInfo != NULL); + + /* TODO: create a function persistObj in obj.c? */ + CHKiRet(strmConstruct(&psSF)); + CHKiRet(strmSetDir(psSF, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir()))); + CHKiRet(strmSettOperationsMode(psSF, STREAMMODE_WRITE)); + CHKiRet(strmSetiAddtlOpenFlags(psSF, O_TRUNC)); + CHKiRet(strmSetsType(psSF, STREAMTYPE_FILE_SINGLE)); + CHKiRet(strmSetFName(psSF, pInfo->pszStateFile, strlen((char*) pInfo->pszStateFile))); + CHKiRet(strmConstructFinalize(psSF)); + + CHKiRet(strmSerialize(pInfo->pStrm, psSF)); + + CHKiRet(strmDestruct(&psSF)); + +finalize_it: + if(psSF != NULL) + strmDestruct(&psSF); + + RETiRet; +} + + +/* This function is called by the framework after runInput() has been terminated. It + * shall free any resources and prepare the module for unload. + */ +BEGINafterRun + int i; +CODESTARTafterRun + /* Close files and persist file state information. We do NOT abort on error iRet as that makes + * matters worse (at least we can try persisting the others...). Please note that, under stress + * conditions, it may happen that we are terminated before we actuall could open all streams. So + * before we change anything, we need to make sure the stream was open. + */ + for(i = 0 ; i < iFilPtr ; ++i) { + if(files[i].pStrm != NULL) { /* stream open? */ + persistStrmState(&files[i]); + strmDestruct(&(files[i].pStrm)); + } + } +ENDafterRun + + +/* The following entry points are defined in module-template.h. + * In general, they need to be present, but you do NOT need to provide + * any code here. + */ +BEGINmodExit +CODESTARTmodExit + /* release objects we used */ + objRelease(datetime, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +/* The following function shall reset all configuration variables to their + * default values. The code provided in modInit() below registers it to be + * called on "$ResetConfigVariables". You may also call it from other places, + * but in general this is not necessary. Once runInput() has been called, this + * function here is never again called. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + + if(pszFileName != NULL) { + free(pszFileName); + pszFileName = NULL; + } + + if(pszFileTag != NULL) { + free(pszFileTag); + pszFileTag = NULL; + } + + if(pszStateFile != NULL) { + free(pszFileTag); + pszFileTag = NULL; + } + + + /* set defaults... */ + iPollInterval = 10; + iFacility = 128; /* local0 */ + iSeverity = 5; /* notice, as of rfc 3164 */ + + RETiRet; +} + + +/* add a new monitor */ +static rsRetVal addMonitor(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + fileInfo_t *pThis; + + free(pNewVal); /* we do not need it, but we must free it! */ + + if(iFilPtr < MAX_INPUT_FILES) { + pThis = &files[iFilPtr]; + /* TODO: check for strdup() NULL return */ + if(pszFileName == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile error: no file name given, file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } else { + pThis->pszFileName = (uchar*) strdup((char*) pszFileName); + } + + if(pszFileTag == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile error: no tag value given , file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } else { + pThis->pszTag = (uchar*) strdup((char*) pszFileTag); + } + + if(pszStateFile == NULL) { + errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile error: not state file name given, file monitor can not be created"); + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } else { + pThis->pszStateFile = (uchar*) strdup((char*) pszStateFile); + } + + pThis->iSeverity = iSeverity; + pThis->iFacility = iFacility; + } else { + errmsg.LogError(0, RS_RET_OUT_OF_DESRIPTORS, "Too many file monitors configured - ignoring this one"); + ABORT_FINALIZE(RS_RET_OUT_OF_DESRIPTORS); + } + + CHKiRet(resetConfigVariables((uchar*) "dummy", (void*) pThis)); /* values are both dummies */ + +finalize_it: + if(iRet == RS_RET_OK) + ++iFilPtr; /* we got a new file to monitor */ + + RETiRet; +} + +/* modInit() is called once the module is loaded. It must perform all module-wide + * initialization tasks. There are also a number of housekeeping tasks that the + * framework requires. These are handled by the macros. Please note that the + * complexity of processing is depending on the actual module. However, only + * thing absolutely necessary should be done here. Actual app-level processing + * is to be performed in runInput(). A good sample of what to do here may be to + * set some variable defaults. The most important thing probably is registration + * of config command handlers. + */ +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(datetime, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilename", 0, eCmdHdlrGetWord, + NULL, &pszFileName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfiletag", 0, eCmdHdlrGetWord, + NULL, &pszFileTag, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilestatefile", 0, eCmdHdlrGetWord, + NULL, &pszStateFile, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfileseverity", 0, eCmdHdlrSeverity, + NULL, &iSeverity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilefacility", 0, eCmdHdlrFacility, + NULL, &iFacility, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputfilepollinterval", 0, eCmdHdlrInt, + NULL, &iPollInterval, STD_LOADABLE_MODULE_ID)); + /* that command ads a new file! */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrunfilemonitor", 0, eCmdHdlrGetWord, + addMonitor, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imgssapi/Makefile.am b/plugins/imgssapi/Makefile.am new file mode 100644 index 00000000..ea016353 --- /dev/null +++ b/plugins/imgssapi/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imgssapi.la + +imgssapi_la_SOURCES = imgssapi.c +imgssapi_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imgssapi_la_LDFLAGS = -module -avoid-version +imgssapi_la_LIBADD = $(GSS_LIBS) diff --git a/plugins/imgssapi/imgssapi.c b/plugins/imgssapi/imgssapi.c new file mode 100644 index 00000000..debe935e --- /dev/null +++ b/plugins/imgssapi/imgssapi.c @@ -0,0 +1,724 @@ +/* imgssapi.c + * This is the implementation of the GSSAPI input module. + * + * Note: the root gssapi code was contributed by varmojfekoj and is most often + * maintened by him. I am just doing the plumbing around it (I event don't have a + * test lab for gssapi yet... ). I am very grateful for this useful code + * contribution -- rgerhards, 2008-03-05 + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include <gssapi/gssapi.h> +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "srUtils.h" +#include "gss-misc.h" +#include "tcpsrv.h" +#include "tcps_sess.h" +#include "errmsg.h" +#include "netstrm.h" +#include "glbl.h" + + +MODULE_TYPE_INPUT + +/* defines */ +#define ALLOWEDMETHOD_GSS 2 +#define ALLOWEDMETHOD_TCP 1 + + +/* some forward definitions - they may go away when we no longer include imtcp.c */ +static rsRetVal addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal); +static int TCPSessGSSInit(void); +static void TCPSessGSSClose(tcps_sess_t* pSess); +static rsRetVal TCPSessGSSRecv(tcps_sess_t *pSess, void *buf, size_t buf_len, ssize_t *); +static rsRetVal onSessAccept(tcpsrv_t *pThis, tcps_sess_t *ppSess); +static rsRetVal OnSessAcceptGSS(tcpsrv_t *pThis, tcps_sess_t *ppSess); + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(gssutil) +DEFobjCurrIf(errmsg) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(net) +DEFobjCurrIf(glbl) + +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ +static gss_cred_id_t gss_server_creds = GSS_C_NO_CREDENTIAL; + +/* our usr structure for the tcpsrv object */ +typedef struct gsssrv_s { + char allowedMethods; +} gsssrv_t; + +/* our usr structure for the session object */ +typedef struct gss_sess_s { + OM_uint32 gss_flags; + gss_ctx_id_t gss_context; + char allowedMethods; +} gss_sess_t; + + +/* config variables */ +static int iTCPSessMax = 200; /* max number of sessions */ +static char *gss_listen_service_name = NULL; +static int bPermitPlainTcp = 0; /* plain tcp syslog allowed on GSSAPI port? */ + + +/* methods */ +/* callbacks */ +static rsRetVal OnSessConstructFinalize(void *ppUsr) +{ + DEFiRet; + gss_sess_t **ppGSess = (gss_sess_t**) ppUsr; + gss_sess_t *pGSess; + + assert(ppGSess != NULL); + + if((pGSess = calloc(1, sizeof(gss_sess_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pGSess->gss_flags = 0; + pGSess->gss_context = GSS_C_NO_CONTEXT; + pGSess->allowedMethods = 0; + + *ppGSess = pGSess; + +finalize_it: + RETiRet; +} + + +/* Destruct the user session pointer for a GSSAPI session. Please note + * that it *is* valid to receive a NULL user pointer. In this case, the + * sessions is to be torn down before it was fully initialized. This + * happens in error cases, e.g. when the host ACL did not match. + * rgerhards, 2008-03-03 + */ +static rsRetVal +OnSessDestruct(void *ppUsr) +{ + DEFiRet; + gss_sess_t **ppGSess = (gss_sess_t**) ppUsr; + + assert(ppGSess != NULL); + if(*ppGSess == NULL) + FINALIZE; + + if((*ppGSess)->allowedMethods & ALLOWEDMETHOD_GSS) { + OM_uint32 maj_stat, min_stat; + maj_stat = gss_delete_sec_context(&min_stat, &(*ppGSess)->gss_context, GSS_C_NO_BUFFER); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("deleting context", maj_stat, min_stat); + } + + free(*ppGSess); + *ppGSess = NULL; + +finalize_it: + RETiRet; +} + + +/* Check if the host is permitted to send us messages. + * Note: the pUsrSess may be zero if the server is running in tcp-only mode! + */ +static int +isPermittedHost(struct sockaddr *addr, char *fromHostFQDN, void *pUsrSrv, void*pUsrSess) +{ + gsssrv_t *pGSrv; + gss_sess_t *pGSess; + char allowedMethods = 0; + + BEGINfunc + assert(pUsrSrv != NULL); + pGSrv = (gsssrv_t*) pUsrSrv; + pGSess = (gss_sess_t*) pUsrSess; + + if((pGSrv->allowedMethods & ALLOWEDMETHOD_TCP) && + net.isAllowedSender((uchar*)"TCP", addr, (char*)fromHostFQDN)) + allowedMethods |= ALLOWEDMETHOD_TCP; + if((pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) && + net.isAllowedSender((uchar*)"GSS", addr, (char*)fromHostFQDN)) + allowedMethods |= ALLOWEDMETHOD_GSS; + if(allowedMethods && pGSess != NULL) + pGSess->allowedMethods = allowedMethods; + ENDfunc + return allowedMethods; +} + + +static rsRetVal +onSessAccept(tcpsrv_t *pThis, tcps_sess_t *pSess) +{ + DEFiRet; + gsssrv_t *pGSrv; + + pGSrv = (gsssrv_t*) pThis->pUsr; + + if(pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) { + iRet = OnSessAcceptGSS(pThis, pSess); + } + + RETiRet; +} + + +static rsRetVal +onRegularClose(tcps_sess_t *pSess) +{ + DEFiRet; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + if(pGSess->allowedMethods & ALLOWEDMETHOD_GSS) + TCPSessGSSClose(pSess); + else { + /* process any incomplete frames left over */ + tcps_sess.PrepareClose(pSess); + /* Session closed */ + tcps_sess.Close(pSess); + } + RETiRet; +} + + +static rsRetVal +onErrClose(tcps_sess_t *pSess) +{ + DEFiRet; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + if(pGSess->allowedMethods & ALLOWEDMETHOD_GSS) + TCPSessGSSClose(pSess); + else + tcps_sess.Close(pSess); + + RETiRet; +} + + +/* open the listen sockets */ +static rsRetVal +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + int *pRet = NULL; + gsssrv_t *pGSrv; + DEFiRet; + + ISOBJ_TYPE_assert(pSrv, tcpsrv); + pGSrv = pSrv->pUsr; + assert(pGSrv != NULL); + + /* first apply some config settings */ + if(pGSrv->allowedMethods) { + if(pGSrv->allowedMethods & ALLOWEDMETHOD_GSS) { + if(TCPSessGSSInit()) { + errmsg.LogError(0, NO_ERRCODE, "GSS-API initialization failed\n"); + pGSrv->allowedMethods &= ~(ALLOWEDMETHOD_GSS); + } + } + if(pGSrv->allowedMethods) { + /* fallback to plain TCP */ + CHKiRet(tcpsrv.create_tcp_socket(pSrv)); + } else { + ABORT_FINALIZE(RS_RET_GSS_ERR); + } + } + +finalize_it: + RETiRet; +} + + +static rsRetVal +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd) +{ + DEFiRet; + int allowedMethods; + gss_sess_t *pGSess; + + assert(pSess != NULL); + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + assert(piLenRcvd != NULL); + + allowedMethods = pGSess->allowedMethods; + if(allowedMethods & ALLOWEDMETHOD_GSS) { + CHKiRet(TCPSessGSSRecv(pSess, buf, lenBuf, piLenRcvd)); + } else { + *piLenRcvd = lenBuf; + CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd) != RS_RET_OK); + } + +finalize_it: + RETiRet; +} + + +/* end callbacks */ + +static rsRetVal +addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + gsssrv_t *pGSrv; + + if(pOurTcpsrv == NULL) { + /* first create/init the gsssrv "object" */ + if((pGSrv = calloc(1, sizeof(gsssrv_t))) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + pGSrv->allowedMethods = ALLOWEDMETHOD_GSS; + if(bPermitPlainTcp) + pGSrv->allowedMethods |= ALLOWEDMETHOD_TCP; + /* gsssrv initialized */ + + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + CHKiRet(tcpsrv.SetUsrP(pOurTcpsrv, pGSrv)); + CHKiRet(tcpsrv.SetCBOnSessConstructFinalize(pOurTcpsrv, OnSessConstructFinalize)); + CHKiRet(tcpsrv.SetCBOnSessDestruct(pOurTcpsrv, OnSessDestruct)); + CHKiRet(tcpsrv.SetCBIsPermittedHost(pOurTcpsrv, isPermittedHost)); + CHKiRet(tcpsrv.SetCBRcvData(pOurTcpsrv, doRcvData)); + CHKiRet(tcpsrv.SetCBOpenLstnSocks(pOurTcpsrv, doOpenLstnSocks)); + CHKiRet(tcpsrv.SetCBOnSessAccept(pOurTcpsrv, onSessAccept)); + CHKiRet(tcpsrv.SetCBOnRegularClose(pOurTcpsrv, onRegularClose)); + CHKiRet(tcpsrv.SetCBOnErrClose(pOurTcpsrv, onErrClose)); + tcpsrv.configureTCPListen(pOurTcpsrv, (char *) pNewVal); + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet); + if(pOurTcpsrv != NULL) + tcpsrv.Destruct(&pOurTcpsrv); + } + RETiRet; +} + + +/* returns 0 if all went OK, -1 if it failed */ +static int TCPSessGSSInit(void) +{ + gss_buffer_desc name_buf; + gss_name_t server_name; + OM_uint32 maj_stat, min_stat; + + if (gss_server_creds != GSS_C_NO_CREDENTIAL) + return 0; + + name_buf.value = (gss_listen_service_name == NULL) ? "host" : gss_listen_service_name; + name_buf.length = strlen(name_buf.value) + 1; + maj_stat = gss_import_name(&min_stat, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, &server_name); + if (maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("importing name", maj_stat, min_stat); + return -1; + } + + maj_stat = gss_acquire_cred(&min_stat, server_name, 0, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, + &gss_server_creds, NULL, NULL); + if (maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("acquiring credentials", maj_stat, min_stat); + return -1; + } + + gss_release_name(&min_stat, &server_name); + dbgprintf("GSS-API initialized\n"); + return 0; +} + + +/* returns 0 if all went OK, -1 if it failed + * tries to guess if the connection uses gssapi. + */ +static rsRetVal +OnSessAcceptGSS(tcpsrv_t *pThis, tcps_sess_t *pSess) +{ + DEFiRet; + gss_buffer_desc send_tok, recv_tok; + gss_name_t client; + OM_uint32 maj_stat, min_stat, acc_sec_min_stat; + gss_ctx_id_t *context; + OM_uint32 *sess_flags; + int fdSess; + char allowedMethods; + gsssrv_t *pGSrv; + gss_sess_t *pGSess; + + assert(pSess != NULL); + + pGSrv = (gsssrv_t*) pThis->pUsr; + pGSess = (gss_sess_t*) pSess->pUsr; + allowedMethods = pGSrv->allowedMethods; + if(allowedMethods & ALLOWEDMETHOD_GSS) { + /* Buffer to store raw message in case that + * gss authentication fails halfway through. This buffer + * is currently dynamically allocated, for performance + * reasons we should look for a better way to do it. + * rgerhars, 2008-09-02 + */ + char *buf; + int ret = 0; + CHKmalloc(buf = (char*) malloc(sizeof(char) * (glbl.GetMaxLine() + 1))); + + dbgprintf("GSS-API Trying to accept TCP session %p\n", pSess); + + CHKiRet(netstrm.GetSock(pSess->pStrm, &fdSess)); // TODO: method access! + if (allowedMethods & ALLOWEDMETHOD_TCP) { + int len; + fd_set fds; + struct timeval tv; + + do { + FD_ZERO(&fds); + FD_SET(fdSess, &fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + ret = select(fdSess + 1, &fds, NULL, NULL, &tv); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + errmsg.LogError(0, RS_RET_ERR, "TCP session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } else if (ret == 0) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } + + do { + ret = recv(fdSess, buf, sizeof (buf), MSG_PEEK); + } while (ret < 0 && errno == EINTR); + if (ret <= 0) { + if (ret == 0) + dbgprintf("GSS-API Connection closed by peer\n"); + else + errmsg.LogError(0, RS_RET_ERR, "TCP(GSS) session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + + if (ret < 4) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } else if (ret == 4) { + /* The client might has been interupted after sending + * the data length (4B), give him another chance. + */ + srSleep(1, 0); + do { + ret = recv(fdSess, buf, sizeof (buf), MSG_PEEK); + } while (ret < 0 && errno == EINTR); + if (ret <= 0) { + if (ret == 0) + dbgprintf("GSS-API Connection closed by peer\n"); + else + errmsg.LogError(0, NO_ERRCODE, "TCP session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + } + + /* TODO: how does this work together with IPv6? Does it? */ + len = ntohl((buf[0] << 24) + | (buf[1] << 16) + | (buf[2] << 8) + | buf[3]); + if ((ret - 4) < len || len == 0) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } + } + + context = &pGSess->gss_context; + *context = GSS_C_NO_CONTEXT; + sess_flags = &pGSess->gss_flags; + do { + if (gssutil.recv_token(fdSess, &recv_tok) <= 0) { + errmsg.LogError(0, NO_ERRCODE, "TCP session %p will be closed, error ignored\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + maj_stat = gss_accept_sec_context(&acc_sec_min_stat, context, gss_server_creds, + &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &client, + NULL, &send_tok, sess_flags, NULL, NULL); + if (recv_tok.value) { + free(recv_tok.value); + recv_tok.value = NULL; + } + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { + gss_release_buffer(&min_stat, &send_tok); + if (*context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + if ((allowedMethods & ALLOWEDMETHOD_TCP) && + (GSS_ROUTINE_ERROR(maj_stat) == GSS_S_DEFECTIVE_TOKEN)) { + dbgprintf("GSS-API Reverting to plain TCP\n"); + dbgprintf("tcp session socket with new data: #%d\n", fdSess); + if(tcps_sess.DataRcvd(pSess, buf, ret) != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "Tearing down TCP Session %p - see " + "previous messages for reason(s)\n", pSess); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + pGSess->allowedMethods = ALLOWEDMETHOD_TCP; + ABORT_FINALIZE(RS_RET_OK); // TODO: define good error codes + } + gssutil.display_status("accepting context", maj_stat, acc_sec_min_stat); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + if (send_tok.length != 0) { + if(gssutil.send_token(fdSess, &send_tok) < 0) { + gss_release_buffer(&min_stat, &send_tok); + errmsg.LogError(0, NO_ERRCODE, "TCP session %p will be closed, error ignored\n", pSess); + if (*context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + ABORT_FINALIZE(RS_RET_ERR); // TODO: define good error codes + } + gss_release_buffer(&min_stat, &send_tok); + } + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + maj_stat = gss_display_name(&min_stat, client, &recv_tok, NULL); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("displaying name", maj_stat, min_stat); + else + dbgprintf("GSS-API Accepted connection from: %s\n", (char*) recv_tok.value); + gss_release_name(&min_stat, &client); + gss_release_buffer(&min_stat, &recv_tok); + + dbgprintf("GSS-API Provided context flags:\n"); + gssutil.display_ctx_flags(*sess_flags); + pGSess->allowedMethods = ALLOWEDMETHOD_GSS; + } + +finalize_it: + RETiRet; +} + + +/* Replaces recv() for gssapi connections. + */ +int TCPSessGSSRecv(tcps_sess_t *pSess, void *buf, size_t buf_len, ssize_t *piLenRcvd) +{ + DEFiRet; + gss_buffer_desc xmit_buf, msg_buf; + gss_ctx_id_t *context; + OM_uint32 maj_stat, min_stat; + int fdSess; + int conf_state; + int state; + gss_sess_t *pGSess; + + assert(pSess->pUsr != NULL); + assert(piLenRcvd != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + netstrm.GetSock(pSess->pStrm, &fdSess); // TODO: method access, CHKiRet! + if ((state = gssutil.recv_token(fdSess, &xmit_buf)) <= 0) + ABORT_FINALIZE(RS_RET_GSS_ERR); + + context = &pGSess->gss_context; + maj_stat = gss_unwrap(&min_stat, *context, &xmit_buf, &msg_buf, + &conf_state, (gss_qop_t *) NULL); + if(maj_stat != GSS_S_COMPLETE) { + gssutil.display_status("unsealing message", maj_stat, min_stat); + if(xmit_buf.value) { + free(xmit_buf.value); + xmit_buf.value = 0; + } + ABORT_FINALIZE(RS_RET_GSS_ERR); + } + if (xmit_buf.value) { + free(xmit_buf.value); + xmit_buf.value = 0; + } + + *piLenRcvd = msg_buf.length < buf_len ? msg_buf.length : buf_len; + memcpy(buf, msg_buf.value, *piLenRcvd); + gss_release_buffer(&min_stat, &msg_buf); + +finalize_it: + RETiRet; +} + + +/* Takes care of cleaning up gssapi stuff and then calls + * TCPSessClose(). + */ +void TCPSessGSSClose(tcps_sess_t* pSess) +{ + OM_uint32 maj_stat, min_stat; + gss_ctx_id_t *context; + gss_sess_t *pGSess; + + assert(pSess->pUsr != NULL); + pGSess = (gss_sess_t*) pSess->pUsr; + + context = &pGSess->gss_context; + maj_stat = gss_delete_sec_context(&min_stat, context, GSS_C_NO_BUFFER); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("deleting context", maj_stat, min_stat); + *context = GSS_C_NO_CONTEXT; + pGSess->gss_flags = 0; + pGSess->allowedMethods = 0; + + tcps_sess.Close(pSess); +} + + +/* Counterpart of TCPSessGSSInit(). This is called to exit the GSS system + * at all. It is a server-based session exit. + */ +static rsRetVal +TCPSessGSSDeinit(void) +{ + DEFiRet; + OM_uint32 maj_stat, min_stat; + + if (gss_server_creds != GSS_C_NO_CREDENTIAL) { + maj_stat = gss_release_cred(&min_stat, &gss_server_creds); + if (maj_stat != GSS_S_COMPLETE) + gssutil.display_status("releasing credentials", maj_stat, min_stat); + } + RETiRet; +} + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + iRet = tcpsrv.Run(pOurTcpsrv); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + if(pOurTcpsrv == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + + net.PrintAllowedSenders(2); /* TCP */ + net.PrintAllowedSenders(3); /* GSS */ +finalize_it: +ENDwillRun + + + +BEGINmodExit +CODESTARTmodExit + if(pOurTcpsrv != NULL) + iRet = tcpsrv.Destruct(&pOurTcpsrv); + TCPSessGSSDeinit(); + + /* release objects we used */ + objRelease(tcps_sess, LM_TCPSRV_FILENAME); + objRelease(tcpsrv, LM_TCPSRV_FILENAME); + objRelease(gssutil, LM_GSSUTIL_FILENAME); + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(netstrm, LM_NETSTRM_FILENAME); + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"TCP"); + net.clearAllowedSenders((uchar*)"GSS"); +ENDafterRun + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + if (gss_listen_service_name != NULL) { + free(gss_listen_service_name); + gss_listen_service_name = NULL; + } + bPermitPlainTcp = 0; + iTCPSessMax = 200; + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current definition */ +CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(tcps_sess, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(tcpsrv, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(gssutil, LM_GSSUTIL_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(netstrm, LM_NETSTRM_FILENAME)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssserverpermitplaintcp", 0, eCmdHdlrBinary, + NULL, &bPermitPlainTcp, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssserverrun", 0, eCmdHdlrGetWord, + addGSSListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssserverservicename", 0, eCmdHdlrGetWord, + NULL, &gss_listen_service_name, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputgssservermaxsessions", 0, eCmdHdlrInt, + NULL, &iTCPSessMax, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/imklog/Makefile.am b/plugins/imklog/Makefile.am new file mode 100644 index 00000000..5d4d0465 --- /dev/null +++ b/plugins/imklog/Makefile.am @@ -0,0 +1,16 @@ +pkglib_LTLIBRARIES = imklog.la + +imklog_la_SOURCES = imklog.c imklog.h + +# select klog "driver" +if ENABLE_IMKLOG_BSD +imklog_la_SOURCES += bsd.c +endif + +if ENABLE_IMKLOG_LINUX +imklog_la_SOURCES += linux.c module.h ksym.c ksyms.h ksym_mod.c +endif + +imklog_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imklog_la_LDFLAGS = -module -avoid-version +imklog_la_LIBADD = diff --git a/plugins/imklog/bsd.c b/plugins/imklog/bsd.c new file mode 100644 index 00000000..090c4e9b --- /dev/null +++ b/plugins/imklog/bsd.c @@ -0,0 +1,202 @@ +/* klog for BSD, based on the FreeBSD syslogd implementation. + * + * This contains OS-specific functionality to read the BSD + * kernel log. For a general overview, see head comment in + * imklog.c. + * + * Copyright (C) 2008 by Rainer Gerhards for the modifications of + * the original FreeBSD sources. + * + * I would like to express my gratitude to those folks which + * layed an important foundation for rsyslog to build on. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * + * This file is based on earlier work included in the FreeBSD sources. We + * integrated it into the rsyslog project. The copyright below applies, and + * I also reproduce the original license under which we aquired the code: + * + * Copyright (c) 1983, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * If you would like to use the code under the BSD license, you should + * aquire your own copy of BSD's syslogd, from which we have taken it. The + * code in this file is modified and may only be used under the terms of + * the GPLv3+ as specified above. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#include "rsyslog.h" +#include "imklog.h" + +/* globals */ +static int fklog = -1; /* /dev/klog */ + +#ifndef _PATH_KLOG +# define _PATH_KLOG "/dev/klog" +#endif + +/* open the kernel log - will be called inside the willRun() imklog + * entry point. -- rgerhards, 2008-04-09 + */ +rsRetVal +klogWillRun(void) +{ + DEFiRet; + + fklog = open(_PATH_KLOG, O_RDONLY, 0); + if (fklog < 0) { + dbgprintf("can't open %s (%d)\n", _PATH_KLOG, errno); + iRet = RS_RET_ERR; // TODO: better error code + } + + RETiRet; +} + + +/* Read /dev/klog while data are available, split into lines. + * Contrary to standard BSD syslogd, we do a blocking read. We can + * afford this as imklog is running on its own threads. So if we have + * a single file, it really doesn't matter if we wait inside a 1-file + * select or the read() directly. + */ +static void +readklog(void) +{ + char *p, *q; + int len, i; + int iMaxLine; + uchar bufRcv[4096+1]; + uchar *pRcv = NULL; /* receive buffer */ + + iMaxLine = klog_getMaxLine(); + + /* we optimize performance: if iMaxLine is below 4K (which it is in almost all + * cases, we use a fixed buffer on the stack. Only if it is higher, heap memory + * is used. We could use alloca() to achive a similar aspect, but there are so + * many issues with alloca() that I do not want to take that route. + * rgerhards, 2008-09-02 + */ + if((size_t) iMaxLine < sizeof(bufRcv) - 1) { + pRcv = bufRcv; + } else { + if((pRcv = (uchar*) malloc(sizeof(uchar) * (iMaxLine + 1))) == NULL) + iMaxLine = sizeof(bufRcv) - 1; /* better this than noting */ + } + + len = 0; + for (;;) { + dbgprintf("----------imklog(BSD) waiting for kernel log line\n"); + i = read(fklog, pRcv + len, iMaxLine - len); + if (i > 0) { + pRcv[i + len] = '\0'; + } else { + if (i < 0 && errno != EINTR && errno != EAGAIN) { + imklogLogIntMsg(LOG_ERR, + "imklog error %d reading kernel log - shutting down imklog", + errno); + fklog = -1; + } + break; + } + + for (p = pRcv; (q = strchr(p, '\n')) != NULL; p = q + 1) { + *q = '\0'; + Syslog(LOG_INFO, (uchar*) p); + } + len = strlen(p); + if (len >= iMaxLine - 1) { + Syslog(LOG_INFO, (uchar*)p); + len = 0; + } + if (len > 0) + memmove(pRcv, p, len + 1); + } + if (len > 0) + Syslog(LOG_INFO, pRcv); + + if(pRcv != NULL && (size_t) iMaxLine >= sizeof(bufRcv) - 1) + free(pRcv); +} + + +/* to be called in the module's AfterRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogAfterRun(void) +{ + DEFiRet; + if(fklog != -1) + close(fklog); + RETiRet; +} + + + +/* to be called in the module's WillRun entry point, this is the main + * "message pull" mechanism. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(void) +{ + DEFiRet; + readklog(); + RETiRet; +} + + +/* provide the (system-specific) default facility for internal messages + * rgerhards, 2008-04-14 + */ +int +klogFacilIntMsg(void) +{ + return LOG_SYSLOG; +} diff --git a/plugins/imklog/imklog.c b/plugins/imklog/imklog.c new file mode 100644 index 00000000..20bc34ab --- /dev/null +++ b/plugins/imklog/imklog.c @@ -0,0 +1,285 @@ +/* The kernel log module. + * + * This is an abstracted module. As Linux and BSD kernel log is conceptually the + * same, we do not do different input plugins for them but use + * imklog in both cases, just with different "backend drivers" for + * the different platforms. This also enables a rsyslog.conf to + * be used on multiple platforms without the need to take care of + * what the kernel log is coming from. + * + * See platform-specific files (e.g. linux.c, bsd.c) in the plugin's + * working directory. For other systems with similar kernel logging + * functionality, no new input plugin shall be written but rather a + * driver be developed for imklog. Please note that imklog itself is + * mostly concerned with handling the interface. Any real action happens + * in the drivers, as things may be pretty different on different + * platforms. + * + * Please note that this file replaces the klogd daemon that was + * also present in pre-v3 versions of rsyslog. + * + * Copyright (C) 2008 by Rainer Gerhards and Adiscon GmbH + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> + +#include "dirty.h" +#include "cfsysline.h" +#include "obj.h" +#include "msg.h" +#include "module-template.h" +#include "datetime.h" +#include "imklog.h" +#include "glbl.h" + +MODULE_TYPE_INPUT + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(datetime) +DEFobjCurrIf(glbl) + +/* configuration settings */ +int dbgPrintSymbols = 0; /* this one is extern so the helpers can access it! */ +int symbols_twice = 0; +int use_syscall = 0; +int symbol_lookup = 0; /* on recent kernels > 2.6, the kernel does this */ +int bPermitNonKernel = 0; /* permit logging of messages not having LOG_KERN facility */ +int iFacilIntMsg; /* the facility to use for internal messages (set by driver) */ +/* TODO: configuration for the following directives must be implemented. It + * was not done yet because we either do not yet have a config handler for + * that type or I thought it was acceptable to push it to a later stage when + * I gained more handson experience with the input module interface (and the + * changes resulting from that). -- rgerhards, 2007-12-20 + */ +char *symfile = NULL; +int console_log_level = -1; + + +/* enqueue the the kernel message into the message queue. + * The provided msg string is not freed - thus must be done + * by the caller. + * rgerhards, 2008-04-12 + */ +static rsRetVal +enqMsg(uchar *msg, uchar* pszTag, int iFacility, int iSeverity) +{ + DEFiRet; + msg_t *pMsg; + + assert(msg != NULL); + assert(pszTag != NULL); + + CHKiRet(msgConstruct(&pMsg)); + MsgSetFlowControlType(pMsg, eFLOWCTL_LIGHT_DELAY); + MsgSetInputName(pMsg, "imklog"); + MsgSetRawMsg(pMsg, (char*)msg); + MsgSetUxTradMsg(pMsg, (char*)msg); + MsgSetRawMsg(pMsg, (char*)msg); + MsgSetMSG(pMsg, (char*)msg); + MsgSetRcvFrom(pMsg, (char*)glbl.GetLocalHostName()); + MsgSetRcvFromIP(pMsg, (uchar*)"127.0.0.1"); + MsgSetHOSTNAME(pMsg, (char*)glbl.GetLocalHostName()); + MsgSetTAG(pMsg, (char*)pszTag); + pMsg->iFacility = LOG_FAC(iFacility); + pMsg->iSeverity = LOG_PRI(iSeverity); + pMsg->bParseHOSTNAME = 0; + CHKiRet(submitMsg(pMsg)); + +finalize_it: + RETiRet; +} + +/* parse the PRI from a kernel message. At least BSD seems to have + * non-kernel messages inside the kernel log... + * Expected format: "<pri>". piPri is only valid if the function + * successfully returns. If there was a proper pri ppSz is advanced to the + * position right after ">". + * rgerhards, 2008-04-14 + */ +static rsRetVal +parsePRI(uchar **ppSz, int *piPri) +{ + DEFiRet; + int i; + uchar *pSz; + + assert(ppSz != NULL); + pSz = *ppSz; + assert(pSz != NULL); + assert(piPri != NULL); + + if(*pSz != '<' || !isdigit(*(pSz+1))) + ABORT_FINALIZE(RS_RET_INVALID_PRI); + + ++pSz; + i = 0; + while(isdigit(*pSz)) { + i = i * 10 + *pSz++ - '0'; + } + + if(*pSz != '>') + ABORT_FINALIZE(RS_RET_INVALID_PRI); + + /* OK, we have a valid PRI */ + *piPri = i; + *ppSz = pSz + 1; /* update msg ptr to position after PRI */ + +finalize_it: + RETiRet; +} + + +/* log an imklog-internal message + * rgerhards, 2008-04-14 + */ +rsRetVal imklogLogIntMsg(int priority, char *fmt, ...) +{ + DEFiRet; + va_list ap; + uchar msgBuf[2048]; /* we use the same size as sysklogd to remain compatible */ + uchar *pLogMsg; + + va_start(ap, fmt); + vsnprintf((char*)msgBuf, sizeof(msgBuf) / sizeof(char), fmt, ap); + pLogMsg = msgBuf; + va_end(ap); + + iRet = enqMsg((uchar*)pLogMsg, (uchar*) ((iFacilIntMsg == LOG_KERN) ? "kernel:" : "imklog:"), + iFacilIntMsg, LOG_PRI(priority)); + + RETiRet; +} + + +/* log a kernel message + * rgerhards, 2008-04-14 + */ +rsRetVal Syslog(int priority, uchar *pMsg) +{ + DEFiRet; + rsRetVal localRet; + + /* Output using syslog */ + localRet = parsePRI(&pMsg, &priority); + if(localRet != RS_RET_INVALID_PRI && localRet != RS_RET_OK) + FINALIZE; + /* if we don't get the pri, we use whatever we were supplied */ + + /* ignore non-kernel messages if not permitted */ + if(bPermitNonKernel == 0 && LOG_FAC(priority) != LOG_KERN) + FINALIZE; /* silently ignore */ + + iRet = enqMsg((uchar*)pMsg, (uchar*) "kernel:", LOG_FAC(priority), LOG_PRI(priority)); + +finalize_it: + RETiRet; +} + + +/* helper for some klog drivers which need to know the MaxLine global setting. They can + * not obtain it themselfs, because they are no modules and can not query the object hander. + * It would probably be a good idea to extend the interface to support it, but so far + * we create a (sufficiently valid) work-around. -- rgerhards, 2008-11-24 + */ +int klog_getMaxLine(void) +{ + return glbl.GetMaxLine(); +} + + +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(!pThrd->bShallStop) { + /* klogLogKMsg() waits for the next kernel message, obtains it + * and then submits it to the rsyslog main queue. + * rgerhards, 2008-04-09 + */ + CHKiRet(klogLogKMsg()); + } +finalize_it: +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + iRet = klogWillRun(); +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + iRet = klogAfterRun(); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release objects we used */ + objRelease(glbl, CORE_COMPONENT); + objRelease(datetime, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + dbgPrintSymbols = 0; + symbols_twice = 0; + use_syscall = 0; + symfile = NULL; + symbol_lookup = 0; + bPermitNonKernel = 0; + iFacilIntMsg = klogFacilIntMsg(); + return RS_RET_OK; +} + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + iFacilIntMsg = klogFacilIntMsg(); + + CHKiRet(omsdRegCFSLineHdlr((uchar *)"debugprintkernelsymbols", 0, eCmdHdlrBinary, NULL, &dbgPrintSymbols, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbollookup", 0, eCmdHdlrBinary, NULL, &symbol_lookup, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogsymbolstwice", 0, eCmdHdlrBinary, NULL, &symbols_twice, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogusesyscallinterface", 0, eCmdHdlrBinary, NULL, &use_syscall, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"klogpermitnonkernelfacility", 0, eCmdHdlrBinary, NULL, &bPermitNonKernel, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"kloginternalmsgfacility", 0, eCmdHdlrFacility, NULL, &iFacilIntMsg, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/imklog/imklog.h b/plugins/imklog/imklog.h new file mode 100644 index 00000000..37bd58b0 --- /dev/null +++ b/plugins/imklog/imklog.h @@ -0,0 +1,71 @@ +/* imklog.h + * These are the definitions for the klog message generation module. + * + * File begun on 2007-12-17 by RGerhards + * Major change: 2008-04-09: switched to a driver interface for + * several platforms + * + * Copyright 2007-2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef IMKLOG_H_INCLUDED +#define IMKLOG_H_INCLUDED 1 + +#include "rsyslog.h" +#include "dirty.h" + +/* interface to "drivers" + * the platform specific drivers must implement these entry points. Only one + * driver may be active at any given time, thus we simply rely on the linker + * to resolve the addresses. + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(void); +rsRetVal klogWillRun(void); +rsRetVal klogAfterRun(void); +int klogFacilIntMsg(void); + +/* the following data members may be accessed by the "drivers" + * I admit this is not the cleanest way to doing things, but I honestly + * believe it is appropriate for the job that needs to be done. + * rgerhards, 2008-04-09 + */ +extern int symbols_twice; +extern int use_syscall; +extern int symbol_lookup; +extern char *symfile; +extern int console_log_level; +extern int dbgPrintSymbols; + +/* the functions below may be called by the drivers */ +rsRetVal imklogLogIntMsg(int priority, char *fmt, ...) __attribute__((format(printf,2, 3))); +rsRetVal Syslog(int priority, uchar *msg); + +/* prototypes */ +extern int klog_getMaxLine(void); /* work-around for klog drivers to get configured max line size */ +extern int InitKsyms(char *); +extern void DeinitKsyms(void); +extern int InitMsyms(void); +extern void DeinitMsyms(void); +extern char * ExpandKadds(char *, char *); +extern void SetParanoiaLevel(int); + +#endif /* #ifndef IMKLOG_H_INCLUDED */ +/* vi:set ai: + */ diff --git a/plugins/imklog/ksym.c b/plugins/imklog/ksym.c new file mode 100644 index 00000000..f636a7bb --- /dev/null +++ b/plugins/imklog/ksym.c @@ -0,0 +1,833 @@ +/* ksym.c - functions for kernel address->symbol translation + * Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> + * Copyright (c) 1996 Enjellic Systems Development + * Copyright (c) 1998-2007 Martin Schulze <joey@infodrom.org> + * Copyright (C) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ + +/* + * This file contains functions which handle the translation of kernel + * numeric addresses into symbols for the klogd utility. + * + * Sat Oct 28 09:00:14 CDT 1995: Dr. Wettstein + * Initial Version. + * + * Fri Nov 24 12:50:52 CST 1995: Dr. Wettstein + * Added VERBOSE_DEBUGGING define to make debugging output more + * manageable. + * + * Added support for verification of the loaded kernel symbols. If + * no version information can be be found in the mapfile a warning + * message is issued but translation will still take place. This + * will be the default case if kernel versions < 1.3.43 are used. + * + * If the symbols in the mapfile are of the same version as the kernel + * that is running an informative message is issued. If the symbols + * in the mapfile do not match the current kernel version a warning + * message is issued and translation is disabled. + * + * Wed Dec 6 16:14:11 CST 1995: Dr. Wettstein + * Added /boot/System.map to the list of symbol maps to search for. + * Also made this map the first item in the search list. I am open + * to CONSTRUCTIVE suggestions for any additions or corrections to + * the list of symbol maps to search for. Be forewarned that the + * list in use is the consensus agreement between myself, Linus and + * some package distributers. It is a given that no list will suit + * everyone's taste. If you have rabid concerns about the list + * please feel free to edit the system_maps array and compile your + * own binaries. + * + * Added support for searching of the list of symbol maps. This + * allows support for access to multiple symbol maps. The theory + * behind this is that a production kernel may have a system map in + * /boot/System.map. If a test kernel is booted this system map + * would be skipped in favor of one found in /usr/src/linux. + * + * Thu Jan 18 11:18:31 CST 1996: Dr. Wettstein + * Added patch from beta-testers to allow for reading of both + * ELF and a.out map files. + * + * Wed Aug 21 09:15:49 CDT 1996: Dr. Wettstein + * Reloading of kernel module symbols is now turned on by the + * SetParanoiaLevel function. The default behavior is to NOT reload + * the kernel module symbols when a protection fault is detected. + * + * Added support for freeing of the current kernel module symbols. + * This was necessary to support reloading of the kernel module symbols. + * + * When a matching static symbol table is loaded the kernel version + * number is printed. + * + * Mon Jun 9 17:12:42 CST 1997: Martin Schulze + * Added #1 and #2 to some error messages in order to being able + * to divide them (ulmo@Q.Net) + * + * Fri Jun 13 10:50:23 CST 1997: Martin Schulze + * Changed definition of LookupSymbol to non-static because it is + * used in klogd.c, too. + * + * Fri Jan 9 23:00:08 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Fixed bug that caused klogd to die if there is no System.map available. + * + * Sun 29 Mar 18:14:07 BST 1998: Mark Simon Phillips <M.S.Phillips@nortel.co.uk> + * Switched to fgets() as gets() is not buffer overrun secure. + * + * Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Modified loop for detecting the correct system map. Now it won't + * stop if a file has been found but doesn't contain the correct map. + * Special thanks go go Mark Simon Phillips for the hint. + * + * Mon Oct 12 00:42:30 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Modified CheckVersion() + * . Use shift to decode the kernel version + * . Compare integers of kernel version + * . extract major.minor.patch from utsname.release via sscanf() + * The reason lays in possible use of kernel flavours which + * modify utsname.release but no the Version_ symbol. + * + * Sun Feb 21 22:27:49 EST 1999: Keith Owens <kaos@ocs.com.au> + * Fixed bug that caused klogd to die if there is no sym_array available. + * + * Tue Sep 12 23:48:12 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Close symbol file in InitKsyms() when an error occurred. + */ + + +/* Includes. */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/utsname.h> +#include <ctype.h> +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include "imklog.h" +#include "ksyms.h" +#include "module.h" + + +int num_syms = 0; +static int i_am_paranoid = 0; +static char vstring[12]; +static struct sym_table *sym_array = (struct sym_table *) 0; + +static char *system_maps[] = +{ + "/boot/System.map", + "/System.map", + NULL +}; + + +/* Function prototypes. */ +static char *FindSymbolFile(void); +static int AddSymbol(unsigned long, char*); +static void FreeSymbols(void); +static int CheckVersion(char *); +static int CheckMapVersion(char *); + + +/************************************************************************* + * Function: InitKsyms + * + * Purpose: This function is responsible for initializing and loading + * the data tables used by the kernel address translations. + * + * Arguements: (char *) mapfile + * + * mapfile:-> A pointer to a complete path + * specification of the file containing + * the kernel map to use. + * + * Return: int + * + * A boolean style context is returned. The return value will + * be true if initialization was successful. False if not. + **************************************************************************/ +extern int InitKsyms(char *mapfile) +{ + auto char type, + sym[512]; + + auto int version = 0; + + auto unsigned long int address; + + auto FILE *sym_file; + + BEGINfunc + + /* Check and make sure that we are starting with a clean slate. */ + if ( num_syms > 0 ) + FreeSymbols(); + + + /* Search for and open the file containing the kernel symbols. */ + if ( mapfile != NULL ) { + if ( (sym_file = fopen(mapfile, "r")) == NULL ) + { + imklogLogIntMsg(LOG_WARNING, "Cannot open map file: %s.", mapfile); + return(0); + } + } else { + if ( (mapfile = FindSymbolFile()) == NULL ) { + imklogLogIntMsg(LOG_WARNING, "Cannot find map file."); + dbgprintf("Cannot find map file.\n"); + return(0); + } + + if ( (sym_file = fopen(mapfile, "r")) == NULL ) { + imklogLogIntMsg(LOG_WARNING, "Cannot open map file."); + dbgprintf("Cannot open map file.\n"); + return(0); + } + } + + + /* Read the kernel symbol table file and add entries for each + * line. I suspect that the use of fscanf is not really in vogue + * but it was quick and dirty and IMHO suitable for fixed format + * data such as this. If anybody doesn't agree with this please + * e-mail me a diff containing a parser with suitable political + * correctness -- GW. + */ + while ( !feof(sym_file) ) { + if ( fscanf(sym_file, "%lx %c %s\n", &address, &type, sym) != 3 ) { + imklogLogIntMsg(LOG_ERR, "Error in symbol table input (#1)."); + fclose(sym_file); + return(0); + } + if(dbgPrintSymbols) + dbgprintf("Address: %lx, Type: %c, Symbol: %s\n", address, type, sym); + + if ( AddSymbol(address, sym) == 0 ) { + imklogLogIntMsg(LOG_ERR, "Error adding symbol - %s.", sym); + fclose(sym_file); + return(0); + } + + if ( version == 0 ) + version = CheckVersion(sym); + } + + + imklogLogIntMsg(LOG_INFO, "Loaded %d symbols from %s.", num_syms, mapfile); + switch(version) { + case -1: + imklogLogIntMsg(LOG_WARNING, "Symbols do not match kernel version."); + num_syms = 0; + break; + + case 0: + imklogLogIntMsg(LOG_WARNING, "Cannot verify that symbols match kernel version."); + break; + + case 1: + imklogLogIntMsg(LOG_INFO, "Symbols match kernel version %s.", vstring); + break; + } + + fclose(sym_file); + ENDfunc + return(1); +} + + +extern void DeinitKsyms(void) +{ + FreeSymbols(); +} + + +/************************************************************************** + * Function: FindSymbolFile + * + * Purpose: This function is responsible for encapsulating the search + * for a valid symbol file. Encapsulating the search for + * the map file in this function allows an intelligent search + * process to be implemented. + * + * The list of symbol files will be searched until either a + * symbol file is found whose version matches the currently + * executing kernel or the end of the list is encountered. If + * the end of the list is encountered the first available + * symbol file is returned to the caller. + * + * This strategy allows klogd to locate valid symbol files + * for both a production and an experimental kernel. For + * example a map for a production kernel could be installed + * in /boot. If an experimental kernel is loaded the map + * in /boot will be skipped and the map in /usr/src/linux would + * be used if its version number matches the executing kernel. + * + * Arguements: None specified. + * + * Return: char * + * + * If a valid system map cannot be located a null pointer + * is returned to the caller. + * + * If the search is succesful a pointer is returned to the + * caller which points to the name of the file containing + * the symbol table to be used. + **************************************************************************/ +static char *FindSymbolFile(void) +{ + auto char *file = NULL, + **mf = system_maps; + auto struct utsname utsname; + static char mysymfile[100]; + auto FILE *sym_file = NULL; + BEGINfunc + + if(uname(&utsname) < 0) { + imklogLogIntMsg(LOG_ERR, "Cannot get kernel version information."); + return(0); + } + + dbgprintf("Searching for symbol map.\n"); + + for(mf = system_maps; *mf != NULL && file == NULL; ++mf) { + snprintf(mysymfile, sizeof(mysymfile), "%s-%s", *mf, utsname.release); + dbgprintf("Trying %s.\n", mysymfile); + if((sym_file = fopen(mysymfile, "r")) != NULL) { + if(CheckMapVersion(mysymfile) == 1) + file = mysymfile; + fclose(sym_file); + } + if(sym_file == NULL || file == NULL) { + sprintf (mysymfile, "%s", *mf); + dbgprintf("Trying %s.\n", mysymfile); + if((sym_file = fopen(mysymfile, "r")) != NULL ) { + if (CheckMapVersion(mysymfile) == 1) + file = mysymfile; + fclose(sym_file); + } + } + } + + /* At this stage of the game we are at the end of the symbol tables. */ + dbgprintf("End of search list encountered.\n"); + ENDfunc + return(file); +} + + +/************************************************************************** + * Function: CheckVersion + * + * Purpose: This function is responsible for determining whether or + * the system map being loaded matches the version of the + * currently running kernel. + * + * The kernel version is checked by examing a variable which + * is of the form: _Version_66347 (a.out) or Version_66437 (ELF). + * + * The suffix of this variable is the current kernel version + * of the kernel encoded in base 256. For example the + * above variable would be decoded as: + * + * (66347 = 1*65536 + 3*256 + 43 = 1.3.43) + * + * (Insert appropriate deities here) help us if Linus ever + * needs more than 255 patch levels to get a kernel out the + * door... :-) + * + * Arguements: (char *) version + * + * version:-> A pointer to the string which + * is to be decoded as a kernel + * version variable. + * + * Return: int + * + * -1:-> The currently running kernel version does + * not match this version string. + * + * 0:-> The string is not a kernel version variable. + * + * 1:-> The executing kernel is of the same version + * as the version string. + **************************************************************************/ +static int CheckVersion(char *version) +{ + auto int vnum, + major, + minor, + patch; + int kvnum; + auto struct utsname utsname; + + static char *prefix = { "Version_" }; + + + /* Early return if there is no hope. */ + if ( strncmp(version, prefix, strlen(prefix)) == 0 /* ELF */ || + (*version == '_' && + strncmp(++version, prefix, strlen(prefix)) == 0 ) /* a.out */ ) + ; + else + return(0); + + + /* Since the symbol looks like a kernel version we can start + * things out by decoding the version string into its component + * parts. + */ + vnum = atoi(version + strlen(prefix)); + patch = vnum & 0x000000FF; + minor = (vnum >> 8) & 0x000000FF; + major = (vnum >> 16) & 0x000000FF; + dbgprintf("Version string = %s, Major = %d, Minor = %d, Patch = %d.\n", version + + strlen(prefix), major, minor, patch); + sprintf(vstring, "%d.%d.%d", major, minor, patch); + + /* We should now have the version string in the vstring variable in + * the same format that it is stored in by the kernel. We now + * ask the kernel for its version information and compare the two + * values to determine if our system map matches the kernel + * version level. + */ + if ( uname(&utsname) < 0 ) { + imklogLogIntMsg(LOG_ERR, "Cannot get kernel version information."); + return(0); + } + dbgprintf("Comparing kernel %s with symbol table %s.\n", utsname.release, vstring); + + if ( sscanf (utsname.release, "%d.%d.%d", &major, &minor, &patch) < 3 ) { + imklogLogIntMsg(LOG_ERR, "Kernel send bogus release string `%s'.", utsname.release); + return(0); + } + + /* Compute the version code from data sent by the kernel */ + kvnum = (major << 16) | (minor << 8) | patch; + + /* Failure. */ + if ( vnum != kvnum ) + return(-1); + + /* Success. */ + return(1); +} + + +/************************************************************************** + * Function: CheckMapVersion + * + * Purpose: This function is responsible for determining whether or + * the system map being loaded matches the version of the + * currently running kernel. It uses CheckVersion as + * backend. + * + * Arguements: (char *) fname + * + * fname:-> A pointer to the string which + * references the system map file to + * be used. + * + * Return: int + * + * -1:-> The currently running kernel version does + * not match the version in the given file. + * + * 0:-> No system map file or no version information. + * + * 1:-> The executing kernel is of the same version + * as the version of the map file. + **************************************************************************/ +static int CheckMapVersion(char *fname) +{ + int version; + FILE *sym_file; + auto unsigned long int address; + auto char type, + sym[512]; + + if ( (sym_file = fopen(fname, "r")) != NULL ) { + /* + * At this point a map file was successfully opened. We + * now need to search this file and look for version + * information. + */ + imklogLogIntMsg(LOG_INFO, "Inspecting %s", fname); + + version = 0; + while ( !feof(sym_file) && (version == 0) ) { + if ( fscanf(sym_file, "%lx %c %s\n", &address, &type, sym) != 3 ) { + imklogLogIntMsg(LOG_ERR, "Error in symbol table input (#2)."); + fclose(sym_file); + return(0); + } + if(dbgPrintSymbols) + dbgprintf("Address: %lx, Type: %c, Symbol: %s\n", address, type, sym); + version = CheckVersion(sym); + } + fclose(sym_file); + + switch ( version ) { + case -1: + imklogLogIntMsg(LOG_ERR, "Symbol table has incorrect version number.\n"); + break; + case 0: + dbgprintf("No version information found.\n"); + break; + case 1: + dbgprintf("Found table with matching version number.\n"); + break; + } + + return(version); + } + + return(0); +} + + +/************************************************************************** + * Function: AddSymbol + * + * Purpose: This function is responsible for adding a symbol name + * and its address to the symbol table. + * + * Arguements: (unsigned long) address, (char *) symbol + * + * Return: int + * + * A boolean value is assumed. True if the addition is + * successful. False if not. + **************************************************************************/ +static int AddSymbol(unsigned long address, char *symbol) +{ + /* Allocate the the symbol table entry. */ + sym_array = (struct sym_table *) realloc(sym_array, (num_syms+1) * + sizeof(struct sym_table)); + if ( sym_array == (struct sym_table *) 0 ) + return(0); + + /* Then the space for the symbol. */ + sym_array[num_syms].name = (char *) malloc(strlen(symbol)*sizeof(char) + 1); + if ( sym_array[num_syms].name == NULL ) + return(0); + + sym_array[num_syms].value = address; + strcpy(sym_array[num_syms].name, symbol); + ++num_syms; + return(1); +} + + +/************************************************************************** + * Function: LookupSymbol + * + * Purpose: Find the symbol which is related to the given kernel + * address. + * + * Arguements: (long int) value, (struct symbol *) sym + * + * value:-> The address to be located. + * + * sym:-> A pointer to a structure which will be + * loaded with the symbol's parameters. + * + * Return: (char *) + * + * If a match cannot be found a diagnostic string is printed. + * If a match is found the pointer to the symbolic name most + * closely matching the address is returned. + **************************************************************************/ +char * LookupSymbol(unsigned long value, struct symbol *sym) +{ + auto int lp; + + auto char *last; + auto char *name; + + struct symbol ksym, msym; + + if (!sym_array) + return(NULL); + + last = sym_array[0].name; + ksym.offset = 0; + ksym.size = 0; + if ( value < sym_array[0].value ) + return(NULL); + + for(lp = 0; lp <= num_syms; ++lp) { + if ( sym_array[lp].value > value ) { + ksym.offset = value - sym_array[lp-1].value; + ksym.size = sym_array[lp].value - \ + sym_array[lp-1].value; + break; + } + last = sym_array[lp].name; + } + + name = LookupModuleSymbol(value, &msym); + + if ( ksym.offset == 0 && msym.offset == 0 ) { + return(NULL); + } + + if ( ksym.offset == 0 || msym.offset < 0 || + (ksym.offset > 0 && ksym.offset < msym.offset) ) { + sym->offset = ksym.offset; + sym->size = ksym.size; + return(last); + } else { + sym->offset = msym.offset; + sym->size = msym.size; + return(name); + } + + + return(NULL); +} + +/************************************************************************** + * Function: FreeSymbols + * + * Purpose: This function is responsible for freeing all memory which + * has been allocated to hold the static symbol table. It + * also initializes the symbol count and in general prepares + * for a re-read of a static symbol table. + * + * Arguements: void + * + * Return: void + **************************************************************************/ +static void FreeSymbols(void) +{ + auto int lp; + + /* Free each piece of memory allocated for symbol names. */ + for(lp= 0; lp < num_syms; ++lp) + free(sym_array[lp].name); + + /* Whack the entire array and initialize everything. */ + free(sym_array); + sym_array = (struct sym_table *) 0; + num_syms = 0; + + return; +} + + +/************************************************************************** + * Function: LogExpanded + * + * Purpose: This function is responsible for logging a kernel message + * line after all potential numeric kernel addresses have + * been resolved symolically. + * + * Arguements: (char *) line, (char *) el + * + * line:-> A pointer to the buffer containing the kernel + * message to be expanded and logged. + * + * el:-> A pointer to the buffer into which the expanded + * kernel line will be written. + * + * Return: void + **************************************************************************/ +extern char *ExpandKadds(char *line, char *el) +{ + auto char dlm, + *kp, + *sl = line, + *elp = el, + *symbol; + char num[15]; + auto unsigned long int value; + auto struct symbol sym; + + sym.offset = 0; + sym.size = 0; + + /* + * This is as handy a place to put this as anyplace. + * + * Since the insertion of kernel modules can occur in a somewhat + * dynamic fashion we need some mechanism to insure that the + * kernel symbol tables get read just prior to when they are + * needed. + * + * To accomplish this we look for the Oops string and use its + * presence as a signal to load the module symbols. + * + * This is not the best solution of course, especially if the + * kernel is rapidly going out to lunch. What really needs to + * be done is to somehow generate a callback from the + * kernel whenever a module is loaded or unloaded. I am + * open for patches. + */ + if ( i_am_paranoid && + (strstr(line, "Oops:") != NULL) && !InitMsyms() ) + imklogLogIntMsg(LOG_WARNING, "Cannot load kernel module symbols.\n"); + + + /* + * Early return if there do not appear to be any kernel + * messages in this line. + */ + if ( (num_syms == 0) || + (kp = strstr(line, "[<")) == NULL ) { +#ifdef __sparc__ + if (num_syms) { + /* On SPARC, register dumps do not have the [< >] characters in it. + */ + static struct sparc_tests { + char *str; + int len; + } tests[] = { { "PC: ", 4 }, + { " o7: ", 5 }, + { " ret_pc: ", 9 }, + { " i7: ", 5 }, + { "Caller[", 7 } + }; + int i, j, ndigits; + char *kp2; + for (i = 0; i < 5; i++) { + kp = strstr(line, tests[i].str); + if (!kp) continue; + kp2 = kp + tests[i].len; + if (!isxdigit(*kp2)) continue; + for (ndigits = 1; isxdigit(kp2[ndigits]); ndigits++); + if (ndigits != 8 && ndigits != 16) continue; + /* On sparc64, all kernel addresses are in first 4GB */ + if (ndigits == 16) { + if (strncmp (kp2, "00000000", 8)) continue; + kp2 += 8; + } + if (!i) { + char *kp3; + if (ndigits == 16 && kp > line && kp[-1L] != 'T') continue; + kp3 = kp2 + 8; + if (ndigits == 16) { + if (strncmp (kp3, " TNPC: 00000000", 15) || !isxdigit(kp3[15])) + continue; + kp3 += 15; + } else { + if (strncmp (kp3, " NPC: ", 6) || !isxdigit(kp3[6])) + continue; + kp3 += 6; + } + for (j = 0; isxdigit(kp3[j]); j++); + if (j != 8) continue; + strncpy(elp, line, kp2 + 8 - line); + elp += kp2 + 8 - line; + value = strtol(kp2, (char **) 0, 16); + if ( (symbol = LookupSymbol(value, &sym)) ) { + if (sym.size) + elp += sprintf(elp, " (%s+%d/%d)", symbol, sym.offset, sym.size); + else + elp += sprintf(elp, " (%s)", symbol); + } + strncpy(elp, kp2 + 8, kp3 - kp2); + elp += kp3 - kp2; + value = strtol(kp3, (char **) 0, 16); + if ( (symbol = LookupSymbol(value, &sym)) ) { + if (sym.size) + elp += sprintf(elp, " (%s+%d/%d)", symbol, sym.offset, sym.size); + else + elp += sprintf(elp, " (%s)", symbol); + } + strcpy(elp, kp3 + 8); + } else { + strncpy(elp, line, kp2 + 8 - line); + elp += kp2 + 8 - line; + value = strtol(kp2, (char **) 0, 16); + if ( (symbol = LookupSymbol(value, &sym)) ) { + if (sym.size) + elp += sprintf(elp, " (%s+%d/%d)", symbol, sym.offset, sym.size); + else + elp += sprintf(elp, " (%s)", symbol); + } + strcpy(elp, kp2 + 8); + } + return el; + } + } +#endif + strcpy(el, line); + return(el); + } + + /* Loop through and expand all kernel messages. */ + do { + while ( sl < kp+1 ) + *elp++ = *sl++; + + /* Now poised at a kernel delimiter. */ + if ( (kp = strstr(sl, ">]")) == NULL ) { + strcpy(el, sl); + return(el); + } + dlm = *kp; + strncpy(num,sl+1,kp-sl-1); + num[kp-sl-1] = '\0'; + value = strtoul(num, (char **) 0, 16); + if ( (symbol = LookupSymbol(value, &sym)) == NULL ) + symbol = sl; + + strcat(elp, symbol); + elp += strlen(symbol); + dbgprintf("Symbol: %s = %lx = %s, %x/%d\n", sl+1, value, + (sym.size==0) ? symbol+1 : symbol, sym.offset, sym.size); + + value = 2; + if ( sym.size != 0 ) { + --value; + ++kp; + elp += sprintf(elp, "+0x%x/0x%02x", sym.offset, sym.size); + } + strncat(elp, kp, value); + elp += value; + sl = kp + value; + if ( (kp = strstr(sl, "[<")) == NULL ) + strcat(elp, sl); + } + while ( kp != NULL); + + dbgprintf("Expanded line: %s\n", el); + return(el); +} + + +/************************************************************************** + * Function: SetParanoiaLevel + * + * Purpose: This function is an interface function for setting the + * mode of loadable module symbol lookups. Probably overkill + * but it does slay another global variable. + * + * Arguements: (int) level + * + * level:-> The amount of paranoia which is to be + * present when resolving kernel exceptions. + * Return: void + **************************************************************************/ +extern void SetParanoiaLevel(int level) +{ + i_am_paranoid = level; + return; +} + diff --git a/plugins/imklog/ksym_mod.c b/plugins/imklog/ksym_mod.c new file mode 100644 index 00000000..6e48e89e --- /dev/null +++ b/plugins/imklog/ksym_mod.c @@ -0,0 +1,482 @@ +/* + * ksym_mod.c - functions for building symbol lookup tables for klogd + * Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> + * Copyright (c) 1996 Enjellic Systems Development + * Copyright (c) 1998-2007 Martin Schulze <joey@infodrom.org> + * Copyright (C) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ + +/* + * This file implements functions which are useful for building + * a symbol lookup table based on the in kernel symbol table + * maintained by the Linux kernel. + * + * Proper logging of kernel panics generated by loadable modules + * tends to be difficult. Since the modules are loaded dynamically + * their addresses are not known at kernel load time. A general + * protection fault (Oops) cannot be properly deciphered with + * classic methods using the static symbol map produced at link time. + * + * One solution to this problem is to have klogd attempt to translate + * addresses from module when the fault occurs. By referencing the + * the kernel symbol table proper resolution of these symbols is made + * possible. + * + * At least that is the plan. + * + * Wed Aug 21 09:20:09 CDT 1996: Dr. Wettstein + * The situation where no module support has been compiled into a + * kernel is now detected. An informative message is output indicating + * that the kernel has no loadable module support whenever kernel + * module symbols are loaded. + * + * An informative message is printed indicating the number of kernel + * modules and the number of symbols loaded from these modules. + * + * Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman + * Some more glibc patches made by <mdorman@debian.org>. + * + * Sat Jan 10 15:00:18 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Fixed problem with klogd not being able to be built on a kernel + * newer than 2.1.18. It was caused by modified structures + * inside the kernel that were included. I have worked in a + * patch from Alessandro Suardi <asuardi@uninetcom.it>. + * + * Sun Jan 25 20:57:34 CET 1998: Martin Schulze <joey@infodrom.north.de> + * Another patch for Linux/alpha by Christopher C Chimelis + * <chris@classnet.med.miami.edu>. + * + * Thu Mar 19 23:39:29 CET 1998: Manuel Rodrigues <pmanuel@cindy.fe.up.pt> + * Changed lseek() to llseek() in order to support > 2GB address + * space which provided by kernels > 2.1.70. + * + * Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de> + * Removed <sys/module.h> as it's no longer part of recent glibc + * versions. Added prototyp for llseek() which has been + * forgotton in <unistd.h> from glibc. Added more log + * information if problems occurred while reading a system map + * file, by submission from Mark Simon Phillips <M.S.Phillips@nortel.co.uk>. + * + * Sun Jan 3 18:38:03 CET 1999: Martin Schulze <joey@infodrom.north.de> + * Corrected return value of AddModule if /dev/kmem can't be + * loaded. This will prevent klogd from segfaulting if /dev/kmem + * is not available. Patch from Topi Miettinen <tom@medialab.sonera.net>. + * + * Tue Sep 12 23:11:13 CEST 2000: Martin Schulze <joey@infodrom.ffis.de> + * Changed llseek() to lseek64() in order to skip a libc warning. + */ + + +/* Includes. */ +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#if !defined(__GLIBC__) +#include <linux/time.h> +#include <linux/module.h> +#else /* __GLIBC__ */ +#include "module.h" +#endif /* __GLIBC__ */ +#include <stdarg.h> +#include <paths.h> +#include <linux/version.h> + +#include "rsyslog.h" +#include "imklog.h" +#include "ksyms.h" + +#define KSYMS "/proc/kallsyms" + +static int num_modules = 0; +struct Module *sym_array_modules = (struct Module *) 0; + +static int have_modules = 0; + + +/* Function prototypes. */ +static void FreeModules(void); +static int AddSymbol(const char *); +struct Module *AddModule(const char *); +static int symsort(const void *, const void *); + +/* Imported from ksym.c */ +extern int num_syms; + + +/************************************************************************** + * Function: InitMsyms + * + * Purpose: This function is responsible for building a symbol + * table which can be used to resolve addresses for + * loadable modules. + * + * Arguements: Void + * + * Return: A boolean return value is assumed. + * + * A false value indicates that something went wrong. + * + * True if loading is successful. + **************************************************************************/ +extern int InitMsyms(void) +{ + + auto int rtn, + tmp; + FILE *ksyms; + char buf[128]; + char *p; + + /* Initialize the kernel module symbol table. */ + FreeModules(); + + ksyms = fopen(KSYMS, "r"); + + if ( ksyms == NULL ) { + if ( errno == ENOENT ) + imklogLogIntMsg(LOG_INFO, "No module symbols loaded - " + "kernel modules not enabled.\n"); + else + imklogLogIntMsg(LOG_ERR, "Error loading kernel symbols " \ + "- %s\n", strerror(errno)); + return(0); + } + + dbgprintf("Loading kernel module symbols - Source: %s\n", KSYMS); + + while ( fgets(buf, sizeof(buf), ksyms) != NULL ) { + if (num_syms > 0 && index(buf, '[') == NULL) + continue; + + p = index(buf, ' '); + + if ( p == NULL ) + continue; + + if ( buf[strlen(buf)-1] == '\n' ) + buf[strlen(buf)-1] = '\0'; + /* overlong lines will be ignored above */ + + AddSymbol(buf); + } + + if(ksyms != NULL) + fclose(ksyms); + + have_modules = 1; + + /* Sort the symbol tables in each module. */ + for (rtn = tmp = 0; tmp < num_modules; ++tmp) { + rtn += sym_array_modules[tmp].num_syms; + if ( sym_array_modules[tmp].num_syms < 2 ) + continue; + qsort(sym_array_modules[tmp].sym_array, \ + sym_array_modules[tmp].num_syms, \ + sizeof(struct sym_table), symsort); + } + + if ( rtn == 0 ) + imklogLogIntMsg(LOG_INFO, "No module symbols loaded."); + else + imklogLogIntMsg(LOG_INFO, "Loaded %d %s from %d module%s", rtn, \ + (rtn == 1) ? "symbol" : "symbols", \ + num_modules, (num_modules == 1) ? "." : "s."); + + return(1); +} + + +static int symsort(const void *p1, const void *p2) +{ + auto const struct sym_table *sym1 = p1, + *sym2 = p2; + + if ( sym1->value < sym2->value ) + return(-1); + if ( sym1->value == sym2->value ) + return(0); + return(1); +} + + +extern void DeinitMsyms(void) +{ + FreeModules(); +} + + +/************************************************************************** + * Function: FreeModules + * + * Purpose: This function is used to free all memory which has been + * allocated for the modules and their symbols. + * + * Arguements: None specified. + * + * Return: void + **************************************************************************/ +static void FreeModules() +{ + auto int nmods, + nsyms; + auto struct Module *mp; + + /* Check to see if the module symbol tables need to be cleared. */ + have_modules = 0; + if ( num_modules == 0 ) + return; + + if ( sym_array_modules == NULL ) + return; + + for (nmods = 0; nmods < num_modules; ++nmods) { + mp = &sym_array_modules[nmods]; + if ( mp->num_syms == 0 ) + continue; + + for (nsyms= 0; nsyms < mp->num_syms; ++nsyms) + free(mp->sym_array[nsyms].name); + free(mp->sym_array); + if ( mp->name != NULL ) + free(mp->name); + } + + free(sym_array_modules); + sym_array_modules = (struct Module *) 0; + num_modules = 0; + return; +} + + +/************************************************************************** + * Function: AddModule + * + * Purpose: This function is responsible for adding a module to + * the list of currently loaded modules. + * + * Arguments: (const char *) module + * + * module:-> The name of the module. + * + * Return: struct Module * + **************************************************************************/ + +struct Module *AddModule(module) + const char *module; +{ + struct Module *mp; + + if ( num_modules == 0 ) { + sym_array_modules = (struct Module *)malloc(sizeof(struct Module)); + + if ( sym_array_modules == NULL ) + { + imklogLogIntMsg(LOG_WARNING, "Cannot allocate Module array.\n"); + return NULL; + } + mp = sym_array_modules; + } else { + /* Allocate space for the module. */ + mp = (struct Module *) \ + realloc(sym_array_modules, \ + (num_modules+1) * sizeof(struct Module)); + + if ( mp == NULL ) + { + imklogLogIntMsg(LOG_WARNING, "Cannot allocate Module array.\n"); + return NULL; + } + + sym_array_modules = mp; + mp = &sym_array_modules[num_modules]; + } + + num_modules++; + mp->sym_array = NULL; + mp->num_syms = 0; + + if ( module != NULL ) + mp->name = strdup(module); + else + mp->name = NULL; + + return mp; +} + + +/************************************************************************** + * Function: AddSymbol + * + * Purpose: This function is responsible for adding a symbol name + * and its address to the symbol table. + * + * Arguements: const char * + * + * Return: int + * + * A boolean value is assumed. True if the addition is + * successful. False if not. + **************************************************************************/ +static int AddSymbol(line) + const char *line; +{ + char *module; + unsigned long address; + char *p; + static char *lastmodule = NULL; + struct Module *mp; + + module = index(line, '['); + + if ( module != NULL ) { + p = index(module, ']'); + if ( p != NULL ) + *p = '\0'; + p = module++; + while ( isspace(*(--p)) ) + /*SKIP*/; + *(++p) = '\0'; + } + + p = index(line, ' '); + + if ( p == NULL ) + return(0); + + *p = '\0'; + + address = strtoul(line, (char **) 0, 16); + + p += 3; + + if ( num_modules == 0 || + ( lastmodule == NULL && module != NULL ) || + ( module == NULL && lastmodule != NULL) || + ( module != NULL && strcmp(module, lastmodule))) { + mp = AddModule(module); + + if ( mp == NULL ) + return(0); + } else + mp = &sym_array_modules[num_modules-1]; + + lastmodule = mp->name; + + /* Allocate space for the symbol table entry. */ + mp->sym_array = (struct sym_table *) realloc(mp->sym_array, \ + (mp->num_syms+1) * sizeof(struct sym_table)); + + if ( mp->sym_array == (struct sym_table *) 0 ) + return(0); + + mp->sym_array[mp->num_syms].name = strdup(p); + if ( mp->sym_array[mp->num_syms].name == (char *) 0 ) + return(0); + + /* Stuff interesting information into the module. */ + mp->sym_array[mp->num_syms].value = address; + ++mp->num_syms; + + return(1); +} + + + +/************************************************************************** + * Function: LookupModuleSymbol + * + * Purpose: Find the symbol which is related to the given address from + * a kernel module. + * + * Arguements: (long int) value, (struct symbol *) sym + * + * value:-> The address to be located. + * + * sym:-> A pointer to a structure which will be + * loaded with the symbol's parameters. + * + * Return: (char *) + * + * If a match cannot be found a diagnostic string is printed. + * If a match is found the pointer to the symbolic name most + * closely matching the address is returned. + **************************************************************************/ +extern char * LookupModuleSymbol(value, sym) + unsigned long value; + struct symbol *sym; +{ + auto int nmod, + nsym; + auto struct sym_table *last; + auto struct Module *mp; + static char ret[100]; + + sym->size = 0; + sym->offset = 0; + if ( num_modules == 0 ) + return((char *) 0); + + for (nmod = 0; nmod < num_modules; ++nmod) { + mp = &sym_array_modules[nmod]; + + /* + * Run through the list of symbols in this module and + * see if the address can be resolved. + */ + for(nsym = 1, last = &mp->sym_array[0]; + nsym < mp->num_syms; + ++nsym) { + if ( mp->sym_array[nsym].value > value ) + { + if ( sym->size == 0 || + (value - last->value) < sym->offset || + ( (sym->offset == (value - last->value)) && + (mp->sym_array[nsym].value-last->value) < sym->size ) ) + { + sym->offset = value - last->value; + sym->size = mp->sym_array[nsym].value - \ + last->value; + ret[sizeof(ret)-1] = '\0'; + if ( mp->name == NULL ) + snprintf(ret, sizeof(ret)-1, + "%s", last->name); + else + snprintf(ret, sizeof(ret)-1, + "%s:%s", mp->name, last->name); + } + break; + } + last = &mp->sym_array[nsym]; + } + } + + if ( sym->size > 0 ) + return(ret); + + /* It has been a hopeless exercise. */ + return((char *) 0); +} diff --git a/plugins/imklog/ksyms.h b/plugins/imklog/ksyms.h new file mode 100644 index 00000000..b5362ff3 --- /dev/null +++ b/plugins/imklog/ksyms.h @@ -0,0 +1,37 @@ +/* ksym.h - Definitions for symbol table utilities. + * Copyright (c) 1995, 1996 Dr. G.W. Wettstein <greg@wind.rmcc.com> + * Copyright (c) 1996 Enjellic Systems Development + * Copyright (c) 2004-7 Martin Schulze <joey@infodrom.org> + * Copyright (c) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +/* Variables, structures and type definitions static to this module. */ + +struct symbol +{ + char *name; + int size; + int offset; +}; + + +/* Function prototypes. */ +extern char * LookupSymbol(unsigned long, struct symbol *); +extern char * LookupModuleSymbol(unsigned long int, struct symbol *); diff --git a/plugins/imklog/linux.c b/plugins/imklog/linux.c new file mode 100644 index 00000000..198b7c0e --- /dev/null +++ b/plugins/imklog/linux.c @@ -0,0 +1,539 @@ +/* klog for linux, based on the FreeBSD syslogd implementation. + * + * This contains OS-specific functionality to read the BSD + * kernel log. For a general overview, see head comment in + * imklog.c. + * + * This file heavily borrows from the klogd daemon provided by + * the sysklogd project. Many thanks for this piece of software. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. +*/ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <signal.h> +#include <string.h> +#include <pthread.h> +#include "cfsysline.h" +#include "template.h" +#include "msg.h" +#include "module-template.h" +#include "imklog.h" + + +/* Includes. */ +#include <unistd.h> +#include <errno.h> +#include <sys/fcntl.h> +#include <sys/stat.h> + +#if HAVE_TIME_H +# include <time.h> +#endif + +#include <stdarg.h> +#include <paths.h> +#include "ksyms.h" + +#define __LIBRARY__ +#include <unistd.h> + + +#if !defined(__GLIBC__) +# define __NR_ksyslog __NR_syslog +_syscall3(int,ksyslog,int, type, char *, buf, int, len); +#else +#include <sys/klog.h> +#define ksyslog klogctl +#endif + + + +#ifndef _PATH_KLOG +#define _PATH_KLOG "/proc/kmsg" +#endif + +#define LOG_BUFFER_SIZE 4096 +#define LOG_LINE_LENGTH 1000 + +static int kmsg; +static char log_buffer[LOG_BUFFER_SIZE]; + +static enum LOGSRC {none, proc, kernel} logsrc; + + +/* Function prototypes. */ +extern int ksyslog(int type, char *buf, int len); + + +static void CloseLogSrc(void) +{ + /* Turn on logging of messages to console, but only if we had the -c + * option -- rgerhards, 2007-08-01 + */ + if (console_log_level != -1) + ksyslog(7, NULL, 0); + + /* Shutdown the log sources. */ + switch ( logsrc ) + { + case kernel: + ksyslog(0, 0, 0); + imklogLogIntMsg(LOG_INFO, "Kernel logging (ksyslog) stopped."); + break; + case proc: + close(kmsg); + imklogLogIntMsg(LOG_INFO, "Kernel logging (proc) stopped."); + break; + case none: + break; + } + + return; +} + + +static enum LOGSRC GetKernelLogSrc(void) +{ + auto struct stat sb; + + /* Set level of kernel console messaging.. */ + if ( (console_log_level != -1) && + (ksyslog(8, NULL, console_log_level) < 0) && + (errno == EINVAL) ) + { + /* + * An invalid arguement error probably indicates that + * a pre-0.14 kernel is being run. At this point we + * issue an error message and simply shut-off console + * logging completely. + */ + imklogLogIntMsg(LOG_WARNING, "Cannot set console log level - disabling " + "console output."); + } + + /* + * First do a stat to determine whether or not the proc based + * file system is available to get kernel messages from. + */ + if ( use_syscall || + ((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT)) ) + { + /* Initialize kernel logging. */ + ksyslog(1, NULL, 0); + imklogLogIntMsg(LOG_INFO, "imklog %s, log source = ksyslog " + "started.", VERSION); + return(kernel); + } + + if ( (kmsg = open(_PATH_KLOG, O_RDONLY)) < 0 ) + { + imklogLogIntMsg(LOG_ERR, "imklog: Cannot open proc file system, %d.\n", errno); + ksyslog(7, NULL, 0); /* TODO: check this, implement more */ + return(none); + } + + imklogLogIntMsg(LOG_INFO, "imklog %s, log source = %s started.", VERSION, _PATH_KLOG); + return(proc); +} + + +/* Copy characters from ptr to line until a char in the delim + * string is encountered or until min( space, len ) chars have + * been copied. + * + * Returns the actual number of chars copied. + */ +static int copyin( uchar *line, int space, + const char *ptr, int len, + const char *delim ) +{ + auto int i; + auto int count; + + count = len < space ? len : space; + + for(i=0; i<count && !strchr(delim, *ptr); i++ ) { + *line++ = *ptr++; + } + + return(i); +} + +/* + * Messages are separated by "\n". Messages longer than + * LOG_LINE_LENGTH are broken up. + * + * Kernel symbols show up in the input buffer as : "[<aaaaaa>]", + * where "aaaaaa" is the address. These are replaced with + * "[symbolname+offset/size]" in the output line - symbolname, + * offset, and size come from the kernel symbol table. + * + * If a kernel symbol happens to fall at the end of a message close + * in length to LOG_LINE_LENGTH, the symbol will not be expanded. + * (This should never happen, since the kernel should never generate + * messages that long. + * + * To preserve the original addresses, lines containing kernel symbols + * are output twice. Once with the symbols converted and again with the + * original text. Just in case somebody wants to run their own Oops + * analysis on the syslog, e.g. ksymoops. + */ +static void LogLine(char *ptr, int len) +{ + enum parse_state_enum { + PARSING_TEXT, + PARSING_SYMSTART, /* at < */ + PARSING_SYMBOL, + PARSING_SYMEND /* at ] */ + }; + + static uchar line_buff[LOG_LINE_LENGTH]; + + static uchar *line =line_buff; + static enum parse_state_enum parse_state = PARSING_TEXT; + static int space = sizeof(line_buff)-1; + + static uchar *sym_start; /* points at the '<' of a symbol */ + + auto int delta = 0; /* number of chars copied */ + auto int symbols_expanded = 0; /* 1 if symbols were expanded */ + auto int skip_symbol_lookup = 0; /* skip symbol lookup on this pass */ + auto char *save_ptr = ptr; /* save start of input line */ + auto int save_len = len; /* save length at start of input line */ + + while( len > 0 ) + { + if( space == 0 ) /* line buffer is full */ + { + /* + ** Line too long. Start a new line. + */ + *line = 0; /* force null terminator */ + + //dbgprintf("Line buffer full:\n"); + //dbgprintf("\tLine: %s\n", line); + + Syslog(LOG_INFO, line_buff); + line = line_buff; + space = sizeof(line_buff)-1; + parse_state = PARSING_TEXT; + symbols_expanded = 0; + skip_symbol_lookup = 0; + save_ptr = ptr; + save_len = len; + } + + switch( parse_state ) + { + case PARSING_TEXT: + delta = copyin(line, space, ptr, len, "\n[" ); + line += delta; + ptr += delta; + space -= delta; + len -= delta; + + if( space == 0 || len == 0 ) + { + break; /* full line_buff or end of input buffer */ + } + + if( *ptr == '\0' ) /* zero byte */ + { + ptr++; /* skip zero byte */ + space -= 1; + len -= 1; + + break; + } + + if( *ptr == '\n' ) /* newline */ + { + ptr++; /* skip newline */ + space -= 1; + len -= 1; + + *line = 0; /* force null terminator */ + Syslog(LOG_INFO, line_buff); + line = line_buff; + space = sizeof(line_buff)-1; + if (symbols_twice) { + if (symbols_expanded) { + /* reprint this line without symbol lookup */ + symbols_expanded = 0; + skip_symbol_lookup = 1; + ptr = save_ptr; + len = save_len; + } + else + { + skip_symbol_lookup = 0; + save_ptr = ptr; + save_len = len; + } + } + break; + } + if( *ptr == '[' ) /* possible kernel symbol */ + { + *line++ = *ptr++; + space -= 1; + len -= 1; + if (!skip_symbol_lookup) + parse_state = PARSING_SYMSTART; /* at < */ + break; + } + /* Now that line_buff is no longer fed to *printf as format + * string, '%'s are no longer "dangerous". + */ + break; + + case PARSING_SYMSTART: + if( *ptr != '<' ) + { + parse_state = PARSING_TEXT; /* not a symbol */ + break; + } + + /* + ** Save this character for now. If this turns out to + ** be a valid symbol, this char will be replaced later. + ** If not, we'll just leave it there. + */ + + sym_start = line; /* this will point at the '<' */ + + *line++ = *ptr++; + space -= 1; + len -= 1; + parse_state = PARSING_SYMBOL; /* symbol... */ + break; + + case PARSING_SYMBOL: + delta = copyin( line, space, ptr, len, ">\n[" ); + line += delta; + ptr += delta; + space -= delta; + len -= delta; + if( space == 0 || len == 0 ) + { + break; /* full line_buff or end of input buffer */ + } + if( *ptr != '>' ) + { + parse_state = PARSING_TEXT; + break; + } + + *line++ = *ptr++; /* copy the '>' */ + space -= 1; + len -= 1; + + parse_state = PARSING_SYMEND; + + break; + + case PARSING_SYMEND: + if( *ptr != ']' ) + { + parse_state = PARSING_TEXT; /* not a symbol */ + break; + } + + /* + ** It's really a symbol! Replace address with the + ** symbol text. + */ + { + auto int sym_space; + + unsigned long value; + auto struct symbol sym; + auto char *symbol; + + *(line-1) = 0; /* null terminate the address string */ + value = strtoul((char*)(sym_start+1), (char **) 0, 16); + *(line-1) = '>'; /* put back delim */ + + if ( !symbol_lookup || (symbol = LookupSymbol(value, &sym)) == (char *)0 ) + { + parse_state = PARSING_TEXT; + break; + } + + /* + ** verify there is room in the line buffer + */ + sym_space = space + ( line - sym_start ); + if( (unsigned) sym_space < strlen(symbol) + 30 ) /*(30 should be overkill)*/ + { + parse_state = PARSING_TEXT; /* not enough space */ + break; + } + + // TODO: sprintf!!!! + delta = sprintf( (char*) sym_start, "%s+%d/%d]", + symbol, sym.offset, sym.size ); + + space = sym_space + delta; + line = sym_start + delta; + symbols_expanded = 1; + } + ptr++; + len--; + parse_state = PARSING_TEXT; + break; + + default: /* Can't get here! */ + parse_state = PARSING_TEXT; + + } + } + + return; +} + + +static void LogKernelLine(void) +{ + auto int rdcnt; + + /* + * Zero-fill the log buffer. This should cure a multitude of + * problems with klogd logging the tail end of the message buffer + * which will contain old messages. Then read the kernel log + * messages into this fresh buffer. + */ + memset(log_buffer, '\0', sizeof(log_buffer)); + if ( (rdcnt = ksyslog(2, log_buffer, sizeof(log_buffer)-1)) < 0 ) + { + if(errno == EINTR) + return; + imklogLogIntMsg(LOG_ERR, "imklog Error return from sys_sycall: %d\n", errno); + } + else + LogLine(log_buffer, rdcnt); + return; +} + + +static void LogProcLine(void) +{ + auto int rdcnt; + + /* + * Zero-fill the log buffer. This should cure a multitude of + * problems with klogd logging the tail end of the message buffer + * which will contain old messages. Then read the kernel messages + * from the message pseudo-file into this fresh buffer. + */ + memset(log_buffer, '\0', sizeof(log_buffer)); + if ( (rdcnt = read(kmsg, log_buffer, sizeof(log_buffer)-1)) < 0 ) { + if ( errno == EINTR ) + return; + imklogLogIntMsg(LOG_ERR, "Cannot read proc file system: %d - %s.", errno, strerror(errno)); + } else { + LogLine(log_buffer, rdcnt); + } + + return; +} + + +/* to be called in the module's WillRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogLogKMsg(void) +{ + DEFiRet; + switch(logsrc) { + case kernel: + LogKernelLine(); + break; + case proc: + LogProcLine(); + break; + case none: + /* TODO: We need to handle this case here somewhat more intelligent + * This is now at least partly done - code should never reach this point + * as willRun() already checked for the "none" status -- rgerhards, 2007-12-17 + */ + pause(); + break; + } + RETiRet; +} + + +/* to be called in the module's WillRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogWillRun(void) +{ + DEFiRet; + /* Initialize this module. If that fails, we tell the engine we don't like to run */ + /* Determine where kernel logging information is to come from. */ + logsrc = GetKernelLogSrc(); + if(logsrc == none) { + iRet = RS_RET_NO_KERNEL_LOGSRC; + } else { + if (symbol_lookup) { + symbol_lookup = (InitKsyms(symfile) == 1); + symbol_lookup |= InitMsyms(); + if (symbol_lookup == 0) { + imklogLogIntMsg(LOG_WARNING, "cannot find any symbols, turning off symbol lookups"); + } + } + } + + RETiRet; +} + + +/* to be called in the module's AfterRun entry point + * rgerhards, 2008-04-09 + */ +rsRetVal klogAfterRun(void) +{ + DEFiRet; + /* cleanup here */ + if(logsrc != none) + CloseLogSrc(); + + DeinitKsyms(); + DeinitMsyms(); + + RETiRet; +} + + +/* provide the (system-specific) default facility for internal messages + * rgerhards, 2008-04-14 + */ +int +klogFacilIntMsg(void) +{ + return LOG_KERN; +} + + +/* vi:set ai: + */ diff --git a/plugins/imklog/module.h b/plugins/imklog/module.h new file mode 100644 index 00000000..38a26fea --- /dev/null +++ b/plugins/imklog/module.h @@ -0,0 +1,35 @@ +/* module.h - Miscellaneous module definitions + * Copyright (c) 1996 Richard Henderson <rth@tamu.edu> + * Copyright (c) 2004-7 Martin Schulze <joey@infodrom.org> + * Copyright (c) 2007-2008 Rainer Gerhards <rgerhards@adiscon.com> + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +struct sym_table +{ + unsigned long value; + char *name; +}; + +struct Module +{ + struct sym_table *sym_array; + int num_syms; + + char *name; +}; diff --git a/plugins/immark/Makefile.am b/plugins/immark/Makefile.am new file mode 100644 index 00000000..6d8ed24a --- /dev/null +++ b/plugins/immark/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = immark.la + +immark_la_SOURCES = immark.c immark.h +immark_la_CPPFLAGS = $(RSRT_CFLAGS) -I$(top_srcdir) $(PTHREADS_CFLAGS) +immark_la_LDFLAGS = -module -avoid-version +immark_la_LIBADD = diff --git a/plugins/immark/immark.c b/plugins/immark/immark.c new file mode 100644 index 00000000..323da3fe --- /dev/null +++ b/plugins/immark/immark.c @@ -0,0 +1,126 @@ +/* immark.c + * This is the implementation of the build-in mark message input module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <signal.h> +#include <string.h> +#include <pthread.h> +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_INPUT + +/* defines */ +#define DEFAULT_MARK_PERIOD (20 * 60) + +/* Module static data */ +DEF_IMOD_STATIC_DATA +static int iMarkMessagePeriod = DEFAULT_MARK_PERIOD; + +/* This function is called to gather input. It must terminate only + * a) on failure (iRet set accordingly) + * b) on termination of the input module (as part of the unload process) + * Code begun 2007-12-12 rgerhards + * + * This code must simply spawn emit a mark message at each mark interval. + * We are running on our own thread, so this is extremely easy: we just + * sleep MarkInterval seconds and each time we awake, we inject the message. + * Please note that we do not do the other fancy things that sysklogd + * (and pre 1.20.2 releases of rsyslog) did in mark procesing. They simply + * do not belong here. + */ +BEGINrunInput +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(1) { + /* we do not need to handle the RS_RET_TERMINATE_NOW case any + * special because we just need to terminate. This may be different + * if a cleanup is needed. But for now, we can just use CHKiRet(). + * rgerhards, 2007-12-17 + */ + CHKiRet(thrdSleep(pThrd, iMarkMessagePeriod, 0)); /* seconds, micro seconds */ + logmsgInternal(NO_ERRCODE, LOG_INFO, (uchar*)"-- MARK --", MARK); + } +finalize_it: + return iRet; +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + /* We set the global MarkInterval to what is configured here -- rgerhards, 2008-07-15 */ + MarkInterval = iMarkMessagePeriod; + if(iMarkMessagePeriod == 0) + iRet = RS_RET_NO_RUN; +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iMarkMessagePeriod = DEFAULT_MARK_PERIOD; + + return RS_RET_OK; +} + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(omsdRegCFSLineHdlr((uchar *)"markmessageperiod", 0, eCmdHdlrInt, NULL, &iMarkMessagePeriod, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/immark/immark.h b/plugins/immark/immark.h new file mode 100644 index 00000000..db98978b --- /dev/null +++ b/plugins/immark/immark.h @@ -0,0 +1,35 @@ +/* immark.h + * These are the definitions for the built-in mark message generation module. This + * file may disappear when this has become a loadable module. + * + * File begun on 2007-12-12 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef IMMARK_H_INCLUDED +#define IMMARK_H_INCLUDED 1 + +/* prototypes */ +rsRetVal immark_runInput(void); + +#endif /* #ifndef IMMARK_H_INCLUDED */ +/* + * vi:set ai: + */ diff --git a/plugins/imrelp/Makefile.am b/plugins/imrelp/Makefile.am new file mode 100644 index 00000000..8c6faff1 --- /dev/null +++ b/plugins/imrelp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imrelp.la + +imrelp_la_SOURCES = imrelp.c +imrelp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RELP_CFLAGS) $(RSRT_CFLAGS) +imrelp_la_LDFLAGS = -module -avoid-version +imrelp_la_LIBADD = $(RELP_LIBS) diff --git a/plugins/imrelp/imrelp.c b/plugins/imrelp/imrelp.c new file mode 100644 index 00000000..b01dd98b --- /dev/null +++ b/plugins/imrelp/imrelp.c @@ -0,0 +1,192 @@ +/* imrelp.c + * + * This is the implementation of the RELP input module. + * + * File begun on 2008-03-13 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <librelp.h> +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" + +MODULE_TYPE_INPUT + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(net) + +/* Module static data */ +static relpEngine_t *pRelpEngine; /* our relp engine */ + + +/* config settings */ +static int iTCPSessMax = 200; /* max number of sessions */ + + +/* ------------------------------ callbacks ------------------------------ */ +#if 0 +/* this shall go into a specific ACL module! */ +static int +isPermittedHost(struct sockaddr *addr, char *fromHostFQDN, void __attribute__((unused)) *pUsrSrv, + void __attribute__((unused)) *pUsrSess) +{ + return net.isAllowedSender(net.pAllowedSenders_TCP, addr, fromHostFQDN); +} + +#endif // #if 0 + +/* callback for receiving syslog messages. This function is invoked from the + * RELP engine when a syslog message arrived. It must return a relpRetVal, + * with anything else but RELP_RET_OK terminating the relp session. Please note + * that RELP_RET_OK is equal to RS_RET_OK and the other libRELP error codes + * are different from our rsRetVal. So we can simply use our own iRet system + * to fulfill the requirement. + * rgerhards, 2008-03-21 + * TODO: we currently do not receive the remote hosts's IP. As a work-around, we + * use "???" for the time being. -- rgerhards, 2008-05-16 + */ +static relpRetVal +onSyslogRcv(uchar *pHostname, uchar __attribute__((unused)) *pIP, uchar *pMsg, size_t lenMsg) +{ + DEFiRet; + parseAndSubmitMessage(pHostname, (uchar*) "[unset]", pMsg, lenMsg, MSG_PARSE_HOSTNAME, + NOFLAG, eFLOWCTL_LIGHT_DELAY, (uchar*)"imrelp"); + + RETiRet; +} + + +/* ------------------------------ end callbacks ------------------------------ */ + + +static rsRetVal addListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + if(pRelpEngine == NULL) { + CHKiRet(relpEngineConstruct(&pRelpEngine)); + CHKiRet(relpEngineSetDbgprint(pRelpEngine, dbgprintf)); + CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar*) "syslog", eRelpCmdState_Required)); + CHKiRet(relpEngineSetSyslogRcv(pRelpEngine, onSyslogRcv)); + } + + CHKiRet(relpEngineAddListner(pRelpEngine, pNewVal)); + + free(pNewVal); /* we do no longer need it */ + +finalize_it: + RETiRet; +} + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + /* TODO: we must be careful to start the listener here. Currently, tcpsrv.c seems to + * do that in ConstructFinalize + */ + iRet = relpEngineRun(pRelpEngine); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first apply some config settings */ + //net.PrintAllowedSenders(2); /* TCP */ + if(pRelpEngine == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ +#if 0 + if(net.pAllowedSenders_TCP != NULL) { + net.clearAllowedSenders(net.pAllowedSenders_TCP); + net.pAllowedSenders_TCP = NULL; + } +#endif +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pRelpEngine != NULL) + iRet = relpEngineDestruct(&pRelpEngine); + + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iTCPSessMax = 200; + return RS_RET_OK; +} + + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + pRelpEngine = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpserverrun", 0, eCmdHdlrGetWord, + addListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpmaxsessions", 0, eCmdHdlrInt, + NULL, &iTCPSessMax, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + + +/* vim:set ai: + */ diff --git a/plugins/imtcp/Makefile.am b/plugins/imtcp/Makefile.am new file mode 100644 index 00000000..26653536 --- /dev/null +++ b/plugins/imtcp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imtcp.la + +imtcp_la_SOURCES = imtcp.c +imtcp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imtcp_la_LDFLAGS = -module -avoid-version +imtcp_la_LIBADD = diff --git a/plugins/imtcp/imtcp.c b/plugins/imtcp/imtcp.c new file mode 100644 index 00000000..7b3eeda5 --- /dev/null +++ b/plugins/imtcp/imtcp.c @@ -0,0 +1,283 @@ +/* imtcp.c + * This is the implementation of the TCP input module. + * + * File begun on 2007-12-21 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +/* This note shall explain the calling sequence while we do not have + * have full RainerScript support for (TLS) sender authentication: + * + * imtcp --> tcpsrv --> netstrms (this sequence stored pPermPeers in netstrms class) + * then a callback (doOpenLstnSocks) into imtcp happens, which in turn calls + * into tcpsrv.create_tcp_socket(), + * which calls into netstrm.LstnInit(), which receives a pointer to netstrms obj + * which calls into the driver function LstnInit (again, netstrms obj passed) + * which finally calls back into netstrms obj's get functions to obtain the auth + * parameters and then applies them to the driver object instance + * + * rgerhards, 2008-05-19 + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdarg.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif +#include "rsyslog.h" +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "netstrm.h" +#include "errmsg.h" +#include "tcpsrv.h" +#include "net.h" /* for permittedPeers, may be removed when this is removed */ + +MODULE_TYPE_INPUT + +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) +DEFobjCurrIf(net) +DEFobjCurrIf(netstrm) +DEFobjCurrIf(errmsg) + +/* Module static data */ +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ +static permittedPeers_t *pPermPeersRoot = NULL; + + +/* config settings */ +static int iTCPSessMax = 200; /* max number of sessions */ +static int iStrmDrvrMode = 0; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */ +static uchar *pszStrmDrvrAuthMode = NULL; /* authentication mode to use */ + + +/* callbacks */ +/* this shall go into a specific ACL module! */ +static int +isPermittedHost(struct sockaddr *addr, char *fromHostFQDN, void __attribute__((unused)) *pUsrSrv, + void __attribute__((unused)) *pUsrSess) +{ + return net.isAllowedSender((uchar*) "TCP", addr, fromHostFQDN); +} + + +static rsRetVal +doOpenLstnSocks(tcpsrv_t *pSrv) +{ + ISOBJ_TYPE_assert(pSrv, tcpsrv); + return tcpsrv.create_tcp_socket(pSrv); +} + + +static rsRetVal +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd) +{ + DEFiRet; + assert(pSess != NULL); + assert(piLenRcvd != NULL); + + *piLenRcvd = lenBuf; + CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd)); +finalize_it: + RETiRet; +} + +static rsRetVal +onRegularClose(tcps_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + /* process any incomplete frames left over */ + tcps_sess.PrepareClose(pSess); + /* Session closed */ + tcps_sess.Close(pSess); + RETiRet; +} + + +static rsRetVal +onErrClose(tcps_sess_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + tcps_sess.Close(pSess); + RETiRet; +} + +/* ------------------------------ end callbacks ------------------------------ */ + + +/* set permitted peer -- rgerhards, 2008-05-19 + */ +static rsRetVal +setPermittedPeer(void __attribute__((unused)) *pVal, uchar *pszID) +{ + DEFiRet; + CHKiRet(net.AddPermittedPeer(&pPermPeersRoot, pszID)); + free(pszID); /* no longer needed, but we need to free as of interface def */ +finalize_it: + RETiRet; +} + + +static rsRetVal addTCPListener(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + + if(pOurTcpsrv == NULL) { + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + CHKiRet(tcpsrv.SetSessMax(pOurTcpsrv, iTCPSessMax)); + CHKiRet(tcpsrv.SetCBIsPermittedHost(pOurTcpsrv, isPermittedHost)); + CHKiRet(tcpsrv.SetCBRcvData(pOurTcpsrv, doRcvData)); + CHKiRet(tcpsrv.SetCBOpenLstnSocks(pOurTcpsrv, doOpenLstnSocks)); + CHKiRet(tcpsrv.SetCBOnRegularClose(pOurTcpsrv, onRegularClose)); + CHKiRet(tcpsrv.SetCBOnErrClose(pOurTcpsrv, onErrClose)); + CHKiRet(tcpsrv.SetDrvrMode(pOurTcpsrv, iStrmDrvrMode)); + /* now set optional params, but only if they were actually configured */ + if(pszStrmDrvrAuthMode != NULL) { + CHKiRet(tcpsrv.SetDrvrAuthMode(pOurTcpsrv, pszStrmDrvrAuthMode)); + } + if(pPermPeersRoot != NULL) { + CHKiRet(tcpsrv.SetDrvrPermPeers(pOurTcpsrv, pPermPeersRoot)); + } + /* most params set, now start listener */ + tcpsrv.configureTCPListen(pOurTcpsrv, (char *) pNewVal); + CHKiRet(tcpsrv.ConstructFinalize(pOurTcpsrv)); + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(0, NO_ERRCODE, "error %d trying to add listener", iRet); + if(pOurTcpsrv != NULL) + tcpsrv.Destruct(&pOurTcpsrv); + } + RETiRet; +} + +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + /* TODO: we must be careful to start the listener here. Currently, tcpsrv.c seems to + * do that in ConstructFinalize + */ + iRet = tcpsrv.Run(pOurTcpsrv); +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first apply some config settings */ + net.PrintAllowedSenders(2); /* TCP */ + if(pOurTcpsrv == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"TCP"); +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + if(pOurTcpsrv != NULL) + iRet = tcpsrv.Destruct(&pOurTcpsrv); + + if(pPermPeersRoot != NULL) { + net.DestructPermittedPeers(&pPermPeersRoot); + } + + /* release objects we used */ + objRelease(net, LM_NET_FILENAME); + objRelease(netstrm, LM_NETSTRMS_FILENAME); + objRelease(tcps_sess, LM_TCPSRV_FILENAME); + objRelease(tcpsrv, LM_TCPSRV_FILENAME); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iTCPSessMax = 200; + iStrmDrvrMode = 0; + return RS_RET_OK; +} + + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ + CHKiRet(objUse(net, LM_NET_FILENAME)); + CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME)); + CHKiRet(objUse(tcps_sess, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(tcpsrv, LM_TCPSRV_FILENAME)); + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverrun", 0, eCmdHdlrGetWord, + addTCPListener, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpmaxsessions", 0, eCmdHdlrInt, + NULL, &iTCPSessMax, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverstreamdrivermode", 0, + eCmdHdlrInt, NULL, &iStrmDrvrMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverstreamdriverauthmode", 0, + eCmdHdlrGetWord, NULL, &pszStrmDrvrAuthMode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverstreamdriverpermittedpeer", 0, + eCmdHdlrGetWord, setPermittedPeer, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + + +/* vim:set ai: + */ diff --git a/plugins/imtemplate/Makefile.am b/plugins/imtemplate/Makefile.am new file mode 100644 index 00000000..1825b5bc --- /dev/null +++ b/plugins/imtemplate/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imtemplate.la + +imtemplate_la_SOURCES = imtemplate.c +imtemplate_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imtemplate_la_LDFLAGS = -module -avoid-version +imtemplate_la_LIBADD = diff --git a/plugins/imtemplate/imtemplate.c b/plugins/imtemplate/imtemplate.c new file mode 100644 index 00000000..366408a0 --- /dev/null +++ b/plugins/imtemplate/imtemplate.c @@ -0,0 +1,458 @@ +/* imtemplate.c + * + * This is NOT a real input module but a (copy)-template to create one. Please + * do NOT edit this file directly. Rather, copy it, together with the rest of + * the directory, to a new location ./plugins/im<yourname>, then replace + * all references to imtemplate in Makefile.am to im<yourname>. Be sure to + * fix the copyright notices to gain proper credit ;) Any derived version, + * however, needs to be placed under GPLv3 (see GPLv3 for details). If you + * do not like that policy, do not use this template or any of the header + * files. The rsyslog project greatly appreciates module contributions, so + * please consider contributing your work - even if you may think it only + * server a single very special purpose. It has turned out that at least some + * folks have similiar special purposes ;) + * + * IMPORTANT + * The comments in this file are actually the interface specification. I decided + * not to put it into a separate file as it is much simpler to keep it up to + * date when it is part of the actual template module. + * + * NAMING + * All input modules shall be named im<something>. While this is not a hard + * requirement, it helps keeping track of things. + * + * Global variables and functions should have a prefix - use as somewhat + * longer one to prevent conflicts with rsyslog itself and other modules + * (OK, hopefully I'll have some more precise advise in the future...). + * + * INCLUDE MODULE IN THE MAIN MAKE SCRIPT + * If the module shall be provided as part of rsyslog (or simply as a build aid, + * you need to add it to the main autoconf files). To do so, you need to edit + * Makefile.am and configure.ac in the main directory. Search for imtemplate + * and copy/modify the relevant code for your plugin. + * + * DEBUGGING + * While you develop your code, you may want to add + * --enable-debug --enable-rtinst + * to your ./configure settings. These enable extra run-time checks, which cost + * a lot of performance but can help detect some of the most frequently made + * bugs. These settings will also provide you with a nice stack dump if something + * goes really wrong. + * + * MORE SAMPLES + * Remember that rsyslog ships with a number of input modules (./plugins/im*). It + * is always a good idea to have a look at them before starting your own. imudp + * may be a good, relatively trivial, sample. + * + * -------------------------------------------------------------------------------- + * + * This template was cretead on 2008-02-01 by Rainer Gerhards. + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" /* this is for autotools and always must be the first include */ +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <pthread.h> /* do NOT remove: will soon be done by the module generation macros */ +#include "rsyslog.h" /* error codes etc... */ +#include "cfsysline.h" /* access to config file objects */ +#include "module-template.h" /* generic module interface code - very important, read it! */ +#include "srUtils.h" /* some utility functions */ + +MODULE_TYPE_INPUT /* must be present for input modules, do not remove */ + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA /* must be present, starts static data */ + +/* Here, define whatever static data is needed. Is it suggested that static variables only are + * used (not externally visible). If you need externally visible variables, make sure you use a + * prefix in order not to conflict with other modules or rsyslogd itself (also see comment + * at file header). + */ +/* static int imtemplateWhateverVar = 0; */ + +/* config settings */ + + +/* You may add any functions that you feel are useful for your needs. No specific restrictions + * apply, but we suggest that you use the "iRet" call order, which enables you to use debug + * support for your own functions and which also makes it easy to communicate exceptions back + * to the upstream caller (rsyslog framework, for example. + * + * The function below is a sample of how one of your functions may look like. Again, the sample + * below is *not* needed to be present in order to meet the interface requirements. + * + * Be sure to use static functions (suggested) or prefixes to prevent name conflicts -- see file + * header for more information. + */ +static rsRetVal /* rsRetVal is our generic error-reporting return type */ +imtemplateMyFunc(int iMyParam) +{ + DEFiRet; /* define iRet, the return code and other plumbing */ + /* define your local variables here */ + + /* code whatever you need to code here. The "iRet" system can be helpful: + * + * CHKiRet(function(param1, param2, ...)); + * calls a function and checks if it returns RS_RET_OK. If so, work + * proceeds. If some other code is returned, the function is aborted + * and control transferred to finalize_it (which you need to define) + * + * CHKiRet_Hdlr(function(param1, param2, ...)) + * much like CHKiRet, but allows you to specify your own code that is + * executed if the function does not return RS_RET_OK, e.g.: + * CHKiRet_Hdlr(function(a, b)) { + * ... some error handling here ... + * } + * control is not transferred to finalize_it, except if you use one + * of the relevant macros (described below) + * + * FINALIZE + * immediately transfers control to finalize_it, using the current + * value of iRet, e.g. + * if(bDone) + * FINALIZE; + * + * ABORT_FINALIZE(retcode) + * just like FINALIZE, except that iRet is set to the provided error + * code before control is transferred, e.g. + * if((ptr = malloc(20)) == NULL) + * ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + * + * In order for all this to work, you need to define finalize_it, e.g. + * + * finalize_it: + * RETiRet; + * + * RETiRet does some housekeeping and then does a "return iRet" to transfer + * control back to the caller. There shall only be one function exit and + * it shall be via RETiRet, preferrably at the end of the function code. + * + */ + +finalize_it: + /* clean up anything that needs to be cleaned up if processing did not + * go well, for example: + */ + if(iRet != RS_RET_OK) { + /* cleanup, e.g. + * free(somePtr); + */ + } + + RETiRet; +} + + +/* This function is the cancel cleanup handler. It is called when rsyslog decides the + * module must be stopped, what most probably happens during shutdown of rsyslogd. When + * this function is called, the runInput() function (below) is already terminated - somewhere + * in the middle of what it was doing. The cancel cleanup handler below should take + * care of any locked mutexes and such, things that really need to be cleaned up + * before processing continues. In general, many plugins do not need to provide + * any code at all here. + * + * IMPORTANT: the calling interface of this function can NOT be modified. It actually is + * called by pthreads. The provided argument is currently not being used. + */ +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ +static void +inputModuleCleanup(void *arg) +{ + BEGINfunc +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + + + /* your code here */ + + + +/* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + ENDfunc +} +/* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + +/* This function is called by the framework to gather the input. The module stays + * most of its lifetime inside this function. It MUST NEVER exit this function. Doing + * so would end module processing and rsyslog would NOT reschedule the module. If + * you exit from this function, you violate the interface specification! + * + * So how is it terminated? When it is time to terminate, rsyslog actually cancels + * the threads. This may sound scary, but is not. There is a cancel cleanup handler + * defined (the function directly above). See comments there for specifics. + * + * runInput is always called on a single thread. If the module neees multiple threads, + * it is free to create them. HOWEVER, it must make sure that any threads created + * are killed and joined in the cancel cleanup handler. + */ +BEGINrunInput + /* define any local variables you need here */ +CODESTARTrunInput + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + pthread_cleanup_push(inputModuleCleanup, NULL); + while(1) { /* endless loop - do NOT break; out of it! */ + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + /* your code here */ + + /* All rsyslog objects (see other modules, e.g. msg.c) are available + * to your here. Some useful things are: + * + * errmsg.LogError(NO_ERRCODE, format-string, ... params ...); + * logs an error message as syslogd, just as printf, e.g. + * errmsg.LogError(NO_ERRCODE, "Error %d occured during %s", 1, "test"); + * + * There are several ways how a message can be enqueued. This part of the + * interface is currently underspecified. Have a look at the function definitions + * in syslogd.c (sorry, folks...). + * + * If you received a full syslog message that must be decoded by a message + * parser, parseAndSubmitMessage() is the way to go. It's not just a funny name + * but also a quite some legacy. Consequently, its interface is, ummm, not + * well designed. + * parseAndSubmitMessage((char*)fromHost, (char*) pRcvBuf, lenRcvd, bParseHost); + * fromHost + * is the host that we received the message from (a string) + * pRcvBuf + * is the received (to-be-decoded) message + * lenRcvd + * is the length of the received message. Please note that pRcvBuf is + * NOT a standard C-string. Most importantly it is NOT expected to be + * \0-terminated. Thus the lenght is vitally imporant (if it is wrong, + * rsyslogd will probably segfault). + * bParseHost + * is a boolean (0-no, 1-yes). It tells the parser whether or not + * a hostname should be parsed from the message. This is important + * for sources that are known not to provide a hostname. + * Use define MSG_PARSE_HOSTNAME and MSG_DONT_PARSE_HOSTNAME + * + * Another, more elaborate, way is to create the message object ourselves and + * pass it to the rule engine. That way is more appropriate if the message + * does not need to be parsed, for example when reading text (log) files. In that way, + * we can set the message properties as of our liking. This is how it works: + * + msg_t *pMsg; + CHKiRet(msgConstruct(&pMsg)); + MsgSetUxTradMsg(pMsg, msg); + MsgSetRawMsg(pMsg, msg); + MsgSetHOSTNAME(pMsg, LocalHostName); + MsgSetTAG(pMsg, "rsyslogd:"); + pMsg->iFacility = LOG_FAC(pri); + pMsg->iSeverity = LOG_PRI(pri); + pMsg->bParseHOSTNAME = 0; + flags |= INTERNAL_MSG; + logmsg(pMsg, flags); / * some time, CHKiRet() will work here, too [today NOT!] * / + * + * Note that UxTradMsg is a wild construct. For the time being, set it to + * the raw message text. I am hard thinking at dropping that beast at all... + * + * This example probably does not set all message properties (but the ones + * that are of practical importance). If you need all, check msg.h. Use + * method access functions whereever possible, unfortunately not all structure + * members are currently exposed in that clean way - so you sometimes need + * to access them directly (it goes without saying that we will fix that + * over time ;)). + */ + + /* ------------------------------------------------------------------------------------------ * + * DO NOT TOUCH the following code - it will soon be part of the module generation macros! */ + } + /*NOTREACHED*/ + + pthread_cleanup_pop(0); /* just for completeness, but never called... */ + RETiRet; /* use it to make sure the housekeeping is done! */ +ENDrunInput + /* END no-touch zone * + * ------------------------------------------------------------------------------------------ */ + + +/* The function is called by rsyslog before runInput() is called. It is a last chance + * to set up anything specific. Most importantly, it can be used to tell rsyslog if the + * input shall run or not. The idea is that if some config settings (or similiar things) + * are not OK, the input can tell rsyslog it will not execute. To do so, return + * RS_RET_NO_RUN or a specific error code. If RS_RET_OK is returned, rsyslog will + * proceed and call the runInput() entry point. If you do not return anything + * specific, RS_RET_OK is automatically returned (as in all functions). + */ +BEGINwillRun + /* place any variables needed here */ +CODESTARTwillRun + + /* ... your code here ... */ + + /* Just to give you an idea, here are some samples (from the actual imudp module: + * + if(udpLstnSocks == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + + if((pRcvBuf = malloc(glbl.GetMaxLine * sizeof(char))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + * + */ +finalize_it: +ENDwillRun + + +/* This function is called by the framework after runInput() has been terminated. It + * shall free any resources and prepare the module for unload. + * + * So it is important that runInput() keeps track of what needs to be cleaned up. + * Objects to think about are files (must be closed), network connections, threads (must + * be stopped and joined) and memory (must be freed). Of course, there are a myriad + * of other things, so use your own judgement what you need to do. + * + * Another important chore of this function is to persist whatever state the module + * needs to persist. Unfortunately, there is currently no standard way of doing that. + * Future version of the module interface will probably support it, but that doesn't + * help you right at the moment. In general, it is suggested that anything that needs + * to be persisted is saved in a file, whose name and location is passed in by a + * module-specific config directive. + */ +BEGINafterRun + /* place any variables needed here */ +CODESTARTafterRun + + /* ... do cleanup here ... */ + + /* if you have a string config variable, remember to free its content: + * + if(pszStr != NULL) { + free(pszStr); + pszStr = NULL; + } + */ +ENDafterRun + + +/* The following entry points are defined in module-template.h. + * In general, they need to be present, but you do NOT need to provide + * any code here. + */ +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + + +/* The following function shall reset all configuration variables to their + * default values. The code provided in modInit() below registers it to be + * called on "$ResetConfigVariables". You may also call it from other places, + * but in general this is not necessary. Once runInput() has been called, this + * function here is never again called. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + + /* if you have string variables in you config settings, you need to do this: + if(pszStr != NULL) { + free(pszStr); + pszStr = NULL; + } + * Note that it is vitally important that the pointer is set to NULL, because + * otherwise the framework handler will try to free it a second time when + * a new value is set! + */ + + + /* ... your code here ... */ + + + RETiRet; +} + + +/* modInit() is called once the module is loaded. It must perform all module-wide + * initialization tasks. There are also a number of housekeeping tasks that the + * framework requires. These are handled by the macros. Please note that the + * complexity of processing is depending on the actual module. However, only + * thing absolutely necessary should be done here. Actual app-level processing + * is to be performed in runInput(). A good sample of what to do here may be to + * set some variable defaults. The most important thing probably is registration + * of config command handlers. + */ +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = 1; /* interface spec version this module is written to (currently always 1) */ +CODEmodInit_QueryRegCFSLineHdlr + /* register config file handlers + * For details, see cfsysline.c/.h. The config file is automatically handled. In general, + * a pointer to a variable receiving the value and the config directive is to be supplied. + * A custom function pointer can only be provided, which then is called when the config + * directive appears. Limit this to cases where it is absolutely necessary. The + * STD_LOADABLE_MODULE_ID is a value that identifies the module. It is use to automatically + * unregister the module's config file handlers upon module unload. Do NOT use any other + * value for this parameter! Available Syntaxes (supported types) can be seen in cfsysline.h, + * the ecslCmdHdrlType enum has all that are currently defined. + * + * Config file directives should always be along the lines of + * + * $Input<moduleobject>ObjObjName + * + * An example would be $InputImtemplateRetriesMax. This is currently not enforced, + * but when we get to our new config file format and reader, this becomes quite + * important. + * + * Please note that config directives must be provided in lower case. The engine + * makes the mapping (what currently means case-insensitive comparison). The dollar + * sign is NOT part of the directive and thus not specified. + * + * Some samples: + * + * A hypothetical integer variable: + * CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputimtemplatemessagenumber", 0, eCmdHdlrInt, + NULL, &intVariable, STD_LOADABLE_MODULE_ID)); + * + * and a hypothetical string variable: + * CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputimtemplatemessagetext", 0, eCmdHdlrGetWord, + * NULL, &pszBindAddr, STD_LOADABLE_MODULE_ID)); + */ + + /* whenever config variables exist, they should be resettable via $ResetConfigVariables. + * The following line adds our handler for that. Note that if you do not have any config + * variables at all (unlikely, I think...), you can remove this handler. + */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + + /* ... do whatever else you need to do, but keep it brief ... */ + +ENDmodInit +/* + * vim:set ai: + */ diff --git a/plugins/imudp/Makefile.am b/plugins/imudp/Makefile.am new file mode 100644 index 00000000..517b1287 --- /dev/null +++ b/plugins/imudp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imudp.la + +imudp_la_SOURCES = imudp.c +imudp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imudp_la_LDFLAGS = -module -avoid-version +imudp_la_LIBADD = diff --git a/plugins/imudp/imudp.c b/plugins/imudp/imudp.c new file mode 100644 index 00000000..79d51263 --- /dev/null +++ b/plugins/imudp/imudp.c @@ -0,0 +1,316 @@ +/* imudp.c + * This is the implementation of the UDP input module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-12-21 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#include "rsyslog.h" +#include "dirty.h" +#include "net.h" +#include "cfsysline.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" +#include "glbl.h" + +MODULE_TYPE_INPUT + +/* defines */ + +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(net) + +static int iMaxLine; /* maximum UDP message size supported */ +static time_t ttLastDiscard = 0; /* timestamp when a message from a non-permitted sender was last discarded + * This shall prevent remote DoS when the "discard on disallowed sender" + * message is configured to be logged on occurance of such a case. + */ +static int *udpLstnSocks = NULL; /* Internet datagram sockets, first element is nbr of elements + * read-only after init(), but beware of restart! */ +static uchar *pszBindAddr = NULL; /* IP to bind socket to */ +static uchar *pRcvBuf = NULL; /* receive buffer (for a single packet). We use a global and alloc + * it so that we can check available memory in willRun() and request + * termination if we can not get it. -- rgerhards, 2007-12-27 + */ + +/* config settings */ + + +/* This function is called when a new listener shall be added. It takes + * the configured parameters, tries to bind the socket and, if that + * succeeds, adds it to the list of existing listen sockets. + * rgerhards, 2007-12-27 + */ +static rsRetVal addListner(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + uchar *bindAddr; + int *newSocks; + int *tmpSocks; + int iSrc, iDst; + + /* check which address to bind to. We could do this more compact, but have not + * done so in order to make the code more readable. -- rgerhards, 2007-12-27 + */ + if(pszBindAddr == NULL) + bindAddr = NULL; + else if(pszBindAddr[0] == '*' && pszBindAddr[1] == '\0') + bindAddr = NULL; + else + bindAddr = pszBindAddr; + + dbgprintf("Trying to open syslog UDP ports at %s:%s.\n", + (bindAddr == NULL) ? (uchar*)"*" : bindAddr, pNewVal); + + newSocks = net.create_udp_socket(bindAddr, (pNewVal == NULL || *pNewVal == '\0') ? (uchar*) "514" : pNewVal, 1); + if(newSocks != NULL) { + /* we now need to add the new sockets to the existing set */ + if(udpLstnSocks == NULL) { + /* esay, we can just replace it */ + udpLstnSocks = newSocks; + } else { + /* we need to add them */ + if((tmpSocks = malloc(sizeof(int) * (1 + newSocks[0] + udpLstnSocks[0]))) == NULL) { + dbgprintf("out of memory trying to allocate udp listen socket array\n"); + /* in this case, we discard the new sockets but continue with what we + * already have + */ + free(newSocks); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } else { + /* ready to copy */ + iDst = 1; + for(iSrc = 1 ; iSrc <= udpLstnSocks[0] ; ++iSrc) + tmpSocks[iDst++] = udpLstnSocks[iSrc]; + for(iSrc = 1 ; iSrc <= newSocks[0] ; ++iSrc) + tmpSocks[iDst++] = newSocks[iSrc]; + tmpSocks[0] = udpLstnSocks[0] + newSocks[0]; + free(newSocks); + free(udpLstnSocks); + udpLstnSocks = tmpSocks; + } + } + } + +finalize_it: + free(pNewVal); /* in any case, this is no longer needed */ + + RETiRet; +} + + +/* This function is called to gather input. + */ +BEGINrunInput + int maxfds; + int nfds; + int i; + fd_set readfds; + struct sockaddr_storage frominet; + socklen_t socklen; + uchar fromHost[NI_MAXHOST]; + uchar fromHostIP[NI_MAXHOST]; + uchar fromHostFQDN[NI_MAXHOST]; + ssize_t l; +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(1) { + /* Add the Unix Domain Sockets to the list of read + * descriptors. + * rgerhards 2005-08-01: we must now check if there are + * any local sockets to listen to at all. If the -o option + * is given without -a, we do not need to listen at all.. + */ + maxfds = 0; + FD_ZERO (&readfds); + + /* Add the UDP listen sockets to the list of read descriptors. + */ + if(udpLstnSocks != NULL) { + for (i = 0; i < *udpLstnSocks; i++) { + if (udpLstnSocks[i+1] != -1) { + if(Debug) + net.debugListenInfo(udpLstnSocks[i+1], "UDP"); + FD_SET(udpLstnSocks[i+1], &readfds); + if(udpLstnSocks[i+1]>maxfds) maxfds=udpLstnSocks[i+1]; + } + } + } + if(Debug) { + dbgprintf("--------imUDP calling select, active file descriptors (max %d): ", maxfds); + for (nfds = 0; nfds <= maxfds; ++nfds) + if ( FD_ISSET(nfds, &readfds) ) + dbgprintf("%d ", nfds); + dbgprintf("\n"); + } + + /* wait for io to become ready */ + nfds = select(maxfds+1, (fd_set *) &readfds, NULL, NULL, NULL); + + if(udpLstnSocks != NULL) { + for (i = 0; nfds && i < *udpLstnSocks; i++) { + if (FD_ISSET(udpLstnSocks[i+1], &readfds)) { + socklen = sizeof(frominet); + l = recvfrom(udpLstnSocks[i+1], (char*) pRcvBuf, iMaxLine, 0, + (struct sockaddr *)&frominet, &socklen); + if (l > 0) { + if(net.cvthname(&frominet, fromHost, fromHostFQDN, fromHostIP) == RS_RET_OK) { + dbgprintf("Message from inetd socket: #%d, host: %s\n", + udpLstnSocks[i+1], fromHost); + /* Here we check if a host is permitted to send us + * syslog messages. If it isn't, we do not further + * process the message but log a warning (if we are + * configured to do this). + * rgerhards, 2005-09-26 + */ + if(net.isAllowedSender((uchar*) "UDP", + (struct sockaddr *)&frominet, (char*)fromHostFQDN)) { + parseAndSubmitMessage(fromHost, fromHostIP, pRcvBuf, l, + MSG_PARSE_HOSTNAME, NOFLAG, eFLOWCTL_NO_DELAY, (uchar*)"imudp"); + } else { + dbgprintf("%s is not an allowed sender\n", (char*)fromHostFQDN); + if(glbl.GetOption_DisallowWarning) { + time_t tt; + + time(&tt); + if(tt > ttLastDiscard + 60) { + ttLastDiscard = tt; + errmsg.LogError(0, NO_ERRCODE, + "UDP message from disallowed sender %s discarded", + (char*)fromHost); + } + } + } + } + } else if (l < 0 && errno != EINTR && errno != EAGAIN) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("INET socket error: %d = %s.\n", errno, errStr); + errmsg.LogError(errno, NO_ERRCODE, "recvfrom inet"); + /* should be harmless */ + sleep(1); + } + --nfds; /* indicate we have processed one */ + } + } + } + } + + return iRet; +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + net.PrintAllowedSenders(1); /* UDP */ + + /* if we could not set up any listners, there is no point in running... */ + if(udpLstnSocks == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); + + iMaxLine = glbl.GetMaxLine(); + + if((pRcvBuf = malloc((iMaxLine + 1) * sizeof(char))) == NULL) { + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } +finalize_it: +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + /* do cleanup here */ + net.clearAllowedSenders((uchar*)"UDP"); + if(udpLstnSocks != NULL) { + net.closeUDPListenSockets(udpLstnSocks); + udpLstnSocks = NULL; + } + if(pRcvBuf != NULL) { + free(pRcvBuf); + pRcvBuf = NULL; + } +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); + objRelease(glbl, CORE_COMPONENT); + objRelease(net, LM_NET_FILENAME); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + if(pszBindAddr != NULL) { + free(pszBindAddr); + pszBindAddr = NULL; + } + if(udpLstnSocks != NULL) { + net.closeUDPListenSockets(udpLstnSocks); + udpLstnSocks = NULL; + } + return RS_RET_OK; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"udpserverrun", 0, eCmdHdlrGetWord, + addListner, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"udpserveraddress", 0, eCmdHdlrGetWord, + NULL, &pszBindAddr, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/imuxsock/Makefile.am b/plugins/imuxsock/Makefile.am new file mode 100644 index 00000000..a2fe0baa --- /dev/null +++ b/plugins/imuxsock/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = imuxsock.la + +imuxsock_la_SOURCES = imuxsock.c +imuxsock_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +imuxsock_la_LDFLAGS = -module -avoid-version +imuxsock_la_LIBADD = diff --git a/plugins/imuxsock/imuxsock.c b/plugins/imuxsock/imuxsock.c new file mode 100644 index 00000000..55b8b2df --- /dev/null +++ b/plugins/imuxsock/imuxsock.c @@ -0,0 +1,412 @@ +/* imuxsock.c + * This is the implementation of the Unix sockets input module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2007-12-20 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/un.h> +#include "dirty.h" +#include "cfsysline.h" +#include "module-template.h" +#include "srUtils.h" +#include "errmsg.h" +#include "net.h" +#include "glbl.h" + +MODULE_TYPE_INPUT + +/* defines */ +#define MAXFUNIX 20 +#ifndef _PATH_LOG +#ifdef BSD +#define _PATH_LOG "/var/run/log" +#else +#define _PATH_LOG "/dev/log" +#endif +#endif + + +/* handle some defines missing on more than one platform */ +#ifndef SUN_LEN +#define SUN_LEN(su) \ + (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) +#endif +/* Module static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +static int startIndexUxLocalSockets; /* process funix from that index on (used to + * suppress local logging. rgerhards 2005-08-01 + * read-only after startup + */ +static int funixParseHost[MAXFUNIX] = { 0, }; /* should parser parse host name? read-only after startup */ +static int funixFlags[MAXFUNIX] = { IGNDATE, }; /* should parser parse host name? read-only after startup */ +static uchar *funixn[MAXFUNIX] = { (uchar*) _PATH_LOG }; /* read-only after startup */ +static uchar *funixHName[MAXFUNIX] = { NULL, }; /* host-name override - if set, use this instead of actual name */ +static int funixFlowCtl[MAXFUNIX] = { eFLOWCTL_NO_DELAY, }; /* flow control settings for this socket */ +static int funix[MAXFUNIX] = { -1, }; /* read-only after startup */ +static int nfunix = 1; /* number of Unix sockets open / read-only after startup */ + +/* config settings */ +static int bOmitLocalLogging = 0; +static uchar *pLogSockName = NULL; +static uchar *pLogHostName = NULL; /* host name to use with this socket */ +static int bUseFlowCtl = 0; /* use flow control or not (if yes, only LIGHT is used! */ +static int bIgnoreTimestamp = 1; /* ignore timestamps present in the incoming message? */ + + +/* set the timestamp ignore / not ignore option for the system + * log socket. This must be done separtely, as it is not added via a command + * but present by default. -- rgerhards, 2008-03-06 + */ +static rsRetVal setSystemLogTimestampIgnore(void __attribute__((unused)) *pVal, int iNewVal) +{ + DEFiRet; + funixFlags[0] = iNewVal ? IGNDATE : NOFLAG; + RETiRet; +} + +/* set flowcontrol for the system log socket + */ +static rsRetVal setSystemLogFlowControl(void __attribute__((unused)) *pVal, int iNewVal) +{ + DEFiRet; + funixFlowCtl[0] = iNewVal ? eFLOWCTL_LIGHT_DELAY : eFLOWCTL_NO_DELAY; + RETiRet; +} + +/* add an additional listen socket. Socket names are added + * until the array is filled up. It is never reset, only at + * module unload. + * TODO: we should change the array to a list so that we + * can support any number of listen socket names. + * rgerhards, 2007-12-20 + * added capability to specify hostname for socket -- rgerhards, 2008-08-01 + */ +static rsRetVal addLstnSocketName(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + if(nfunix < MAXFUNIX) { + if(*pNewVal == ':') { + funixParseHost[nfunix] = 1; + } + else { + funixParseHost[nfunix] = 0; + } + funixHName[nfunix] = pLogHostName; + pLogHostName = NULL; /* re-init for next, not freed because funixHName[] now owns it */ + funixFlowCtl[nfunix] = bUseFlowCtl ? eFLOWCTL_LIGHT_DELAY : eFLOWCTL_NO_DELAY; + funixFlags[nfunix] = bIgnoreTimestamp ? IGNDATE : NOFLAG; + funixn[nfunix++] = pNewVal; + } + else { + errmsg.LogError(0, NO_ERRCODE, "Out of unix socket name descriptors, ignoring %s\n", + pNewVal); + } + + return RS_RET_OK; +} + +/* free the funixn[] socket names - needed as cleanup on several places + * note that nfunix is NOT reset! funixn[0] is never freed, as it comes from + * the constant memory pool - and if not, it is freeed via some other pointer. + */ +static rsRetVal discardFunixn(void) +{ + int i; + + for (i = 1; i < nfunix; i++) { + if(funixn[i] != NULL) { + free(funixn[i]); + funixn[i] = NULL; + } + if(funixHName[i] != NULL) { + free(funixHName[i]); + funixHName[i] = NULL; + } + } + + return RS_RET_OK; +} + + +static int create_unix_socket(const char *path) +{ + struct sockaddr_un sunx; + int fd; + + if (path[0] == '\0') + return -1; + + unlink(path); + + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_UNIX; + (void) strncpy(sunx.sun_path, path, sizeof(sunx.sun_path)); + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0 || bind(fd, (struct sockaddr *) &sunx, SUN_LEN(&sunx)) < 0 || + chmod(path, 0666) < 0) { + errmsg.LogError(errno, NO_ERRCODE, "connot create '%s'", path); + dbgprintf("cannot create %s (%d).\n", path, errno); + close(fd); + return -1; + } + return fd; +} + + +/* This function receives data from a socket indicated to be ready + * to receive and submits the message received for processing. + * rgerhards, 2007-12-20 + * Interface changed so that this function is passed the array index + * of the socket which is to be processed. This eases access to the + * growing number of properties. -- rgerhards, 2008-08-01 + */ +static rsRetVal readSocket(int fd, int iSock) +{ + DEFiRet; + int iRcvd; + int iMaxLine; + uchar bufRcv[4096+1]; + uchar *pRcv = NULL; /* receive buffer */ + + assert(iSock >= 0); + + iMaxLine = glbl.GetMaxLine(); + + /* we optimize performance: if iMaxLine is below 4K (which it is in almost all + * cases, we use a fixed buffer on the stack. Only if it is higher, heap memory + * is used. We could use alloca() to achive a similar aspect, but there are so + * many issues with alloca() that I do not want to take that route. + * rgerhards, 2008-09-02 + */ + if((size_t) iMaxLine < sizeof(bufRcv) - 1) { + pRcv = bufRcv; + } else { + CHKmalloc(pRcv = (uchar*) malloc(sizeof(uchar) * (iMaxLine + 1))); + } + + iRcvd = recv(fd, pRcv, iMaxLine, 0); + dbgprintf("Message from UNIX socket: #%d\n", fd); + if (iRcvd > 0) { + parseAndSubmitMessage(funixHName[iSock] == NULL ? glbl.GetLocalHostName() : funixHName[iSock], + (uchar*)"127.0.0.1", pRcv, + iRcvd, funixParseHost[iSock], funixFlags[iSock], funixFlowCtl[iSock], (uchar*)"imuxsock"); + } else if (iRcvd < 0 && errno != EINTR) { + char errStr[1024]; + rs_strerror_r(errno, errStr, sizeof(errStr)); + dbgprintf("UNIX socket error: %d = %s.\n", errno, errStr); + errmsg.LogError(errno, NO_ERRCODE, "recvfrom UNIX"); + } + +finalize_it: + if(pRcv != NULL && (size_t) iMaxLine >= sizeof(bufRcv) - 1) + free(pRcv); + + RETiRet; +} + + +/* This function is called to gather input. */ +BEGINrunInput + int maxfds; + int nfds; + int i; + int fd; + fd_set readfds; +CODESTARTrunInput + /* this is an endless loop - it is terminated when the thread is + * signalled to do so. This, however, is handled by the framework, + * right into the sleep below. + */ + while(1) { + /* Add the Unix Domain Sockets to the list of read + * descriptors. + * rgerhards 2005-08-01: we must now check if there are + * any local sockets to listen to at all. If the -o option + * is given without -a, we do not need to listen at all.. + */ + maxfds = 0; + FD_ZERO (&readfds); + /* Copy master connections */ + for (i = startIndexUxLocalSockets; i < nfunix; i++) { + if (funix[i] != -1) { + FD_SET(funix[i], &readfds); + if (funix[i]>maxfds) maxfds=funix[i]; + } + } + + if(Debug) { + dbgprintf("--------imuxsock calling select, active file descriptors (max %d): ", maxfds); + for (nfds= 0; nfds <= maxfds; ++nfds) + if ( FD_ISSET(nfds, &readfds) ) + dbgprintf("%d ", nfds); + dbgprintf("\n"); + } + + /* wait for io to become ready */ + nfds = select(maxfds+1, (fd_set *) &readfds, NULL, NULL, NULL); + + for (i = 0; i < nfunix && nfds > 0; i++) { + if ((fd = funix[i]) != -1 && FD_ISSET(fd, &readfds)) { + readSocket(fd, i); + --nfds; /* indicate we have processed one */ + } + } + } + + RETiRet; +ENDrunInput + + +BEGINwillRun +CODESTARTwillRun + register int i; + + /* first apply some config settings */ + startIndexUxLocalSockets = bOmitLocalLogging ? 1 : 0; + if(pLogSockName != NULL) + funixn[0] = pLogSockName; + + /* initialize and return if will run or not */ + for (i = startIndexUxLocalSockets ; i < nfunix ; i++) { + if ((funix[i] = create_unix_socket((char*) funixn[i])) != -1) + dbgprintf("Opened UNIX socket '%s' (fd %d).\n", funixn[i], funix[i]); + } + + RETiRet; +ENDwillRun + + +BEGINafterRun +CODESTARTafterRun + int i; + /* do cleanup here */ + /* Close the UNIX sockets. */ + for (i = 0; i < nfunix; i++) + if (funix[i] != -1) + close(funix[i]); + + /* Clean-up files. */ + for (i = 0; i < nfunix; i++) + if (funixn[i] && funix[i] != -1) + unlink((char*) funixn[i]); + /* free no longer needed string */ + if(pLogSockName != NULL) + free(pLogSockName); + if(pLogHostName != NULL) { + free(pLogHostName); + } + + discardFunixn(); + nfunix = 1; +ENDafterRun + + +BEGINmodExit +CODESTARTmodExit + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_IMOD_QUERIES +ENDqueryEtryPt + +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + bOmitLocalLogging = 0; + if(pLogSockName != NULL) { + free(pLogSockName); + pLogSockName = NULL; + } + if(pLogHostName != NULL) { + free(pLogHostName); + pLogHostName = NULL; + } + + discardFunixn(); + nfunix = 1; + bIgnoreTimestamp = 1; + bUseFlowCtl = 0; + + return RS_RET_OK; +} + + +BEGINmodInit() + int i; +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + dbgprintf("imuxsock version %s initializing\n", PACKAGE_VERSION); + + /* initialize funixn[] array */ + for(i = 1 ; i < MAXFUNIX ; ++i) { + funixn[i] = NULL; + funix[i] = -1; + } + + /* register config file handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"omitlocallogging", 0, eCmdHdlrBinary, + NULL, &bOmitLocalLogging, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketignoremsgtimestamp", 0, eCmdHdlrBinary, + NULL, &bIgnoreTimestamp, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"systemlogsocketname", 0, eCmdHdlrGetWord, + NULL, &pLogSockName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensockethostname", 0, eCmdHdlrGetWord, + NULL, &pLogHostName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputunixlistensocketflowcontrol", 0, eCmdHdlrBinary, + NULL, &bUseFlowCtl, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"addunixlistensocket", 0, eCmdHdlrGetWord, + addLstnSocketName, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, + resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); + /* the following one is a (dirty) trick: the system log socket is not added via + * an "addUnixListenSocket" config format. As such, it's properties can not be modified + * via $InputUnixListenSocket*". So we need to add a special directive + * for that. We should revisit all of that once we have the new config format... + * rgerhards, 2008-03-06 + */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"systemlogsocketignoremsgtimestamp", 0, eCmdHdlrBinary, + setSystemLogTimestampIgnore, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"systemlogsocketflowcontrol", 0, eCmdHdlrBinary, + setSystemLogFlowControl, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* vim:set ai: + */ diff --git a/plugins/omgssapi/.cvsignore b/plugins/omgssapi/.cvsignore deleted file mode 100644 index 9730646f..00000000 --- a/plugins/omgssapi/.cvsignore +++ /dev/null @@ -1,6 +0,0 @@ -.deps -.libs -Makefile -Makefile.in -*.la -*.lo diff --git a/plugins/omgssapi/Makefile.am b/plugins/omgssapi/Makefile.am index 9fa6a241..a57a64b3 100644 --- a/plugins/omgssapi/Makefile.am +++ b/plugins/omgssapi/Makefile.am @@ -1,6 +1,6 @@ pkglib_LTLIBRARIES = omgssapi.la -omgssapi_la_SOURCES = omgssapi.c ../../module-template.h -omgssapi_la_CPPFLAGS = $(pgsql_cflags) -I$(srcdir)/../.. +omgssapi_la_SOURCES = omgssapi.c +omgssapi_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) omgssapi_la_LDFLAGS = -module -avoid-version -omgssapi_la_LIBADD = $(gss_libs) +omgssapi_la_LIBADD = $(GSS_LIBS) diff --git a/plugins/omgssapi/omgssapi.c b/plugins/omgssapi/omgssapi.c index 97b8bd55..e0cc8af6 100644 --- a/plugins/omgssapi/omgssapi.c +++ b/plugins/omgssapi/omgssapi.c @@ -4,7 +4,7 @@ * NOTE: read comments in module-template.h to understand how this file * works! * - * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH. * * This file is part of rsyslog. * @@ -43,65 +43,48 @@ #endif #include <pthread.h> #include <gssapi/gssapi.h> -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "net.h" -#include "omfwd.h" #include "template.h" #include "msg.h" -#include "tcpsyslog.h" #include "cfsysline.h" #include "module-template.h" #include "gss-misc.h" +#include "tcpclt.h" +#include "glbl.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT -#define INET_SUSPEND_TIME 60 /* equal to 1 minute */ - /* rgerhards, 2005-07-26: This was 3 minutes. As the - * same timer is used for tcp based syslog, we have - * reduced it. However, it might actually be worth - * thinking about a buffered tcp sender, which would be - * a much better alternative. When that happens, this - * time here can be re-adjusted to 3 minutes (or, - * even better, made configurable). - */ -#define INET_RETRY_MAX 30 /* maximum of retries for gethostbyname() */ - /* was 10, changed to 30 because we reduced INET_SUSPEND_TIME by one third. So - * this "fixes" some of implications of it (see comment on INET_SUSPEND_TIME). - * rgerhards, 2005-07-26 - */ /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) +DEFobjCurrIf(gssutil) +DEFobjCurrIf(tcpclt) typedef struct _instanceData { - char f_hname[MAXHOSTNAMELEN+1]; + char *f_hname; short sock; /* file descriptor */ enum { /* TODO: we shoud revisit these definitions */ eDestFORW, eDestFORW_SUSP, eDestFORW_UNKN } eDestState; - int iRtryCnt; struct addrinfo *f_addr; int compressionLevel; /* 0 - no compression, else level for zlib */ char *port; - char *savedMsg; - int savedMsgLen; /* length of savedMsg in octets */ - TCPFRAMINGMODE tcp_framing; - enum TCPSendStatus { - TCP_SEND_NOTCONNECTED = 0, - TCP_SEND_CONNECTING = 1, - TCP_SEND_READY = 2 - } status; - time_t ttSuspend; /* time selector was suspended */ + tcpclt_t *pTCPClt; /* our tcpclt object */ gss_ctx_id_t gss_context; OM_uint32 gss_flags; -# ifdef USE_PTHREADS - pthread_mutex_t mtxTCPSend; -# endif } instanceData; +/* config data */ +static uchar *pszTplName = NULL; /* name of the default template to use */ static char *gss_base_service_name = NULL; static enum gss_mode_t { GSSMODE_MIC, @@ -114,7 +97,7 @@ static enum gss_mode_t { * We may change the implementation to try to lookup the port * if it is unspecified. So far, we use the IANA default auf 514. */ -char *getFwdSyslogPt(instanceData *pData) +static char *getFwdSyslogPt(instanceData *pData) { assert(pData != NULL); if(pData->port == NULL) @@ -123,48 +106,6 @@ char *getFwdSyslogPt(instanceData *pData) return(pData->port); } -/* get send status - * rgerhards, 2005-10-24 - */ -static void TCPSendSetStatus(instanceData *pData, enum TCPSendStatus iNewState) -{ - assert(pData != NULL); - assert( (iNewState == TCP_SEND_NOTCONNECTED) - || (iNewState == TCP_SEND_CONNECTING) - || (iNewState == TCP_SEND_READY)); - - /* there can potentially be a race condition, so guard by mutex */ -# ifdef USE_PTHREADS - pthread_mutex_lock(&pData->mtxTCPSend); -# endif - pData->status = iNewState; -# ifdef USE_PTHREADS - pthread_mutex_unlock(&pData->mtxTCPSend); -# endif -} - - -/* get send status - * rgerhards, 2005-10-24 - */ -static enum TCPSendStatus TCPSendGetStatus(instanceData *pData) -{ - enum TCPSendStatus eState; - assert(pData != NULL); - - /* there can potentially be a race condition, so guard by mutex */ -# ifdef USE_PTHREADS - pthread_mutex_lock(&pData->mtxTCPSend); -# endif - eState = pData->status; -# ifdef USE_PTHREADS - pthread_mutex_unlock(&pData->mtxTCPSend); -# endif - - return eState; -} - - BEGINcreateInstance CODESTARTcreateInstance ENDcreateInstance @@ -194,7 +135,7 @@ CODESTARTfreeInstance if (pData->gss_context != GSS_C_NO_CONTEXT) { maj_stat = gss_delete_sec_context(&min_stat, &pData->gss_context, GSS_C_NO_BUFFER); if (maj_stat != GSS_S_COMPLETE) - display_status("deleting context", maj_stat, min_stat); + gssutil.display_status("deleting context", maj_stat, min_stat); } /* this is meant to be done when module is unloaded, but since this module is static... @@ -204,13 +145,13 @@ CODESTARTfreeInstance gss_base_service_name = NULL; } -# ifdef USE_PTHREADS - /* delete any mutex objects, if present */ - pthread_mutex_destroy(&pData->mtxTCPSend); -# endif /* final cleanup */ + tcpclt.Destruct(&pData->pTCPClt); if(pData->sock >= 0) close(pData->sock); + + if(pData->f_hname != NULL) + free(pData->f_hname); ENDfreeInstance @@ -220,8 +161,6 @@ CODESTARTdbgPrintInstInfo ENDdbgPrintInstInfo -/* CODE FOR SENDING TCP MESSAGES */ - /* This function is called immediately before a send retry is attempted. * It shall clean up whatever makes sense. * rgerhards, 2007-12-28 @@ -253,9 +192,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) base = (gss_base_service_name == NULL) ? "host" : gss_base_service_name; out_tok.length = strlen(pData->f_hname) + strlen(base) + 2; - if ((out_tok.value = malloc(out_tok.length)) == NULL) { - ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); - } + CHKmalloc(out_tok.value = malloc(out_tok.length)); strcpy(out_tok.value, base); strcat(out_tok.value, "@"); strcat(out_tok.value, pData->f_hname); @@ -271,7 +208,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) out_tok.length = 0; if (maj_stat != GSS_S_COMPLETE) { - display_status("parsing name", maj_stat, min_stat); + gssutil.display_status("parsing name", maj_stat, min_stat); goto fail; } @@ -284,7 +221,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) *sess_flags |= GSS_C_CONF_FLAG; } dbgprintf("GSS-API requested context flags:\n"); - display_ctx_flags(*sess_flags); + gssutil.display_ctx_flags(*sess_flags); do { maj_stat = gss_init_sec_context(&init_sec_min_stat, GSS_C_NO_CREDENTIAL, context, @@ -295,17 +232,17 @@ static rsRetVal TCPSendGSSInit(void *pvData) if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { - display_status("initializing context", maj_stat, init_sec_min_stat); + gssutil.display_status("initializing context", maj_stat, init_sec_min_stat); goto fail; } if (s == -1) - if ((s = pData->sock = TCPSendCreateSocket(pData->f_addr)) == -1) + if ((s = pData->sock = tcpclt.CreateSocket(pData->f_addr)) == -1) goto fail; if (out_tok.length != 0) { dbgprintf("GSS-API Sending init_sec_context token (length: %ld)\n", (long) out_tok.length); - if (send_token(s, &out_tok) < 0) { + if (gssutil.send_token(s, &out_tok) < 0) { goto fail; } } @@ -313,7 +250,7 @@ static rsRetVal TCPSendGSSInit(void *pvData) if (maj_stat == GSS_S_CONTINUE_NEEDED) { dbgprintf("GSS-API Continue needed...\n"); - if (recv_token(s, &in_tok) <= 0) { + if (gssutil.recv_token(s, &in_tok) <= 0) { goto fail; } tok_ptr = &in_tok; @@ -322,16 +259,16 @@ static rsRetVal TCPSendGSSInit(void *pvData) dbgprintf("GSS-API Provided context flags:\n"); *sess_flags = ret_flags; - display_ctx_flags(*sess_flags); + gssutil.display_ctx_flags(*sess_flags); dbgprintf("GSS-API Context initialized\n"); gss_release_name(&min_stat, &target_name); finalize_it: - return iRet; + RETiRet; fail: - logerror("GSS-API Context initialization failed\n"); + errmsg.LogError(0, RS_RET_GSS_SENDINIT_ERROR, "GSS-API Context initialization failed\n"); gss_release_name(&min_stat, &target_name); gss_release_buffer(&min_stat, &out_tok); if (*context != GSS_C_NO_CONTEXT) { @@ -341,7 +278,7 @@ finalize_it: if (s != -1) close(s); pData->sock = -1; - return RS_RET_GSS_SENDINIT_ERROR; + ABORT_FINALIZE(RS_RET_GSS_SENDINIT_ERROR); } @@ -364,11 +301,11 @@ static rsRetVal TCPSendGSSSend(void *pvData, char *msg, size_t len) maj_stat = gss_wrap(&min_stat, *context, (gss_mode == GSSMODE_ENC) ? 1 : 0, GSS_C_QOP_DEFAULT, &in_buf, NULL, &out_buf); if (maj_stat != GSS_S_COMPLETE) { - display_status("wrapping message", maj_stat, min_stat); + gssutil.display_status("wrapping message", maj_stat, min_stat); goto fail; } - if (send_token(s, &out_buf) < 0) { + if (gssutil.send_token(s, &out_buf) < 0) { goto fail; } gss_release_buffer(&min_stat, &out_buf); @@ -411,13 +348,12 @@ static rsRetVal doTryResume(instanceData *pData) * a common function. */ hints.ai_flags = AI_NUMERICSERV; - hints.ai_family = family; + hints.ai_family = glbl.GetDefPFFamily(); hints.ai_socktype = SOCK_STREAM; if((e = getaddrinfo(pData->f_hname, getFwdSyslogPt(pData), &hints, &res)) == 0) { dbgprintf("%s found, resuming.\n", pData->f_hname); pData->f_addr = res; - pData->iRtryCnt = 0; pData->eDestState = eDestFORW; } else { iRet = RS_RET_SUSPENDED; @@ -430,7 +366,7 @@ static rsRetVal doTryResume(instanceData *pData) break; } - return iRet; + RETiRet; } @@ -442,6 +378,7 @@ ENDtryResume BEGINdoAction char *psz; /* temporary buffering */ register unsigned l; + int iMaxLine; CODESTARTdoAction switch (pData->eDestState) { case eDestFORW_SUSP: @@ -456,11 +393,11 @@ CODESTARTdoAction case eDestFORW: dbgprintf(" %s:%s/%s\n", pData->f_hname, getFwdSyslogPt(pData), "tcp-gssapi"); - pData->ttSuspend = time(NULL); + iMaxLine = glbl.GetMaxLine(); psz = (char*) ppString[0]; l = strlen((char*) psz); - if (l > MAXLINE) - l = MAXLINE; + if((int) l > iMaxLine) + l = iMaxLine; # ifdef USE_NETZIP /* Check if we should compress and, if so, do it. We also @@ -472,10 +409,14 @@ CODESTARTdoAction * rgerhards, 2006-11-30 */ if(pData->compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) { - Bytef out[MAXLINE+MAXLINE/100+12] = "z"; + Bytef *out; uLongf destLen = sizeof(out) / sizeof(Bytef); uLong srcLen = l; int ret; + /* TODO: optimize malloc sequence? -- rgerhards, 2008-09-02 */ + CHKmalloc(out = (Bytef*) malloc(iMaxLine + iMaxLine/100 + 12)); + 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", @@ -499,7 +440,7 @@ CODESTARTdoAction } # endif - CHKiRet_Hdlr(TCPSend(pData, psz, l, pData->tcp_framing, TCPSendGSSInit, TCPSendGSSSend, TCPSendGSSPrepRetry)) { + CHKiRet_Hdlr(tcpclt.Send(pData->pTCPClt, pData, psz, l)) { /* error! */ dbgprintf("error forwarding via tcp, suspending\n"); pData->eDestState = eDestFORW_SUSP; @@ -507,6 +448,7 @@ CODESTARTdoAction } break; } +finalize_it: ENDdoAction @@ -516,6 +458,7 @@ BEGINparseSelectorAct int error; int bErr; struct addrinfo hints, *res; + TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING; CODESTARTparseSelectorAct CODE_STD_STRING_REQUESTparseSelectorAct(1) /* first check if this config line is actually for us @@ -537,10 +480,6 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) if((iRet = createInstance(&pData)) != RS_RET_OK) goto finalize_it; -# ifdef USE_PTHREADS - pthread_mutex_init(&pData->mtxTCPSend, 0); -# endif - /* 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 @@ -569,20 +508,20 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) ++p; /* eat */ pData->compressionLevel = iLevel; } else { - logerrorInt("Invalid compression level '%c' specified in " + errmsg.LogError(0, NO_ERRCODE, "Invalid compression level '%c' specified in " "forwardig action - NOT turning on compression.", *p); } # else - logerror("Compression requested, but rsyslogd is not compiled " + errmsg.LogError(0, NO_ERRCODE, "Compression requested, but rsyslogd is not compiled " "with compression support - request ignored."); # endif /* #ifdef USE_NETZIP */ } else if(*p == 'o') { /* octet-couting based TCP framing? */ ++p; /* eat */ /* no further options settable */ - pData->tcp_framing = TCP_FRAMING_OCTET_COUNTING; + tcp_framing = TCP_FRAMING_OCTET_COUNTING; } else { /* invalid option! Just skip it... */ - logerrorInt("Invalid option %c in forwarding action - ignoring.", *p); + 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 @@ -598,12 +537,12 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* we probably have end of string - leave it for the rest * of the code to handle it (but warn the user) */ - logerror("Option block not terminated in gssapi forward action."); + errmsg.LogError(0, NO_ERRCODE, "Option block not terminated in gssapi forward action."); } /* 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 */ - for(q = p ; *p && *p != ';' && *p != ':' ; ++p) + for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p) /* JUST SKIP */; pData->port = NULL; @@ -616,7 +555,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) /* SKIP AND COUNT */; pData->port = malloc(i + 1); if(pData->port == NULL) { - logerror("Could not get memory to store syslog forwarding port, " + 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 * getFwdSyslogPt(). @@ -627,6 +566,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) } } + /* now skip to template */ bErr = 0; while(*p && *p != ';') { @@ -634,7 +574,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) if(bErr == 0) { /* only 1 error msg! */ bErr = 1; errno = 0; - logerror("invalid selector line (port), probably not doing " + errmsg.LogError(0, NO_ERRCODE, "invalid selector line (port), probably not doing " "what was intended"); } } @@ -642,33 +582,40 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) } /* TODO: make this if go away! */ - if(*p == ';') { + if(*p == ';' || *p == '#' || isspace(*p)) { + uchar cTmp = *p; *p = '\0'; /* trick to obtain hostname (later)! */ - strcpy(pData->f_hname, (char*) q); - *p = ';'; - } else - strcpy(pData->f_hname, (char*) q); + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = cTmp; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } /* process template */ - if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " StdFwdFmt")) - != RS_RET_OK) - goto finalize_it; + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, + (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName)); /* first set the pData->eDestState */ memset(&hints, 0, sizeof(hints)); /* port must be numeric, because config file syntax requests this */ hints.ai_flags = AI_NUMERICSERV; - hints.ai_family = family; + hints.ai_family = glbl.GetDefPFFamily(); hints.ai_socktype = SOCK_STREAM; if( (error = getaddrinfo(pData->f_hname, getFwdSyslogPt(pData), &hints, &res)) != 0) { pData->eDestState = eDestFORW_UNKN; - pData->iRtryCnt = INET_RETRY_MAX; - pData->ttSuspend = time(NULL); } else { pData->eDestState = eDestFORW; pData->f_addr = res; } + /* now create our tcpclt */ + CHKiRet(tcpclt.Construct(&pData->pTCPClt)); + /* and set callbacks */ + CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendGSSInit)); + CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendGSSSend)); + CHKiRet(tcpclt.SetSendPrepRetry(pData->pTCPClt, TCPSendGSSPrepRetry)); + CHKiRet(tcpclt.SetFraming(pData->pTCPClt, tcp_framing)); + /* TODO: do we need to call freeInstance if we failed - this is a general question for * all output modules. I'll address it lates as the interface evolves. rgerhards, 2007-07-25 */ @@ -676,45 +623,17 @@ CODE_STD_FINALIZERparseSelectorAct ENDparseSelectorAct -BEGINneedUDPSocket -CODESTARTneedUDPSocket - iRet = RS_RET_FALSE; -ENDneedUDPSocket - - -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite - dbgprintf("tcp send socket %d ready for writing.\n", pData->sock); - TCPSendSetStatus(pData, TCP_SEND_READY); - /* Send stored message (if any) */ - if(pData->savedMsg != NULL) { - if(TCPSend(pData, pData->savedMsg, pData->savedMsgLen, pData->tcp_framing, - TCPSendGSSInit, TCPSendGSSSend, TCPSendGSSPrepRetry) != RS_RET_OK) { - /* error! */ - pData->eDestState = eDestFORW_SUSP; - errno = 0; - logerror("error forwarding via tcp, suspending..."); - } - free(pData->savedMsg); - pData->savedMsg = NULL; - } -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect - if( (pData->eDestState == eDestFORW) - && TCPSendGetStatus(pData) == TCP_SEND_CONNECTING) { - *fd = pData->sock; - iRet = RS_RET_OK; - } -ENDgetWriteFDForSelect - - - - BEGINmodExit CODESTARTmodExit + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); + objRelease(gssutil, LM_GSSUTIL_FILENAME); + objRelease(tcpclt, LM_TCPCLT_FILENAME); + + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } ENDmodExit @@ -727,21 +646,21 @@ ENDqueryEtryPt /* set a new GSSMODE based on config directive */ static rsRetVal setGSSMode(void __attribute__((unused)) *pVal, uchar *mode) { + DEFiRet; + if (!strcmp((char *) mode, "integrity")) { gss_mode = GSSMODE_MIC; - free(mode); dbgprintf("GSS-API gssmode set to GSSMODE_MIC\n"); } else if (!strcmp((char *) mode, "encryption")) { gss_mode = GSSMODE_ENC; - free(mode); dbgprintf("GSS-API gssmode set to GSSMODE_ENC\n"); } else { - logerrorSz("unknown gssmode parameter: %s", (char *) mode); - free(mode); - return RS_RET_ERR; + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "unknown gssmode parameter: %s", (char *) mode); + iRet = RS_RET_INVALID_PARAMS; } + free(mode); - return RS_RET_OK; + RETiRet; } @@ -752,20 +671,29 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a free(gss_base_service_name); gss_base_service_name = NULL; } + if(pszTplName != NULL) { + free(pszTplName); + pszTplName = NULL; + } return RS_RET_OK; } BEGINmodInit() CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(gssutil, LM_GSSUTIL_FILENAME)); + CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"gssforwardservicename", 0, eCmdHdlrGetWord, NULL, &gss_base_service_name, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"gssmode", 0, eCmdHdlrGetWord, setGSSMode, &gss_mode, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actiongssforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, STD_LOADABLE_MODULE_ID)); CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit #endif /* #ifdef USE_GSSAPI */ -/* - * vi:set ai: +/* vi:set ai: */ diff --git a/plugins/omlibdbi/Makefile.am b/plugins/omlibdbi/Makefile.am new file mode 100644 index 00000000..6a26f807 --- /dev/null +++ b/plugins/omlibdbi/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omlibdbi.la + +omlibdbi_la_SOURCES = omlibdbi.c +omlibdbi_la_CPPFLAGS = -I$(top_srcdir) $(LIBDBI_CFLAGS) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omlibdbi_la_LDFLAGS = -module -avoid-version +omlibdbi_la_LIBADD = $(LIBDBI_LIBS) diff --git a/plugins/omlibdbi/omlibdbi.c b/plugins/omlibdbi/omlibdbi.c new file mode 100644 index 00000000..6f130f54 --- /dev/null +++ b/plugins/omlibdbi/omlibdbi.c @@ -0,0 +1,372 @@ +/* omlibdbi.c + * This is the implementation of the dbi output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * This depends on libdbi being present with the proper settings. Older + * versions do not necessarily have them. Please visit this bug tracker + * for details: http://bugzilla.adiscon.com/show_bug.cgi?id=31 + * + * File begun on 2008-02-14 by RGerhards (extracted from syslogd.c) + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <errno.h> +#include <time.h> +#include <dbi/dbi.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "cfsysline.h" +#include "srUtils.h" +#include "template.h" +#include "module-template.h" +#include "debug.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +static int bDbiInitialized = 0; /* dbi_initialize() can only be called one - this keeps track of it */ + +typedef struct _instanceData { + dbi_conn conn; /* handle to database */ + uchar *drvrName; /* driver to use */ + uchar *host; /* host to connect to */ + uchar *usrName; /* user name for connect */ + uchar *pwd; /* password for connect */ + uchar *dbName; /* database to use */ + unsigned uLastDBErrno; /* last errno returned by libdbi or 0 if all is well */ +} instanceData; + + +/* config settings */ +static uchar *dbiDrvrDir = NULL;/* global: where do the dbi drivers reside? */ +static uchar *drvrName = NULL; /* driver to use */ +static uchar *host = NULL; /* host to connect to */ +static uchar *usrName = NULL; /* user name for connect */ +static uchar *pwd = NULL; /* password for connect */ +static uchar *dbName = NULL; /* database to use */ +#ifdef HAVE_DBI_R +static dbi_inst dbiInst; +#endif + + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we do not like repeated message reduction inside the database */ +ENDisCompatibleWithFeature + + +/* The following function is responsible for closing a + * database connection. + */ +static void closeConn(instanceData *pData) +{ + ASSERT(pData != NULL); + + if(pData->conn != NULL) { /* just to be on the safe side... */ + dbi_conn_close(pData->conn); + pData->conn = NULL; + } +} + +BEGINfreeInstance +CODESTARTfreeInstance + closeConn(pData); +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + /* nothing special here */ +ENDdbgPrintInstInfo + + +/* log a database error with descriptive message. + * We check if we have a valid database handle. If not, we simply + * report an error, but can not be specific. RGerhards, 2007-01-30 + */ +static void +reportDBError(instanceData *pData, int bSilent) +{ + unsigned uDBErrno; + char errMsg[1024]; + const char *pszDbiErr; + + BEGINfunc + ASSERT(pData != NULL); + + /* output log message */ + errno = 0; + if(pData->conn == NULL) { + errmsg.LogError(0, NO_ERRCODE, "unknown DB error occured - could not obtain connection handle"); + } else { /* we can ask dbi for the error description... */ + uDBErrno = dbi_conn_error(pData->conn, &pszDbiErr); + snprintf(errMsg, sizeof(errMsg)/sizeof(char), "db error (%d): %s\n", uDBErrno, pszDbiErr); + if(bSilent || uDBErrno == pData->uLastDBErrno) + dbgprintf("libdbi, DBError(silent): %s\n", errMsg); + else { + pData->uLastDBErrno = uDBErrno; + errmsg.LogError(0, NO_ERRCODE, "%s", errMsg); + } + } + + ENDfunc +} + + +/* The following function is responsible for initializing a connection + */ +static rsRetVal initConn(instanceData *pData, int bSilent) +{ + DEFiRet; + int iDrvrsLoaded; + + ASSERT(pData != NULL); + ASSERT(pData->conn == NULL); + + if(bDbiInitialized == 0) { + /* we need to init libdbi first */ +# ifdef HAVE_DBI_R + iDrvrsLoaded = dbi_initialize_r((char*) dbiDrvrDir, &dbiInst); +# else + iDrvrsLoaded = dbi_initialize((char*) dbiDrvrDir); +# endif + if(iDrvrsLoaded == 0) { + errmsg.LogError(0, RS_RET_SUSPENDED, "libdbi error: libdbi or libdbi drivers not present on this system - suspending."); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } else if(iDrvrsLoaded < 0) { + errmsg.LogError(0, RS_RET_SUSPENDED, "libdbi error: libdbi could not be initialized - suspending."); + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + bDbiInitialized = 1; /* we are done for the rest of our existence... */ + } + +# ifdef HAVE_DBI_R + pData->conn = dbi_conn_new_r((char*)pData->drvrName, dbiInst); +# else + pData->conn = dbi_conn_new((char*)pData->drvrName); +# endif + if(pData->conn == NULL) { + errmsg.LogError(0, RS_RET_SUSPENDED, "can not initialize libdbi connection"); + iRet = RS_RET_SUSPENDED; + } else { /* we could get the handle, now on with work... */ + /* Connect to database */ + dbi_conn_set_option(pData->conn, "host", (char*) pData->host); + dbi_conn_set_option(pData->conn, "username", (char*) pData->usrName); + dbi_conn_set_option(pData->conn, "dbname", (char*) pData->dbName); + if(pData->pwd != NULL) + dbi_conn_set_option(pData->conn, "password", (char*) pData->pwd); + if(dbi_conn_connect(pData->conn) < 0) { + reportDBError(pData, bSilent); + closeConn(pData); /* ignore any error we may get */ + iRet = RS_RET_SUSPENDED; + } + } + +finalize_it: + RETiRet; +} + + +/* The following function writes the current log entry + * to an established database connection. + */ +rsRetVal writeDB(uchar *psz, instanceData *pData) +{ + DEFiRet; + dbi_result dbiRes = NULL; + + ASSERT(psz != NULL); + ASSERT(pData != NULL); + + /* see if we are ready to proceed */ + if(pData->conn == NULL) { + CHKiRet(initConn(pData, 0)); + } + + /* try insert */ + if((dbiRes = dbi_conn_query(pData->conn, (const char*)psz)) == NULL) { + /* error occured, try to re-init connection and retry */ + closeConn(pData); /* close the current handle */ + CHKiRet(initConn(pData, 0)); /* try to re-open */ + if((dbiRes = dbi_conn_query(pData->conn, (const char*)psz)) == NULL) { /* re-try insert */ + /* we failed, giving up for now */ + reportDBError(pData, 0); + closeConn(pData); /* free ressources */ + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + } + +finalize_it: + if(iRet == RS_RET_OK) { + pData->uLastDBErrno = 0; /* reset error for error supression */ + } + + if(dbiRes != NULL) + dbi_result_free(dbiRes); + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + if(pData->conn == NULL) { + iRet = initConn(pData, 1); + } +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + dbgprintf("\n"); + iRet = writeDB(ppString[0], pData); +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omlibdbi:", sizeof(":omlibdbi:") - 1)) { + p += sizeof(":omlibdbi:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + CHKiRet(createInstance(&pData)); + + /* no create the instance based on what we currently have */ + if(drvrName == NULL) { + errmsg.LogError(0, RS_RET_NO_DRIVERNAME, "omlibdbi: no db driver name given - action can not be created"); + ABORT_FINALIZE(RS_RET_NO_DRIVERNAME); + } + + if((pData->drvrName = (uchar*) strdup((char*)drvrName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + /* NULL values are supported because drivers have different needs. + * They will err out on connect. -- rgerhards, 2008-02-15 + */ + if(host != NULL) + if((pData->host = (uchar*) strdup((char*)host)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + if(usrName != NULL) + if((pData->usrName = (uchar*) strdup((char*)usrName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + if(dbName != NULL) + if((pData->dbName = (uchar*) strdup((char*)dbName)) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + if(pwd != NULL) + if((pData->pwd = (uchar*) strdup((char*)"")) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_RQD_TPL_OPT_SQL, (uchar*) " StdDBFmt")); + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + /* if we initialized libdbi, we now need to cleanup */ + if(bDbiInitialized) { +# ifdef HAVE_DBI_R + dbi_shutdown_r(dbiInst); +# else + dbi_shutdown(); +# endif + } +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + + if(dbiDrvrDir != NULL) { + free(dbiDrvrDir); + dbiDrvrDir = NULL; + } + + if(drvrName != NULL) { + free(drvrName); + drvrName = NULL; + } + + if(host != NULL) { + free(host); + host = NULL; + } + + if(usrName != NULL) { + free(usrName); + usrName = NULL; + } + + if(pwd != NULL) { + free(pwd); + pwd = NULL; + } + + if(dbName != NULL) { + free(dbName); + dbName = NULL; + } + + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbidriverdirectory", 0, eCmdHdlrGetWord, NULL, &dbiDrvrDir, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbidriver", 0, eCmdHdlrGetWord, NULL, &drvrName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbihost", 0, eCmdHdlrGetWord, NULL, &host, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbiusername", 0, eCmdHdlrGetWord, NULL, &usrName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbipassword", 0, eCmdHdlrGetWord, NULL, &pwd, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionlibdbidbname", 0, eCmdHdlrGetWord, NULL, &dbName, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/ommail/Makefile.am b/plugins/ommail/Makefile.am new file mode 100644 index 00000000..97c9296a --- /dev/null +++ b/plugins/ommail/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = ommail.la + +ommail_la_SOURCES = ommail.c +ommail_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +ommail_la_LDFLAGS = -module -avoid-version +ommail_la_LIBADD = diff --git a/plugins/ommail/ommail.c b/plugins/ommail/ommail.c new file mode 100644 index 00000000..5faadce3 --- /dev/null +++ b/plugins/ommail/ommail.c @@ -0,0 +1,714 @@ +/* ommail.c + * + * This is an implementation of a mail sending output module. So far, we + * only support direct SMTP, that is talking to a SMTP server. In the long + * term, support for using sendmail should also be implemented. Please note + * that the SMTP protocol implementation is a very bare one. We support + * RFC821/822 messages, without any authentication and any other nice + * features (no MIME, no nothing). It is assumed that proper firewalling + * and/or STMP server configuration is used together with this module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2008-04-04 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <errno.h> +#include <netdb.h> +#include <time.h> +#include <sys/socket.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "cfsysline.h" +#include "module-template.h" +#include "errmsg.h" +#include "glbl.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +/* we add a little support for multiple recipients. We do this via a + * singly-linked list, enqueued from the top. -- rgerhards, 2008-08-04 + */ +typedef struct toRcpt_s toRcpt_t; +struct toRcpt_s { + uchar *pszTo; + toRcpt_t *pNext; +}; +static toRcpt_t *lstRcpt = NULL; +static uchar *pszSrv = NULL; +static uchar *pszSrvPort = NULL; +static uchar *pszFrom = NULL; +static uchar *pszSubject = NULL; +static int bEnableBody = 1; /* should a mail body be generated? (set to 0 eg for SMS gateways) */ + +typedef struct _instanceData { + int iMode; /* 0 - smtp, 1 - sendmail */ + int bHaveSubject; /* is a subject configured? (if so, it is the second string provided by rsyslog core) */ + int bEnableBody; /* is a body configured? (if so, it is the second string provided by rsyslog core) */ + union { + struct { + uchar *pszSrv; + uchar *pszSrvPort; + uchar *pszFrom; + toRcpt_t *lstRcpt; + char RcvBuf[1024]; /* buffer for receiving server responses */ + size_t lenRcvBuf; + size_t iRcvBuf; /* current index into the rcvBuf (buf empty if iRcvBuf == lenRcvBuf) */ + int sock; /* socket to this server (most important when we do multiple msgs per mail) */ + } smtp; + } md; /* mode-specific data */ +} instanceData; + +/* forward definitions (as few as possible) */ +static rsRetVal Send(int sock, char *msg, size_t len); +static rsRetVal readResponse(instanceData *pData, int *piState, int iExpected); + + +/* helpers for handling the recipient lists */ + +/* destroy a complete recipient list */ +static void lstRcptDestruct(toRcpt_t *pRoot) +{ + toRcpt_t *pDel; + + while(pRoot != NULL) { + pDel = pRoot; + pRoot = pRoot->pNext; + /* ready to disalloc */ + free(pDel->pszTo); + free(pDel); + } +} + +/* This function is called when a new recipient email address is to be + * added. rgerhards, 2008-08-04 + */ +static rsRetVal +addRcpt(void __attribute__((unused)) *pVal, uchar *pNewVal) +{ + DEFiRet; + toRcpt_t *pNew = NULL; + + CHKmalloc(pNew = calloc(1, sizeof(toRcpt_t))); + + pNew->pszTo = pNewVal; + pNew->pNext = lstRcpt; + lstRcpt = pNew; + + dbgprintf("ommail::addRcpt adds recipient %s\n", pNewVal); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) + free(pNew); + free(pNewVal); /* in any case, this is no longer needed */ + } + + RETiRet; +} + + +/* output the recipient list to the mail server + * iStatusToCheck < 0 means no checking should happen + */ +static rsRetVal +WriteRcpts(instanceData *pData, uchar *pszOp, size_t lenOp, int iStatusToCheck) +{ + toRcpt_t *pRcpt; + int iState; + DEFiRet; + + assert(pData != NULL); + assert(pszOp != NULL); + assert(lenOp != 0); + + for(pRcpt = pData->md.smtp.lstRcpt ; pRcpt != NULL ; pRcpt = pRcpt->pNext) { + dbgprintf("Sending '%s: <%s>'\n", pszOp, pRcpt->pszTo); + CHKiRet(Send(pData->md.smtp.sock, (char*)pszOp, lenOp)); + CHKiRet(Send(pData->md.smtp.sock, ": <", sizeof(": <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pRcpt->pszTo, strlen((char*)pRcpt->pszTo))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + if(iStatusToCheck >= 0) + CHKiRet(readResponse(pData, &iState, iStatusToCheck)); + } + +finalize_it: + RETiRet; +} +/* end helpers for handling the recipient lists */ + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->iMode == 0) { + if(pData->md.smtp.pszSrv != NULL) + free(pData->md.smtp.pszSrv); + if(pData->md.smtp.pszSrvPort != NULL) + free(pData->md.smtp.pszSrvPort); + if(pData->md.smtp.pszFrom != NULL) + free(pData->md.smtp.pszFrom); + lstRcptDestruct(pData->md.smtp.lstRcpt); + } +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("mail"); /* TODO: extend! */ +ENDdbgPrintInstInfo + + +/* TCP support code, should probably be moved to net.c or some place else... -- rgerhards, 2008-04-04 */ + +/* "receive" a character from the remote server. A single character + * is returned. Returns RS_RET_NO_MORE_DATA if the server has closed + * the connection and RS_RET_IO_ERROR if something goes wrong. This + * is a blocking read. + * rgerhards, 2008-04-04 + */ +static rsRetVal +getRcvChar(instanceData *pData, char *pC) +{ + DEFiRet; + ssize_t lenBuf; + assert(pData != NULL); + + if(pData->md.smtp.iRcvBuf == pData->md.smtp.lenRcvBuf) { /* buffer empty? */ + /* yes, we need to read the next server response */ + do { + lenBuf = recv(pData->md.smtp.sock, pData->md.smtp.RcvBuf, sizeof(pData->md.smtp.RcvBuf), 0); + if(lenBuf == 0) { + ABORT_FINALIZE(RS_RET_NO_MORE_DATA); + } else if(lenBuf < 0) { + if(errno != EAGAIN) { + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + } else { + /* good read */ + pData->md.smtp.iRcvBuf = 0; + pData->md.smtp.lenRcvBuf = lenBuf; + } + + } while(lenBuf < 1); + } + + /* when we reach this point, we have a non-empty buffer */ + *pC = pData->md.smtp.RcvBuf[pData->md.smtp.iRcvBuf++]; + +finalize_it: + RETiRet; +} + + +/* close the mail server connection + * rgerhards, 2008-04-08 + */ +static rsRetVal +serverDisconnect(instanceData *pData) +{ + DEFiRet; + assert(pData != NULL); + + if(pData->md.smtp.sock != -1) { + close(pData->md.smtp.sock); + pData->md.smtp.sock = -1; + } + + RETiRet; +} + + +/* open a connection to the mail server + * rgerhards, 2008-04-04 + */ +static rsRetVal +serverConnect(instanceData *pData) +{ + struct addrinfo *res = NULL; + struct addrinfo hints; + char *smtpPort; + char *smtpSrv; + char errStr[1024]; + + DEFiRet; + assert(pData != NULL); + + if(pData->md.smtp.pszSrv == NULL) + smtpSrv = "127.0.0.1"; + else + smtpSrv = (char*)pData->md.smtp.pszSrv; + + if(pData->md.smtp.pszSrvPort == NULL) + smtpPort = "25"; + else + smtpPort = (char*)pData->md.smtp.pszSrvPort; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* TODO: make configurable! */ + hints.ai_socktype = SOCK_STREAM; + if(getaddrinfo(smtpSrv, smtpPort, &hints, &res) != 0) { + dbgprintf("error %d in getaddrinfo\n", errno); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if((pData->md.smtp.sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { + dbgprintf("couldn't create send socket, reason %s", rs_strerror_r(errno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + + if(connect(pData->md.smtp.sock, res->ai_addr, res->ai_addrlen) != 0) { + dbgprintf("create tcp connection failed, reason %s", rs_strerror_r(errno, errStr, sizeof(errStr))); + ABORT_FINALIZE(RS_RET_IO_ERROR); + } + +finalize_it: + if(res != NULL) + freeaddrinfo(res); + + if(iRet != RS_RET_OK) { + if(pData->md.smtp.sock != -1) { + close(pData->md.smtp.sock); + pData->md.smtp.sock = -1; + } + } + + RETiRet; +} + + +/* send text to the server, blocking send */ +static rsRetVal +Send(int sock, char *msg, size_t len) +{ + DEFiRet; + size_t offsBuf = 0; + ssize_t lenSend; + + assert(msg != NULL); + + if(len == 0) /* it's valid, but does not make much sense ;) */ + FINALIZE; + + do { + lenSend = send(sock, msg + offsBuf, len - offsBuf, 0); + if(lenSend == -1) { + if(errno != EAGAIN) { + dbgprintf("message not (tcp)send, errno %d", errno); + ABORT_FINALIZE(RS_RET_TCP_SEND_ERROR); + } + } else if(lenSend != (ssize_t) len) { + offsBuf += len; /* on to next round... */ + } else { + FINALIZE; + } + } while(1); + +finalize_it: + RETiRet; +} + + +/* send body text to the server, blocking send + * The body is special in that we must escape a leading dot inside a line + */ +static rsRetVal +bodySend(instanceData *pData, char *msg, size_t len) +{ + DEFiRet; + char szBuf[2048]; + size_t iSrc; + size_t iBuf = 0; + int bHadCR = 0; + int bInStartOfLine = 1; + + assert(pData != NULL); + assert(msg != NULL); + + for(iSrc = 0 ; iSrc < len ; ++iSrc) { + if(iBuf >= sizeof(szBuf) - 1) { /* one is reserved for our extra dot */ + CHKiRet(Send(pData->md.smtp.sock, szBuf, iBuf)); + iBuf = 0; + } + szBuf[iBuf++] = msg[iSrc]; + switch(msg[iSrc]) { + case '\r': + bHadCR = 1; + break; + case '\n': + if(bHadCR) + bInStartOfLine = 1; + bHadCR = 0; + break; + case '.': + if(bInStartOfLine) + szBuf[iBuf++] = '.'; /* space is always reserved for this! */ + /*FALLTHROUGH*/ + default: + bInStartOfLine = 0; + bHadCR = 0; + break; + } + } + + if(iBuf > 0) { /* incomplete buffer to send (the *usual* case)? */ + CHKiRet(Send(pData->md.smtp.sock, szBuf, iBuf)); + } + +finalize_it: + RETiRet; +} + + +/* read response line from server + */ +static rsRetVal +readResponseLn(instanceData *pData, char *pLn, size_t lenLn) +{ + DEFiRet; + size_t i = 0; + char c; + + assert(pData != NULL); + assert(pLn != NULL); + + do { + CHKiRet(getRcvChar(pData, &c)); + if(c == '\n') + break; + if(i < (lenLn - 1)) /* if line is too long, we simply discard the rest */ + pLn[i++] = c; + } while(1); + pLn[i] = '\0'; + dbgprintf("smtp server response: %s\n", pLn); /* do not remove, this is helpful in troubleshooting SMTP probs! */ + +finalize_it: + RETiRet; +} + + +/* read numerical response code from server and compare it to requried response code. + * If they two don't match, return RS_RET_SMTP_ERROR. + * rgerhards, 2008-04-07 + */ +static rsRetVal +readResponse(instanceData *pData, int *piState, int iExpected) +{ + DEFiRet; + int bCont; + char buf[128]; + + assert(pData != NULL); + assert(piState != NULL); + + bCont = 1; + do { + CHKiRet(readResponseLn(pData, buf, sizeof(buf))); + /* note: the code below is not 100% clean as we may have received less than 4 characters. + * However, as we have a fixed size this will not create a vulnerability. An error will + * also most likely be generated, so it is quite acceptable IMHO -- rgerhards, 2008-04-08 + */ + if(buf[3] != '-') { /* last or only response line? */ + bCont = 0; + *piState = buf[0] - '0'; + *piState = *piState * 10 + buf[1] - '0'; + *piState = *piState * 10 + buf[2] - '0'; + if(*piState != iExpected) + ABORT_FINALIZE(RS_RET_SMTP_ERROR); + } + } while(bCont); + +finalize_it: + RETiRet; +} + + +/* create a timestamp suitable for use with the Date: SMTP body header + * rgerhards, 2008-04-08 + */ +static void +mkSMTPTimestamp(uchar *pszBuf, size_t lenBuf) +{ + time_t tCurr; + struct tm tmCurr; + static const char szDay[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static const char szMonth[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + time(&tCurr); + gmtime_r(&tCurr, &tmCurr); + snprintf((char*)pszBuf, lenBuf, "Date: %s, %2d %s %4d %2d:%02d:%02d UT\r\n", szDay[tmCurr.tm_wday], tmCurr.tm_mday, + szMonth[tmCurr.tm_mon], 1900 + tmCurr.tm_year, tmCurr.tm_hour, tmCurr.tm_min, tmCurr.tm_sec); +} + + +/* send a message via SMTP + * rgerhards, 2008-04-04 + */ +static rsRetVal +sendSMTP(instanceData *pData, uchar *body, uchar *subject) +{ + DEFiRet; + int iState; /* SMTP state */ + uchar szDateBuf[64]; + + assert(pData != NULL); + + CHKiRet(serverConnect(pData)); + CHKiRet(readResponse(pData, &iState, 220)); + + CHKiRet(Send(pData->md.smtp.sock, "HELO ", 5)); + CHKiRet(Send(pData->md.smtp.sock, (char*)glbl.GetLocalHostName(), strlen((char*)glbl.GetLocalHostName()))); + CHKiRet(Send(pData->md.smtp.sock, "\r\n", sizeof("\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 250)); + + CHKiRet(Send(pData->md.smtp.sock, "MAIL FROM: <", sizeof("MAIL FROM: <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pData->md.smtp.pszFrom, strlen((char*)pData->md.smtp.pszFrom))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 250)); + + CHKiRet(WriteRcpts(pData, (uchar*)"RCPT TO", sizeof("RCPT TO") - 1, 250)); + + CHKiRet(Send(pData->md.smtp.sock, "DATA\r\n", sizeof("DATA\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 354)); + + /* now come the data part */ + /* header */ + mkSMTPTimestamp(szDateBuf, sizeof(szDateBuf)); + CHKiRet(Send(pData->md.smtp.sock, (char*)szDateBuf, strlen((char*)szDateBuf))); + + CHKiRet(Send(pData->md.smtp.sock, "From: <", sizeof("From: <") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)pData->md.smtp.pszFrom, strlen((char*)pData->md.smtp.pszFrom))); + CHKiRet(Send(pData->md.smtp.sock, ">\r\n", sizeof(">\r\n") - 1)); + + CHKiRet(WriteRcpts(pData, (uchar*)"To", sizeof("To") - 1, -1)); + + CHKiRet(Send(pData->md.smtp.sock, "Subject: ", sizeof("Subject: ") - 1)); + CHKiRet(Send(pData->md.smtp.sock, (char*)subject, strlen((char*)subject))); + CHKiRet(Send(pData->md.smtp.sock, "\r\n", sizeof("\r\n") - 1)); + + CHKiRet(Send(pData->md.smtp.sock, "X-Mailer: rsyslog-immail\r\n", sizeof("x-mailer: rsyslog-immail\r\n") - 1)); + + CHKiRet(Send(pData->md.smtp.sock, "\r\n", sizeof("\r\n") - 1)); /* indicate end of header */ + + /* body */ + if(pData->bEnableBody) + CHKiRet(bodySend(pData, (char*)body, strlen((char*) body))); + + /* end of data, back to envelope transaction */ + CHKiRet(Send(pData->md.smtp.sock, "\r\n.\r\n", sizeof("\r\n.\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 250)); + + CHKiRet(Send(pData->md.smtp.sock, "QUIT\r\n", sizeof("QUIT\r\n") - 1)); + CHKiRet(readResponse(pData, &iState, 221)); + + /* we are finished, a new connection is created for each request, so let's close it now */ + CHKiRet(serverDisconnect(pData)); + +finalize_it: + RETiRet; +} + + +/* in tryResume we check if we can connect to the server in question. If that is OK, + * we close the connection without doing any actual SMTP transaction. It will be + * reopened during the actual send process. This may not be the best way to do it if + * there is a problem inside the SMTP transaction. However, we can't find that out without + * actually initiating something, and that would be bad. The logic here helps us + * correctly recover from an unreachable/down mail server, which is probably the majority + * of problem cases. For SMTP transaction problems, we will do lots of retries, but if it + * is a temporary problem, it will be fixed anyhow. So I consider this implementation to + * be clean enough, especially as I think other approaches have other weaknesses. + * rgerhards, 2008-04-08 + */ +BEGINtryResume +CODESTARTtryResume + CHKiRet(serverConnect(pData)); + CHKiRet(serverDisconnect(pData)); /* if we fail, we will never reach this line */ +finalize_it: + if(iRet == RS_RET_IO_ERROR) + iRet = RS_RET_SUSPENDED; +ENDtryResume + + +BEGINdoAction +CODESTARTdoAction + dbgprintf(" Mail\n"); + + /* forward */ + if(pData->bHaveSubject) + iRet = sendSMTP(pData, ppString[0], ppString[1]); + else + iRet = sendSMTP(pData, ppString[0], (uchar*)"message from rsyslog"); + + if(iRet != RS_RET_OK) { + /* error! */ + dbgprintf("error sending mail, suspending\n"); + iRet = RS_RET_SUSPENDED; + } +ENDdoAction + + +BEGINparseSelectorAct +CODESTARTparseSelectorAct + if(!strncmp((char*) p, ":ommail:", sizeof(":ommail:") - 1)) { + p += sizeof(":ommail:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + FINALIZE; + + /* TODO: check strdup() result */ + + if(pszFrom == NULL) { + errmsg.LogError(0, RS_RET_MAIL_NO_FROM, "no sender address given - specify $ActionMailFrom"); + ABORT_FINALIZE(RS_RET_MAIL_NO_FROM); + } + if(lstRcpt == NULL) { + errmsg.LogError(0, RS_RET_MAIL_NO_TO, "no recipient address given - specify $ActionMailTo"); + ABORT_FINALIZE(RS_RET_MAIL_NO_TO); + } + + pData->md.smtp.pszFrom = (uchar*) strdup((char*)pszFrom); + pData->md.smtp.lstRcpt = lstRcpt; /* we "hand over" this memory */ + lstRcpt = NULL; /* note: this is different from pre-3.21.2 versions! */ + + if(pszSubject == NULL) { + /* if no subject is configured, we need just one template string */ + CODE_STD_STRING_REQUESTparseSelectorAct(1) + } else { + CODE_STD_STRING_REQUESTparseSelectorAct(2) + pData->bHaveSubject = 1; + CHKiRet(OMSRsetEntry(*ppOMSR, 1, (uchar*)strdup((char*) pszSubject), OMSR_NO_RQD_TPL_OPTS)); + } + if(pszSrv != NULL) + pData->md.smtp.pszSrv = (uchar*) strdup((char*)pszSrv); + if(pszSrvPort != NULL) + pData->md.smtp.pszSrvPort = (uchar*) strdup((char*)pszSrvPort); + pData->bEnableBody = bEnableBody; + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_FileFormat")); +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* Free string config variables and reset them to NULL (not necessarily the default!) */ +static rsRetVal freeConfigVariables(void) +{ + DEFiRet; + + if(pszSrv != NULL) { + free(pszSrv); + pszSrv = NULL; + } + if(pszSrvPort != NULL) { + free(pszSrvPort); + pszSrvPort = NULL; + } + if(pszFrom != NULL) { + free(pszFrom); + pszFrom = NULL; + } + lstRcptDestruct(lstRcpt); + lstRcpt = NULL; + + RETiRet; +} + + +BEGINmodExit +CODESTARTmodExit + /* cleanup our allocations */ + freeConfigVariables(); + + /* release what we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + bEnableBody = 1; + iRet = freeConfigVariables(); + RETiRet; +} + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* tell which objects we need */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + + dbgprintf("ommail version %s initializing\n", VERSION); + + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsmtpserver", 0, eCmdHdlrGetWord, NULL, &pszSrv, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsmtpport", 0, eCmdHdlrGetWord, NULL, &pszSrvPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailfrom", 0, eCmdHdlrGetWord, NULL, &pszFrom, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailto", 0, eCmdHdlrGetWord, addRcpt, NULL, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailsubject", 0, eCmdHdlrGetWord, NULL, &pszSubject, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionmailenablebody", 0, eCmdHdlrBinary, NULL, &bEnableBody, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/ommysql/.cvsignore b/plugins/ommysql/.cvsignore deleted file mode 100644 index 9730646f..00000000 --- a/plugins/ommysql/.cvsignore +++ /dev/null @@ -1,6 +0,0 @@ -.deps -.libs -Makefile -Makefile.in -*.la -*.lo diff --git a/plugins/ommysql/Makefile.am b/plugins/ommysql/Makefile.am index 6397de5b..e253b9da 100644 --- a/plugins/ommysql/Makefile.am +++ b/plugins/ommysql/Makefile.am @@ -1,8 +1,8 @@ pkglib_LTLIBRARIES = ommysql.la -ommysql_la_SOURCES = ommysql.c ommysql.h ../../module-template.h -ommysql_la_CPPFLAGS = $(mysql_cflags) -I$(srcdir)/../.. $(pthreads_cflags) +ommysql_la_SOURCES = ommysql.c ommysql.h +ommysql_la_CPPFLAGS = $(RSRT_CFLAGS) $(MYSQL_CFLAGS) $(PTHREADS_CFLAGS) ommysql_la_LDFLAGS = -module -avoid-version -ommysql_la_LIBADD = $(mysql_libs) +ommysql_la_LIBADD = $(MYSQL_LIBS) EXTRA_DIST = createDB.sql contrib/delete_mysql diff --git a/plugins/ommysql/ommysql.c b/plugins/ommysql/ommysql.c index 02ab68a7..ecf738a9 100644 --- a/plugins/ommysql/ommysql.c +++ b/plugins/ommysql/ommysql.c @@ -8,19 +8,20 @@ * * Copyright 2007 Rainer Gerhards and Adiscon GmbH. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. + * This file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ @@ -34,28 +35,36 @@ #include <signal.h> #include <errno.h> #include <time.h> -#include <mysql/mysql.h> -#include <mysql/errmsg.h> -#include "syslogd.h" +#include <mysql.h> +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "template.h" #include "ommysql.h" #include "module-template.h" +#include "errmsg.h" +#include "cfsysline.h" + +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) typedef struct _instanceData { MYSQL *f_hmysql; /* handle to MySQL */ char f_dbsrv[MAXHOSTNAMELEN+1]; /* IP or hostname of DB server*/ + unsigned int f_dbsrvPort; /* port of MySQL server */ char f_dbname[_DB_MAXDBLEN+1]; /* DB name */ char f_dbuid[_DB_MAXUNAMELEN+1]; /* DB user */ char f_dbpwd[_DB_MAXPWDLEN+1]; /* DB user's password */ unsigned uLastMySQLErrno; /* last errno returned by MySQL or 0 if all is well */ } instanceData; +/* config variables */ +static int iSrvPort = 0; /* database server port */ + BEGINcreateInstance CODESTARTcreateInstance @@ -75,7 +84,7 @@ ENDisCompatibleWithFeature */ static void closeMySQL(instanceData *pData) { - assert(pData != NULL); + ASSERT(pData != NULL); if(pData->f_hmysql != NULL) { /* just to be on the safe side... */ mysql_server_end(); @@ -90,27 +99,12 @@ CODESTARTfreeInstance ENDfreeInstance -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - BEGINdbgPrintInstInfo CODESTARTdbgPrintInstInfo /* nothing special here */ ENDdbgPrintInstInfo -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - /* log a database error with descriptive message. * We check if we have a valid MySQL handle. If not, we simply * report an error, but can not be specific. RGerhards, 2007-01-30 @@ -120,12 +114,12 @@ static void reportDBError(instanceData *pData, int bSilent) char errMsg[512]; unsigned uMySQLErrno; - assert(pData != NULL); + ASSERT(pData != NULL); /* output log message */ errno = 0; if(pData->f_hmysql == NULL) { - logerror("unknown DB error occured - could not obtain MySQL handle"); + errmsg.LogError(0, NO_ERRCODE, "unknown DB error occured - could not obtain MySQL handle"); } else { /* we can ask mysql for the error description... */ uMySQLErrno = mysql_errno(pData->f_hmysql); snprintf(errMsg, sizeof(errMsg)/sizeof(char), "db error (%d): %s\n", uMySQLErrno, @@ -134,7 +128,7 @@ static void reportDBError(instanceData *pData, int bSilent) dbgprintf("mysql, DBError(silent): %s\n", errMsg); else { pData->uLastMySQLErrno = uMySQLErrno; - logerror(errMsg); + errmsg.LogError(0, NO_ERRCODE, "%s", errMsg); } } @@ -150,24 +144,24 @@ static rsRetVal initMySQL(instanceData *pData, int bSilent) { DEFiRet; - assert(pData != NULL); - assert(pData->f_hmysql == NULL); + ASSERT(pData != NULL); + ASSERT(pData->f_hmysql == NULL); pData->f_hmysql = mysql_init(NULL); if(pData->f_hmysql == NULL) { - logerror("can not initialize MySQL handle"); + errmsg.LogError(0, RS_RET_SUSPENDED, "can not initialize MySQL handle"); iRet = RS_RET_SUSPENDED; } else { /* we could get the handle, now on with work... */ /* Connect to database */ if(mysql_real_connect(pData->f_hmysql, pData->f_dbsrv, pData->f_dbuid, - pData->f_dbpwd, pData->f_dbname, 0, NULL, 0) == NULL) { + pData->f_dbpwd, pData->f_dbname, pData->f_dbsrvPort, NULL, 0) == NULL) { reportDBError(pData, bSilent); closeMySQL(pData); /* ignore any error we may get */ iRet = RS_RET_SUSPENDED; } } - return iRet; + RETiRet; } @@ -179,8 +173,13 @@ rsRetVal writeMySQL(uchar *psz, instanceData *pData) { DEFiRet; - assert(psz != NULL); - assert(pData != NULL); + ASSERT(psz != NULL); + ASSERT(pData != NULL); + + /* see if we are ready to proceed */ + if(pData->f_hmysql == NULL) { + CHKiRet(initMySQL(pData, 0)); + } /* try insert */ if(mysql_query(pData->f_hmysql, (char*)psz)) { @@ -200,7 +199,7 @@ finalize_it: pData->uLastMySQLErrno = 0; /* reset error for error supression */ } - return iRet; + RETiRet; } @@ -239,9 +238,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) } /* ok, if we reach this point, we have something for us */ - if((iRet = createInstance(&pData)) != RS_RET_OK) - goto finalize_it; - + CHKiRet(createInstance(&pData)); /* rger 2004-10-28: added support for MySQL * >server,dbname,userid,password @@ -277,10 +274,11 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) * Retries make no sense. */ if (iMySQLPropErr) { - logerror("Trouble with MySQL connection properties. -MySQL logging disabled"); + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "Trouble with MySQL connection properties. -MySQL logging disabled"); ABORT_FINALIZE(RS_RET_INVALID_PARAMS); } else { - CHKiRet(initMySQL(pData, 0)); + pData->f_dbsrvPort = (unsigned) iSrvPort; /* set configured port */ + pData->f_hmysql = NULL; /* initialize, but connect only on first message (important for queued mode!) */ } CODE_STD_FINALIZERparseSelectorAct @@ -298,11 +296,24 @@ CODEqueryEtryPt_STD_OMOD_QUERIES ENDqueryEtryPt +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + iSrvPort = 0; /* zero is the default port */ + RETiRet; +} + BEGINmodInit() CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + /* register our config handlers */ + CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionommysqlserverport", 0, eCmdHdlrInt, NULL, &iSrvPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit -/* - * vi:set ai: + +/* vi:set ai: */ diff --git a/plugins/ommysql/ommysql.h b/plugins/ommysql/ommysql.h index d8d2d538..d8075785 100644 --- a/plugins/ommysql/ommysql.h +++ b/plugins/ommysql/ommysql.h @@ -5,19 +5,20 @@ * * Copyright 2007 Rainer Gerhards and Adiscon GmbH. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. + * This file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ diff --git a/plugins/ompgsql/.cvsignore b/plugins/ompgsql/.cvsignore deleted file mode 100644 index 9730646f..00000000 --- a/plugins/ompgsql/.cvsignore +++ /dev/null @@ -1,6 +0,0 @@ -.deps -.libs -Makefile -Makefile.in -*.la -*.lo diff --git a/plugins/ompgsql/Makefile.am b/plugins/ompgsql/Makefile.am index 5206b36d..607239cd 100644 --- a/plugins/ompgsql/Makefile.am +++ b/plugins/ompgsql/Makefile.am @@ -1,8 +1,8 @@ pkglib_LTLIBRARIES = ompgsql.la -ompgsql_la_SOURCES = ompgsql.c ompgsql.h ../../module-template.h -ompgsql_la_CPPFLAGS = $(pgsql_cflags) -I$(srcdir)/../.. +ompgsql_la_SOURCES = ompgsql.c ompgsql.h +ompgsql_la_CPPFLAGS = -I$(top_srcdir) $(PGSQL_CFLAGS) $(RSRT_CFLAGS) ompgsql_la_LDFLAGS = -module -avoid-version -ompgsql_la_LIBADD = $(pgsql_libs) +ompgsql_la_LIBADD = $(PGSQL_LIBS) EXTRA_DIST = createDB.sql diff --git a/plugins/ompgsql/ompgsql.c b/plugins/ompgsql/ompgsql.c index 937539b9..6daac1c7 100644 --- a/plugins/ompgsql/ompgsql.c +++ b/plugins/ompgsql/ompgsql.c @@ -12,19 +12,20 @@ * when setting up a test environment (on Fedora): * http://www.jboss.org/community/wiki/InstallPostgreSQLonFedora * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. + * This file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ @@ -39,16 +40,20 @@ #include <errno.h> #include <time.h> #include <libpq-fe.h> -#include "syslogd.h" +#include "dirty.h" #include "syslogd-types.h" #include "srUtils.h" #include "template.h" #include "ompgsql.h" #include "module-template.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT /* internal structures */ DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) typedef struct _instanceData { PGconn *f_hpgsql; /* handle to PgSQL */ @@ -91,27 +96,12 @@ CODESTARTfreeInstance ENDfreeInstance -BEGINneedUDPSocket -CODESTARTneedUDPSocket -ENDneedUDPSocket - - BEGINdbgPrintInstInfo CODESTARTdbgPrintInstInfo /* nothing special here */ ENDdbgPrintInstInfo -BEGINonSelectReadyWrite -CODESTARTonSelectReadyWrite -ENDonSelectReadyWrite - - -BEGINgetWriteFDForSelect -CODESTARTgetWriteFDForSelect -ENDgetWriteFDForSelect - - /* log a database error with descriptive message. * We check if we have a valid handle. If not, we simply * report an error, but can not be specific. RGerhards, 2007-01-30 @@ -127,7 +117,7 @@ static void reportDBError(instanceData *pData, int bSilent) /* output log message */ errno = 0; if(pData->f_hpgsql == NULL) { - logerror("unknown DB error occured - could not obtain PgSQL handle"); + errmsg.LogError(0, NO_ERRCODE, "unknown DB error occured - could not obtain PgSQL handle"); } else { /* we can ask pgsql for the error description... */ ePgSQLStatus = PQstatus(pData->f_hpgsql); snprintf(errMsg, sizeof(errMsg)/sizeof(char), "db error (%d): %s\n", ePgSQLStatus, @@ -136,7 +126,7 @@ static void reportDBError(instanceData *pData, int bSilent) dbgprintf("pgsql, DBError(silent): %s\n", errMsg); else { pData->eLastPgSQLStatus = ePgSQLStatus; - logerror(errMsg); + errmsg.LogError(0, NO_ERRCODE, "%s", errMsg); } } @@ -164,7 +154,7 @@ static rsRetVal initPgSQL(instanceData *pData, int bSilent) iRet = RS_RET_SUSPENDED; } - return iRet; + RETiRet; } @@ -229,7 +219,7 @@ finalize_it: pData->eLastPgSQLStatus = CONNECTION_OK; /* reset error for error supression */ } - return iRet; + RETiRet; } @@ -307,7 +297,7 @@ CODE_STD_STRING_REQUESTparseSelectorAct(1) * Retries make no sense. */ if (iPgSQLPropErr) { - logerror("Trouble with PgSQL connection properties. -PgSQL logging disabled"); + errmsg.LogError(0, RS_RET_INVALID_PARAMS, "Trouble with PgSQL connection properties. -PgSQL logging disabled"); ABORT_FINALIZE(RS_RET_INVALID_PARAMS); } else { CHKiRet(initPgSQL(pData, 0)); @@ -329,8 +319,9 @@ ENDqueryEtryPt BEGINmodInit() CODESTARTmodInit - *ipIFVersProvided = 1; /* so far, we only support the initial definition */ + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); ENDmodInit /* vi:set ai: */ diff --git a/plugins/ompgsql/ompgsql.h b/plugins/ompgsql/ompgsql.h index 8285ac33..495291f4 100644 --- a/plugins/ompgsql/ompgsql.h +++ b/plugins/ompgsql/ompgsql.h @@ -5,19 +5,20 @@ * * Copyright 2007 Rainer Gerhards and Adiscon GmbH. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. + * This file is part of rsyslog. * - * This program is distributed in the hope that it will be useful, + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ diff --git a/plugins/omrelp/Makefile.am b/plugins/omrelp/Makefile.am new file mode 100644 index 00000000..906aab43 --- /dev/null +++ b/plugins/omrelp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omrelp.la + +omrelp_la_SOURCES = omrelp.c +omrelp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RELP_CFLAGS) $(RSRT_CFLAGS) +omrelp_la_LDFLAGS = -module -avoid-version +omrelp_la_LIBADD = $(RELP_LIBS) diff --git a/plugins/omrelp/omrelp.c b/plugins/omrelp/omrelp.c new file mode 100644 index 00000000..8d74c82f --- /dev/null +++ b/plugins/omrelp/omrelp.c @@ -0,0 +1,341 @@ +/* omrelp.c + * + * This is the implementation of the RELP output module. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2008-03-13 by RGerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> +#include <librelp.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "cfsysline.h" +#include "module-template.h" +#include "glbl.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) +DEFobjCurrIf(glbl) + +static relpEngine_t *pRelpEngine; /* our relp engine */ + +typedef struct _instanceData { + char *f_hname; + int compressionLevel; /* 0 - no compression, else level for zlib */ + char *port; + int bInitialConnect; /* is this the initial connection request of our module? (0-no, 1-yes) */ + int bIsConnected; /* currently connected to server? 0 - no, 1 - yes */ + relpClt_t *pRelpClt; /* relp client for this instance */ +} instanceData; + +/* get the syslog forward port from selector_t. The passed in + * struct must be one that is setup for forwarding. + * rgerhards, 2007-06-28 + * We may change the implementation to try to lookup the port + * if it is unspecified. So far, we use the IANA default auf 514. + */ +static char *getRelpPt(instanceData *pData) +{ + assert(pData != NULL); + if(pData->port == NULL) + return("514"); + else + return(pData->port); +} + +BEGINcreateInstance +CODESTARTcreateInstance + pData->bInitialConnect = 1; +ENDcreateInstance + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + if(eFeat == sFEATURERepeatedMsgReduction) + iRet = RS_RET_OK; +ENDisCompatibleWithFeature + + +BEGINfreeInstance +CODESTARTfreeInstance + if(pData->port != NULL) + free(pData->port); + + /* final cleanup */ + if(pData->pRelpClt != NULL) + relpEngineCltDestruct(pRelpEngine, &pData->pRelpClt); + + if(pData->f_hname != NULL) + free(pData->f_hname); + +ENDfreeInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + printf("RELP/%s", pData->f_hname); +ENDdbgPrintInstInfo + + +/* try to connect to server + * rgerhards, 2008-03-21 + */ +static rsRetVal doConnect(instanceData *pData) +{ + DEFiRet; + + if(pData->bInitialConnect) { + iRet = relpCltConnect(pData->pRelpClt, glbl.GetDefPFFamily(), (uchar*) pData->port, (uchar*) pData->f_hname); + if(iRet == RELP_RET_OK) + pData->bInitialConnect = 0; + } else { + iRet = relpCltReconnect(pData->pRelpClt); + } + + if(iRet == RELP_RET_OK) { + pData->bIsConnected = 1; + } else { + pData->bIsConnected = 0; + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = doConnect(pData); +ENDtryResume + + +BEGINdoAction + uchar *pMsg; /* temporary buffering */ + size_t lenMsg; + relpRetVal ret; +CODESTARTdoAction + dbgprintf(" %s:%s/RELP\n", pData->f_hname, getRelpPt(pData)); + + if(!pData->bIsConnected) { + CHKiRet(doConnect(pData)); + } + + pMsg = ppString[0]; + lenMsg = strlen((char*) pMsg); /* TODO: don't we get this? */ + + /* TODO: think about handling oversize messages! */ + if((int) lenMsg > glbl.GetMaxLine()) + lenMsg = glbl.GetMaxLine(); + + /* forward */ + ret = relpCltSendSyslog(pData->pRelpClt, (uchar*) pMsg, lenMsg); + if(ret != RELP_RET_OK) { + /* error! */ + dbgprintf("error forwarding via relp, suspending\n"); + iRet = RS_RET_SUSPENDED; + } + +finalize_it: +ENDdoAction + + +BEGINparseSelectorAct + uchar *q; + int i; + int bErr; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omrelp:", sizeof(":omrelp:") - 1)) { + p += sizeof(":omrelp:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + FINALIZE; + + /* 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: + * :omrelp:(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 + * TODO: think of all this in spite of RELP -- rgerhards, 2008-03-13 + */ + 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."); + } + /* 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 + */ + for(q = 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 relp 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 getRelpPt() */ + } else { + memcpy(pData->port, tmp, i); + *(pData->port + i) = '\0'; + } + } + + /* now skip to template */ + bErr = 0; + while(*p && *p != ';') { + if(*p && *p != ';' && !isspace((int) *p)) { + if(bErr == 0) { /* only 1 error msg! */ + bErr = 1; + errno = 0; + errmsg.LogError(0, NO_ERRCODE, "invalid selector line (port), probably not doing " + "what was intended"); + } + } + ++p; + } + + /* TODO: make this if go away! */ + if(*p == ';') { + *p = '\0'; /* trick to obtain hostname (later)! */ + CHKmalloc(pData->f_hname = strdup((char*) q)); + *p = ';'; + } else { + CHKmalloc(pData->f_hname = strdup((char*) q)); + } + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_ForwardFormat")); + + /* create our relp client */ + CHKiRet(relpEngineCltConstruct(pRelpEngine, &pData->pRelpClt)); /* we use CHKiRet as librelp has a similar return value range */ + + /* TODO: do we need to call freeInstance if we failed - this is a general question for + * all output modules. I'll address it later as the interface evolves. rgerhards, 2007-07-25 + */ +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit + relpEngineDestruct(&pRelpEngine); + + /* release what we no longer need */ + objRelease(glbl, CORE_COMPONENT); + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + /* create our relp engine */ + CHKiRet(relpEngineConstruct(&pRelpEngine)); + CHKiRet(relpEngineSetDbgprint(pRelpEngine, dbgprintf)); + CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar*) "syslog", eRelpCmdState_Required)); + + /* tell which objects we need */ + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); +ENDmodInit + +/* vim:set ai: + */ diff --git a/plugins/omsnmp/Makefile.am b/plugins/omsnmp/Makefile.am new file mode 100644 index 00000000..f75fb091 --- /dev/null +++ b/plugins/omsnmp/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omsnmp.la + +omsnmp_la_SOURCES = omsnmp.c omsnmp.h +omsnmp_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omsnmp_la_LDFLAGS = -module -avoid-version +omsnmp_la_LIBADD = $(SNMP_LIBS) diff --git a/plugins/omsnmp/mibs/ADISCON-MIB.txt b/plugins/omsnmp/mibs/ADISCON-MIB.txt new file mode 100644 index 00000000..741ea84f --- /dev/null +++ b/plugins/omsnmp/mibs/ADISCON-MIB.txt @@ -0,0 +1,38 @@ +-- ***************************************************************** +-- ADISCON-RSYSLOG-MIB.txt: Adiscon RSyslog message MIB file +-- +-- March 2008, Andre Lorbach +-- +-- Copyright (c) 2008 by Adiscon GmbH +-- All rights reserved. +-- ***************************************************************** +-- +-- This is a basic MIB which defines our main enterprise OID + +ADISCON-MIB DEFINITIONS ::= BEGIN + +-- +-- Top-level infrastructure for the Adiscon enterprise MIB tree +-- + +IMPORTS + MODULE-IDENTITY, enterprises FROM SNMPv2-SMI; + +adiscon MODULE-IDENTITY + LAST-UPDATED "200803040000Z" + ORGANIZATION "www.adiscon.com" + CONTACT-INFO + "postal: Adiscon GmbH + Mozartstrasse 21 + D-97950 Großrinderfeld + Deutschland + + email: info@adiscon.com" + DESCRIPTION + "Top-level infrastructure for the Adiscon enterprise MIB tree" + REVISION "200803040000Z" + DESCRIPTION + "First draft" + ::= { enterprises 19406} + +END diff --git a/plugins/omsnmp/mibs/ADISCON-MONITORWARE-MIB.txt b/plugins/omsnmp/mibs/ADISCON-MONITORWARE-MIB.txt new file mode 100644 index 00000000..d26d7746 --- /dev/null +++ b/plugins/omsnmp/mibs/ADISCON-MONITORWARE-MIB.txt @@ -0,0 +1,362 @@ +-- ***************************************************************** +-- ADISCON-MONITORWARE-MIB.txt: Adiscon Monitorware message MIB file +-- +-- March 2008, Andre Lorbach +-- +-- Copyright (c) 2008 by Adiscon GmbH +-- All rights reserved. +-- ***************************************************************** +-- +-- This MIB defines traps and variables to wrap syslog messages into +-- snmp traps. + +ADISCON-MONITORWARE-MIB DEFINITIONS ::= BEGIN + +IMPORTS + enterprises, + MODULE-IDENTITY, OBJECT-TYPE, Integer32, + NOTIFICATION-TYPE FROM SNMPv2-SMI, + adiscon FROM ADISCON-MIB +; + +monitorware MODULE-IDENTITY + LAST-UPDATED "200803050000Z" + ORGANIZATION "www.adiscon-com" + CONTACT-INFO + "postal: Adiscon GmbH + Mozartstrasse 21 + D-97950 Großrinderfeld + Deutschland + + email: info@adiscon.com" + DESCRIPTION + "This MIB defines traps and variables to wrap syslog messages into snmp traps." + REVISION "200803040000Z" + DESCRIPTION + "Added a few new variables for the representation of MonitorWare properties. Also added a few new traps." + REVISION "200803050000Z" + DESCRIPTION + "First draft" + ::= { adiscon 1 } + +-- Printable string, using the ISO 8859-1 character set. +DisplayString ::= OCTET STRING (SIZE (0..255)) +SmallString ::= OCTET STRING (SIZE (0..64)) +-- + +-- +-- top level structure +-- +-- adiscon OBJECT IDENTIFIER ::= { enterprises 19406 } +monitorware OBJECT IDENTIFIER ::= { adiscon 1 } +monitorwarevars OBJECT IDENTIFIER ::= { monitorware 1 } +monitorwaretraps OBJECT IDENTIFIER ::= { monitorware 2 } +genericvars OBJECT IDENTIFIER ::= { monitorwarevars 1 } +syslogvars OBJECT IDENTIFIER ::= { monitorwarevars 2 } +eventlogvars OBJECT IDENTIFIER ::= { monitorwarevars 3 } +filemonvars OBJECT IDENTIFIER ::= { monitorwarevars 4 } +ntservicemonvars OBJECT IDENTIFIER ::= { monitorwarevars 5 } + +-- ***************************************************************** +-- Trap variables +-- ***************************************************************** + +syslogMsg OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Syslog Message, this will contain the full + syslog message including the full syslog header" + ::= { syslogvars 1 } + +syslogSeverity OBJECT-TYPE + SYNTAX INTEGER { + emergency (0), + alert (1), + critical (2), + error (3), + warning (4), + notice (5), + info (6), + debug (7) + } + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Syslog severity(priority)." + DEFVAL { 5 } + ::= { syslogvars 2 } + +syslogFacility OBJECT-TYPE + SYNTAX INTEGER { + kern (0), + user (1), + mail (2), + daemon (3), + auth (4), + syslog (5), + lpr (6), + news (7), + uucp (8), + cron (9), + local0 (16), + local1 (17), + local2 (18), + local3 (19), + local4 (20), + local5 (21), + local6 (22), + local7 (23) + } + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Syslog facility." + DEFVAL { 16 } + ::= { syslogvars 3 } + +syslogTag OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Contains the SyslogTag Value." + ::= { syslogvars 4 } + +genCustomerID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Property CustomerID." + ::= { genericvars 1 } + +genSystemID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Property SystemID." + ::= { genericvars 2 } + +genSource OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Source Property." + ::= { genericvars 3 } + +genTimereported OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Timestamp of when the event was reported." + ::= { genericvars 4 } + +genTimegenerated OBJECT-TYPE + SYNTAX TimeTicks + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Timestamp of when the event was generated." + ::= { genericvars 5 } + +genIut OBJECT-TYPE + SYNTAX INTEGER { + Unknown (0), + Syslog (1), + Heartbeat (2), + NTEventReport (3), + SNMPTrap (4), + FileMonitor (5), + PingProbe (8), + PortProbe (9), + NTServiceMonitor (10), + DiskSpaceMonitor (11), + DBMonitor (12), + SerialMonitor (13), + CPUMonitor (14), + AliveMonRequest (16), + SMTPProbe (17), + FTPProbe (18), + HTTPProbe (19), + POP3Probe (20), + IMAPProbe (21), + NNTPProbe (22), + WEVTMONV2 (23), + SMTPLISTENER (24), + SNMPMONITOR (25), + AliveMonECHO (1999998) + } + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "InfoUnit TypeID, defines from which Source the event is derived from." + DEFVAL { 0 } + ::= { genericvars 6 } + +genMsg OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Message for this event" + ::= { genericvars 7 } + +eventlogEventID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "EventID of the EventLog Entry" + ::= { eventlogvars 1 } + +eventlogEventType OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "EventLog Type of the EventLog Entry (Like Application, Security or System)" + ::= { eventlogvars 2 } + +eventlogEventSource OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "EventLog Source of the EventLog Entry" + ::= { eventlogvars 3 } + +eventlogEventCategoryID OBJECT-TYPE + SYNTAX Integer32 (1..2147483647) + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Event Category number of the EventLog Entry" + ::= { eventlogvars 4 } + +eventlogEventCategoryName OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Event Category name of the EventLog Entry" + ::= { eventlogvars 5 } + +eventlogEventUser OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Event User of the EventLog Entry" + ::= { eventlogvars 6 } + +filemonGenericFilename OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generic Filename template used to create the filename" + ::= { filemonvars 1 } + +filemonGeneratedFilename OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Generated Filename, the source file of this event." + ::= { filemonvars 2 } + +filemonMsgseperator OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "The message seperator which was used." + ::= { filemonvars 3 } + +ntserviceServiceName OBJECT-TYPE + SYNTAX SmallString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Internal Name of the monitored service." + ::= { ntservicemonvars 1 } + +ntserviceServiceDisplayName OBJECT-TYPE + SYNTAX DisplayString + MAX-ACCESS accessible-for-notify + STATUS current + DESCRIPTION + "Display Name of the monitored service." + ::= { ntservicemonvars 2 } + + +-- ***************************************************************** +-- Trap definitions +-- ***************************************************************** + +syslogtrap NOTIFICATION-TYPE + OBJECTS { syslogMsg, + syslogSeverity, + syslogFacility + } + STATUS current + DESCRIPTION + "Syslogmessage Trap." +::= { monitorwaretraps 1 } + +monitorwaretrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + genTimegenerated, + genIut + } + STATUS current + DESCRIPTION + "Generic Trap from monitorware events." +::= { monitorwaretraps 2 } + +eventmontrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + eventlogEventID, + eventlogEventType, + eventlogEventSource, + eventlogEventCategoryID, + eventlogEventCategoryName, + eventlogEventUser + } + STATUS current + DESCRIPTION + "Trap generated by the EventLog Monitor." +::= { monitorwaretraps 3 } + +filemontrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + genTimegenerated, + filemonGenericFilename, + filemonGeneratedFilename + } + STATUS current + DESCRIPTION + "Trap generated by the FileMonitor." +::= { monitorwaretraps 4 } + +ntservicetrap NOTIFICATION-TYPE + OBJECTS { genMsg, + genSource, + genTimegenerated, + ntserviceServiceName, + ntserviceServiceDisplayName + } + STATUS current + DESCRIPTION + "Trap generated by the NT Service Monitor." +::= { monitorwaretraps 5 } + +END diff --git a/plugins/omsnmp/omsnmp.c b/plugins/omsnmp/omsnmp.c new file mode 100644 index 00000000..72fa8d64 --- /dev/null +++ b/plugins/omsnmp/omsnmp.c @@ -0,0 +1,533 @@ +/* omsnmp.c + * + * This module sends an snmp trap. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netdb.h> +#include <ctype.h> +#include <assert.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "cfsysline.h" +#include "module-template.h" + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-includes.h> +#include "omsnmp.h" +#include "errmsg.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA +DEFobjCurrIf(errmsg) + +/* Default static snmp OID's */ +/*unused +static oid objid_enterprise[] = { 1, 3, 6, 1, 4, 1, 3, 1, 1 }; +static oid objid_sysdescr[] = { 1, 3, 6, 1, 2, 1, 1, 1, 0 }; +*/ +static oid objid_snmptrap[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 }; +static oid objid_sysuptime[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 }; + +static uchar* pszTransport = NULL; /* default transport */ +static uchar* pszTarget = NULL; +/* note using an unsigned for a port number is not a good idea from an IPv6 point of view */ +static int iPort = 0; +static int iSNMPVersion = 1; /* 0 Means SNMPv1, 1 Means SNMPv2c */ +static uchar* pszCommunity = NULL; +static uchar* pszEnterpriseOID = NULL; +static uchar* pszSnmpTrapOID = NULL; +static uchar* pszSyslogMessageOID = NULL; +static int iSpecificType = 0; +static int iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC;/*Default is SNMP_TRAP_ENTERPRISESPECIFIC */ +/* + Possible Values + SNMP_TRAP_COLDSTART (0) + SNMP_TRAP_WARMSTART (1) + SNMP_TRAP_LINKDOWN (2) + SNMP_TRAP_LINKUP (3) + SNMP_TRAP_AUTHFAIL (4) + SNMP_TRAP_EGPNEIGHBORLOSS (5) + SNMP_TRAP_ENTERPRISESPECIFIC (6) +*/ + +typedef struct _instanceData { + uchar szTransport[OMSNMP_MAXTRANSPORLENGTH+1]; /* Transport - Can be udp, tcp, udp6, tcp6 and other types supported by NET-SNMP */ + uchar *szTarget; /* IP/hostname of Snmp Target*/ + uchar *szTargetAndPort; /* IP/hostname + Port,needed format for SNMP LIB */ + uchar szCommunity[OMSNMP_MAXCOMMUNITYLENGHT+1]; /* Snmp Community */ + uchar szEnterpriseOID[OMSNMP_MAXOIDLENGHT+1]; /* Snmp Enterprise OID - default is (1.3.6.1.4.1.3.1.1 = enterprises.cmu.1.1) */ + uchar szSnmpTrapOID[OMSNMP_MAXOIDLENGHT+1]; /* Snmp Trap OID - default is (1.3.6.1.4.1.19406.1.2.1 = ADISCON-MONITORWARE-MIB::syslogtrap) */ + uchar szSyslogMessageOID[OMSNMP_MAXOIDLENGHT+1]; /* Snmp OID used for the Syslog Message: + * default is 1.3.6.1.4.1.19406.1.1.2.1 - ADISCON-MONITORWARE-MIB::syslogMsg + * You will need the ADISCON-MONITORWARE-MIB and ADISCON-MIB mibs installed on the receiver + * side in order to decode this mib. + * Downloads of these mib files can be found here: + * http://www.adiscon.org/download/ADISCON-MONITORWARE-MIB.txt + * http://www.adiscon.org/download/ADISCON-MIB.txt + */ + int iPort; /* Target Port */ + int iSNMPVersion; /* SNMP Version to use */ + int iTrapType; /* Snmp TrapType or GenericType */ + int iSpecificType; /* Snmp Specific Type */ + + netsnmp_session *snmpsession; /* Holds to SNMP Session, NULL if not initialized */ +} instanceData; + +BEGINcreateInstance +CODESTARTcreateInstance +ENDcreateInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("SNMPTransport: %s\n", pData->szTransport); + dbgprintf("SNMPTarget: %s\n", pData->szTarget); + dbgprintf("SNMPPort: %d\n", pData->iPort); + dbgprintf("SNMPTarget+PortStr: %s\n", pData->szTargetAndPort); + dbgprintf("SNMPVersion (0=v1, 1=v2c): %d\n", pData->iSNMPVersion); + dbgprintf("Community: %s\n", pData->szCommunity); + dbgprintf("EnterpriseOID: %s\n", pData->szEnterpriseOID); + dbgprintf("SnmpTrapOID: %s\n", pData->szSnmpTrapOID); + dbgprintf("SyslogMessageOID: %s\n", pData->szSyslogMessageOID); + dbgprintf("TrapType: %d\n", pData->iTrapType); + dbgprintf("SpecificType: %d\n", pData->iSpecificType); +ENDdbgPrintInstInfo + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we are not compatible with repeated msg reduction feature, so do not allow it */ +ENDisCompatibleWithFeature + +/* Exit SNMP Session + * alorbach, 2008-02-12 + */ +static rsRetVal omsnmp_exitSession(instanceData *pData) +{ + DEFiRet; + + if(pData->snmpsession != NULL) { + dbgprintf( "omsnmp_exitSession: Clearing Session to '%s' on Port = '%d'\n", pData->szTarget, pData->iPort); + snmp_close(pData->snmpsession); + pData->snmpsession = NULL; + } + + RETiRet; +} + +/* Init SNMP Session + * alorbach, 2008-02-12 + */ +static rsRetVal omsnmp_initSession(instanceData *pData) +{ + DEFiRet; + netsnmp_session session; + + /* should not happen, but if session is not cleared yet - we do it now! */ + if (pData->snmpsession != NULL) + omsnmp_exitSession(pData); + + dbgprintf( "omsnmp_initSession: ENTER - Target = '%s' on Port = '%d'\n", pData->szTarget, pData->iPort); + + putenv(strdup("POSIXLY_CORRECT=1")); + + snmp_sess_init(&session); + session.version = pData->iSNMPVersion; + session.callback = NULL; /* NOT NEEDED */ + session.callback_magic = NULL; + session.peername = (char*) pData->szTargetAndPort; + + /* Set SNMP Community */ + if (session.version == SNMP_VERSION_1 || session.version == SNMP_VERSION_2c) + { + session.community = (unsigned char *) pData->szCommunity; + session.community_len = strlen((char*) pData->szCommunity); + } + + pData->snmpsession = snmp_open(&session); + if (pData->snmpsession == NULL) { + errmsg.LogError(0, RS_RET_SUSPENDED, "omsnmp_initSession: snmp_open to host '%s' on Port '%d' failed\n", pData->szTarget, pData->iPort); + /* Stay suspended */ + iRet = RS_RET_SUSPENDED; + } + + RETiRet; +} + +static rsRetVal omsnmp_sendsnmp(instanceData *pData, uchar *psz) +{ + DEFiRet; + + netsnmp_pdu *pdu = NULL; + oid enterpriseoid[MAX_OID_LEN]; + size_t enterpriseoidlen = MAX_OID_LEN; + oid oidSyslogMessage[MAX_OID_LEN]; + size_t oLen = MAX_OID_LEN; + int status; + char *trap = NULL; + const char *strErr = NULL; + + /* Init SNMP Session if necessary */ + if (pData->snmpsession == NULL) { + CHKiRet(omsnmp_initSession(pData)); + } + + /* String should not be NULL */ + ASSERT(psz != NULL); + dbgprintf( "omsnmp_sendsnmp: ENTER - Syslogmessage = '%s'\n", (char*)psz); + + /* If SNMP Version1 is configured !*/ + if ( pData->snmpsession->version == SNMP_VERSION_1) + { + pdu = snmp_pdu_create(SNMP_MSG_TRAP); + + /* Set enterprise */ + if (!snmp_parse_oid( (char*) pData->szEnterpriseOID, enterpriseoid, &enterpriseoidlen )) + { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Parsing EnterpriseOID failed '%s' with error '%s' \n", pData->szSyslogMessageOID, strErr); + + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + pdu->enterprise = (oid *) malloc(enterpriseoidlen * sizeof(oid)); + memcpy(pdu->enterprise, enterpriseoid, enterpriseoidlen * sizeof(oid)); + pdu->enterprise_length = enterpriseoidlen; + + /* Set Traptype */ + pdu->trap_type = pData->iTrapType; + + /* Set SpecificType */ + pdu->specific_type = pData->iSpecificType; + + /* Set Updtime */ + pdu->time = get_uptime(); + } + /* If SNMP Version2c is configured !*/ + else if (pData->snmpsession->version == SNMP_VERSION_2c) + { + long sysuptime; + char csysuptime[20]; + + /* Create PDU */ + pdu = snmp_pdu_create(SNMP_MSG_TRAP2); + + /* Set uptime */ + sysuptime = get_uptime(); + snprintf( csysuptime, sizeof(csysuptime) , "%ld", sysuptime); + trap = csysuptime; + snmp_add_var(pdu, objid_sysuptime, sizeof(objid_sysuptime) / sizeof(oid), 't', trap); + + /* Now set the SyslogMessage Trap OID */ + if ( snmp_add_var(pdu, objid_snmptrap, sizeof(objid_snmptrap) / sizeof(oid), 'o', (char*) pData->szSnmpTrapOID ) != 0) + { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Adding trap OID failed '%s' with error '%s' \n", pData->szSnmpTrapOID, strErr); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + } + + /* SET TRAP PARAMETER for SyslogMessage! */ +/* dbgprintf( "omsnmp_sendsnmp: SyslogMessage '%s'\n", psz );*/ + + /* First create new OID object */ + if (snmp_parse_oid( (char*) pData->szSyslogMessageOID, oidSyslogMessage, &oLen)) + { + int iErrCode = snmp_add_var(pdu, oidSyslogMessage, oLen, 's', (char*) psz); + if (iErrCode) + { + const char *str = snmp_api_errstring(iErrCode); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Invalid SyslogMessage OID, error code '%d' - '%s'\n", iErrCode, str ); + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + } + else + { + strErr = snmp_api_errstring(snmp_errno); + errmsg.LogError(0, RS_RET_DISABLE_ACTION, "omsnmp_sendsnmp: Parsing SyslogMessageOID failed '%s' with error '%s' \n", pData->szSyslogMessageOID, strErr); + + ABORT_FINALIZE(RS_RET_DISABLE_ACTION); + } + + /* Send the TRAP */ + status = snmp_send(pData->snmpsession, pdu) == 0; + if (status) + { + /* Debug Output! */ + int iErrorCode = pData->snmpsession->s_snmp_errno; + errmsg.LogError(0, RS_RET_SUSPENDED, "omsnmp_sendsnmp: snmp_send failed error '%d', Description='%s'\n", iErrorCode*(-1), api_errors[iErrorCode*(-1)]); + + /* Clear Session */ + omsnmp_exitSession(pData); + + ABORT_FINALIZE(RS_RET_SUSPENDED); + } + +finalize_it: + if(iRet != RS_RET_OK) { + if(pdu != NULL) { + snmp_free_pdu(pdu); + } + } + + dbgprintf( "omsnmp_sendsnmp: LEAVE\n"); + RETiRet; +} + + +BEGINtryResume +CODESTARTtryResume + iRet = omsnmp_initSession(pData); +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + /* Abort if the STRING is not set, should never happen */ + if (ppString[0] == NULL) { + ABORT_FINALIZE(RS_RET_INVALID_PARAMS); + } + + /* This will generate and send the SNMP Trap */ + iRet = omsnmp_sendsnmp(pData, ppString[0]); +finalize_it: +ENDdoAction + +BEGINfreeInstance +CODESTARTfreeInstance + /* free snmp Session here */ + omsnmp_exitSession(pData); + + if(pData->szTarget != NULL) + free(pData->szTarget); + if(pData->szTargetAndPort != NULL) + free(pData->szTargetAndPort); + +ENDfreeInstance + + +BEGINparseSelectorAct + uchar szTargetAndPort[MAXHOSTNAMELEN+128]; /* work buffer for specifying a full target and port string */ +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(1) + if(!strncmp((char*) p, ":omsnmp:", sizeof(":omsnmp:") - 1)) { + p += sizeof(":omsnmp:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + FINALIZE; + + /* Check Transport */ + if (pszTransport == NULL) { + /* + * Default transport is UDP. Other values supported by NETSNMP are possible as well + */ + strncpy( (char*) pData->szTransport, "udp", sizeof("udp") ); + } else { + /* Copy Transport */ + strncpy( (char*) pData->szTransport, (char*) pszTransport, strlen((char*) pszTransport) ); + } + + /* Check Target */ + if (pszTarget == NULL) { + ABORT_FINALIZE( RS_RET_PARAM_ERROR ); + } else { + /* Copy Target */ + CHKmalloc(pData->szTarget = (uchar*) strdup((char*)pszTarget)); + } + + /* Copy Community */ + if (pszCommunity == NULL) /* Failsave */ + strncpy( (char*) pData->szCommunity, "public", sizeof("public") ); + else /* Copy Target */ + strncpy( (char*) pData->szCommunity, (char*) pszCommunity, strlen((char*) pszCommunity) ); + + /* Copy Enterprise OID */ + if (pszEnterpriseOID == NULL) /* Failsave */ + strncpy( (char*) pData->szEnterpriseOID, "1.3.6.1.4.1.3.1.1", sizeof("1.3.6.1.4.1.3.1.1") ); + else /* Copy Target */ + strncpy( (char*) pData->szEnterpriseOID, (char*) pszEnterpriseOID, strlen((char*) pszEnterpriseOID) ); + + /* Copy SnmpTrap OID */ + if (pszSnmpTrapOID == NULL) /* Failsave */ + strncpy( (char*) pData->szSnmpTrapOID, "1.3.6.1.4.1.19406.1.2.1", sizeof("1.3.6.1.4.1.19406.1.2.1") ); + else /* Copy Target */ + strncpy( (char*) pData->szSnmpTrapOID, (char*) pszSnmpTrapOID, strlen((char*) pszSnmpTrapOID) ); + + + /* Copy SyslogMessage OID */ + if (pszSyslogMessageOID == NULL) /* Failsave */ + strncpy( (char*) pData->szSyslogMessageOID, "1.3.6.1.4.1.19406.1.1.2.1", sizeof("1.3.6.1.4.1.19406.1.1.2.1") ); + else /* Copy Target */ + strncpy( (char*) pData->szSyslogMessageOID, (char*) pszSyslogMessageOID, strlen((char*) pszSyslogMessageOID) ); + + /* Copy Port */ + if ( iPort == 0) /* If no Port is set we use the default Port 162 */ + pData->iPort = 162; + else + pData->iPort = iPort; + + /* Set SNMPVersion */ + if ( iSNMPVersion < 0 || iSNMPVersion > 1) /* Set default to 1 if out of range */ + pData->iSNMPVersion = 1; + else + pData->iSNMPVersion = iSNMPVersion; + + /* Copy SpecificType */ + if ( iSpecificType == 0) /* If no iSpecificType is set, we use the default 0 */ + pData->iSpecificType = 0; + else + pData->iSpecificType = iSpecificType; + + /* Copy TrapType */ + if ( iTrapType < 0 && iTrapType >= 6) /* Only allow values from 0 to 6 !*/ + pData->iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + else + pData->iTrapType = iTrapType; + + /* Create string for session peername! */ + snprintf((char*)szTargetAndPort, sizeof(szTargetAndPort), "%s:%s:%d", pData->szTransport, pData->szTarget, pData->iPort); + CHKmalloc(pData->szTargetAndPort = (uchar*)strdup((char*)szTargetAndPort)); + + /* Print Debug info */ + dbgprintf("SNMPTransport: %s\n", pData->szTransport); + dbgprintf("SNMPTarget: %s\n", pData->szTarget); + dbgprintf("SNMPPort: %d\n", pData->iPort); + dbgprintf("SNMPTarget+PortStr: %s\n", pData->szTargetAndPort); + dbgprintf("SNMPVersion (0=v1, 1=v2c): %d\n", pData->iSNMPVersion); + dbgprintf("Community: %s\n", pData->szCommunity); + dbgprintf("EnterpriseOID: %s\n", pData->szEnterpriseOID); + dbgprintf("SnmpTrapOID: %s\n", pData->szSnmpTrapOID); + dbgprintf("SyslogMessageOID: %s\n", pData->szSyslogMessageOID); + dbgprintf("TrapType: %d\n", pData->iTrapType); + dbgprintf("SpecificType: %d\n", pData->iSpecificType); + + /* process template */ + CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) "RSYSLOG_TraditionalForwardFormat")); + + /* Init NetSNMP library and read in MIB database */ + init_snmp("rsyslog"); + + /* Set some defaults in the NetSNMP library */ + netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DEFAULT_PORT, pData->iPort ); + + /* Init Session Pointer */ + pData->snmpsession = NULL; +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +/* Reset config variables for this module to default values. + */ +static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + DEFiRet; + + if (pszTarget != NULL) + free(pszTarget); + pszTarget = NULL; + + if (pszCommunity != NULL) + free(pszCommunity); + pszCommunity = NULL; + + if (pszEnterpriseOID != NULL) + free(pszEnterpriseOID); + pszEnterpriseOID = NULL; + + if (pszSnmpTrapOID != NULL) + free(pszSnmpTrapOID); + pszSnmpTrapOID = NULL; + + if (pszSyslogMessageOID != NULL) + free(pszSyslogMessageOID); + pszSyslogMessageOID = NULL; + + iPort = 0; + iSNMPVersion = 1; + iSpecificType = 0; + iTrapType = SNMP_TRAP_ENTERPRISESPECIFIC; + + RETiRet; +} + + +BEGINmodExit +CODESTARTmodExit + if (pszTarget != NULL) + free(pszTarget); + if (pszCommunity != NULL) + free(pszCommunity); + if (pszEnterpriseOID != NULL) + free(pszEnterpriseOID); + if (pszSnmpTrapOID != NULL) + free(pszSnmpTrapOID); + if (pszSyslogMessageOID != NULL) + free(pszSyslogMessageOID); + + /* release what we no longer need */ + objRelease(errmsg, CORE_COMPONENT); +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(errmsg, CORE_COMPONENT)); + + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptransport", 0, eCmdHdlrGetWord, NULL, &pszTransport, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptarget", 0, eCmdHdlrGetWord, NULL, &pszTarget, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptargetport", 0, eCmdHdlrInt, NULL, &iPort, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpversion", 0, eCmdHdlrInt, NULL, &iSNMPVersion, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpcommunity", 0, eCmdHdlrGetWord, NULL, &pszCommunity, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpenterpriseoid", 0, eCmdHdlrGetWord, NULL, &pszEnterpriseOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptrapoid", 0, eCmdHdlrGetWord, NULL, &pszSnmpTrapOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpsyslogmessageoid", 0, eCmdHdlrGetWord, NULL, &pszSyslogMessageOID, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmpspecifictype", 0, eCmdHdlrInt, NULL, &iSpecificType, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"actionsnmptraptype", 0, eCmdHdlrInt, NULL, &iTrapType, STD_LOADABLE_MODULE_ID)); + CHKiRet(omsdRegCFSLineHdlr( (uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); +ENDmodInit +/* + * vi:set ai: + */ diff --git a/plugins/omsnmp/omsnmp.h b/plugins/omsnmp/omsnmp.h new file mode 100644 index 00000000..be3eb2a2 --- /dev/null +++ b/plugins/omsnmp/omsnmp.h @@ -0,0 +1,106 @@ +/* omsnmp.h + * These are the definitions for the build-in MySQL output module. + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#ifndef OMSNMP_H_INCLUDED +#define OMSNMP_H_INCLUDED 1 + +#define OMSNMP_MAXTRANSPORLENGTH 10 +#define OMSNMP_MAXPORTLENGHT 5 +#define OMSNMP_MAXCOMMUNITYLENGHT 255 +#define OMSNMP_MAXOIDLENGHT 255 + + +#endif /* #ifndef OMMYSQL_H_INCLUDED */ +/* + * vi:set ai: + */ + +#include <net-snmp/library/snmp_api.h> + +static const char *api_errors[-SNMPERR_MAX + 1] = { + "No error", /* SNMPERR_SUCCESS */ + "Generic error", /* SNMPERR_GENERR */ + "Invalid local port", /* SNMPERR_BAD_LOCPORT */ + "Unknown host", /* SNMPERR_BAD_ADDRESS */ + "Unknown session", /* SNMPERR_BAD_SESSION */ + "Too long", /* SNMPERR_TOO_LONG */ + "No socket", /* SNMPERR_NO_SOCKET */ + "Cannot send V2 PDU on V1 session", /* SNMPERR_V2_IN_V1 */ + "Cannot send V1 PDU on V2 session", /* SNMPERR_V1_IN_V2 */ + "Bad value for non-repeaters", /* SNMPERR_BAD_REPEATERS */ + "Bad value for max-repetitions", /* SNMPERR_BAD_REPETITIONS */ + "Error building ASN.1 representation", /* SNMPERR_BAD_ASN1_BUILD */ + "Failure in sendto", /* SNMPERR_BAD_SENDTO */ + "Bad parse of ASN.1 type", /* SNMPERR_BAD_PARSE */ + "Bad version specified", /* SNMPERR_BAD_VERSION */ + "Bad source party specified", /* SNMPERR_BAD_SRC_PARTY */ + "Bad destination party specified", /* SNMPERR_BAD_DST_PARTY */ + "Bad context specified", /* SNMPERR_BAD_CONTEXT */ + "Bad community specified", /* SNMPERR_BAD_COMMUNITY */ + "Cannot send noAuth/Priv", /* SNMPERR_NOAUTH_DESPRIV */ + "Bad ACL definition", /* SNMPERR_BAD_ACL */ + "Bad Party definition", /* SNMPERR_BAD_PARTY */ + "Session abort failure", /* SNMPERR_ABORT */ + "Unknown PDU type", /* SNMPERR_UNKNOWN_PDU */ + "Timeout", /* SNMPERR_TIMEOUT */ + "Failure in recvfrom", /* SNMPERR_BAD_RECVFROM */ + "Unable to determine contextEngineID", /* SNMPERR_BAD_ENG_ID */ + "No securityName specified", /* SNMPERR_BAD_SEC_NAME */ + "Unable to determine securityLevel", /* SNMPERR_BAD_SEC_LEVEL */ + "ASN.1 parse error in message", /* SNMPERR_ASN_PARSE_ERR */ + "Unknown security model in message", /* SNMPERR_UNKNOWN_SEC_MODEL */ + "Invalid message (e.g. msgFlags)", /* SNMPERR_INVALID_MSG */ + "Unknown engine ID", /* SNMPERR_UNKNOWN_ENG_ID */ + "Unknown user name", /* SNMPERR_UNKNOWN_USER_NAME */ + "Unsupported security level", /* SNMPERR_UNSUPPORTED_SEC_LEVEL */ + "Authentication failure (incorrect password, community or key)", /* SNMPERR_AUTHENTICATION_FAILURE */ + "Not in time window", /* SNMPERR_NOT_IN_TIME_WINDOW */ + "Decryption error", /* SNMPERR_DECRYPTION_ERR */ + "SCAPI general failure", /* SNMPERR_SC_GENERAL_FAILURE */ + "SCAPI sub-system not configured", /* SNMPERR_SC_NOT_CONFIGURED */ + "Key tools not available", /* SNMPERR_KT_NOT_AVAILABLE */ + "Unknown Report message", /* SNMPERR_UNKNOWN_REPORT */ + "USM generic error", /* SNMPERR_USM_GENERICERROR */ + "USM unknown security name (no such user exists)", /* SNMPERR_USM_UNKNOWNSECURITYNAME */ + "USM unsupported security level (this user has not been configured for that level of security)", /* SNMPERR_USM_UNSUPPORTEDSECURITYLEVEL */ + "USM encryption error", /* SNMPERR_USM_ENCRYPTIONERROR */ + "USM authentication failure (incorrect password or key)", /* SNMPERR_USM_AUTHENTICATIONFAILURE */ + "USM parse error", /* SNMPERR_USM_PARSEERROR */ + "USM unknown engineID", /* SNMPERR_USM_UNKNOWNENGINEID */ + "USM not in time window", /* SNMPERR_USM_NOTINTIMEWINDOW */ + "USM decryption error", /* SNMPERR_USM_DECRYPTIONERROR */ + "MIB not initialized", /* SNMPERR_NOMIB */ + "Value out of range", /* SNMPERR_RANGE */ + "Sub-id out of range", /* SNMPERR_MAX_SUBID */ + "Bad sub-id in object identifier", /* SNMPERR_BAD_SUBID */ + "Object identifier too long", /* SNMPERR_LONG_OID */ + "Bad value name", /* SNMPERR_BAD_NAME */ + "Bad value notation", /* SNMPERR_VALUE */ + "Unknown Object Identifier", /* SNMPERR_UNKNOWN_OBJID */ + "No PDU in snmp_send", /* SNMPERR_NULL_PDU */ + "Missing variables in PDU", /* SNMPERR_NO_VARS */ + "Bad variable type", /* SNMPERR_VAR_TYPE */ + "Out of memory (malloc failure)", /* SNMPERR_MALLOC */ + "Kerberos related error", /* SNMPERR_KRB5 */ +}; diff --git a/plugins/omtesting/Makefile.am b/plugins/omtesting/Makefile.am new file mode 100644 index 00000000..4700e1eb --- /dev/null +++ b/plugins/omtesting/Makefile.am @@ -0,0 +1,6 @@ +pkglib_LTLIBRARIES = omtesting.la + +omtesting_la_SOURCES = omtesting.c +omtesting_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) +omtesting_la_LDFLAGS = -module -avoid-version +omtesting_la_LIBADD = diff --git a/plugins/omtesting/omtesting.c b/plugins/omtesting/omtesting.c new file mode 100644 index 00000000..411bcf88 --- /dev/null +++ b/plugins/omtesting/omtesting.c @@ -0,0 +1,183 @@ +/* omtesting.c + * + * This module is a testing aid. It is not meant to be used in production. I have + * initially written it to introduce delays of custom length to action processing. + * This is needed for development of new message queueing methods. However, I think + * there are other uses for this module. For example, I can envision that it is a good + * thing to have an output module that requests a retry on every "n"th invocation + * and such things. I implement only what I need. But should further testing needs + * arise, it makes much sense to add them here. + * + * This module will become part of the CVS and the rsyslog project because I think + * it is a generally useful debugging, testing and development aid for everyone + * involved with rsyslog. + * + * CURRENT SUPPORTED COMMANDS: + * + * :omtesting:sleep <seconds> <milliseconds> + * + * Must be specified exactly as above. Keep in mind milliseconds are a millionth + * of a second! + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Rsyslog is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ +#include "config.h" +#include "rsyslog.h" +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include "dirty.h" +#include "syslogd-types.h" +#include "module-template.h" + +MODULE_TYPE_OUTPUT + +/* internal structures + */ +DEF_OMOD_STATIC_DATA + +typedef struct _instanceData { + int iWaitSeconds; + int iWaitUSeconds; /* milli-seconds (one million of a second, just to make sure...) */ +} instanceData; + +BEGINcreateInstance +CODESTARTcreateInstance + pData->iWaitSeconds = 1; + pData->iWaitUSeconds = 0; +ENDcreateInstance + + +BEGINdbgPrintInstInfo +CODESTARTdbgPrintInstInfo + dbgprintf("Action delays rule by %d second(s) and %d millisecond(s)\n", + pData->iWaitSeconds, pData->iWaitUSeconds); + /* do nothing */ +ENDdbgPrintInstInfo + + +BEGINisCompatibleWithFeature +CODESTARTisCompatibleWithFeature + /* we are not compatible with repeated msg reduction feature, so do not allow it */ +ENDisCompatibleWithFeature + + +BEGINtryResume +CODESTARTtryResume +ENDtryResume + +BEGINdoAction +CODESTARTdoAction + struct timeval tvSelectTimeout; + + dbgprintf("sleep(%d, %d)\n", pData->iWaitSeconds, pData->iWaitUSeconds); + tvSelectTimeout.tv_sec = pData->iWaitSeconds; + tvSelectTimeout.tv_usec = pData->iWaitUSeconds; /* milli seconds */ + select(0, NULL, NULL, NULL, &tvSelectTimeout); + //dbgprintf(":omtesting: end doAction(), iRet %d\n", iRet); +ENDdoAction + + +BEGINfreeInstance +CODESTARTfreeInstance + /* we do not have instance data, so we do not need to + * do anything here. -- rgerhards, 2007-07-25 + */ +ENDfreeInstance + + +BEGINparseSelectorAct + int i; + uchar szBuf[1024]; +CODESTARTparseSelectorAct +CODE_STD_STRING_REQUESTparseSelectorAct(0) + /* code here is quick and dirty - if you like, clean it up. But keep + * in mind it is just a testing aid ;) -- rgerhards, 2007-12-31 + */ + if(!strncmp((char*) p, ":omtesting:", sizeof(":omtesting:") - 1)) { + p += sizeof(":omtesting:") - 1; /* eat indicator sequence (-1 because of '\0'!) */ + } else { + ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED); + } + + /* ok, if we reach this point, we have something for us */ + if((iRet = createInstance(&pData)) != RS_RET_OK) + goto finalize_it; + + /* check mode */ + for(i = 0 ; *p && !isspace((char) *p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = (uchar) *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + + if(!strcmp((char*) szBuf, "sleep")) { + /* parse seconds */ + for(i = 0 ; *p && !isspace(*p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + pData->iWaitSeconds = atoi((char*) szBuf); + /* parse milliseconds */ + for(i = 0 ; *p && !isspace(*p) && ((unsigned) i < sizeof(szBuf) - 1) ; ++i) { + szBuf[i] = *p++; + } + szBuf[i] = '\0'; + if(isspace(*p)) + ++p; + pData->iWaitUSeconds = atoi((char*) szBuf); + } + /* once there are other modes, here is the spot to add it! */ + else { + dbgprintf("invalid mode '%s', doing 'sleep 1 0' - fix your config\n", szBuf); + } + +CODE_STD_FINALIZERparseSelectorAct +ENDparseSelectorAct + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_OMOD_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ +CODEmodInit_QueryRegCFSLineHdlr +ENDmodInit +/* + * vi:set ai: + */ |