diff options
author | Rainer Gerhards <rgerhards@adiscon.com> | 2008-03-02 10:19:25 +0000 |
---|---|---|
committer | Rainer Gerhards <rgerhards@adiscon.com> | 2008-03-02 10:19:25 +0000 |
commit | 9d8eb28ebd941694d8dcbc66c6e5c80cf90a206d (patch) | |
tree | 7b6e8cc1a84ea7234a57ce4d85131819d820b292 | |
parent | b494d54b98e6240a20f494ecaa0bad646c531686 (diff) | |
download | rsyslog-9d8eb28ebd941694d8dcbc66c6e5c80cf90a206d.tar.gz rsyslog-9d8eb28ebd941694d8dcbc66c6e5c80cf90a206d.tar.xz rsyslog-9d8eb28ebd941694d8dcbc66c6e5c80cf90a206d.zip |
- added class tcps
- added class tcps_sess
- changed imtcp to use new classes; seems to work; imgssapi currently
broken
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | conf.c | 1 | ||||
-rw-r--r-- | conf.h | 10 | ||||
-rw-r--r-- | debug.c | 12 | ||||
-rw-r--r-- | module-template.h | 2 | ||||
-rw-r--r-- | obj.c | 1 | ||||
-rw-r--r-- | plugins/imgssapi/imgssapi.c | 171 | ||||
-rw-r--r-- | plugins/imtcp/imtcp.c | 958 | ||||
-rw-r--r-- | syslogd.c | 4 | ||||
-rw-r--r-- | tcps_sess.c | 421 | ||||
-rw-r--r-- | tcps_sess.h | 73 | ||||
-rw-r--r-- | tcpsrv.c | 762 | ||||
-rw-r--r-- | tcpsrv.h | 68 |
13 files changed, 1591 insertions, 896 deletions
diff --git a/Makefile.am b/Makefile.am index 6dee36e8..83503390 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,6 +3,10 @@ sbin_PROGRAMS = rfc3195d rsyslogd rfc3195d_SOURCES = rfc3195d.c rsyslog.h rsyslogd_SOURCES = \ + tcps_sess.c \ + tcps_sess.h \ + tcpsrv.c \ + tcpsrv.h \ syslogd.c \ syslogd.h \ sysvar.c \ @@ -1203,7 +1203,6 @@ finalize_it: ENDobjQueryInterface(conf) - /* Initialize our class. Must be called as the very first method * before anything else is called inside this class. * rgerhards, 2008-02-29 @@ -37,21 +37,11 @@ BEGINinterface(conf) /* name must also be changed in ENDinterface macro! */ rsRetVal (*doIncludeLine)(uchar **pp, __attribute__((unused)) void* pVal); rsRetVal (*cfline)(uchar *line, selector_t **pfCurr); rsRetVal (*processConfFile)(uchar *pConfFile); - //rsRetVal (*confClassInit)(void); /* TODO: make this a real object! */ ENDinterface(conf) #define confCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ /* prototypes */ -#if 0 -rsRetVal doNameLine(uchar **pp, void* pVal); -rsRetVal cfsysline(uchar *p); -rsRetVal doModLoad(uchar **pp, __attribute__((unused)) void* pVal); -rsRetVal doIncludeLine(uchar **pp, __attribute__((unused)) void* pVal); -rsRetVal cfline(uchar *line, selector_t **pfCurr); -rsRetVal processConfFile(uchar *pConfFile); -rsRetVal confClassInit(void); /* TODO: make this a real object! */ -#endif PROTOTYPEObj(conf); @@ -60,6 +60,7 @@ static int bPrintFuncDBOnExit = 0; /* shall the function entry and exit be logge static int bPrintMutexAction = 0; /* shall mutex calls be printed to the debug log? */ static int bPrintTime = 1; /* print a timestamp together with debug message */ static int bPrintAllDebugOnExit = 0; +static int bAbortTrace = 1; /* print a trace after SIGABRT or SIGSEGV */ static char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */ static FILE *altdbg = NULL; /* and the handle for alternate debug output */ static FILE *stddbg; @@ -718,12 +719,15 @@ sigsegvHdlr(int signum) dbgprintf("\n\n\n\nSignal %d%s occured, execution must be terminated.\n\n\n\n", signum, signame); - dbgPrintAllDebugInfo(); + if(bAbortTrace) { + dbgPrintAllDebugInfo(); + dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n"); + dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); + } - dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n"); dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n"); - if(stddbg != NULL) fflush(stddbg); + if(altdbg != NULL) fflush(altdbg); /* and finally abort... */ /* TODO: think about restarting rsyslog in this case: may be a good idea, @@ -1172,6 +1176,8 @@ dbgGetRuntimeOptions(void) bPrintTime = 0; } else if(!strcasecmp((char*)optname, "nostdout")) { stddbg = NULL; + } else if(!strcasecmp((char*)optname, "noaborttrace")) { + bAbortTrace = 0; } else if(!strcasecmp((char*)optname, "filetrace")) { if(*optval == '\0') { fprintf(stderr, "Error: logfile debug option requires filename, " diff --git a/module-template.h b/module-template.h index 7ac5a3e5..efa07be9 100644 --- a/module-template.h +++ b/module-template.h @@ -54,7 +54,7 @@ static rsRetVal modGetType(eModType_t *modType) \ #define MODULE_TYPE_INPUT MODULE_TYPE(eMOD_IN) #define MODULE_TYPE_OUTPUT MODULE_TYPE(eMOD_OUT) -#define MODULE_TYPE_FILTER MODULE_TYPE(EMOD_FILTER) +#define MODULE_TYPE_LIB MODULE_TYPE(EMOD_LIB) /* macro to define a unique module id. This must be able to fit in a void*. The * module id must be unique inside a running rsyslogd application. It is used to @@ -992,7 +992,6 @@ finalize_it: } else { dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet); } -dbgPrintAllDebugInfo(); RETiRet; } diff --git a/plugins/imgssapi/imgssapi.c b/plugins/imgssapi/imgssapi.c index 01e4cdea..215adfa0 100644 --- a/plugins/imgssapi/imgssapi.c +++ b/plugins/imgssapi/imgssapi.c @@ -38,6 +38,7 @@ #include "net.h" #include "srUtils.h" #include "gss-misc.h" +#include "tcpsrv.h" /* some forward definitions - they may go away when we no longer include imtcp.c */ static rsRetVal addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal); @@ -48,30 +49,135 @@ void TCPSessGSSDeinit(void); int TCPSessGSSRecv(int iSess, void *buf, size_t buf_len); /* static data */ +DEFobjCurrIf(tcpsrv) static gss_cred_id_t gss_server_creds = GSS_C_NO_CREDENTIAL; +typedef struct GSSTCPSession_s { + OM_uint32 gss_flags; + gss_ctx_id_t gss_context; + char allowedMethods; +} GSSTCPSession_t; + + /* config variables */ static char *gss_listen_service_name = NULL; static int bPermitPlainTcp = 0; /* plain tcp syslog allowed on GSSAPI port? */ -/* ################################################################################ * - * # THE FOLLOWING LINE IS VITALLY IMPORTANT # * - * #------------------------------------------------------------------------------# * - * # It includes imtcp.c, which has many common code with this module. Over time, # * - * # we will move this into separate clases, but for the time being it permits # * - * # use to use a somewhat clean build process and keep the files focussed. # * - * ################################################################################ */ -#include "../imtcp/imtcp.c" +/* methods */ +/* callbacks */ +static rsRetVal onTCPSessInit(GSSTCPSession_t *pGSS) +{ + DEFiRet; + assert(pGSS != NULL) + pGSS->gss_flags = 0; + pGSS->gss_context = GSS_C_NO_CONTEXT; + pGSS->allowedMethods = 0; + RETiRet; +} + + +static rsRetVal +onListenDeinit(GSSTCPSession_t *pGSS) +{ + DEFiRet; + assert(pGSS != NULL) + if(bEnableTCP & ALLOWEDMETHOD_GSS) { + OM_uint32 maj_stat, min_stat; + maj_stat = gss_delete_sec_context(&min_stat, &pTCPSessions[iTCPSess].gss_context, GSS_C_NO_BUFFER); + if (maj_stat != GSS_S_COMPLETE) + display_status("deleting context", maj_stat, min_stat); + } + RETiRet; +} + + +static int +isPermittedHost(TCPSession_t *pSess) +{ + char allowedMethods = 0; + + assert(pSess != NULL); + + if((bEnableTCP & ALLOWEDMETHOD_TCP) && + isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) + allowedMethods |= ALLOWEDMETHOD_TCP; + if((bEnableTCP & ALLOWEDMETHOD_GSS) && + isAllowedSender(pAllowedSenders_GSS, (struct sockaddr *)&addr, (char*)fromHostFQDN)) + allowedMethods |= ALLOWEDMETHOD_GSS; + if(allowedMethods) + pSess->allowedMethods = allowedMethods; + return allowedMethods; +} + +static rsRetVal +onTCPSessAccept(int fd) +{ + DEFiRet; + assert(pSess != NULL); + + if(bEnableTCP & ALLOWEDMETHOD_GSS) + TCPSessGSSAccept(fd); + else + TCPSessAccept(fd); + RETiRet; +} + +static rsRetVal +onRegularClose(TCPSession_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + + if(allowedMethods & ALLOWEDMETHOD_GSS) + TCPSessGSSClose(pSess); + else { + /* process any incomplete frames left over */ + TCPSessPrepareClose(pSess); + /* Session closed */ + TCPSessClose(pSess); + } + RETiRet; +} + -/* ################################################################################ * - * # END IMPORTANT PART # * - * ################################################################################ */ +static rsRetVal +onErrClose(TCPSession_t *pSess) +{ + DEFiRet; + assert(pSess != NULL); + + if(allowedMethods & ALLOWEDMETHOD_GSS) + TCPSessGSSClose(pSess); + else + TCPSessClose(pSess); + RETiRet; +} + + + +static int pRcvData(TCPSession_t *pSess, char *buf, size_t lenBuf) +{ + int state; + int allowedMethods; + assert(pSess != NULL); + + allowedMethods = pSess->allowedMethods; + if(allowedMethods & ALLOWEDMETHOD_GSS) + state = TCPSessGSSRecv(iTCPSess, buf, lenBuf); + else + state = recv(pSess->sock, buf, lenBuf, 0); + return state; +} + + +/* end callbacks */ static rsRetVal addGSSListener(void __attribute__((unused)) *pVal, uchar *pNewVal) { if (!bEnableTCP) - configureTCPListen((char *) pNewVal); + tcpsrv.configureTCPListen((char *) pNewVal); bEnableTCP |= ALLOWEDMETHOD_GSS; return RS_RET_OK; @@ -362,10 +468,51 @@ static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __a } +/* This function is called to gather input. + */ +BEGINrunInput +CODESTARTrunInput + iRet = tcpsrv.RunInput(); + return iRet; +ENDrunInput + + +/* initialize and return if will run or not */ +BEGINwillRun +CODESTARTwillRun + /* first check if we must support plain TCP, too. If so, set mode + * accordingly. -- rgerhards, 2008-02-26 + */ + if(bPermitPlainTcp) + bEnableTCP |= ALLOWEDMETHOD_TCP; + + /* first apply some config settings */ + PrintAllowedSenders(2); /* TCP */ + PrintAllowedSenders(3); /* GSS */ + if (bEnableTCP) { + if(sockTCPLstn == NULL) { + if(bEnableTCP & ALLOWEDMETHOD_GSS) { + if(TCPSessGSSInit()) { + logerror("GSS-API initialization failed\n"); + bEnableTCP &= ~(ALLOWEDMETHOD_GSS); + } + } + // TODO: do we still need this here? I don't think + // so... + if(bEnableTCP) + if((sockTCPLstn = create_tcp_socket()) != NULL) { + dbgprintf("Opened %d syslog TCP port(s).\n", *sockTCPLstn); + } + } + } +ENDwillRun + + BEGINmodInit() CODESTARTmodInit *ipIFVersProvided = 1; /* so far, we only support the initial definition */ CODEmodInit_QueryRegCFSLineHdlr + CHKiRet(objUse(tcpsrv, "tcpsrv")); /* register config file handlers */ CHKiRet(omsdRegCFSLineHdlr((uchar *)"permitplaintcp", 0, eCmdHdlrBinary, NULL, &bPermitPlainTcp, STD_LOADABLE_MODULE_ID)); diff --git a/plugins/imtcp/imtcp.c b/plugins/imtcp/imtcp.c index d3752d42..9ad85044 100644 --- a/plugins/imtcp/imtcp.c +++ b/plugins/imtcp/imtcp.c @@ -1,9 +1,6 @@ /* imtcp.c * This is the implementation of the TCP 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. @@ -27,12 +24,6 @@ */ #include "config.h" -#ifdef FORCE_NO_GSS - #ifdef USE_GSSAPI - #undef USE_GSSAPI - #endif -#endif - #include <stdlib.h> #include <assert.h> #include <string.h> @@ -52,856 +43,112 @@ #include "cfsysline.h" #include "module-template.h" #include "net.h" -#include "srUtils.h" +#include "tcpsrv.h" MODULE_TYPE_INPUT -/* defines */ -#ifdef USE_GSSAPI -#define ALLOWEDMETHOD_GSS 2 -#endif -#define ALLOWEDMETHOD_TCP 1 - -#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */ +/* static data */ +DEF_IMOD_STATIC_DATA +DEFobjCurrIf(obj) +DEFobjCurrIf(tcpsrv) +DEFobjCurrIf(tcps_sess) /* Module static data */ -DEF_IMOD_STATIC_DATA typedef struct _instanceData { } instanceData; -typedef enum _TCPFRAMINGMODE { - TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ - TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ - } TCPFRAMINGMODE; - -struct TCPSession { - int sock; - int iMsg; /* index of next char to store in msg */ - int bAtStrtOfFram; /* are we at the very beginning of a new frame? */ - int iOctetsRemain; /* Number of Octets remaining in message */ - TCPFRAMINGMODE eFraming; - char msg[MAXLINE+1]; - char *fromHost; -#ifdef USE_GSSAPI - OM_uint32 gss_flags; - gss_ctx_id_t gss_context; - char allowedMethods; -#endif -}; - -static int iTCPSessMax = TCPSESS_MAX_DEFAULT; /* actual number of sessions */ -static char *TCPLstnPort = "0"; /* read-only after startup */ -static int bEnableTCP = 0; /* read-only after startup */ -static int *sockTCPLstn = NULL; /* read-only after startup, modified by restart */ -static struct TCPSession *pTCPSessions; -/* The thread-safeness of the sesion table is doubtful */ +static tcpsrv_t *pOurTcpsrv = NULL; /* our TCP server(listener) TODO: change for multiple instances */ /* config settings */ +static int iTCPSessMax = 200; /* max number of sessions */ +static char *TCPLstnPort = "0"; /* read-only after startup */ -/* code to free all sockets within a socket table. - * A socket table is a descriptor table where the zero - * element has the count of elements. This is used for - * listening sockets. The socket table itself is also - * freed. - * A POINTER to this structure must be provided, thus - * double indirection! - * rgerhards, 2007-06-28 - */ -static void freeAllSockets(int **socks) -{ - assert(socks != NULL); - assert(*socks != NULL); - while(**socks) { - dbgprintf("Closing socket %d.\n", (*socks)[**socks]); - close((*socks)[**socks]); - (**socks)--; - } - free(*socks); - socks = NULL; -} - - -/* configure TCP listener settings. This is called during command - * line parsing. The argument following -t is supplied as an argument. - * The format of this argument is - * "<port-to-use>, <nbr-of-sessions>" - * Typically, there is no whitespace between port and session number. - * (but it may be...). - * NOTE: you can not use dbgprintf() in here - the dbgprintf() system is - * not yet initilized when this function is called. - * rgerhards, 2007-06-21 - * We can also not use logerror(), as that system is also not yet - * initialized... rgerhards, 2007-06-28 - */ -void configureTCPListen(char *cOptarg) -{ - register int i; - register char *pArg = cOptarg; - - assert(cOptarg != NULL); - - /* extract port */ - i = 0; - while(isdigit((int) *pArg)) { - i = i * 10 + *pArg++ - '0'; - } - - if( i >= 0 && i <= 65535) { - TCPLstnPort = cOptarg; - } else { - logerrorSz("Invalid TCP listen port %s - changed to 514.\n", cOptarg); - TCPLstnPort = "514"; - } -} - - -void configureTCPListenSessMax(char *cOptarg) -{ - register int i; - register char *pArg = cOptarg; - - assert(cOptarg != NULL); - - /* number of sessions */ - i = 0; - while(isdigit((int) *pArg)) { - i = i * 10 + *pArg++ - '0'; - } - - if(i > 0) - iTCPSessMax = i; - else { - /* too small, need to adjust */ - logerrorSz("TCP session max configured to %s - changing to 1.\n", cOptarg); - iTCPSessMax = 1; - } -} - - -/* Initialize the session table - * returns 0 if OK, somewhat else otherwise - */ -static int TCPSessInit(void) -{ - register int i; - - assert(pTCPSessions == NULL); - dbgprintf("Allocating buffer for %d TCP sessions.\n", iTCPSessMax); - if((pTCPSessions = (struct TCPSession *) malloc(sizeof(struct TCPSession) * iTCPSessMax)) - == NULL) { - dbgprintf("Error: TCPSessInit() could not alloc memory for TCP session table.\n"); - return(1); - } - - for(i = 0 ; i < iTCPSessMax ; ++i) { - pTCPSessions[i].sock = -1; /* no sock */ - pTCPSessions[i].iMsg = 0; /* just make sure... */ - pTCPSessions[i].bAtStrtOfFram = 1; /* indicate frame header expected */ - pTCPSessions[i].eFraming = TCP_FRAMING_OCTET_STUFFING; /* just make sure... */ -#ifdef USE_GSSAPI - pTCPSessions[i].gss_flags = 0; - pTCPSessions[i].gss_context = GSS_C_NO_CONTEXT; - pTCPSessions[i].allowedMethods = 0; -#endif - } - return(0); -} - - -/* find a free spot in the session table. If the table - * is full, -1 is returned, else the index of the free - * entry (0 or higher). - */ -static int TCPSessFindFreeSpot(void) -{ - register int i; - - for(i = 0 ; i < iTCPSessMax ; ++i) { - if(pTCPSessions[i].sock == -1) - break; - } - - return((i < iTCPSessMax) ? i : -1); -} - - -/* Get the next session index. Free session tables entries are - * skipped. This function is provided the index of the last - * session entry, or -1 if no previous entry was obtained. It - * returns the index of the next session or -1, if there is no - * further entry in the table. Please note that the initial call - * might as well return -1, if there is no session at all in the - * session table. - */ -int TCPSessGetNxtSess(int iCurr) -{ - register int i; - - for(i = iCurr + 1 ; i < iTCPSessMax ; ++i) - if(pTCPSessions[i].sock != -1) - break; - - return((i < iTCPSessMax) ? i : -1); -} - - -/* De-Initialize TCP listner sockets. - * This function deinitializes everything, including freeing the - * session table. No TCP listen receive operations are permitted - * unless the subsystem is reinitialized. - * rgerhards, 2007-06-21 - */ -void deinit_tcp_listener(void) +/* callbacks */ +/* this shall go into a specific ACL module! */ +static int +isPermittedHost(struct sockaddr *addr, char *fromHostFQDN) { - int iTCPSess; - - assert(pTCPSessions != NULL); - /* close all TCP connections! */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fd; - fd = pTCPSessions[iTCPSess].sock; - dbgprintf("Closing TCP Session %d\n", fd); - close(fd); - free(pTCPSessions[iTCPSess].fromHost); -#ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) { - OM_uint32 maj_stat, min_stat; - maj_stat = gss_delete_sec_context(&min_stat, &pTCPSessions[iTCPSess].gss_context, GSS_C_NO_BUFFER); - if (maj_stat != GSS_S_COMPLETE) - display_status("deleting context", maj_stat, min_stat); - } -#endif - /* now get next... */ - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - - /* we are done with the session table - so get rid of it... - */ - free(pTCPSessions); - pTCPSessions = NULL; /* just to make sure... */ - - /* finally close the listen sockets themselfs */ - freeAllSockets(&sockTCPLstn); + return isAllowedSender(pAllowedSenders_TCP, addr, fromHostFQDN); } -/* Initialize TCP sockets (for listener) - * This function returns either NULL (which means it failed) or - * a pointer to an array of file descriptiors. If the pointer is - * returned, the zeroest element [0] contains the count of valid - * descriptors. The descriptors themself follow in range - * [1] ... [num-descriptors]. It is guaranteed that each of these - * descriptors is valid, at least when this function returns. - * Please note that technically the array may be larger than the number - * of valid pointers stored in it. The memory overhead is minimal, so - * we do not bother to re-allocate an array of the exact size. Logically, - * the array still contains the exactly correct number of descriptors. - */ -int *create_tcp_socket(void) +static rsRetVal +onSessAccept(tcpsrv_t *pThis, int fd) { - struct addrinfo hints, *res, *r; - int error, maxs, *s, *socks, on = 1; - - if(!strcmp(TCPLstnPort, "0")) - TCPLstnPort = "514"; - /* use default - we can not do service db update, because there is - * no IANA-assignment for syslog/tcp. In the long term, we might - * re-use RFC 3195 port of 601, but that would probably break to - * many existing configurations. - * rgerhards, 2007-06-28 - */ - dbgprintf("creating tcp socket on port %s\n", TCPLstnPort); - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; - hints.ai_family = family; - hints.ai_socktype = SOCK_STREAM; - - error = getaddrinfo(NULL, TCPLstnPort, &hints, &res); - if(error) { - logerror((char*) gai_strerror(error)); - return NULL; - } - - /* Count max number of sockets we may open */ - for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) - /* EMPTY */; - socks = malloc((maxs+1) * sizeof(int)); - if (socks == NULL) { - logerror("couldn't allocate memory for TCP listen sockets, suspending TCP message reception."); - freeaddrinfo(res); - return NULL; - } - - *socks = 0; /* num of sockets counter at start of array */ - s = socks + 1; - for (r = res; r != NULL ; r = r->ai_next) { - *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); - if (*s < 0) { - if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) - logerror("create_udp_socket(), socket"); - /* it is debatable if PF_INET with EAFNOSUPPORT should - * also be ignored... - */ - continue; - } - -#ifdef IPV6_V6ONLY - if (r->ai_family == AF_INET6) { - int iOn = 1; - if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, - (char *)&iOn, sizeof (iOn)) < 0) { - logerror("TCP setsockopt"); - close(*s); - *s = -1; - continue; - } - } -#endif - if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, - (char *) &on, sizeof(on)) < 0 ) { - logerror("TCP setsockopt(REUSEADDR)"); - close(*s); - *s = -1; - continue; - } - - /* We need to enable BSD compatibility. Otherwise an attacker - * could flood our log files by sending us tons of ICMP errors. - */ -#ifndef BSD - if (should_use_so_bsdcompat()) { - if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, - (char *) &on, sizeof(on)) < 0) { - logerror("TCP setsockopt(BSDCOMPAT)"); - close(*s); - *s = -1; - continue; - } - } -#endif - - if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) -#ifndef IPV6_V6ONLY - && (errno != EADDRINUSE) -#endif - ) { - logerror("TCP bind"); - close(*s); - *s = -1; - continue; - } - - if( listen(*s,iTCPSessMax / 10 + 5) < 0) { - /* If the listen fails, it most probably fails because we ask - * for a too-large backlog. So in this case we first set back - * to a fixed, reasonable, limit that should work. Only if - * that fails, too, we give up. - */ - logerrorInt("listen with a backlog of %d failed - retrying with default of 32.", - iTCPSessMax / 10 + 5); - if(listen(*s, 32) < 0) { - logerror("TCP listen, suspending tcp inet"); - close(*s); - *s = -1; - continue; - } - } - - (*socks)++; - s++; - } - - if(res != NULL) - freeaddrinfo(res); - - if(Debug && *socks != maxs) - dbgprintf("We could initialize %d TCP listen sockets out of %d we received " - "- this may or may not be an error indication.\n", *socks, maxs); - - if(*socks == 0) { - logerror("No TCP listen socket could successfully be initialized, " - "message reception via TCP disabled.\n"); - free(socks); - return(NULL); - } + DEFiRet; - /* OK, we had success. Now it is also time to - * initialize our connections - */ - if(TCPSessInit() != 0) { - /* OK, we are in some trouble - we could not initialize the - * session table, so we can not continue. We need to free all - * we have assigned so far, because we can not really use it... - */ - logerror("Could not initialize TCP session table, suspending TCP message reception."); - freeAllSockets(&socks); /* prevent a socket leak */ - return(NULL); - } - - return(socks); + tcpsrv.SessAccept(pThis, fd); + RETiRet; } -/* Accept new TCP connection; make entry in session table. If there - * is no more space left in the connection table, the new TCP - * connection is immediately dropped. - */ -int TCPSessAccept(int fd) +static int +doRcvData(tcps_sess_t *pSess, char *buf, size_t lenBuf) { - int newConn; - int iSess; - struct sockaddr_storage addr; - socklen_t addrlen = sizeof(struct sockaddr_storage); - size_t lenHostName; - uchar fromHost[NI_MAXHOST]; - uchar fromHostFQDN[NI_MAXHOST]; - char *pBuf; -#ifdef USE_GSSAPI - char allowedMethods = 0; -#endif + int state; + assert(pSess != NULL); - newConn = accept(fd, (struct sockaddr*) &addr, &addrlen); - if (newConn < 0) { - logerror("tcp accept, ignoring error and connection request"); - return -1; - } - - /* Add to session list */ - iSess = TCPSessFindFreeSpot(); - if(iSess == -1) { - errno = 0; - logerror("too many tcp sessions - dropping incoming request"); - close(newConn); - return -1; - } - - /* OK, we have a "good" index... */ - /* get the host name */ - if(cvthname(&addr, fromHost, fromHostFQDN) != RS_RET_OK) { - /* we seem to have something malicous - at least we - * are now told to discard the connection request. - * Error message has been generated by cvthname. - */ - close (newConn); - return -1; - } - - /* 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 - */ -#ifdef USE_GSSAPI - if((bEnableTCP & ALLOWEDMETHOD_TCP) && - isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) - allowedMethods |= ALLOWEDMETHOD_TCP; - if((bEnableTCP & ALLOWEDMETHOD_GSS) && - isAllowedSender(pAllowedSenders_GSS, (struct sockaddr *)&addr, (char*)fromHostFQDN)) - allowedMethods |= ALLOWEDMETHOD_GSS; - if(allowedMethods) - pTCPSessions[iSess].allowedMethods = allowedMethods; - else -#else - if(!isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) -#endif - { - dbgprintf("%s is not an allowed sender\n", (char *) fromHostFQDN); - if(option_DisallowWarning) { - errno = 0; - logerrorSz("TCP message from disallowed sender %s discarded", - (char*)fromHost); - } - close(newConn); - return -1; - } - - /* OK, we have an allowed sender, so let's continue */ - lenHostName = strlen((char*)fromHost) + 1; /* for \0 byte */ - if((pBuf = (char*) malloc(sizeof(char) * lenHostName)) == NULL) { - glblHadMemShortage = 1; - pTCPSessions[iSess].fromHost = "NO-MEMORY-FOR-HOSTNAME"; - } else { - memcpy(pBuf, fromHost, lenHostName); - pTCPSessions[iSess].fromHost = pBuf; - } - - pTCPSessions[iSess].sock = newConn; - pTCPSessions[iSess].iMsg = 0; /* init msg buffer! */ - return iSess; + state = recv(pSess->sock, buf, lenBuf, 0); + return state; } - -/* This should be called before a normal (non forced) close - * of a TCP session. This function checks if there is any unprocessed - * message left in the TCP stream. Such a message is probably a - * fragement. If evrything goes well, we must be right at the - * beginnig of a new frame without any data received from it. If - * not, there is some kind of a framing error. I think I remember that - * some legacy syslog/TCP implementations have non-LF terminated - * messages at the end of the stream. For now, we allow this behaviour. - * Later, it should probably become a configuration option. - * rgerhards, 2006-12-07 - */ -void TCPSessPrepareClose(int iTCPSess) +static rsRetVal +onRegularClose(tcps_sess_t *pSess) { - if(iTCPSess < 0 || iTCPSess > iTCPSessMax) { - errno = 0; - logerror("internal error, trying to close an invalid TCP session!"); - return; - } - - if(pTCPSessions[iTCPSess].bAtStrtOfFram == 1) { - /* this is how it should be. There is no unprocessed - * data left and such we have nothing to do. For simplicity - * reasons, we immediately return in that case. - */ - return; - } - - /* we have some data left! */ - if(pTCPSessions[iTCPSess].eFraming == TCP_FRAMING_OCTET_COUNTING) { - /* In this case, we have an invalid frame count and thus - * generate an error message and discard the frame. - */ - logerrorInt("Incomplete frame at end of stream in session %d - " - "ignoring extra data (a message may be lost).\n", - pTCPSessions[iTCPSess].sock); - /* nothing more to do */ - } else { /* here, we have traditional framing. Missing LF at the end - * of message may occur. As such, we process the message in - * this case. - */ - dbgprintf("Extra data at end of stream in legacy syslog/tcp message - processing\n"); - parseAndSubmitMessage(pTCPSessions[iTCPSess].fromHost, pTCPSessions[iTCPSess].msg, - pTCPSessions[iTCPSess].iMsg, MSG_PARSE_HOSTNAME); - pTCPSessions[iTCPSess].bAtStrtOfFram = 1; - } + DEFiRet; + assert(pSess != NULL); + + /* process any incomplete frames left over */ + tcps_sess.PrepareClose(pSess); + /* Session closed */ + tcps_sess.Close(pSess); + RETiRet; } -/* Closes a TCP session and marks its slot in the session - * table as unused. No attention is paid to the return code - * of close, so potential-double closes are not detected. - */ -void TCPSessClose(int iSess) +static rsRetVal +onErrClose(tcps_sess_t *pSess) { - if(iSess < 0 || iSess > iTCPSessMax) { - errno = 0; - logerror("internal error, trying to close an invalid TCP session!"); - return; - } + DEFiRet; + assert(pSess != NULL); - close(pTCPSessions[iSess].sock); - pTCPSessions[iSess].sock = -1; - free(pTCPSessions[iSess].fromHost); - pTCPSessions[iSess].fromHost = NULL; /* not really needed, but... */ + tcps_sess.Close(pSess); + RETiRet; } +/* ------------------------------ end callbacks ------------------------------ */ -/* Processes the data received via a TCP session. If there - * is no other way to handle it, data is discarded. - * Input parameter data is the data received, iLen is its - * len as returned from recv(). iLen must be 1 or more (that - * is errors must be handled by caller!). iTCPSess must be - * the index of the TCP session that received the data. - * rgerhards 2005-07-04 - * Changed this functions interface. We now return a status of - * what shall happen with the session. This is information for - * the caller. If 1 is returned, the session should remain open - * and additional data be accepted. If we return 0, the TCP - * session is to be closed by the caller. This functionality is - * needed in order to support framing errors, from which there - * is no recovery possible other than session termination and - * re-establishment. The need for this functionality thus is - * primarily rooted in support for -transport-tls I-D framing. - * rgerhards, 2006-12-07 - */ -int TCPSessDataRcvd(int iTCPSess, char *pData, int iLen) -{ - register int iMsg; - char *pMsg; - char *pEnd; - assert(pData != NULL); - assert(iLen > 0); - assert(iTCPSess >= 0); - assert(iTCPSess < iTCPSessMax); - assert(pTCPSessions[iTCPSess].sock != -1); - - /* We now copy the message to the session buffer. As - * it looks, we need to do this in any case because - * we might run into multiple messages inside a single - * buffer. Of course, we could think about optimizations, - * but as this code is to be replaced by liblogging, it - * probably doesn't make so much sense... - * rgerhards 2005-07-04 - * - * Algo: - * - copy message to buffer until the first LF is found - * - printline() the buffer - * - continue with copying - */ - iMsg = pTCPSessions[iTCPSess].iMsg; /* copy for speed */ - pMsg = pTCPSessions[iTCPSess].msg; /* just a shortcut */ - pEnd = pData + iLen; /* this is one off, which is intensional */ - - while(pData < pEnd) { - /* Check if we are at a new frame */ - if(pTCPSessions[iTCPSess].bAtStrtOfFram) { - /* we need to look at the message and detect - * the framing mode used - *//* - * Contrary to -transport-tls, we accept leading zeros in the message - * length. We do this in the spirit of "Be liberal in what you accept, - * and conservative in what you send". We expect that including leading - * zeros could be a common coding error. - * rgerhards, 2006-12-07 - * The chairs of the IETF syslog-sec WG have announced that it is - * consensus to do the octet count on the SYSLOG-MSG part only. I am - * now changing the code to reflect this. Hopefully, it will not change - * once again (there can no compatibility layer programmed for this). - * To be on the save side, I just comment the code out. I mark these - * comments with "IETF20061218". - * rgerhards, 2006-12-19 - */ - if(isdigit((int) *pData)) { - int iCnt; /* the frame count specified */ - pTCPSessions[iTCPSess].eFraming = TCP_FRAMING_OCTET_COUNTING; - /* in this mode, we have OCTET-COUNT SP MSG - so we now need - * to extract the OCTET-COUNT and the SP and then extract - * the msg. - */ - iCnt = 0; - /* IETF20061218 int iNbrOctets = 0; / * number of octets already consumed */ - while(isdigit((int) *pData)) { - iCnt = iCnt * 10 + *pData - '0'; - /* IETF20061218 ++iNbrOctets; */ - ++pData; - } - dbgprintf("TCP Message with octet-counter, size %d.\n", iCnt); - if(*pData == ' ') { - ++pData; /* skip over SP */ - /* IETF20061218 ++iNbrOctets; */ - } else { - /* TODO: handle "invalid frame" case */ - logerrorInt("Framing Error in received TCP message: " - "delimiter is not SP but has ASCII value %d.\n", - *pData); - return(0); /* unconditional error exit */ - } - /* IETF20061218 pTCPSessions[iTCPSess].iOctetsRemain = iCnt - iNbrOctets; */ - pTCPSessions[iTCPSess].iOctetsRemain = iCnt; - if(pTCPSessions[iTCPSess].iOctetsRemain < 1) { - /* TODO: handle the case where the octet count is 0 or negative! */ - dbgprintf("Framing Error: invalid octet count\n"); - logerrorInt("Framing Error in received TCP message: " - "invalid octet count %d.\n", - pTCPSessions[iTCPSess].iOctetsRemain); - return(0); /* unconditional error exit */ - } - } else { - pTCPSessions[iTCPSess].eFraming = TCP_FRAMING_OCTET_STUFFING; - /* No need to do anything else here in this case */ - } - pTCPSessions[iTCPSess].bAtStrtOfFram = 0; /* done frame header */ - } - - /* now copy message until end of record */ - - if(iMsg >= MAXLINE) { - /* emergency, we now need to flush, no matter if - * we are at end of message or not... - */ - parseAndSubmitMessage(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg, MSG_PARSE_HOSTNAME); - iMsg = 0; - /* we might think if it is better to ignore the rest of the - * message than to treat it as a new one. Maybe this is a good - * candidate for a configuration parameter... - * rgerhards, 2006-12-04 - */ - } - - if(*pData == '\n' && - pTCPSessions[iTCPSess].eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delemiter? */ - parseAndSubmitMessage(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg, MSG_PARSE_HOSTNAME); - iMsg = 0; - pTCPSessions[iTCPSess].bAtStrtOfFram = 1; - ++pData; - } else { - /* IMPORTANT: here we copy the actual frame content to the message! */ - *(pMsg + iMsg++) = *pData++; - } - - if(pTCPSessions[iTCPSess].eFraming == TCP_FRAMING_OCTET_COUNTING) { - /* do we need to find end-of-frame via octet counting? */ - pTCPSessions[iTCPSess].iOctetsRemain--; - if(pTCPSessions[iTCPSess].iOctetsRemain < 1) { - /* we have end of frame! */ - parseAndSubmitMessage(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg, MSG_PARSE_HOSTNAME); - iMsg = 0; - pTCPSessions[iTCPSess].bAtStrtOfFram = 1; - } - } - } - - pTCPSessions[iTCPSess].iMsg = iMsg; /* persist value */ - return(1); /* successful return */ -} - - -#ifndef USE_GSSAPI static rsRetVal addTCPListener(void __attribute__((unused)) *pVal, uchar *pNewVal) { - if (!bEnableTCP) - configureTCPListen((char *) pNewVal); - bEnableTCP |= ALLOWEDMETHOD_TCP; - - return RS_RET_OK; + DEFiRet; + if(pOurTcpsrv == NULL) { + CHKiRet(tcpsrv.Construct(&pOurTcpsrv)); + /* TODO: fill params! */ + CHKiRet(tcpsrv.SetCBIsPermittedHost(pOurTcpsrv, isPermittedHost)); + CHKiRet(tcpsrv.SetCBRcvData(pOurTcpsrv, doRcvData)); + //CHKiRet(tcpsrv.SetCBOnListenDeinit(pOurTcpsrv, )); + 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: + RETiRet; } -#endif /* This function is called to gather input. */ BEGINrunInput - int maxfds; - int nfds; - int i; - int iTCPSess; - 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. + /* TODO: we must be careful to start the listener here. Currently, tcpsrv.c seems to + * do that in ConstructFinalize */ - 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 TCP listen sockets to the list of read descriptors. - */ - if(sockTCPLstn != NULL && *sockTCPLstn) { - for (i = 0; i < *sockTCPLstn; i++) { - /* The if() below is theoretically not needed, but I leave it in - * so that a socket may become unsuable during execution. That - * feature is not yet supported by the current code base. - */ - if (sockTCPLstn[i+1] != -1) { - if(Debug) - debugListenInfo(sockTCPLstn[i+1], "TCP"); - FD_SET(sockTCPLstn[i+1], &readfds); - if(sockTCPLstn[i+1]>maxfds) maxfds=sockTCPLstn[i+1]; - } - } - /* do the sessions */ - iTCPSess = TCPSessGetNxtSess(-1); - while(iTCPSess != -1) { - int fdSess; - fdSess = pTCPSessions[iTCPSess].sock; - dbgprintf("Adding TCP Session %d\n", fdSess); - FD_SET(fdSess, &readfds); - if (fdSess>maxfds) maxfds=fdSess; - /* now get next... */ - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - } - - if(Debug) { - dbgprintf("--------imTCP 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 < *sockTCPLstn; i++) { - if (FD_ISSET(sockTCPLstn[i+1], &readfds)) { - dbgprintf("New connect on TCP inetd socket: #%d\n", sockTCPLstn[i+1]); -# ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) - TCPSessGSSAccept(sockTCPLstn[i+1]); - else -# endif - TCPSessAccept(sockTCPLstn[i+1]); - --nfds; /* indicate we have processed one */ - } - } - - /* now check the sessions */ - iTCPSess = TCPSessGetNxtSess(-1); - while(nfds && iTCPSess != -1) { - int fdSess; - int state; - fdSess = pTCPSessions[iTCPSess].sock; - if(FD_ISSET(fdSess, &readfds)) { - char buf[MAXLINE]; - dbgprintf("tcp session socket with new data: #%d\n", fdSess); - - /* Receive message */ -# ifdef USE_GSSAPI - int allowedMethods = pTCPSessions[iTCPSess].allowedMethods; - if(allowedMethods & ALLOWEDMETHOD_GSS) - state = TCPSessGSSRecv(iTCPSess, buf, sizeof(buf)); - else -# endif - state = recv(fdSess, buf, sizeof(buf), 0); - if(state == 0) { -# ifdef USE_GSSAPI - if(allowedMethods & ALLOWEDMETHOD_GSS) - TCPSessGSSClose(iTCPSess); - else { -# endif - /* process any incomplete frames left over */ - TCPSessPrepareClose(iTCPSess); - /* Session closed */ - TCPSessClose(iTCPSess); -# ifdef USE_GSSAPI - } -# endif - } else if(state == -1) { - logerrorInt("TCP session %d will be closed, error ignored\n", - fdSess); -# ifdef USE_GSSAPI - if(allowedMethods & ALLOWEDMETHOD_GSS) - TCPSessGSSClose(iTCPSess); - else -# endif - TCPSessClose(iTCPSess); - } else { - /* valid data received, process it! */ - if(TCPSessDataRcvd(iTCPSess, buf, state) == 0) { - /* in this case, something went awfully wrong. - * We are instructed to terminate the session. - */ - logerrorInt("Tearing down TCP Session %d - see " - "previous messages for reason(s)\n", - iTCPSess); -# ifdef USE_GSSAPI - if(allowedMethods & ALLOWEDMETHOD_GSS) - TCPSessGSSClose(iTCPSess); - else -# endif - TCPSessClose(iTCPSess); - } - } - --nfds; /* indicate we have processed one */ - } - iTCPSess = TCPSessGetNxtSess(iTCPSess); - } - } - + iRet = tcpsrv.Run(pOurTcpsrv); return iRet; ENDrunInput @@ -909,35 +156,11 @@ ENDrunInput /* initialize and return if will run or not */ BEGINwillRun CODESTARTwillRun -#ifdef USE_GSSAPI - /* first check if we must support plain TCP, too. If so, set mode - * accordingly. -- rgerhards, 2008-02-26 - */ - if(bPermitPlainTcp) - bEnableTCP |= ALLOWEDMETHOD_TCP; - /* first apply some config settings */ -#endif PrintAllowedSenders(2); /* TCP */ -#ifdef USE_GSSAPI - PrintAllowedSenders(3); /* GSS */ -#endif - if (bEnableTCP) { - if(sockTCPLstn == NULL) { -# ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) { - if(TCPSessGSSInit()) { - logerror("GSS-API initialization failed\n"); - bEnableTCP &= ~(ALLOWEDMETHOD_GSS); - } - } - if(bEnableTCP) -# endif - if((sockTCPLstn = create_tcp_socket()) != NULL) { - dbgprintf("Opened %d syslog TCP port(s).\n", *sockTCPLstn); - } - } - } + if(pOurTcpsrv == NULL) + ABORT_FINALIZE(RS_RET_NO_RUN); +finalize_it: ENDwillRun @@ -948,14 +171,27 @@ CODESTARTafterRun clearAllowedSenders (pAllowedSenders_TCP); pAllowedSenders_TCP = NULL; } -#ifdef USE_GSSAPI - if (pAllowedSenders_GSS != NULL) { - clearAllowedSenders (pAllowedSenders_GSS); - pAllowedSenders_GSS = NULL; - } -#endif ENDafterRun +BEGINmodExit +CODESTARTmodExit + iRet = tcpsrv.Destruct(&pOurTcpsrv); +#if 0 // TODO: remove + /* Close the TCP inet socket. */ + if(sockTCPLstn != NULL && *sockTCPLstn) { + deinit_tcp_listener(); + } +#endif +ENDmodExit + + +static rsRetVal +resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) +{ + iTCPSessMax = 200; + return RS_RET_OK; +} + BEGINfreeInstance CODESTARTfreeInstance @@ -967,36 +203,22 @@ CODESTARTdbgPrintInstInfo ENDdbgPrintInstInfo -BEGINmodExit -CODESTARTmodExit - /* Close the TCP inet socket. */ - if(sockTCPLstn != NULL && *sockTCPLstn) { - deinit_tcp_listener(); - } -#ifdef USE_GSSAPI - if(bEnableTCP & ALLOWEDMETHOD_GSS) - TCPSessGSSDeinit(); -#endif -ENDmodExit - - BEGINqueryEtryPt CODESTARTqueryEtryPt CODEqueryEtryPt_STD_IMOD_QUERIES ENDqueryEtryPt -#ifndef USE_GSSAPI -static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal) -{ - return RS_RET_OK; -} - - BEGINmodInit() CODESTARTmodInit *ipIFVersProvided = 1; /* so far, we only support the initial definition */ CODEmodInit_QueryRegCFSLineHdlr + pOurTcpsrv = NULL; + /* request objects we use */ +CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */ // TODO: framework must do this + CHKiRet(objUse(tcps_sess, "tcps_sess")); + CHKiRet(objUse(tcpsrv, "tcpsrv")); + /* register config file handlers */ CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputtcpserverrun", 0, eCmdHdlrGetWord, addTCPListener, NULL, STD_LOADABLE_MODULE_ID)); @@ -1005,7 +227,7 @@ CODEmodInit_QueryRegCFSLineHdlr CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID)); ENDmodInit -#endif + /* vim:set ai: */ @@ -3594,6 +3594,10 @@ static rsRetVal InitGlobalClasses(void) CHKiRet(exprClassInit()); CHKiRet(confClassInit()); +/* testing aides */ +CHKiRet(tcps_sessClassInit()); +CHKiRet(tcpsrvClassInit()); + /* dummy "classes" */ CHKiRet(actionClassInit()); diff --git a/tcps_sess.c b/tcps_sess.c new file mode 100644 index 00000000..0cff9d88 --- /dev/null +++ b/tcps_sess.c @@ -0,0 +1,421 @@ +/* tcps_sess.c + * + * This implements a session of the tcpsrv object. For general + * comments, see header of tcpsrv.c. + * + * NOTE: read comments in module-template.h to understand how this file + * works! + * + * File begun on 2008-03-01 by RGerhards (extracted from tcpsrv.c) + * + * 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. + */ + +#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 "syslogd.h" +#include "module-template.h" +#include "net.h" +#include "tcps_sess.h" +#include "obj.h" + + +/* static data */ +DEFobjStaticHelpers + +/* Standard-Constructor + */ +BEGINobjConstruct(tcps_sess) /* be sure to specify the object type also in END macro! */ + pThis->sock = -1; /* no sock */ + pThis->iMsg = 0; /* just make sure... */ + pThis->bAtStrtOfFram = 1; /* indicate frame header expected */ + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; /* just make sure... */ + if(pThis->pOnTCPSessConstruct != NULL) { + // TODO: return value! Supply user pointer or whole + // object? + pThis->pOnTCPSessConstruct(pThis->pUsr); + } +ENDobjConstruct(tcps_sess) + + +/* ConstructionFinalizer + */ +static rsRetVal +tcps_sessConstructFinalize(tcps_sess_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + if(pThis->pOnTCPSessConstructFinalize != NULL) { + // TODO: return value! Supply user pointer or whole + // object? + pThis->pOnTCPSessConstructFinalize(pThis->pUsr); + } + RETiRet; +} + + +/* destructor for the tcps_sess object */ +BEGINobjDestruct(tcps_sess) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(tcps_sess) + if(pThis->pOnTCPSessDestruct != NULL) { + // TODO: return value! Supply user pointer or whole + // object? + pThis->pOnTCPSessDestruct(pThis->pUsr); + } +ENDobjDestruct(tcps_sess) + + +/* debugprint for the tcps_sess object */ +BEGINobjDebugPrint(tcps_sess) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(tcps_sess) +ENDobjDebugPrint(tcps_sess) + + +/* set property functions */ +static rsRetVal +SetHost(tcps_sess_t *pThis, uchar *pszHost) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + +RUNLOG_VAR("%p", pThis->fromHost); + if(pThis->fromHost != NULL) { + free(pThis->fromHost); + pThis->fromHost = NULL; + } + +RUNLOG; + if((pThis->fromHost = strdup((char*)pszHost)) == NULL) + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + +RUNLOG; +finalize_it: + RETiRet; +} + +static rsRetVal +SetSock(tcps_sess_t *pThis, int sock) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + pThis->sock = sock; + RETiRet; +} + +static rsRetVal +SetMsgIdx(tcps_sess_t *pThis, int idx) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcps_sess); + pThis->iMsg = idx; + RETiRet; +} + + + +/* This should be called before a normal (non forced) close + * of a TCP session. This function checks if there is any unprocessed + * message left in the TCP stream. Such a message is probably a + * fragement. If evrything goes well, we must be right at the + * beginnig of a new frame without any data received from it. If + * not, there is some kind of a framing error. I think I remember that + * some legacy syslog/TCP implementations have non-LF terminated + * messages at the end of the stream. For now, we allow this behaviour. + * Later, it should probably become a configuration option. + * rgerhards, 2006-12-07 + */ +static rsRetVal +PrepareClose(tcps_sess_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + + if(pThis->bAtStrtOfFram == 1) { + /* this is how it should be. There is no unprocessed + * data left and such we have nothing to do. For simplicity + * reasons, we immediately return in that case. + */ + FINALIZE; + } + + /* we have some data left! */ + if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) { + /* In this case, we have an invalid frame count and thus + * generate an error message and discard the frame. + */ + logerrorInt("Incomplete frame at end of stream in session %d - " + "ignoring extra data (a message may be lost).\n", + pThis->sock); + /* nothing more to do */ + } else { /* here, we have traditional framing. Missing LF at the end + * of message may occur. As such, we process the message in + * this case. + */ + dbgprintf("Extra data at end of stream in legacy syslog/tcp message - processing\n"); + parseAndSubmitMessage(pThis->fromHost, pThis->msg, + pThis->iMsg, MSG_PARSE_HOSTNAME); + pThis->bAtStrtOfFram = 1; + } + +finalize_it: + RETiRet; +} + + +/* Closes a TCP session and marks its slot in the session + * table as unused. No attention is paid to the return code + * of close, so potential-double closes are not detected. + */ +static rsRetVal +Close(tcps_sess_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + close(pThis->sock); + pThis->sock = -1; + free(pThis->fromHost); + pThis->fromHost = NULL; /* not really needed, but... */ + + RETiRet; +} + + +/* Processes the data received via a TCP session. If there + * is no other way to handle it, data is discarded. + * Input parameter data is the data received, iLen is its + * len as returned from recv(). iLen must be 1 or more (that + * is errors must be handled by caller!). iTCPSess must be + * the index of the TCP session that received the data. + * rgerhards 2005-07-04 + * Changed this functions interface. We now return a status of + * what shall happen with the session. This is information for + * the caller. If 1 is returned, the session should remain open + * and additional data be accepted. If we return 0, the TCP + * session is to be closed by the caller. This functionality is + * needed in order to support framing errors, from which there + * is no recovery possible other than session termination and + * re-establishment. The need for this functionality thus is + * primarily rooted in support for -transport-tls I-D framing. + * rgerhards, 2006-12-07 + * And another change while generalizing. We now return either + * RS_RET_OK, which means the session should be kept open + * or anything else, which means it must be closed. + * rgerhards, 2008-03-01 + */ +static rsRetVal +DataRcvd(tcps_sess_t *pThis, char *pData, size_t iLen) +{ + DEFiRet; + register int iMsg; + char *pMsg; + char *pEnd; + + ISOBJ_TYPE_assert(pThis, tcps_sess); + assert(pData != NULL); + assert(iLen > 0); + + /* We now copy the message to the session buffer. As + * it looks, we need to do this in any case because + * we might run into multiple messages inside a single + * buffer. Of course, we could think about optimizations, + * but as this code is to be replaced by liblogging, it + * probably doesn't make so much sense... + * rgerhards 2005-07-04 + * + * Algo: + * - copy message to buffer until the first LF is found + * - printline() the buffer + * - continue with copying + */ + iMsg = pThis->iMsg; /* copy for speed */ + pMsg = pThis->msg; /* just a shortcut */ + pEnd = pData + iLen; /* this is one off, which is intensional */ + + while(pData < pEnd) { + /* Check if we are at a new frame */ + if(pThis->bAtStrtOfFram) { + /* we need to look at the message and detect + * the framing mode used + *//* + * Contrary to -transport-tls, we accept leading zeros in the message + * length. We do this in the spirit of "Be liberal in what you accept, + * and conservative in what you send". We expect that including leading + * zeros could be a common coding error. + * rgerhards, 2006-12-07 + * The chairs of the IETF syslog-sec WG have announced that it is + * consensus to do the octet count on the SYSLOG-MSG part only. I am + * now changing the code to reflect this. Hopefully, it will not change + * once again (there can no compatibility layer programmed for this). + * To be on the save side, I just comment the code out. I mark these + * comments with "IETF20061218". + * rgerhards, 2006-12-19 + */ + if(isdigit((int) *pData)) { + int iCnt; /* the frame count specified */ + pThis->eFraming = TCP_FRAMING_OCTET_COUNTING; + /* in this mode, we have OCTET-COUNT SP MSG - so we now need + * to extract the OCTET-COUNT and the SP and then extract + * the msg. + */ + iCnt = 0; + /* IETF20061218 int iNbrOctets = 0; / * number of octets already consumed */ + while(isdigit((int) *pData)) { + iCnt = iCnt * 10 + *pData - '0'; + /* IETF20061218 ++iNbrOctets; */ + ++pData; + } + dbgprintf("TCP Message with octet-counter, size %d.\n", iCnt); + if(*pData == ' ') { + ++pData; /* skip over SP */ + /* IETF20061218 ++iNbrOctets; */ + } else { + /* TODO: handle "invalid frame" case */ + logerrorInt("Framing Error in received TCP message: " + "delimiter is not SP but has ASCII value %d.\n", + *pData); + return(0); /* unconditional error exit */ + } + /* IETF20061218 pThis->iOctetsRemain = iCnt - iNbrOctets; */ + pThis->iOctetsRemain = iCnt; + if(pThis->iOctetsRemain < 1) { + /* TODO: handle the case where the octet count is 0 or negative! */ + dbgprintf("Framing Error: invalid octet count\n"); + logerrorInt("Framing Error in received TCP message: " + "invalid octet count %d.\n", + pThis->iOctetsRemain); + return(0); /* unconditional error exit */ + } + } else { + pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; + /* No need to do anything else here in this case */ + } + pThis->bAtStrtOfFram = 0; /* done frame header */ + } + + /* now copy message until end of record */ + + if(iMsg >= MAXLINE) { + /* emergency, we now need to flush, no matter if + * we are at end of message or not... + */ + parseAndSubmitMessage(pThis->fromHost, pMsg, iMsg, MSG_PARSE_HOSTNAME); + iMsg = 0; + /* we might think if it is better to ignore the rest of the + * message than to treat it as a new one. Maybe this is a good + * candidate for a configuration parameter... + * rgerhards, 2006-12-04 + */ + } + + if(*pData == '\n' && + pThis->eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delemiter? */ + parseAndSubmitMessage(pThis->fromHost, pMsg, iMsg, MSG_PARSE_HOSTNAME); + iMsg = 0; + pThis->bAtStrtOfFram = 1; + ++pData; + } else { + /* IMPORTANT: here we copy the actual frame content to the message! */ + *(pMsg + iMsg++) = *pData++; + } + + if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) { + /* do we need to find end-of-frame via octet counting? */ + pThis->iOctetsRemain--; + if(pThis->iOctetsRemain < 1) { + /* we have end of frame! */ + parseAndSubmitMessage(pThis->fromHost, pMsg, iMsg, MSG_PARSE_HOSTNAME); + iMsg = 0; + pThis->bAtStrtOfFram = 1; + } + } + } + + pThis->iMsg = iMsg; /* persist value */ + + return(1); /* successful return */ + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(tcps_sess) +CODESTARTobjQueryInterface(tcps_sess) + if(pIf->ifVersion != tcps_sessCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->DebugPrint = tcps_sessDebugPrint; + pIf->Construct = tcps_sessConstruct; + pIf->ConstructFinalize = tcps_sessConstructFinalize; + pIf->Destruct = tcps_sessDestruct; + + pIf->PrepareClose = PrepareClose; + pIf->Close = Close; + pIf->DataRcvd = DataRcvd; + + pIf->SetHost = SetHost; + pIf->SetSock = SetSock; + pIf->SetMsgIdx = SetMsgIdx; +finalize_it: +ENDobjQueryInterface(tcps_sess) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +//BEGINObjClassInit(tcps_sess, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */ +BEGINObjClassInit(tcps_sess, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + //CHKiRet(objUse(expr, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, tcps_sessDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcps_sessConstructFinalize); +ENDObjClassInit(tcps_sess) + + + +/* vim:set ai: + */ diff --git a/tcps_sess.h b/tcps_sess.h new file mode 100644 index 00000000..d3dc9c6d --- /dev/null +++ b/tcps_sess.h @@ -0,0 +1,73 @@ +/* Definitions for tcps_sess class. This implements a session of the + * plain TCP server. + * + * 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. + */ +#ifndef INCLUDED_TCPS_SESS_H +#define INCLUDED_TCPS_SESS_H + +#include "obj.h" + +/* framing modes for TCP */ +typedef enum _TCPFRAMINGMODE { + TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */ + TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */ + } TCPFRAMINGMODE; + +/* the tcps_sess object */ +typedef struct tcps_sess_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + int sock; + int iMsg; /* index of next char to store in msg */ + int bAtStrtOfFram; /* are we at the very beginning of a new frame? */ + int iOctetsRemain; /* Number of Octets remaining in message */ + TCPFRAMINGMODE eFraming; + char msg[MAXLINE+1]; + char *fromHost; + void *pUsr; /* a user-pointer */ + /* callbacks */ + rsRetVal (*pOnTCPSessConstruct)(void*); + rsRetVal (*pOnTCPSessConstructFinalize)(void*); + rsRetVal (*pOnTCPSessDestruct)(void*); +} tcps_sess_t; + + +/* interfaces */ +BEGINinterface(tcps_sess) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(tcps_sess); + rsRetVal (*Construct)(tcps_sess_t **ppThis); + rsRetVal (*ConstructFinalize)(tcps_sess_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(tcps_sess_t **ppThis); + rsRetVal (*PrepareClose)(tcps_sess_t *pThis); + rsRetVal (*Close)(tcps_sess_t *pThis); + rsRetVal (*DataRcvd)(tcps_sess_t *pThis, char *pData, size_t iLen); + /* set methods */ + rsRetVal (*SetHost)(tcps_sess_t *pThis, uchar*); + rsRetVal (*SetSock)(tcps_sess_t *pThis, int); + rsRetVal (*SetMsgIdx)(tcps_sess_t *pThis, int); +ENDinterface(tcps_sess) +#define tcps_sessCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(tcps_sess); + + +#endif /* #ifndef INCLUDED_TCPS_SESS_H */ diff --git a/tcpsrv.c b/tcpsrv.c new file mode 100644 index 00000000..0453b2d4 --- /dev/null +++ b/tcpsrv.c @@ -0,0 +1,762 @@ +/* tcpsrv.c + * + * Common code for plain TCP based servers. This is currently being + * utilized by imtcp and imgssapi. I suspect that when we implement + * SSL/TLS, that module could also use tcpsrv. + * + * There are actually two classes within the tcpserver code: one is + * the tcpsrv itself, the other one is its sessions. This is a helper + * class to tcpsrv. + * + * The common code here calls upon specific functionality by using + * callbacks. The specialised input modules need to set the proper + * callbacks before the code is run. The tcpsrv then calls back + * into the specific input modules at the appropriate time. + * + * 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, 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 "syslogd.h" +#include "cfsysline.h" +#include "module-template.h" +#include "net.h" +#include "srUtils.h" +#include "conf.h" +#include "tcpsrv.h" +#include "obj.h" + + +/* defines */ +#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */ + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(conf) +DEFobjCurrIf(tcps_sess) + +static char *TCPLstnPort = "0"; /* read-only after startup */ +static tcps_sess_t **pTCPSessions; +/* The thread-safeness of the sesion table is doubtful */ + + + +/* code to free all sockets within a socket table. + * A socket table is a descriptor table where the zero + * element has the count of elements. This is used for + * listening sockets. The socket table itself is also + * freed. + * A POINTER to this structure must be provided, thus + * double indirection! + * rgerhards, 2007-06-28 + */ +static void freeAllSockets(int **socks) +{ + assert(socks != NULL); + assert(*socks != NULL); + while(**socks) { + dbgprintf("Closing socket %d.\n", (*socks)[**socks]); + close((*socks)[**socks]); + (**socks)--; + } + free(*socks); + socks = NULL; +} + + +/* configure TCP listener settings. This is called during command + * line parsing. The argument following -t is supplied as an argument. + * The format of this argument is + * "<port-to-use>, <nbr-of-sessions>" + * Typically, there is no whitespace between port and session number. + * (but it may be...). + * NOTE: you can not use dbgprintf() in here - the dbgprintf() system is + * not yet initilized when this function is called. + * rgerhards, 2007-06-21 + * We can also not use logerror(), as that system is also not yet + * initialized... rgerhards, 2007-06-28 + */ +static void +configureTCPListen(tcpsrv_t *pThis, char *cOptarg) +{ + register int i; + register char *pArg = cOptarg; + + assert(cOptarg != NULL); + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* extract port */ + i = 0; + while(isdigit((int) *pArg)) { + i = i * 10 + *pArg++ - '0'; + } + + if( i >= 0 && i <= 65535) { + TCPLstnPort = cOptarg; + } else { + logerrorSz("Invalid TCP listen port %s - changed to 514.\n", cOptarg); + TCPLstnPort = "514"; + } +} + + +#if 0 // I think this is no longer needed +static void +configureTCPListenSessMax(char *cOptarg) +{ + register int i; + register char *pArg = cOptarg; + + assert(cOptarg != NULL); + + /* number of sessions */ + i = 0; + while(isdigit((int) *pArg)) { + i = i * 10 + *pArg++ - '0'; + } + + if(i > 0) + pThis->iSessMax = i; + else { + /* too small, need to adjust */ + logerrorSz("TCP session max configured to %s - changing to 1.\n", cOptarg); + pThis->iSessMax = 1; + } +} +#endif + + +/* Initialize the session table + * returns 0 if OK, somewhat else otherwise + */ +static rsRetVal +TCPSessTblInit(tcpsrv_t *pThis) +{ + DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pTCPSessions == NULL); + + dbgprintf("Allocating buffer for %d TCP sessions.\n", pThis->iSessMax); + if((pTCPSessions = (tcps_sess_t **) calloc(pThis->iSessMax, sizeof(tcps_sess_t *))) == NULL) { + dbgprintf("Error: TCPSessInit() could not alloc memory for TCP session table.\n"); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + +finalize_it: + RETiRet; +} + + +/* find a free spot in the session table. If the table + * is full, -1 is returned, else the index of the free + * entry (0 or higher). + */ +static int +TCPSessTblFindFreeSpot(tcpsrv_t *pThis) +{ + register int i; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + for(i = 0 ; i < pThis->iSessMax ; ++i) { + if(pTCPSessions[i] == NULL) + break; + } + + return((i < pThis->iSessMax) ? i : -1); +} + + +/* Get the next session index. Free session tables entries are + * skipped. This function is provided the index of the last + * session entry, or -1 if no previous entry was obtained. It + * returns the index of the next session or -1, if there is no + * further entry in the table. Please note that the initial call + * might as well return -1, if there is no session at all in the + * session table. + */ +static int +TCPSessGetNxtSess(tcpsrv_t *pThis, int iCurr) +{ + register int i; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) + if(pTCPSessions[i] != NULL) + break; + + return((i < pThis->iSessMax) ? i : -1); +} + + +/* De-Initialize TCP listner sockets. + * This function deinitializes everything, including freeing the + * session table. No TCP listen receive operations are permitted + * unless the subsystem is reinitialized. + * rgerhards, 2007-06-21 + */ +static void deinit_tcp_listener(tcpsrv_t *pThis) +{ + int iTCPSess; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + assert(pTCPSessions != NULL); + + /* close all TCP connections! */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(iTCPSess != -1) { + tcps_sess.Destruct(&pTCPSessions[iTCPSess]); + /* now get next... */ + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + + /* we are done with the session table - so get rid of it... + */ + free(pTCPSessions); + pTCPSessions = NULL; /* just to make sure... */ + + /* finally close the listen sockets themselfs */ + freeAllSockets(&pThis->pSocksLstn); +} + + +/* Initialize TCP sockets (for listener) + * This function returns either NULL (which means it failed) or + * a pointer to an array of file descriptiors. If the pointer is + * returned, the zeroest element [0] contains the count of valid + * descriptors. The descriptors themself follow in range + * [1] ... [num-descriptors]. It is guaranteed that each of these + * descriptors is valid, at least when this function returns. + * Please note that technically the array may be larger than the number + * of valid pointers stored in it. The memory overhead is minimal, so + * we do not bother to re-allocate an array of the exact size. Logically, + * the array still contains the exactly correct number of descriptors. + */ +static int *create_tcp_socket(tcpsrv_t *pThis) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks, on = 1; +DEFiRet; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + if(!strcmp(TCPLstnPort, "0")) + TCPLstnPort = "514"; + /* use default - we can not do service db update, because there is + * no IANA-assignment for syslog/tcp. In the long term, we might + * re-use RFC 3195 port of 601, but that would probably break to + * many existing configurations. + * rgerhards, 2007-06-28 + */ + dbgprintf("creating tcp socket on port %s\n", TCPLstnPort); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo(NULL, TCPLstnPort, &hints, &res); + if(error) { + logerror((char*) gai_strerror(error)); + return NULL; + } +RUNLOG; + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) + /* EMPTY */; + socks = malloc((maxs+1) * sizeof(int)); + if (socks == NULL) { + logerror("couldn't allocate memory for TCP listen sockets, suspending TCP message reception."); + freeaddrinfo(res); + return NULL; + } +RUNLOG; + + *socks = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r != NULL ; r = r->ai_next) { + *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (*s < 0) { + if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) + logerror("create_tcp_socket(), socket"); + /* it is debatable if PF_INET with EAFNOSUPPORT should + * also be ignored... + */ + continue; + } + +#ifdef IPV6_V6ONLY + if (r->ai_family == AF_INET6) { + int iOn = 1; + if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&iOn, sizeof (iOn)) < 0) { + logerror("TCP setsockopt"); + close(*s); + *s = -1; + continue; + } + } +#endif + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)) < 0 ) { + logerror("TCP setsockopt(REUSEADDR)"); + close(*s); + *s = -1; + continue; + } + + /* We need to enable BSD compatibility. Otherwise an attacker + * could flood our log files by sending us tons of ICMP errors. + */ +RUNLOG; +#ifndef BSD + if (should_use_so_bsdcompat()) { + if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, + (char *) &on, sizeof(on)) < 0) { + logerror("TCP setsockopt(BSDCOMPAT)"); + close(*s); + *s = -1; + continue; + } + } +#endif + + if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) +#ifndef IPV6_V6ONLY + && (errno != EADDRINUSE) +#endif + ) { + logerror("TCP bind"); + close(*s); + *s = -1; + continue; + } +RUNLOG; + + if( listen(*s,pThis->iSessMax / 10 + 5) < 0) { + /* If the listen fails, it most probably fails because we ask + * for a too-large backlog. So in this case we first set back + * to a fixed, reasonable, limit that should work. Only if + * that fails, too, we give up. + */ + logerrorInt("listen with a backlog of %d failed - retrying with default of 32.", + pThis->iSessMax / 10 + 5); + if(listen(*s, 32) < 0) { + logerror("TCP listen, suspending tcp inet"); + close(*s); + *s = -1; + continue; + } + } + + (*socks)++; + s++; + } +RUNLOG; + + if(res != NULL) + freeaddrinfo(res); + + if(Debug && *socks != maxs) + dbgprintf("We could initialize %d TCP listen sockets out of %d we received " + "- this may or may not be an error indication.\n", *socks, maxs); + + if(*socks == 0) { + logerror("No TCP listen socket could successfully be initialized, " + "message reception via TCP disabled.\n"); + free(socks); + return(NULL); + } + +RUNLOG; + /* OK, we had success. Now it is also time to + * initialize our connections + */ + if(TCPSessTblInit(pThis) != 0) { + /* OK, we are in some trouble - we could not initialize the + * session table, so we can not continue. We need to free all + * we have assigned so far, because we can not really use it... + */ + logerror("Could not initialize TCP session table, suspending TCP message reception."); + freeAllSockets(&socks); /* prevent a socket leak */ + return(NULL); + } + +dbgprintf("TCP socket(s) successfully created.\n"); + return(socks); +} + + +/* Accept new TCP connection; make entry in session table. If there + * is no more space left in the connection table, the new TCP + * connection is immediately dropped. + */ +static int +SessAccept(tcpsrv_t *pThis, int fd) +{ + DEFiRet; + + int newConn; + int iSess; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(struct sockaddr_storage); + uchar fromHost[NI_MAXHOST]; + uchar fromHostFQDN[NI_MAXHOST]; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + newConn = accept(fd, (struct sockaddr*) &addr, &addrlen); + if (newConn < 0) { + logerror("tcp accept, ignoring error and connection request"); + return -1; + } + + /* Add to session list */ + iSess = TCPSessTblFindFreeSpot(pThis); + if(iSess == -1) { + errno = 0; + logerror("too many tcp sessions - dropping incoming request"); + close(newConn); + return -1; + } + + /* OK, we have a "good" index... */ + /* get the host name */ + if(cvthname(&addr, fromHost, fromHostFQDN) != RS_RET_OK) { + /* we seem to have something malicous - at least we + * are now told to discard the connection request. + * Error message has been generated by cvthname. + */ + close (newConn); + return -1; + } + + /* 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(!pThis->pIsPermittedHost((struct sockaddr*) &addr, (char*) fromHostFQDN)) + { + dbgprintf("%s is not an allowed sender\n", (char *) fromHostFQDN); + if(option_DisallowWarning) { + errno = 0; + logerrorSz("TCP message from disallowed sender %s discarded", + (char*)fromHost); + } + close(newConn); + return -1; + } + + /* OK, we have an allowed sender, so let's continue */ + /* we first need to construct a new session object */ + CHKiRet(tcps_sess.Construct(&pTCPSessions[iSess])); + CHKiRet(tcps_sess.SetHost(pTCPSessions[iSess], fromHost)); + CHKiRet(tcps_sess.SetSock(pTCPSessions[iSess], newConn)); + CHKiRet(tcps_sess.SetMsgIdx(pTCPSessions[iSess], 0)); + CHKiRet(tcps_sess.ConstructFinalize(pTCPSessions[iSess])); + +finalize_it: +RUNLOG_VAR("%d", iRet); + if(iRet != RS_RET_OK) + iSess = -1; // TODO: change this to be fully iRet compliant ;) + + ENDfunc + return iSess; +} + + +/* This function is called to gather input. + */ +static rsRetVal +Run(tcpsrv_t *pThis) +{ + DEFiRet; + int maxfds; + int nfds; + int i; + int iTCPSess; + fd_set readfds; + + ISOBJ_TYPE_assert(pThis, tcpsrv); + + /* 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) { + maxfds = 0; + FD_ZERO (&readfds); + + /* Add the TCP listen sockets to the list of read descriptors. + */ + if(pThis->pSocksLstn != NULL && *pThis->pSocksLstn) { + for (i = 0; i < *pThis->pSocksLstn; i++) { + /* The if() below is theoretically not needed, but I leave it in + * so that a socket may become unsuable during execution. That + * feature is not yet supported by the current code base. + */ + if (pThis->pSocksLstn[i+1] != -1) { + if(Debug) + debugListenInfo(pThis->pSocksLstn[i+1], "TCP"); + FD_SET(pThis->pSocksLstn[i+1], &readfds); + if(pThis->pSocksLstn[i+1]>maxfds) maxfds=pThis->pSocksLstn[i+1]; + } + } + /* do the sessions */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(iTCPSess != -1) { + int fdSess; + fdSess = pTCPSessions[iTCPSess]->sock; // TODO: NOT CLEAN!, use method + dbgprintf("Adding TCP Session %d\n", fdSess); + FD_SET(fdSess, &readfds); + if (fdSess>maxfds) maxfds=fdSess; + /* now get next... */ + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + } + + if(Debug) { + // TODO: name in dbgprintf! + dbgprintf("--------<TCPSRV> 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 < *pThis->pSocksLstn; i++) { + if (FD_ISSET(pThis->pSocksLstn[i+1], &readfds)) { + dbgprintf("New connect on TCP inetd socket: #%d\n", pThis->pSocksLstn[i+1]); + pThis->pOnSessAccept(pThis, pThis->pSocksLstn[i+1]); + --nfds; /* indicate we have processed one */ + } + } + + /* now check the sessions */ + iTCPSess = TCPSessGetNxtSess(pThis, -1); + while(nfds && iTCPSess != -1) { + int fdSess; + int state; + fdSess = pTCPSessions[iTCPSess]->sock; // TODO: not clean, use method + if(FD_ISSET(fdSess, &readfds)) { + char buf[MAXLINE]; + dbgprintf("tcp session socket with new data: #%d\n", fdSess); + + /* Receive message */ + state = pThis->pRcvData(pTCPSessions[iTCPSess], buf, sizeof(buf)); + if(state == 0) { + pThis->pOnRegularClose(pTCPSessions[iTCPSess]); + tcps_sess.Destruct(&pTCPSessions[iTCPSess]); + } else if(state == -1) { + logerrorInt("TCP session %d will be closed, error ignored\n", fdSess); + pThis->pOnErrClose(pTCPSessions[iTCPSess]); + tcps_sess.Destruct(&pTCPSessions[iTCPSess]); + } else { + /* valid data received, process it! */ + if(tcps_sess.DataRcvd(pTCPSessions[iTCPSess], buf, state) == 0) { + /* in this case, something went awfully wrong. + * We are instructed to terminate the session. + */ + logerrorInt("Tearing down TCP Session %d - see " + "previous messages for reason(s)\n", + iTCPSess); + pThis->pOnErrClose(pTCPSessions[iTCPSess]); + tcps_sess.Destruct(&pTCPSessions[iTCPSess]); + } + } + --nfds; /* indicate we have processed one */ + } + iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); + } + } + + RETiRet; +} + + + + +/* Standard-Constructor + */ +BEGINobjConstruct(tcpsrv) /* be sure to specify the object type also in END macro! */ + pThis->pSocksLstn = NULL; + pThis->iSessMax = 200; // TODO: useful default ;) +ENDobjConstruct(tcpsrv) + + +/* ConstructionFinalizer + */ +static rsRetVal +tcpsrvConstructFinalize(tcpsrv_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, tcpsrv); + pThis->pSocksLstn = create_tcp_socket(pThis); + + RETiRet; +} + + +/* destructor for the tcpsrv object */ +BEGINobjDestruct(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(tcpsrv) + deinit_tcp_listener(pThis); +ENDobjDestruct(tcpsrv) + + +/* debugprint for the tcpsrv object */ +BEGINobjDebugPrint(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(tcpsrv) +ENDobjDebugPrint(tcpsrv) + +/* set functions */ +static rsRetVal +SetCBIsPermittedHost(tcpsrv_t *pThis, int (*pCB)(struct sockaddr *addr, char *fromHostFQDN)) +{ + DEFiRet; + pThis->pIsPermittedHost = pCB; + RETiRet; +} + +static rsRetVal +SetCBRcvData(tcpsrv_t *pThis, int (*pRcvData)(tcps_sess_t*, char*, size_t)) +{ + DEFiRet; + pThis->pRcvData = pRcvData; + RETiRet; +} + +static rsRetVal +SetCBOnListenDeinit(tcpsrv_t *pThis, int (*pCB)(void*)) +{ + DEFiRet; + pThis->pOnListenDeinit = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnSessAccept(tcpsrv_t *pThis, rsRetVal (*pCB)(tcpsrv_t*,int)) +{ + DEFiRet; + pThis->pOnSessAccept = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnRegularClose(tcpsrv_t *pThis, rsRetVal (*pCB)(tcps_sess_t*)) +{ + DEFiRet; + pThis->pOnRegularClose = pCB; + RETiRet; +} + +static rsRetVal +SetCBOnErrClose(tcpsrv_t *pThis, rsRetVal (*pCB)(tcps_sess_t*)) +{ + DEFiRet; + pThis->pOnErrClose = pCB; + RETiRet; +} + + + +/* queryInterface function + * rgerhards, 2008-02-29 + */ +BEGINobjQueryInterface(tcpsrv) +CODESTARTobjQueryInterface(tcpsrv) + if(pIf->ifVersion != tcpsrvCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + //pIf->freeAllSockets = freeAllSockets; + //pIf->configureTCPListen = configureTCPListen; + //pIf->configureTCPListenSessMax = configureTCPListenSessMax; + //pIf->TCPSessAccept = TCPSessAccept; + pIf->DebugPrint = tcpsrvDebugPrint; + pIf->Construct = tcpsrvConstruct; + pIf->ConstructFinalize = tcpsrvConstructFinalize; + pIf->Destruct = tcpsrvDestruct; + + pIf->SessAccept = SessAccept; + pIf->configureTCPListen = configureTCPListen; + pIf->Run = Run; + + pIf->SetCBIsPermittedHost = SetCBIsPermittedHost; + pIf->SetCBRcvData = SetCBRcvData; + pIf->SetCBOnListenDeinit = SetCBOnListenDeinit; + pIf->SetCBOnSessAccept = SetCBOnSessAccept; + pIf->SetCBOnRegularClose = SetCBOnRegularClose; + pIf->SetCBOnErrClose = SetCBOnErrClose; + +finalize_it: +ENDobjQueryInterface(tcpsrv) + + +/* Initialize our class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-29 + */ +//BEGINObjClassInit(tcpsrv, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */ +BEGINObjClassInit(tcpsrv, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */ + /* request objects we use */ + CHKiRet(objUse(tcps_sess, "tcps_sess")); + CHKiRet(objUse(conf, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, tcpsrvDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcpsrvConstructFinalize); +ENDObjClassInit(tcpsrv) + + + +/* vim:set ai: + */ diff --git a/tcpsrv.h b/tcpsrv.h new file mode 100644 index 00000000..cfde40d4 --- /dev/null +++ b/tcpsrv.h @@ -0,0 +1,68 @@ +/* Definitions for tcpsrv class. + * + * 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. + */ +#ifndef INCLUDED_TCPSRV_H +#define INCLUDED_TCPSRV_H + +#include "obj.h" +#include "tcps_sess.h" + +/* the tcpsrv object */ +typedef struct tcpsrv_s { + BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ + int *pSocksLstn; /* listen socket array for server [0] holds count */ + int iSessMax; /* max number of sessions supported */ + /* callbacks */ + int (*pIsPermittedHost)(struct sockaddr *addr, char *fromHostFQDN); + int (*pRcvData)(tcps_sess_t*, char*, size_t); + rsRetVal (*pOnListenDeinit)(void*); + rsRetVal (*pOnSessAccept)(struct tcpsrv_s *, int fd); + rsRetVal (*pOnRegularClose)(tcps_sess_t *pSess); + rsRetVal (*pOnErrClose)(tcps_sess_t *pSess); +} tcpsrv_t; + + +/* interfaces */ +BEGINinterface(tcpsrv) /* name must also be changed in ENDinterface macro! */ + INTERFACEObjDebugPrint(tcpsrv); + rsRetVal (*Construct)(tcpsrv_t **ppThis); + rsRetVal (*ConstructFinalize)(tcpsrv_t __attribute__((unused)) *pThis); + rsRetVal (*Destruct)(tcpsrv_t **ppThis); + void (*configureTCPListen)(tcpsrv_t*, char *cOptarg); + //no longer needed? void (*configureTCPListenSessMax)(char *cOptarg); + int (*SessAccept)(tcpsrv_t *pThis, int fd); + rsRetVal (*Run)(tcpsrv_t *pThis); + /* set methods */ + rsRetVal (*SetCBIsPermittedHost)(tcpsrv_t*, int (*) (struct sockaddr *addr, char*)); + rsRetVal (*SetCBRcvData)(tcpsrv_t *, int (*)(tcps_sess_t*, char*, size_t)); + rsRetVal (*SetCBOnListenDeinit)(tcpsrv_t*, rsRetVal (*)(void*)); + rsRetVal (*SetCBOnSessAccept)(tcpsrv_t*, rsRetVal (*) (tcpsrv_t*,int)); + rsRetVal (*SetCBOnRegularClose)(tcpsrv_t*, rsRetVal (*) (tcps_sess_t*)); + rsRetVal (*SetCBOnErrClose)(tcpsrv_t*, rsRetVal (*) (tcps_sess_t*)); +ENDinterface(tcpsrv) +#define tcpsrvCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */ + + +/* prototypes */ +PROTOTYPEObj(tcpsrv); + + +#endif /* #ifndef INCLUDED_TCPSRV_H */ |