/* nsd_ptcp.c
*
* An implementation of the nsd interface for plain tcp sockets.
*
* Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH.
*
* This file is part of the rsyslog runtime library.
*
* The rsyslog runtime library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The rsyslog runtime library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the rsyslog runtime library. If not, see .
*
* A copy of the GPL can be found in the file "COPYING" in this distribution.
* A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
*/
#include "config.h"
#include "rsyslog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "syslogd-types.h"
#include "module-template.h"
#include "parse.h"
#include "srUtils.h"
#include "obj.h"
#include "errmsg.h"
#include "net.h"
#include "nsd_ptcp.h"
MODULE_TYPE_LIB
/* static data */
DEFobjStaticHelpers
DEFobjCurrIf(errmsg)
DEFobjCurrIf(glbl)
DEFobjCurrIf(net)
/* Standard-Constructor
*/
BEGINobjConstruct(nsd_ptcp) /* be sure to specify the object type also in END macro! */
pThis->sock = -1;
pThis->iSessMax = 500; /* default max nbr of sessions -TODO:make configurable--rgerhards, 2008-04-17*/
ENDobjConstruct(nsd_ptcp)
/* destructor for the nsd_ptcp object */
BEGINobjDestruct(nsd_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */
int i;
CODESTARTobjDestruct(nsd_ptcp)
if(pThis->sock != -1) {
close(pThis->sock);
pThis->sock = -1;
}
if(pThis->socks != NULL) {
/* if we have some sockets at this stage, we need to close them */
for(i = 1 ; i <= pThis->socks[0] ; ++i)
close(pThis->socks[i]);
free(pThis->socks);
}
if(pThis->pRemHostIP != NULL)
free(pThis->pRemHostIP);
if(pThis->pRemHostName != NULL)
free(pThis->pRemHostName);
ENDobjDestruct(nsd_ptcp)
/* abort a connection. This is meant to be called immediately
* before the Destruct call. -- rgerhards, 2008-03-24
*/
static rsRetVal
Abort(nsd_t *pNsd)
{
struct linger ling;
nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
DEFiRet;
ISOBJ_TYPE_assert((pThis), nsd_ptcp);
if((pThis)->sock != -1) {
ling.l_onoff = 1;
ling.l_linger = 0;
if(setsockopt((pThis)->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0 ) {
dbgprintf("could not set SO_LINGER, errno %d\n", errno);
}
}
RETiRet;
}
/* Set pRemHost based on the address provided. This is to be called upon accept()ing
* a connection request. It must be provided by the socket we received the
* message on as well as a NI_MAXHOST size large character buffer for the FQDN.
* Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
* for some explanation of the code found below. If we detect a malicious
* hostname, we return RS_RET_MALICIOUS_HNAME and let the caller decide
* on how to deal with that.
* rgerhards, 2008-03-31
*/
static rsRetVal
FillRemHost(nsd_ptcp_t *pThis, struct sockaddr *pAddr)
{
int error;
uchar szIP[NI_MAXHOST] = "";
uchar szHname[NI_MAXHOST] = "";
struct addrinfo hints, *res;
size_t len;
DEFiRet;
ISOBJ_TYPE_assert(pThis, nsd_ptcp);
assert(pAddr != NULL);
error = getnameinfo(pAddr, SALEN(pAddr), (char*)szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST);
if(error) {
dbgprintf("Malformed from address %s\n", gai_strerror(error));
strcpy((char*)szHname, "???");
strcpy((char*)szIP, "???");
ABORT_FINALIZE(RS_RET_INVALID_HNAME);
}
if(!glbl.GetDisableDNS()) {
error = getnameinfo(pAddr, SALEN(pAddr), (char*)szHname, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
if(error == 0) {
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_socktype = SOCK_STREAM;
/* we now do a lookup once again. This one should fail,
* because we should not have obtained a non-numeric address. If
* we got a numeric one, someone messed with DNS!
*/
if(getaddrinfo((char*)szHname, NULL, &hints, &res) == 0) {
freeaddrinfo (res);
/* OK, we know we have evil, so let's indicate this to our caller */
snprintf((char*)szHname, NI_MAXHOST, "[MALICIOUS:IP=%s]", szIP);
dbgprintf("Malicious PTR record, IP = \"%s\" HOST = \"%s\"", szIP, szHname);
iRet = RS_RET_MALICIOUS_HNAME;
}
} else {
strcpy((char*)szHname, (char*)szIP);
}
} else {
strcpy((char*)szHname, (char*)szIP);
}
/* We now have the names, so now let's allocate memory and store them permanently.
* (side note: we may hold on to these values for quite a while, thus we trim their
* memory consumption)
*/
len = strlen((char*)szIP) + 1; /* +1 for \0 byte */
if((pThis->pRemHostIP = malloc(len)) == NULL)
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
memcpy(pThis->pRemHostIP, szIP, len);
len = strlen((char*)szHname) + 1; /* +1 for \0 byte */
if((pThis->pRemHostName = malloc(len)) == NULL) {
free(pThis->pRemHostIP); /* prevent leak */
pThis->pRemHostIP = NULL;
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
memcpy(pThis->pRemHostName, szHname, len);
finalize_it:
RETiRet;
}
/* accept an incoming connection request, sock provides the socket on which we can
* accept the new session.
* rgerhards, 2008-03-17
*/
static rsRetVal
AcceptConnReq(nsd_t **ppThis, int sock)
{
int sockflags;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
nsd_ptcp_t *pThis = NULL;
int iNewSock = -1;
DEFiRet;
assert(ppThis != NULL);
iNewSock = accept(sock, (struct sockaddr*) &addr, &addrlen);
if(iNewSock < 0) {
ABORT_FINALIZE(RS_RET_ACCEPT_ERR);
}
/* construct our object so that we can use it... */
CHKiRet(nsd_ptcpConstruct(&pThis));
CHKiRet(FillRemHost(pThis, (struct sockaddr*) &addr));
/* set the new socket to non-blocking IO */
if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check.
*/
sockflags = fcntl(iNewSock, F_SETFL, sockflags);
}
if(sockflags == -1) {
dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket %d", errno, iNewSock);
ABORT_FINALIZE(RS_RET_IO_ERROR);
}
pThis->sock = iNewSock;
*ppThis = (nsd_t*) pThis;
finalize_it:
if(iRet != RS_RET_OK) {
if(pThis != NULL)
nsd_ptcpDestruct(&pThis);
/* the close may be redundant, but that doesn't hurt... */
if(iNewSock >= 0)
close(iNewSock);
}
RETiRet;
}
/* initialize the tcp socket for a listner
* pLstnPort must point to a port name or number. NULL is NOT permitted
* (hint: we need to be careful when we use this module together with librelp,
* there NULL indicates the default port
* default is used.
* gerhards, 2008-03-17
*/
static rsRetVal
LstnInit(nsd_t *pNsd, uchar *pLstnPort)
{
nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
struct addrinfo hints, *res, *r;
int error, maxs, *s, on = 1;
int sockflags;
DEFiRet;
ISOBJ_TYPE_assert(pThis, nsd_ptcp);
assert(pLstnPort != NULL);
dbgprintf("creating tcp listen socket on port %s\n", pLstnPort);
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = glbl.GetDefPFFamily();
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(NULL, (char*) pLstnPort, &hints, &res);
if(error) {
dbgprintf("error %d querying port '%s'\n", error, pLstnPort);
ABORT_FINALIZE(RS_RET_INVALID_PORT);
}
/* Count max number of sockets we may open */
for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++)
/* EMPTY */;
pThis->socks = malloc((maxs+1) * sizeof(int));
if (pThis->socks == NULL) {
dbgprintf("couldn't allocate memory for TCP listen sockets, suspending RELP message reception.");
freeaddrinfo(res);
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
*pThis->socks = 0; /* num of sockets counter at start of array */
s = pThis->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))
dbgprintf("creating tcp listen 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) {
close(*s);
*s = -1;
continue;
}
}
#endif
if(setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
dbgprintf("error %d setting tcp socket option\n", errno);
close(*s);
*s = -1;
continue;
}
/* We use non-blocking IO! */
if((sockflags = fcntl(*s, F_GETFL)) != -1) {
sockflags |= O_NONBLOCK;
/* SETFL could fail too, so get it caught by the subsequent
* error check.
*/
sockflags = fcntl(*s, F_SETFL, sockflags);
}
if(sockflags == -1) {
dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket", errno);
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(net.should_use_so_bsdcompat()) {
if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT,
(char *) &on, sizeof(on)) < 0) {
errmsg.LogError(NO_ERRCODE, "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
) {
dbgprintf("error %d while binding tcp socket", errno);
close(*s);
*s = -1;
continue;
}
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.
*/
dbgprintf("listen with a backlog of %d failed - retrying with default of 32.",
pThis->iSessMax / 10 + 5);
if(listen(*s, 32) < 0) {
dbgprintf("tcp listen error %d, suspending\n", errno);
close(*s);
*s = -1;
continue;
}
}
(*pThis->socks)++;
s++;
}
if(res != NULL)
freeaddrinfo(res);
if(*pThis->socks != maxs)
dbgprintf("We could initialize %d RELP TCP listen sockets out of %d we received "
"- this may or may not be an error indication.\n", *pThis->socks, maxs);
if(*pThis->socks == 0) {
dbgprintf("No RELP TCP listen socket could successfully be initialized, "
"message reception via RELP disabled.\n");
free(pThis->socks);
ABORT_FINALIZE(RS_RET_COULD_NOT_BIND);
}
finalize_it:
RETiRet;
}
/* receive data from a tcp socket
* The lenBuf parameter must contain the max buffer size on entry and contains
* the number of octets read (or -1 in case of error) on exit. This function
* never blocks, not even when called on a blocking socket. That is important
* for client sockets, which are set to block during send, but should not
* block when trying to read data. If *pLenBuf is -1, an error occured and
* errno holds the exact error cause.
* rgerhards, 2008-03-17
*/
static rsRetVal
Rcv(nsd_t *pNsd, uchar *pRcvBuf, ssize_t *pLenBuf)
{
DEFiRet;
nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
ISOBJ_TYPE_assert(pThis, nsd_ptcp);
*pLenBuf = recv(pThis->sock, pRcvBuf, *pLenBuf, MSG_DONTWAIT);
RETiRet;
}
/* send a buffer. On entry, pLenBuf contains the number of octets to
* write. On exit, it contains the number of octets actually written.
* If this number is lower than on entry, only a partial buffer has
* been written.
* rgerhards, 2008-03-19
*/
static rsRetVal
Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf)
{
nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
ssize_t written;
DEFiRet;
ISOBJ_TYPE_assert(pThis, nsd_ptcp);
written = send(pThis->sock, pBuf, *pLenBuf, 0);
if(written == -1) {
switch(errno) {
case EAGAIN:
case EINTR:
/* this is fine, just retry... */
written = 0;
break;
default:
ABORT_FINALIZE(RS_RET_IO_ERROR);
break;
}
}
*pLenBuf = written;
finalize_it:
RETiRet;
}
/* open a connection to a remote host (server).
* rgerhards, 2008-03-19
*/
static rsRetVal
Connect(nsd_t *pNsd, int family, uchar *port, uchar *host)
{
nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
struct addrinfo *res = NULL;
struct addrinfo hints;
DEFiRet;
ISOBJ_TYPE_assert(pThis, nsd_ptcp);
assert(port != NULL);
assert(host != NULL);
assert(pThis->sock == -1);
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
if(getaddrinfo((char*)host, (char*)port, &hints, &res) != 0) {
dbgprintf("error %d in getaddrinfo\n", errno);
ABORT_FINALIZE(RS_RET_IO_ERROR);
}
if((pThis->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
ABORT_FINALIZE(RS_RET_IO_ERROR);
}
if(connect(pThis->sock, res->ai_addr, res->ai_addrlen) != 0) {
ABORT_FINALIZE(RS_RET_IO_ERROR);
}
finalize_it:
if(res != NULL)
freeaddrinfo(res);
if(iRet != RS_RET_OK) {
if(pThis->sock != -1) {
close(pThis->sock);
pThis->sock = -1;
}
}
RETiRet;
}
/* queryInterface function */
BEGINobjQueryInterface(nsd_ptcp)
CODESTARTobjQueryInterface(nsd_ptcp)
if(pIf->ifVersion != nsdCURR_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->Construct = (rsRetVal(*)(nsd_t**)) nsd_ptcpConstruct;
pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_ptcpDestruct;
pIf->Abort = Abort;
pIf->LstnInit = LstnInit;
pIf->AcceptConnReq = AcceptConnReq;
pIf->Rcv = Rcv;
pIf->Send = Send;
pIf->Connect = Connect;
finalize_it:
ENDobjQueryInterface(nsd_ptcp)
/* exit our class
*/
BEGINObjClassExit(nsd_ptcp, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
CODESTARTObjClassExit(nsd_ptcp)
/* release objects we no longer need */
objRelease(net, CORE_COMPONENT);
objRelease(glbl, CORE_COMPONENT);
objRelease(errmsg, CORE_COMPONENT);
ENDObjClassExit(nsd_ptcp)
/* Initialize the nsd_ptcp class. Must be called as the very first method
* before anything else is called inside this class.
* rgerhards, 2008-02-19
*/
BEGINObjClassInit(nsd_ptcp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
/* request objects we use */
CHKiRet(objUse(errmsg, CORE_COMPONENT));
CHKiRet(objUse(glbl, CORE_COMPONENT));
CHKiRet(objUse(net, CORE_COMPONENT));
/* set our own handlers */
ENDObjClassInit(nsd_ptcp)
/* --------------- here now comes the plumbing that makes as a library module --------------- */
BEGINmodExit
CODESTARTmodExit
nsd_ptcpClassExit();
ENDmodExit
BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_LIB_QUERIES
ENDqueryEtryPt
BEGINmodInit()
CODESTARTmodInit
*ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
/* Initialize all classes that are in our module - this includes ourselfs */
CHKiRet(nsd_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
ENDmodInit
/* vi:set ai:
*/