summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRainer Gerhards <rgerhards@adiscon.com>2007-07-22 10:15:39 +0000
committerRainer Gerhards <rgerhards@adiscon.com>2007-07-22 10:15:39 +0000
commite38d58bc743fe0c80789c5954488fdd54680f72a (patch)
tree32f34390ab9fae68e08d68c30d4a92d71f5c9b91
parent38c0cf2b0115202b67156d6938698f5d911c5def (diff)
downloadrsyslog-e38d58bc743fe0c80789c5954488fdd54680f72a.tar.gz
rsyslog-e38d58bc743fe0c80789c5954488fdd54680f72a.tar.xz
rsyslog-e38d58bc743fe0c80789c5954488fdd54680f72a.zip
moved some more modules to their own file sets
-rw-r--r--Makefile.am2
-rw-r--r--net.c242
-rw-r--r--net.h46
-rw-r--r--omfwd.c242
-rw-r--r--omfwd.h34
-rw-r--r--syslogd-types.h14
-rw-r--r--syslogd.c1481
-rw-r--r--syslogd.h52
-rw-r--r--tcpsyslog.c1062
-rw-r--r--tcpsyslog.h59
10 files changed, 1770 insertions, 1464 deletions
diff --git a/Makefile.am b/Makefile.am
index b1c85466..d098191b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,7 +7,7 @@ rfc3195d_SOURCES=rfc3195d.c rsyslog.h
man_MANS = rfc3195d.8 rklogd.8 rsyslogd.8 rsyslog.conf.5
-rsyslogd_SOURCES=syslogd.c pidfile.c template.c outchannel.c stringbuf.c srUtils.c parse.c syslogd-types.h template.h outchannel.h syslogd.h stringbuf.h parse.h srUtils.h liblogging-stub.h net.h msg.c msg.h omshell.c omshell.h omusrmsg.c omusrmsg.h ommysql.c ommysql.h
+rsyslogd_SOURCES=syslogd.c pidfile.c template.c outchannel.c stringbuf.c srUtils.c parse.c syslogd-types.h template.h outchannel.h syslogd.h stringbuf.h parse.h srUtils.h liblogging-stub.h net.c net.h msg.c msg.h omshell.c omshell.h omusrmsg.c omusrmsg.h ommysql.c ommysql.h omfwd.c omfwd.h tcpsyslog.c tcpsyslog.h
rsyslogd_CPPFLAGS=$(mysql_includes)
rsyslogd_LDADD=$(mysql_libs) $(zlib_libs) $(pthreads_libs)
diff --git a/net.c b/net.c
new file mode 100644
index 00000000..d540ad28
--- /dev/null
+++ b/net.c
@@ -0,0 +1,242 @@
+/* net.c
+ * Implementation of network-related stuff.
+ *
+ * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c)
+ * This file is under development and has not yet arrived at being fully
+ * self-contained and a real object. So far, it is mostly an excerpt
+ * of the "old" message code without any modifications. However, it
+ * helps to have things at the right place one we go to the meat of it.
+ *
+ * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ */
+#include "config.h"
+
+#ifdef SYSLOG_INET
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <netdb.h>
+
+#include "rsyslog.h"
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "net.h"
+
+/* The following #ifdef sequence is a small compatibility
+ * layer. It tries to work around the different availality
+ * levels of SO_BSDCOMPAT on linuxes...
+ * I borrowed this code from
+ * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html
+ * It still needs to be a bit better adapted to rsyslog.
+ * rgerhards 2005-09-19
+ */
+#ifndef BSD
+#include <sys/utsname.h>
+int should_use_so_bsdcompat(void)
+{
+ static int init_done;
+ static int so_bsdcompat_is_obsolete;
+
+ if (!init_done) {
+ struct utsname utsname;
+ unsigned int version, patchlevel;
+
+ init_done = 1;
+ if (uname(&utsname) < 0) {
+ dprintf("uname: %s\r\n", strerror(errno));
+ return 1;
+ }
+ /* Format is <version>.<patchlevel>.<sublevel><extraversion>
+ where the first three are unsigned integers and the last
+ is an arbitrary string. We only care about the first two. */
+ if (sscanf(utsname.release, "%u.%u", &version, &patchlevel) != 2) {
+ dprintf("uname: unexpected release '%s'\r\n",
+ utsname.release);
+ return 1;
+ }
+ /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5
+ kernels. It is a no-op in 2.4 but not in 2.2 kernels. */
+ if (version > 2 || (version == 2 && patchlevel >= 5))
+ so_bsdcompat_is_obsolete = 1;
+ }
+ return !so_bsdcompat_is_obsolete;
+}
+#else /* #ifndef BSD */
+#define should_use_so_bsdcompat() 1
+#endif /* #ifndef BSD */
+#ifndef SO_BSDCOMPAT
+/* this shall prevent compiler errors due to undfined name */
+#define SO_BSDCOMPAT 0
+#endif
+
+
+/* Return a printable representation of a host address.
+ * Now (2007-07-16) also returns the full host name (if it could be obtained)
+ * in the second param [thanks to mildew@gmail.com for the patch].
+ * The caller must provide buffer space for pszHost and pszHostFQDN. These
+ * buffers must be of size NI_MAXHOST. This is not checked here, because
+ * there is no way to check it. We use this way of doing things because it
+ * frees us from using dynamic memory allocation where it really does not
+ * pay.
+ * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
+ * for some explanation of the code found below. We do by default not
+ * discard message where we detected malicouos DNS PTR records. However,
+ * there is a user-configurabel option that will tell us if
+ * we should abort. For this, the return value tells the caller if the
+ * message should be processed (1) or discarded (0).
+ */
+int cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN)
+{
+ register uchar *p;
+ int count, error;
+ sigset_t omask, nmask;
+ char ip[NI_MAXHOST];
+ struct addrinfo hints, *res;
+
+ assert(f != NULL);
+ assert(pszHost != NULL);
+ assert(pszHostFQDN != NULL);
+
+ error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f),
+ ip, sizeof ip, NULL, 0, NI_NUMERICHOST);
+
+ if (error) {
+ dprintf("Malformed from address %s\n", gai_strerror(error));
+ strcpy((char*) pszHost, "???");
+ strcpy((char*) pszHostFQDN, "???");
+ return 1;
+ }
+
+ if (!DisableDNS) {
+ sigemptyset(&nmask);
+ sigaddset(&nmask, SIGHUP);
+ sigprocmask(SIG_BLOCK, &nmask, &omask);
+
+ error = getnameinfo((struct sockaddr *)f, sizeof(*f),
+ (char*)pszHostFQDN, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
+
+ if (error == 0) {
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ /* we now do a lookup once again. This one should fail,
+ * because we should not have obtained a numeric address. If
+ * we got a numeric one, someone messed with DNS!
+ */
+ if (getaddrinfo ((char*)pszHostFQDN, NULL, &hints, &res) == 0) {
+ uchar szErrMsg[1024];
+ freeaddrinfo (res);
+ /* OK, we know we have evil. The question now is what to do about
+ * it. One the one hand, the message might probably be intended
+ * to harm us. On the other hand, losing the message may also harm us.
+ * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords
+ * option. If it tells us we should discard, we do so, else we proceed,
+ * but log an error message together with it.
+ * time being, we simply drop the name we obtained and use the IP - that one
+ * is OK in any way. We do also log the error message. rgerhards, 2007-07-16
+ */
+ if(bDropMalPTRMsgs == 1) {
+ snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
+ "Malicious PTR record, message dropped "
+ "IP = \"%s\" HOST = \"%s\"",
+ ip, pszHostFQDN);
+ logerror((char*)szErrMsg);
+ return 0;
+ }
+
+ /* Please note: we deal with a malicous entry. Thus, we have crafted
+ * the snprintf() below so that all text is in front of the entry - maybe
+ * it would contain characters that would make the message unreadble
+ * (OK, I admit this is more or less impossible, but I am paranoid...)
+ * rgerhards, 2007-07-16
+ */
+ snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
+ "Malicious PTR record (message accepted, but used IP "
+ "instead of PTR name: IP = \"%s\" HOST = \"%s\"",
+ ip, pszHostFQDN);
+ logerror((char*)szErrMsg);
+
+ error = 1; /* that will trigger using IP address below. */
+ }
+ }
+ sigprocmask(SIG_SETMASK, &omask, NULL);
+ }
+
+ if (error || DisableDNS) {
+ dprintf("Host name for your address (%s) unknown\n", ip);
+ strcpy((char*) pszHostFQDN, ip);
+ strcpy((char*) pszHost, ip);
+ return 1;
+ }
+
+ /* Convert to lower case, just like LocalDomain above
+ */
+ for (p = pszHostFQDN ; *p ; p++)
+ if (isupper((int) *p))
+ *p = tolower(*p);
+
+ /* OK, the fqdn is now known. Now it is time to extract only the hostname
+ * part if we were instructed to do so.
+ */
+ /* TODO: quick and dirty right now: we need to optimize that. We simply
+ * copy over the buffer and then use the old code.
+ */
+ strcpy((char*)pszHost, (char*)pszHostFQDN);
+ if ((p = (uchar*) strchr((char*)pszHost, '.'))) {
+ if(strcmp((char*) (p + 1), LocalDomain) == 0) {
+ *p = '\0'; /* simply terminate the string */
+ return 1;
+ } else {
+ if (StripDomains) {
+ count=0;
+ while (StripDomains[count]) {
+ if (strcmp((char*)(p + 1), StripDomains[count]) == 0) {
+ *p = '\0';
+ return 1;
+ }
+ count++;
+ }
+ }
+ /* TODO: bug in syslogd? That all doesn't make so much sense... rger 2007-07-16 */
+ if (LocalHosts) {
+ count=0;
+ while (LocalHosts[count]) {
+ if (!strcmp((char*)pszHost, LocalHosts[count])) {
+ *p = '\0';
+ return 1;
+ }
+ count++;
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+#endif /* #ifdef SYSLOG_INET */
+/*
+ * vi:set ai:
+ */
diff --git a/net.h b/net.h
index b110d46b..5f6705d7 100644
--- a/net.h
+++ b/net.h
@@ -1,10 +1,29 @@
-/* All network stuff should go here (and to net.c)
+/* Definitions for network-related stuff.
+ *
+ * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
*/
#ifndef INCLUDED_NET_H
#define INCLUDED_NET_H
#ifdef SYSLOG_INET
+#include <netinet/in.h>
#define F_SET(where, flag) (where)|=(flag)
#define F_ISSET(where, flag) ((where)&(flag))==(flag)
@@ -27,5 +46,30 @@ struct NetAddr {
} addr;
};
+#ifndef BSD
+ int should_use_so_bsdcompat(void);
+#else
+# define should_use_so_bsdcompat() 1
+#endif /* #ifndef BSD */
+
+#ifndef SO_BSDCOMPAT
+ /* this shall prevent compiler errors due to undfined name */
+# define SO_BSDCOMPAT 0
+#endif
+
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+#define SALEN(sa) ((sa)->sa_len)
+#else
+static inline size_t SALEN(struct sockaddr *sa) {
+ switch (sa->sa_family) {
+ case AF_INET: return (sizeof (struct sockaddr_in));
+ case AF_INET6: return (sizeof (struct sockaddr_in6));
+ default: return 0;
+ }
+}
+#endif
+
+int cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN);
+
#endif /* #ifdef SYSLOG_INET */
#endif /* #ifndef INCLUDED_NET_H */
diff --git a/omfwd.c b/omfwd.c
new file mode 100644
index 00000000..ab4dcc5b
--- /dev/null
+++ b/omfwd.c
@@ -0,0 +1,242 @@
+/* omfwd.c
+ * This is the implementation of the build-in forwarding output module.
+ *
+ * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c)
+ * This file is under development and has not yet arrived at being fully
+ * self-contained and a real object. So far, it is mostly an excerpt
+ * of the "old" message code without any modifications. However, it
+ * helps to have things at the right place one we go to the meat of it.
+ *
+ * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ */
+#include "config.h"
+#ifdef SYSLOG_INET
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <fnmatch.h>
+#include <assert.h>
+#include <errno.h>
+#include "rsyslog.h"
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "net.h"
+#include "omfwd.h"
+#include "msg.h"
+#include "tcpsyslog.h"
+
+/*
+ * This table contains plain text for h_errno errors used by the
+ * net subsystem.
+ */
+static const char *sys_h_errlist[] = {
+ "No problem", /* NETDB_SUCCESS */
+ "Authoritative answer: host not found", /* HOST_NOT_FOUND */
+ "Non-authoritative answer: host not found, or serverfail", /* TRY_AGAIN */
+ "Non recoverable errors", /* NO_RECOVERY */
+ "Valid name, no data record of requested type", /* NO_DATA */
+ "no address, look for MX record" /* NO_ADDRESS */
+ };
+
+/* call the shell action
+ * returns 0 if it succeeds, something else otherwise
+ */
+int doActionFwd(selector_t *f, time_t now)
+{
+ char *psz; /* temporary buffering */
+ register unsigned l;
+ int i;
+ unsigned e, lsent = 0;
+ int bSendSuccess;
+ time_t fwd_suspend;
+ struct addrinfo *res, *r;
+ struct addrinfo hints;
+
+ assert(f != NULL);
+
+ switch (f->f_type) {
+ case F_FORW_SUSP:
+ fwd_suspend = time(NULL) - f->f_time;
+ if ( fwd_suspend >= INET_SUSPEND_TIME ) {
+ dprintf("\nForwarding suspension over, retrying FORW ");
+ f->f_type = F_FORW;
+ goto f_forw;
+ }
+ else {
+ dprintf(" %s\n", f->f_un.f_forw.f_hname);
+ dprintf("Forwarding suspension not over, time left: %d.\n",
+ INET_SUSPEND_TIME - fwd_suspend);
+ }
+ break;
+
+ /* The trick is to wait some time, then retry to get the
+ * address. If that fails retry x times and then give up.
+ *
+ * You'll run into this problem mostly if the name server you
+ * need for resolving the address is on the same machine, but
+ * is started after syslogd.
+ */
+ case F_FORW_UNKN:
+ /* The remote address is not yet known and needs to be obtained */
+ dprintf(" %s\n", f->f_un.f_forw.f_hname);
+ fwd_suspend = time(NULL) - f->f_time;
+ if(fwd_suspend >= INET_SUSPEND_TIME) {
+ dprintf("Forwarding suspension to unknown over, retrying\n");
+ memset(&hints, 0, sizeof(hints));
+ /* port must be numeric, because config file syntax requests this */
+ /* TODO: this code is a duplicate from cfline() - we should later create
+ * a common function.
+ */
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = family;
+ hints.ai_socktype = f->f_un.f_forw.protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM;
+ if((e = getaddrinfo(f->f_un.f_forw.f_hname,
+ getFwdSyslogPt(f), &hints, &res)) != 0) {
+ dprintf("Failure: %s\n", sys_h_errlist[h_errno]);
+ dprintf("Retries: %d\n", f->f_prevcount);
+ if ( --f->f_prevcount < 0 ) {
+ dprintf("Giving up.\n");
+ f->f_type = F_UNUSED;
+ }
+ else
+ dprintf("Left retries: %d\n", f->f_prevcount);
+ }
+ else {
+ dprintf("%s found, resuming.\n", f->f_un.f_forw.f_hname);
+ f->f_un.f_forw.f_addr = res;
+ f->f_prevcount = 0;
+ f->f_type = F_FORW;
+ goto f_forw;
+ }
+ }
+ else
+ dprintf("Forwarding suspension not over, time " \
+ "left: %d\n", INET_SUSPEND_TIME - fwd_suspend);
+ break;
+
+ case F_FORW:
+ f_forw:
+ dprintf(" %s:%s/%s\n", f->f_un.f_forw.f_hname, getFwdSyslogPt(f),
+ f->f_un.f_forw.protocol == FORW_UDP ? "udp" : "tcp");
+ iovCreate(f);
+ if ( strcmp(getHOSTNAME(f->f_pMsg), LocalHostName) && NoHops )
+ dprintf("Not sending message to remote.\n");
+ else {
+ f->f_time = now;
+ psz = iovAsString(f);
+ l = f->f_iLenpsziov;
+ if (l > MAXLINE)
+ l = MAXLINE;
+
+# ifdef USE_NETZIP
+ /* Check if we should compress and, if so, do it. We also
+ * check if the message is large enough to justify compression.
+ * The smaller the message, the less likely is a gain in compression.
+ * To save CPU cycles, we do not try to compress very small messages.
+ * What "very small" means needs to be configured. Currently, it is
+ * hard-coded but this may be changed to a config parameter.
+ * rgerhards, 2006-11-30
+ */
+ if(f->f_un.f_forw.compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) {
+ Bytef out[MAXLINE+MAXLINE/100+12] = "z";
+ uLongf destLen = sizeof(out) / sizeof(Bytef);
+ uLong srcLen = l;
+ int ret;
+ ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz,
+ srcLen, f->f_un.f_forw.compressionLevel);
+ dprintf("Compressing message, length was %d now %d, return state %d.\n",
+ l, (int) destLen, ret);
+ if(ret != Z_OK) {
+ /* if we fail, we complain, but only in debug mode
+ * Otherwise, we are silent. In any case, we ignore the
+ * failed compression and just sent the uncompressed
+ * data, which is still valid. So this is probably the
+ * best course of action.
+ * rgerhards, 2006-11-30
+ */
+ dprintf("Compression failed, sending uncompressed message\n");
+ } else if(destLen+1 < l) {
+ /* only use compression if there is a gain in using it! */
+ dprintf("there is gain in compression, so we do it\n");
+ psz = (char*) out;
+ l = destLen + 1; /* take care for the "z" at message start! */
+ }
+ ++destLen;
+ }
+# endif
+
+ if(f->f_un.f_forw.protocol == FORW_UDP) {
+ /* forward via UDP */
+ if(finet != NULL) {
+ /* we need to track if we have success sending to the remote
+ * peer. Success is indicated by at least one sendto() call
+ * succeeding. We track this be bSendSuccess. We can not simply
+ * rely on lsent, as a call might initially work, but a later
+ * call fails. Then, lsent has the error status, even though
+ * the sendto() succeeded.
+ * rgerhards, 2007-06-22
+ */
+ bSendSuccess = FALSE;
+ for (r = f->f_un.f_forw.f_addr; r; r = r->ai_next) {
+ for (i = 0; i < *finet; i++) {
+ lsent = sendto(finet[i+1], psz, l, 0,
+ r->ai_addr, r->ai_addrlen);
+ if (lsent == l) {
+ bSendSuccess = TRUE;
+ break;
+ } else {
+ int eno = errno;
+ dprintf("sendto() error: %d = %s.\n",
+ eno, strerror(eno));
+ }
+ }
+ if (lsent == l && !send_to_all)
+ break;
+ }
+ /* finished looping */
+ if (bSendSuccess == FALSE) {
+ f->f_type = F_FORW_SUSP;
+ errno = 0;
+ logerror("error forwarding via udp, suspending");
+ }
+ }
+ } else {
+ /* forward via TCP */
+ if(TCPSend(f, psz, l) != 0) {
+ /* error! */
+ f->f_type = F_FORW_SUSP;
+ errno = 0;
+ logerror("error forwarding via tcp, suspending...");
+ }
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+#endif /* #ifdef SYSLOG_INET */
+/*
+ * vi:set ai:
+ */
diff --git a/omfwd.h b/omfwd.h
new file mode 100644
index 00000000..e89c7698
--- /dev/null
+++ b/omfwd.h
@@ -0,0 +1,34 @@
+/* omfwd.h
+ * These are the definitions for the build-in forwarding output module.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ */
+#ifndef OMFWD_H_INCLUDED
+#define OMFWD_H_INCLUDED 1
+
+/* prototypes */
+
+int doActionFwd(selector_t *f, time_t now);
+
+#endif /* #ifndef OMFWD_H_INCLUDED */
+/*
+ * vi:set ai:
+ */
diff --git a/syslogd-types.h b/syslogd-types.h
index c5da7da4..f41e18b7 100644
--- a/syslogd-types.h
+++ b/syslogd-types.h
@@ -24,12 +24,16 @@
*/
#ifndef SYSLOGD_TYPES_INCLUDED
#define SYSLOGD_TYPES_INCLUDED 1
-
#include "config.h" /* make sure we have autoconf macros */
+
#include "stringbuf.h"
+#include "net.h"
#include <sys/param.h>
#include <sys/syslog.h>
+#define FALSE 0
+#define TRUE 1
+
#ifdef UT_NAMESIZE
# define UNAMESZ UT_NAMESIZE /* length of a login name */
#else
@@ -285,6 +289,14 @@ typedef struct moduleInfo {
} mod;
} modInfo_t;
+#ifdef SYSLOG_INET
+struct AllowedSenders {
+ struct NetAddr allowedSender; /* ip address allowed */
+ uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */
+ struct AllowedSenders *pNext;
+};
+#endif
+
#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */
/*
* vi:set ai:
diff --git a/syslogd.c b/syslogd.c
index ef80657b..929a3df5 100644
--- a/syslogd.c
+++ b/syslogd.c
@@ -133,7 +133,6 @@
* most probably be able to survive it and carry on with processing
* once the situation has been resolved.
*/
-#define MAXLINE 2048 /* maximum line length */
#define DEFUPRI (LOG_USER|LOG_NOTICE)
#define DEFSPRI (LOG_KERN|LOG_CRIT)
#define TIMERINTVL 30 /* interval for checking flush, mark */
@@ -173,7 +172,6 @@
#include <netinet/in.h>
#include <netdb.h>
#include <fnmatch.h>
-#include "net.h" /* struct NetAddr */
#ifndef __sun
#endif
@@ -194,21 +192,6 @@
#include <paths.h>
#endif
-#ifdef USE_NETZIP
-#include <zlib.h>
-/* config param: minimum message size to try compression. The smaller
- * the message, the less likely is any compression gain. We check for
- * gain before we submit the message. But to do so we still need to
- * do the (costly) compress() call. The following setting sets a size
- * for which no call to compress() is done at all. This may result in
- * a few more bytes being transmited but better overall performance.
- * Note: I have not yet checked the minimum UDP packet size. It might be
- * that we do not save anything by compressing very small messages, because
- * UDP might need to pad ;)
- * rgerhards, 2006-11-30
- */
-#define MIN_SIZE_FOR_COMPRESS 60
-#endif
/* handle some defines missing on more than one platform */
#ifndef SUN_LEN
@@ -225,14 +208,17 @@
#include "template.h"
#include "outchannel.h"
#include "syslogd.h"
+#include "net.h" /* struct NetAddr */
#include "stringbuf.h"
#include "parse.h"
#include "srUtils.h"
#include "msg.h"
+#include "tcpsyslog.h"
#include "omshell.h"
#include "omusrmsg.h"
#include "ommysql.h"
+#include "omfwd.h"
/* We define our own set of syslog defintions so that we
* do not need to rely on (possibly different) implementations.
@@ -369,53 +355,6 @@ syslogCODE rs_facilitynames[] =
#endif
-/* The following #ifdef sequence is a small compatibility
- * layer. It tries to work around the different availality
- * levels of SO_BSDCOMPAT on linuxes...
- * I borrowed this code from
- * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html
- * It still needs to be a bit better adapted to rsyslog.
- * rgerhards 2005-09-19
- */
-#ifndef BSD
-#include <sys/utsname.h>
-static int should_use_so_bsdcompat(void)
-{
- static int init_done;
- static int so_bsdcompat_is_obsolete;
-
- if (!init_done) {
- struct utsname utsname;
- unsigned int version, patchlevel;
-
- init_done = 1;
- if (uname(&utsname) < 0) {
- dprintf("uname: %s\r\n", strerror(errno));
- return 1;
- }
- /* Format is <version>.<patchlevel>.<sublevel><extraversion>
- where the first three are unsigned integers and the last
- is an arbitrary string. We only care about the first two. */
- if (sscanf(utsname.release, "%u.%u", &version, &patchlevel) != 2) {
- dprintf("uname: unexpected release '%s'\r\n",
- utsname.release);
- return 1;
- }
- /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5
- kernels. It is a no-op in 2.4 but not in 2.2 kernels. */
- if (version > 2 || (version == 2 && patchlevel >= 5))
- so_bsdcompat_is_obsolete = 1;
- }
- return !so_bsdcompat_is_obsolete;
-}
-#else /* #ifndef BSD */
-#define should_use_so_bsdcompat() 1
-#endif /* #ifndef BSD */
-#ifndef SO_BSDCOMPAT
-/* this shall prevent compiler errors due to undfined name */
-#define SO_BSDCOMPAT 0
-#endif
-
static char *ConfFile = _PATH_LOGCONF; /* read-only after startup */
static char *PidFile = _PATH_LOGPID; /* read-only after startup */
static char ctty[] = _PATH_CONSOLE; /* this is read-only */
@@ -453,19 +392,6 @@ int funix[MAXFUNIX] = { -1, }; /* read-only after startup */
#define ADDDATE 0x004 /* add a date to the message */
#define MARK 0x008 /* this message is a mark */
-/*
- * This table contains plain text for h_errno errors used by the
- * net subsystem.
- */
-static const char *sys_h_errlist[] = {
- "No problem", /* NETDB_SUCCESS */
- "Authoritative answer: host not found", /* HOST_NOT_FOUND */
- "Non-authoritative answer: host not found, or serverfail", /* TRY_AGAIN */
- "Non recoverable errors", /* NO_RECOVERY */
- "Valid name, no data record of requested type", /* NO_DATA */
- "no address, look for MX record" /* NO_ADDRESS */
- };
-
/* This table lists the directive lines:
*/
static const char *directive_name_list[] = {
@@ -542,21 +468,6 @@ union sockunion {
struct sockaddr_in su_sin;
struct sockaddr_in6 su_sin6;
};
-
-#define INET_SUSPEND_TIME 60 /* equal to 1 minute
- * rgerhards, 2005-07-26: This was 3 minutes. As the
- * same timer is used for tcp based syslog, we have
- * reduced it. However, it might actually be worth
- * thinking about a buffered tcp sender, which would be
- * a much better alternative. When that happens, this
- * time here can be re-adjusted to 3 minutes (or,
- * even better, made configurable).
- */
-#define INET_RETRY_MAX 30 /* maximum of retries for gethostbyname() */
- /* was 10, changed to 30 because we reduced INET_SUSPEND_TIME by one third. So
- * this "fixes" some of implications of it (see comment on INET_SUSPEND_TIME).
- * rgerhards, 2005-07-26
- */
#endif
#define LIST_DELIMITER ':' /* delimiter between two hosts */
@@ -626,7 +537,7 @@ static struct code FacNames[] = {
};
/* global variables for config file state */
-static int Debug; /* debug flag - read-only after startup */
+int Debug; /* debug flag - read-only after startup */
static int bFailOnChown; /* fail if chown fails? */
static uid_t fileUID; /* UID to be used for newly created files */
static uid_t fileGID; /* GID to be used for newly created files */
@@ -634,7 +545,7 @@ static uid_t dirUID; /* UID to be used for newly created directories */
static uid_t dirGID; /* GID to be used for newly created directories */
static int bDebugPrintTemplateList;/* output template list in debug mode? */
static int bCreateDirs; /* auto-create directories for dynaFiles: 0 - no, 1 - yes */
-static int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */
+int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */
static uchar cCCEscapeChar = '\\';/* character to be used to start an escape sequence for control chars */
static int bEscapeCCOnRcv; /* escape control characters on reception: 0 - no, 1 - yes */
static int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */
@@ -644,21 +555,21 @@ static int logEveryMsg = 0;/* no repeat message processing - read-only after st
*/
/* end global config file state variables */
-static char LocalHostName[MAXHOSTNAMELEN+1];/* our hostname - read-only after startup */
-static char *LocalDomain; /* our local domain name - read-only after startup */
-static int *finet = NULL; /* Internet datagram sockets, first element is nbr of elements
+char LocalHostName[MAXHOSTNAMELEN+1];/* our hostname - read-only after startup */
+char *LocalDomain; /* our local domain name - read-only after startup */
+int *finet = NULL; /* Internet datagram sockets, first element is nbr of elements
* read-only after init(), but beware of restart! */
static char *LogPort = "514"; /* port number for INET connections */
static int MarkInterval = 5;//20 * 60; /* interval between marks in seconds - read-only after startup */
-static int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both), set via cmdline */
-static int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */
+int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both), set via cmdline */
+int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */
static int MarkSeq = 0; /* mark sequence number - modified in domark() only */
static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */
-static int DisableDNS = 0; /* don't look up IP addresses of remote messages */
static int AcceptRemote = 0;/* receive messages that come via UDP - read-only after startup */
-static char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u.*/
-static char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup*/
-static int NoHops = 1; /* Can we bounce syslog messages through an
+int DisableDNS = 0; /* don't look up IP addresses of remote messages */
+char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u.*/
+char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup*/
+int NoHops = 1; /* Can we bounce syslog messages through an
intermediate host. Read-only after startup */
static int Initialized = 0; /* set when we have initialized ourselves
* rgerhards 2004-11-09: and by initialized, we mean that
@@ -729,20 +640,14 @@ static void resetConfigVariables(void)
* rgerhards, 2005-09-26
*/
#ifdef SYSLOG_INET
-struct AllowedSenders {
- struct NetAddr allowedSender; /* ip address allowed */
- uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */
- struct AllowedSenders *pNext;
-};
-
/* All of the five below are read-only after startup */
static struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */
-static struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */
+struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */
static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */
static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */
#endif /* #ifdef SYSLOG_INET */
-static int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */
+int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */
/* hardcoded standard templates (used for defaults) */
@@ -761,12 +666,10 @@ static void *singleWorker(); /* REMOVEME later 2005-10-24 */
#endif
/* Function prototypes. */
static char **crunch_list(char *list);
-static void printchopped(char *hname, char *msg, int len, int fd, int iSourceType);
static void printline(char *hname, char *msg, int iSource);
static void logmsg(int pri, msg_t*, int flags);
static void fprintlog(register selector_t *f);
static void reapchild();
-static int cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN);
static void debug_switch();
static rsRetVal cfline(char *line, register selector_t *f);
static int decode(uchar *name, struct code *codetab);
@@ -1016,18 +919,6 @@ static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedS
* rgerhards, 2005-09-27
*/
-#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
-#define SALEN(sa) ((sa)->sa_len)
-#else
-static inline size_t SALEN(struct sockaddr *sa) {
- switch (sa->sa_family) {
- case AF_INET: return (sizeof (struct sockaddr_in));
- case AF_INET6: return (sizeof (struct sockaddr_in6));
- default: return 0;
- }
-}
-#endif
-
static void PrintAllowedSenders(int iListToPrint)
{
struct AllowedSenders *pSender;
@@ -1142,7 +1033,7 @@ static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr
* returns 1, if the sender is allowed, 0 otherwise.
* rgerhards, 2005-09-26
*/
-static int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost)
+int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost)
{
struct AllowedSenders *pAllow;
@@ -1176,7 +1067,7 @@ static int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *p
* double indirection!
* rgerhards, 2007-06-28
*/
-static void freeAllSockets(int **socks)
+void freeAllSockets(int **socks)
{
assert(socks != NULL);
assert(*socks != NULL);
@@ -1189,1023 +1080,6 @@ static void freeAllSockets(int **socks)
socks = NULL;
}
-/********************************************************************
- * ### SYSLOG/TCP CODE ###
- * This is code for syslog/tcp. This code would belong to a separate
- * file - but I have put it here to avoid hassle with CVS. Over
- * time, I expect rsyslog to utilize liblogging for actual network
- * I/O. So the tcp code will be (re)moved some time. I don't like
- * to add a new file to cvs that I will push to the attic in just
- * a few weeks (month at most...). So I simply add the code here.
- *
- * Place no unrelated code between this comment and the
- * END tcp comment!
- *
- * 2005-07-04 RGerhards (Happy independence day to our US friends!)
- ********************************************************************/
-#ifdef SYSLOG_INET
-
-#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */
-
-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 */
-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;
-} *pTCPSessions = NULL;
-/* The thread-safeness of the sesion table is doubtful */
-
-
-/* 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 dprintf() in here - the dprintf() 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(char *cOptarg)
-{
- register int i;
- register char *pArg = cOptarg;
-
- assert(cOptarg != NULL);
- bEnableTCP = -1; /* enable TCP listening */
-
- /* extract port */
- i = 0;
- while(isdigit((int) *pArg)) {
- i = i * 10 + *pArg++ - '0';
- }
-
- if( i >= 0 && i <= 65535) {
- TCPLstnPort = cOptarg;
- } else {
- fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i);
- TCPLstnPort = "514";
- }
-
- /* number of sessions */
- if(*pArg == ','){
- *pArg = '\0'; /* hack: terminates port (see a few lines above, same buffer!) */
- ++pArg;
- while(isspace((int) *pArg))
- ++pArg;
- /* ok, here should be the number... */
- i = 0;
- while(isdigit((int) *pArg)) {
- i = i * 10 + *pArg++ - '0';
- }
- if(i > 1)
- iTCPSessMax = i;
- else {
- /* too small, need to adjust */
- fprintf(stderr,
- "rsyslogd: TCP session max configured to %d [-t %s] - changing to 1.\n",
- i, cOptarg);
- iTCPSessMax = 1;
- }
- } else if(*pArg == '\0') {
- /* use default for session number - that's already set...*/
- /*EMPTY BY INTENSION*/
- } else {
- fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", cOptarg);
- }
-}
-
-
-/* Initialize the session table
- * returns 0 if OK, somewhat else otherwise
- */
-static int TCPSessInit(void)
-{
- register int i;
-
- assert(pTCPSessions == NULL);
- dprintf("Allocating buffer for %d TCP sessions.\n", iTCPSessMax);
- if((pTCPSessions = (struct TCPSession *) malloc(sizeof(struct TCPSession) * iTCPSessMax))
- == NULL) {
- dprintf("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... */
- }
- 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.
- */
-static 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
- */
-static void deinit_tcp_listener(void)
-{
- int iTCPSess;
-
- assert(pTCPSessions != NULL);
- /* close all TCP connections! */
- iTCPSess = TCPSessGetNxtSess(-1);
- while(iTCPSess != -1) {
- int fd;
- fd = pTCPSessions[iTCPSess].sock;
- dprintf("Closing TCP Session %d\n", fd);
- close(fd);
- /* 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);
-}
-
-
-/* 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(void)
-{
- 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
- */
- 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)
- dprintf("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);
- }
-
- /* 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);
-}
-
-
-/* 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 void TCPSessAccept(int fd)
-{
- 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;
-
- newConn = accept(fd, (struct sockaddr*) &addr, &addrlen);
- if (newConn < 0) {
- logerror("tcp accept, ignoring error and connection request");
- return;
- }
-
- /* Add to session list */
- iSess = TCPSessFindFreeSpot();
- if(iSess == -1) {
- errno = 0;
- logerror("too many tcp sessions - dropping incoming request");
- close(newConn);
- return;
- }
-
- /* OK, we have a "good" index... */
- /* get the host name */
- if(cvthname(&addr, fromHost, fromHostFQDN) == 0) {
- /* 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;
- }
-
- /* 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(!isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) {
- if(option_DisallowWarning) {
- errno = 0;
- logerrorSz("TCP message from disallowed sender %s discarded",
- (char*)fromHost);
- }
- close(newConn);
- return;
- }
-
- /* 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! */
-}
-
-
-/* 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 void TCPSessPrepareClose(int iTCPSess)
-{
- 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.
- */
- dprintf("Extra data at end of stream in legacy syslog/tcp message - processing\n");
- printchopped(pTCPSessions[iTCPSess].fromHost, pTCPSessions[iTCPSess].msg,
- pTCPSessions[iTCPSess].iMsg, pTCPSessions[iTCPSess].sock, 1);
- pTCPSessions[iTCPSess].bAtStrtOfFram = 1;
- }
-}
-
-
-/* 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 void TCPSessClose(int iSess)
-{
- if(iSess < 0 || iSess > iTCPSessMax) {
- errno = 0;
- logerror("internal error, trying to close an invalid TCP session!");
- return;
- }
-
- close(pTCPSessions[iSess].sock);
- pTCPSessions[iSess].sock = -1;
- free(pTCPSessions[iSess].fromHost);
- pTCPSessions[iSess].fromHost = NULL; /* not really needed, but... */
-}
-
-
-/* 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
- */
-static 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;
- }
- dprintf("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! */
- dprintf("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...
- */
- printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg,
- pTCPSessions[iTCPSess].sock, 1);
- 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? */
- printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg,
- pTCPSessions[iTCPSess].sock, 1);
- 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! */
- printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg,
- pTCPSessions[iTCPSess].sock, 1);
- iMsg = 0;
- pTCPSessions[iTCPSess].bAtStrtOfFram = 1;
- }
- }
- }
-
- pTCPSessions[iTCPSess].iMsg = iMsg; /* persist value */
-
- return(1); /* successful return */
-}
-
-
-/* CODE FOR SENDING TCP MESSAGES */
-
-/* get send status
- * rgerhards, 2005-10-24
- */
-static void TCPSendSetStatus(selector_t *f, enum TCPSendStatus iNewState)
-{
- assert(f != NULL);
- assert(f->f_type == F_FORW);
- assert(f->f_un.f_forw.protocol == FORW_TCP);
- assert( (iNewState == TCP_SEND_NOTCONNECTED)
- || (iNewState == TCP_SEND_CONNECTING)
- || (iNewState == TCP_SEND_READY));
-
- /* there can potentially be a race condition, so guard by mutex */
-# ifdef USE_PTHREADS
- pthread_mutex_lock(&f->f_un.f_forw.mtxTCPSend);
-# endif
- f->f_un.f_forw.status = iNewState;
-# ifdef USE_PTHREADS
- pthread_mutex_unlock(&f->f_un.f_forw.mtxTCPSend);
-# endif
-}
-
-
-/* set send status
- * rgerhards, 2005-10-24
- */
-static enum TCPSendStatus TCPSendGetStatus(selector_t *f)
-{
- enum TCPSendStatus eState;
- assert(f != NULL);
- assert(f->f_type == F_FORW);
- assert(f->f_un.f_forw.protocol == FORW_TCP);
-
- /* there can potentially be a race condition, so guard by mutex */
-# ifdef USE_PTHREADS
- pthread_mutex_lock(&f->f_un.f_forw.mtxTCPSend);
-# endif
- eState = f->f_un.f_forw.status;
-# ifdef USE_PTHREADS
- pthread_mutex_unlock(&f->f_un.f_forw.mtxTCPSend);
-# endif
-
- return eState;
-}
-
-
-/* Initialize TCP sockets (for sender)
- * This is done once per selector line, if not yet initialized.
- */
-static int TCPSendCreateSocket(selector_t *f)
-{
- int fd;
- struct addrinfo *r;
-
- assert(f != NULL);
-
- r = f->f_un.f_forw.f_addr;
-
- while(r != NULL) {
- fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
- if (fd != -1) {
- /* We can not allow the TCP sender to block syslogd, at least
- * not in a single-threaded design. That would cause rsyslogd to
- * loose input messages - which obviously also would affect
- * other selector lines, too. So we do set it to non-blocking and
- * handle the situation ourselfs (by discarding messages). IF we run
- * dual-threaded, however, the situation is different: in this case,
- * the receivers and the selector line processing are only loosely
- * coupled via a memory buffer. Now, I think, we can afford the extra
- * wait time. Thus, we enable blocking mode for TCP if we compile with
- * pthreads.
- * rgerhards, 2005-10-25
- */
-# ifndef USE_PTHREADS
- /* set to nonblocking - rgerhards 2005-07-20 */
- fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
-# endif
- if (connect (fd, r->ai_addr, r->ai_addrlen) != 0) {
- if(errno == EINPROGRESS) {
- /* this is normal - will complete during select */
- TCPSendSetStatus(f, TCP_SEND_CONNECTING);
- return fd;
- } else {
- dprintf("create tcp connection failed, reason %s",
- strerror(errno));
- }
-
- }
- else {
- TCPSendSetStatus(f, TCP_SEND_READY);
- return fd;
- }
- close(fd);
- }
- else {
- dprintf("couldn't create send socket, reason %s", strerror(errno));
- }
- r = r->ai_next;
- }
-
- dprintf("no working socket could be obtained");
-
- return -1;
-}
-
-
-/* Sends a TCP message. It is first checked if the
- * session is open and, if not, it is opened. Then the send
- * is tried. If it fails, one silent re-try is made. If the send
- * fails again, an error status (-1) is returned. If all goes well,
- * 0 is returned. The TCP session is NOT torn down.
- * For now, EAGAIN is ignored (causing message loss) - but it is
- * hard to do something intelligent in this case. With this
- * implementation here, we can not block and/or defer. Things are
- * probably a bit better when we move to liblogging. The alternative
- * would be to enhance the current select server with buffering and
- * write descriptors. This seems not justified, given the expected
- * short life span of this code (and the unlikeliness of this event).
- * rgerhards 2005-07-06
- * This function is now expected to stay. Libloging won't be used for
- * that purpose. I have added the param "len", because it is known by the
- * caller and so safes us some time. Also, it MUST be given because there
- * may be NULs inside msg so that we can not rely on strlen(). Please note
- * that the restrictions outlined above do not existin in multi-threaded
- * mode, which we assume will now be most often used. So there is no
- * real issue with the potential message loss in single-threaded builds.
- * rgerhards, 2006-11-30
- *
- * In order to support compressed messages via TCP, we must support an
- * octet-counting based framing (LF may be part of the compressed message).
- * We are now supporting the same mode that is available in IETF I-D
- * syslog-transport-tls-05 (current at the time of this writing). This also
- * eases things when we go ahead and implement that framing. I have now made
- * available two cases where this framing is used: either by explitely
- * specifying it in the config file or implicitely when sending a compressed
- * message. In the later case, compressed and uncompressed messages within
- * the same session have different framings. If it is explicitely set to
- * octet-counting, only this framing mode is used within the session.
- * rgerhards, 2006-12-07
- */
-static int TCPSend(selector_t *f, char *msg, size_t len)
-{
- int retry = 0;
- int done = 0;
- int bIsCompressed;
- int lenSend;
- short f_type;
- char *buf = NULL; /* if this is non-NULL, it MUST be freed before return! */
- enum TCPSendStatus eState;
- TCPFRAMINGMODE framingToUse;
-
- assert(f != NULL);
- assert(msg != NULL);
- assert(len > 0);
-
- bIsCompressed = *msg == 'z'; /* cache this, so that we can modify the message buffer */
- /* select framing for this record. If we have a compressed record, we always need to
- * use octet counting because the data potentially contains all control characters
- * including LF.
- */
- framingToUse = bIsCompressed ? TCP_FRAMING_OCTET_COUNTING : f->f_un.f_forw.tcp_framing;
-
- do { /* try to send message */
- if(f->f_file <= 0) {
- /* we need to open the socket first */
- if((f->f_file = TCPSendCreateSocket(f)) <= 0) {
- return -1;
- }
- }
-
- eState = TCPSendGetStatus(f); /* cache info */
-
- if(eState == TCP_SEND_CONNECTING) {
- /* In this case, we save the buffer. If we have a
- * system with few messages, that hopefully prevents
- * message loss at all. However, we make no further attempts,
- * just the first message is saved. So we only try this
- * if there is not yet a saved message present.
- * rgerhards 2005-07-20
- */
- if(f->f_un.f_forw.savedMsg == NULL) {
- f->f_un.f_forw.savedMsg = malloc(len * sizeof(char));
- if(f->f_un.f_forw.savedMsg == NULL)
- return 0; /* nothing we can do... */
- memcpy(f->f_un.f_forw.savedMsg, msg, len);
- f->f_un.f_forw.savedMsgLen = len;
- }
- return 0;
- } else if(eState != TCP_SEND_READY)
- /* This here is debatable. For the time being, we
- * accept the loss of a single message (e.g. during
- * connection setup in favour of not messing with
- * wait time and timeouts. The reason is that such
- * things might otherwise cost us considerable message
- * loss on the receiving side (even at a timeout set
- * to just 1 second). - rgerhards 2005-07-20
- */
- return 0;
-
- /* now check if we need to add a line terminator. We need to
- * copy the string in memory in this case, this is probably
- * quicker than using writev and definitely quicker than doing
- * two socket calls.
- * rgerhards 2005-07-22
- *//*
- * Some messages already contain a \n character at the end
- * of the message. We append one only if we there is not
- * already one. This seems the best fit, though this also
- * means the message does not arrive unaltered at the final
- * destination. But in the spirit of legacy syslog, this is
- * probably the best to do...
- * rgerhards 2005-07-20
- */
-
- /* Build frame based on selected framing */
- if(framingToUse == TCP_FRAMING_OCTET_STUFFING) {
- if((*(msg+len-1) != '\n')) {
- if(buf != NULL)
- free(buf);
- /* in the malloc below, we need to add 2 to the length. The
- * reason is that we a) add one character and b) len does
- * not take care of the '\0' byte. Up until today, it was just
- * +1 , which caused rsyslogd to sometimes dump core.
- * I have added this comment so that the logic is not accidently
- * changed again. rgerhards, 2005-10-25
- */
- if((buf = malloc((len + 2) * sizeof(char))) == NULL) {
- /* extreme mem shortage, try to solve
- * as good as we can. No point in calling
- * any alarms, they might as well run out
- * of memory (the risk is very high, so we
- * do NOT risk that). If we have a message of
- * more than 1 byte (what I guess), we simply
- * overwrite the last character.
- * rgerhards 2005-07-22
- */
- if(len > 1) {
- *(msg+len-1) = '\n';
- } else {
- /* we simply can not do anything in
- * this case (its an error anyhow...).
- */
- }
- } else {
- /* we got memory, so we can copy the message */
- memcpy(buf, msg, len); /* do not copy '\0' */
- *(buf+len) = '\n';
- *(buf+len+1) = '\0';
- msg = buf; /* use new one */
- ++len; /* care for the \n */
- }
- }
- } else {
- /* Octect-Counting
- * In this case, we need to always allocate a buffer. This is because
- * we need to put a header in front of the message text
- */
- char szLenBuf[16];
- int iLenBuf;
-
- /* important: the printf-mask is "%d<sp>" because there must be a
- * space after the len!
- *//* 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
- */
- iLenBuf = snprintf(szLenBuf, sizeof(szLenBuf)/sizeof(char), "%d ", (int) len);
- /* IETF20061218 iLenBuf =
- snprintf(szLenBuf, sizeof(szLenBuf)/sizeof(char), "%d ", len + iLenBuf);*/
-
- if((buf = malloc((len + iLenBuf) * sizeof(char))) == NULL) {
- /* we are out of memory. This is an extreme situation. We do not
- * call any alarm handlers because they most likely run out of mem,
- * too. We are brave enough to call debug output, though. Other than
- * that, there is nothing left to do. We can not sent the message (as
- * in case of the other framing, because the message is incomplete.
- * We could, however, send two chunks (header and text separate), but
- * that would cause a lot of complexity in the code. So we think it
- * is appropriate enough to just make sure we do not crash in this
- * very unlikely case. For this, it is justified just to loose
- * the message. Rgerhards, 2006-12-07
- */
- dprintf("Error: out of memory when building TCP octet-counted "
- "frame. Message is lost, trying to continue.\n");
- return 0;
- }
-
- memcpy(buf, szLenBuf, iLenBuf); /* header */
- memcpy(buf + iLenBuf, msg, len); /* message */
- len += iLenBuf; /* new message size */
- msg = buf; /* set message buffer */
- }
-
- /* frame building complete, on to actual sending */
-
- lenSend = send(f->f_file, msg, len, 0);
- dprintf("TCP sent %d bytes, requested %d, msg: '%s'\n", lenSend, len,
- bIsCompressed ? "***compressed***" : msg);
- if((unsigned)lenSend == len) {
- /* all well */
- if(buf != NULL) {
- free(buf);
- }
- return 0;
- } else if(lenSend != -1) {
- /* no real error, could "just" not send everything...
- * For the time being, we ignore this...
- * rgerhards, 2005-10-25
- */
- dprintf("message not completely (tcp)send, ignoring %d\n", lenSend);
-# if USE_PTHREADS
- usleep(1000); /* experimental - might be benefitial in this situation */
-# endif
- if(buf != NULL)
- free(buf);
- return 0;
- }
-
- switch(errno) {
- case EMSGSIZE:
- dprintf("message not (tcp)send, too large\n");
- /* This is not a real error, so it is not flagged as one */
- if(buf != NULL)
- free(buf);
- return 0;
- break;
- case EINPROGRESS:
- case EAGAIN:
- dprintf("message not (tcp)send, would block\n");
-# if USE_PTHREADS
- usleep(1000); /* experimental - might be benefitial in this situation */
-# endif
- /* we loose this message, but that's better than loosing
- * all ;)
- */
- /* This is not a real error, so it is not flagged as one */
- if(buf != NULL)
- free(buf);
- return 0;
- break;
- default:
- f_type = f->f_type;
- f->f_type = F_UNUSED;
- logerror("message not (tcp)send");
- f->f_type = f_type;
- break;
- }
-
- if(retry == 0) {
- ++retry;
- /* try to recover */
- close(f->f_file);
- TCPSendSetStatus(f, TCP_SEND_NOTCONNECTED);
- f->f_file = -1;
- } else {
- if(buf != NULL)
- free(buf);
- return -1;
- }
- } while(!done); /* warning: do ... while() */
- /*NOT REACHED*/
-
- if(buf != NULL)
- free(buf);
- return -1; /* only to avoid compiler warning! */
-}
-
-#endif
-/********************************************************************
- * ### END OF SYSLOG/TCP CODE ###
- ********************************************************************/
-
/* get the syslog forward port from selector_t. The passed in
* struct must be one that is setup for forwarding.
@@ -2213,7 +1087,7 @@ static int TCPSend(selector_t *f, char *msg, size_t len)
* We may change the implementation to try to lookup the port
* if it is unspecified. So far, we use the IANA default auf 514.
*/
-static char *getFwdSyslogPt(selector_t *f)
+char *getFwdSyslogPt(selector_t *f)
{
assert(f != NULL);
if(f->f_un.f_forw.port == NULL)
@@ -2237,8 +1111,6 @@ static char *getFwdSyslogPt(selector_t *f)
* DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!!
*/
-#define FALSE 0
-#define TRUE 1
/**
* Parse a 32 bit integer number from a string.
*
@@ -3554,7 +2426,7 @@ static void untty()
* no longer rely just on the source (rfc3195d forwarded messages arrive via
* unix domain sockets but contain the hostname). rgerhards, 2005-10-06
*/
-static void printchopped(char *hname, char *msg, int len, int fd, int bParseHost)
+void printchopped(char *hname, char *msg, int len, int fd, int bParseHost)
{
register int iMsg;
char *pMsg;
@@ -5443,19 +4315,8 @@ again:
*/
void fprintlog(register selector_t *f)
{
- char *psz; /* temporary buffering */
- register unsigned l;
msg_t *pMsgSave; /* to save current message pointer, necessary to restore
it in case it needs to be updated (e.g. repeated msgs) */
-#ifdef SYSLOG_INET
- int i;
- unsigned e, lsent = 0;
- int bSendSuccess;
- time_t fwd_suspend;
- struct addrinfo *res, *r;
- struct addrinfo hints;
-#endif
-
pMsgSave = NULL; /* indicate message poiner not saved */
/* first check if this is a regular message or the repeation of
@@ -5499,169 +4360,13 @@ void fprintlog(register selector_t *f)
dprintf("\n");
break;
-#ifdef SYSLOG_INET
case F_FORW_SUSP:
- fwd_suspend = time(NULL) - f->f_time;
- if ( fwd_suspend >= INET_SUSPEND_TIME ) {
- dprintf("\nForwarding suspension over, retrying FORW ");
- f->f_type = F_FORW;
- goto f_forw;
- }
- else {
- dprintf(" %s\n", f->f_un.f_forw.f_hname);
- dprintf("Forwarding suspension not over, time left: %d.\n",
- INET_SUSPEND_TIME - fwd_suspend);
- }
- break;
-
- /* The trick is to wait some time, then retry to get the
- * address. If that fails retry x times and then give up.
- *
- * You'll run into this problem mostly if the name server you
- * need for resolving the address is on the same machine, but
- * is started after syslogd.
- */
case F_FORW_UNKN:
- /* The remote address is not yet known and needs to be obtained */
- dprintf(" %s\n", f->f_un.f_forw.f_hname);
- fwd_suspend = time(NULL) - f->f_time;
- if(fwd_suspend >= INET_SUSPEND_TIME) {
- dprintf("Forwarding suspension to unknown over, retrying\n");
- memset(&hints, 0, sizeof(hints));
- /* port must be numeric, because config file syntax requests this */
- /* TODO: this code is a duplicate from cfline() - we should later create
- * a common function.
- */
- hints.ai_flags = AI_NUMERICSERV;
- hints.ai_family = family;
- hints.ai_socktype = f->f_un.f_forw.protocol == FORW_UDP ? SOCK_DGRAM : SOCK_STREAM;
- if((e = getaddrinfo(f->f_un.f_forw.f_hname,
- getFwdSyslogPt(f), &hints, &res)) != 0) {
- dprintf("Failure: %s\n", sys_h_errlist[h_errno]);
- dprintf("Retries: %d\n", f->f_prevcount);
- if ( --f->f_prevcount < 0 ) {
- dprintf("Giving up.\n");
- f->f_type = F_UNUSED;
- }
- else
- dprintf("Left retries: %d\n", f->f_prevcount);
- }
- else {
- dprintf("%s found, resuming.\n", f->f_un.f_forw.f_hname);
- f->f_un.f_forw.f_addr = res;
- f->f_prevcount = 0;
- f->f_type = F_FORW;
- goto f_forw;
- }
- }
- else
- dprintf("Forwarding suspension not over, time " \
- "left: %d\n", INET_SUSPEND_TIME - fwd_suspend);
- break;
-
case F_FORW:
- f_forw:
- dprintf(" %s:%s/%s\n", f->f_un.f_forw.f_hname, getFwdSyslogPt(f),
- f->f_un.f_forw.protocol == FORW_UDP ? "udp" : "tcp");
- iovCreate(f);
- if ( strcmp(getHOSTNAME(f->f_pMsg), LocalHostName) && NoHops )
- dprintf("Not sending message to remote.\n");
- else {
- f->f_time = now;
- psz = iovAsString(f);
- l = f->f_iLenpsziov;
- if (l > MAXLINE)
- l = MAXLINE;
-
-# ifdef USE_NETZIP
- /* Check if we should compress and, if so, do it. We also
- * check if the message is large enough to justify compression.
- * The smaller the message, the less likely is a gain in compression.
- * To save CPU cycles, we do not try to compress very small messages.
- * What "very small" means needs to be configured. Currently, it is
- * hard-coded but this may be changed to a config parameter.
- * rgerhards, 2006-11-30
- */
- if(f->f_un.f_forw.compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) {
- Bytef out[MAXLINE+MAXLINE/100+12] = "z";
- uLongf destLen = sizeof(out) / sizeof(Bytef);
- uLong srcLen = l;
- int ret;
- ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz,
- srcLen, f->f_un.f_forw.compressionLevel);
- dprintf("Compressing message, length was %d now %d, return state %d.\n",
- l, (int) destLen, ret);
- if(ret != Z_OK) {
- /* if we fail, we complain, but only in debug mode
- * Otherwise, we are silent. In any case, we ignore the
- * failed compression and just sent the uncompressed
- * data, which is still valid. So this is probably the
- * best course of action.
- * rgerhards, 2006-11-30
- */
- dprintf("Compression failed, sending uncompressed message\n");
- } else if(destLen+1 < l) {
- /* only use compression if there is a gain in using it! */
- dprintf("there is gain in compression, so we do it\n");
- psz = (char*) out;
- l = destLen + 1; /* take care for the "z" at message start! */
- }
- ++destLen;
- }
-# endif
-
- if(f->f_un.f_forw.protocol == FORW_UDP) {
- /* forward via UDP */
- if(finet != NULL) {
- /* we need to track if we have success sending to the remote
- * peer. Success is indicated by at least one sendto() call
- * succeeding. We track this be bSendSuccess. We can not simply
- * rely on lsent, as a call might initially work, but a later
- * call fails. Then, lsent has the error status, even though
- * the sendto() succeeded.
- * rgerhards, 2007-06-22
- */
- bSendSuccess = FALSE;
- for (r = f->f_un.f_forw.f_addr; r; r = r->ai_next) {
- for (i = 0; i < *finet; i++) {
- lsent = sendto(finet[i+1], psz, l, 0,
- r->ai_addr, r->ai_addrlen);
- if (lsent == l) {
- bSendSuccess = TRUE;
- break;
- } else {
- int eno = errno;
- dprintf("sendto() error: %d = %s.\n",
- eno, strerror(eno));
- }
- }
- if (lsent == l && !send_to_all)
- break;
- }
- /* finished looping */
- if (bSendSuccess == FALSE) {
- f->f_type = F_FORW_SUSP;
- errno = 0;
- logerror("error forwarding via udp, suspending");
- }
- }
- } else {
- /* forward via TCP */
- if(TCPSend(f, psz, l) != 0) {
- /* error! */
- f->f_type = F_FORW_SUSP;
- errno = 0;
- logerror("error forwarding via tcp, suspending...");
- }
- }
- }
+ doActionFwd(f, now);
break;
-#endif
case F_CONSOLE:
- f->f_time = now;
- /* FALLTHROUGH */
-
case F_TTY:
case F_FILE:
case F_PIPE:
@@ -5721,150 +4426,6 @@ static void reapchild()
errno = saved_errno;
}
-/* Return a printable representation of a host address.
- * Now (2007-07-16) also returns the full host name (if it could be obtained)
- * in the second param [thanks to mildew@gmail.com for the patch].
- * The caller must provide buffer space for pszHost and pszHostFQDN. These
- * buffers must be of size NI_MAXHOST. This is not checked here, because
- * there is no way to check it. We use this way of doing things because it
- * frees us from using dynamic memory allocation where it really does not
- * pay.
- * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
- * for some explanation of the code found below. We do by default not
- * discard message where we detected malicouos DNS PTR records. However,
- * there is a user-configurabel option that will tell us if
- * we should abort. For this, the return value tells the caller if the
- * message should be processed (1) or discarded (0).
- */
-static int cvthname(struct sockaddr_storage *f, uchar *pszHost, uchar *pszHostFQDN)
-{
- register uchar *p;
- int count, error;
- sigset_t omask, nmask;
- char ip[NI_MAXHOST];
- struct addrinfo hints, *res;
-
- assert(f != NULL);
- assert(pszHost != NULL);
- assert(pszHostFQDN != NULL);
-
- error = getnameinfo((struct sockaddr *)f, SALEN((struct sockaddr *)f),
- ip, sizeof ip, NULL, 0, NI_NUMERICHOST);
-
- if (error) {
- dprintf("Malformed from address %s\n", gai_strerror(error));
- strcpy((char*) pszHost, "???");
- strcpy((char*) pszHostFQDN, "???");
- return 1;
- }
-
- if (!DisableDNS) {
- sigemptyset(&nmask);
- sigaddset(&nmask, SIGHUP);
- sigprocmask(SIG_BLOCK, &nmask, &omask);
-
- error = getnameinfo((struct sockaddr *)f, sizeof(*f),
- (char*)pszHostFQDN, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
-
- if (error == 0) {
- memset (&hints, 0, sizeof (struct addrinfo));
- hints.ai_flags = AI_NUMERICHOST;
- hints.ai_socktype = SOCK_DGRAM;
-
- /* we now do a lookup once again. This one should fail,
- * because we should not have obtained a numeric address. If
- * we got a numeric one, someone messed with DNS!
- */
- if (getaddrinfo ((char*)pszHostFQDN, NULL, &hints, &res) == 0) {
- uchar szErrMsg[1024];
- freeaddrinfo (res);
- /* OK, we know we have evil. The question now is what to do about
- * it. One the one hand, the message might probably be intended
- * to harm us. On the other hand, losing the message may also harm us.
- * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords
- * option. If it tells us we should discard, we do so, else we proceed,
- * but log an error message together with it.
- * time being, we simply drop the name we obtained and use the IP - that one
- * is OK in any way. We do also log the error message. rgerhards, 2007-07-16
- */
- if(bDropMalPTRMsgs == 1) {
- snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
- "Malicious PTR record, message dropped "
- "IP = \"%s\" HOST = \"%s\"",
- ip, pszHostFQDN);
- logerror((char*)szErrMsg);
- return 0;
- }
-
- /* Please note: we deal with a malicous entry. Thus, we have crafted
- * the snprintf() below so that all text is in front of the entry - maybe
- * it would contain characters that would make the message unreadble
- * (OK, I admit this is more or less impossible, but I am paranoid...)
- * rgerhards, 2007-07-16
- */
- snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
- "Malicious PTR record (message accepted, but used IP "
- "instead of PTR name: IP = \"%s\" HOST = \"%s\"",
- ip, pszHostFQDN);
- logerror((char*)szErrMsg);
-
- error = 1; /* that will trigger using IP address below. */
- }
- }
- sigprocmask(SIG_SETMASK, &omask, NULL);
- }
-
- if (error || DisableDNS) {
- dprintf("Host name for your address (%s) unknown\n", ip);
- strcpy((char*) pszHostFQDN, ip);
- strcpy((char*) pszHost, ip);
- return 1;
- }
-
- /* Convert to lower case, just like LocalDomain above
- */
- for (p = pszHostFQDN ; *p ; p++)
- if (isupper((int) *p))
- *p = tolower(*p);
-
- /* OK, the fqdn is now known. Now it is time to extract only the hostname
- * part if we were instructed to do so.
- */
- /* TODO: quick and dirty right now: we need to optimize that. We simply
- * copy over the buffer and then use the old code.
- */
- strcpy((char*)pszHost, (char*)pszHostFQDN);
- if ((p = (uchar*) strchr((char*)pszHost, '.'))) {
- if(strcmp((char*) (p + 1), LocalDomain) == 0) {
- *p = '\0'; /* simply terminate the string */
- return 1;
- } else {
- if (StripDomains) {
- count=0;
- while (StripDomains[count]) {
- if (strcmp((char*)(p + 1), StripDomains[count]) == 0) {
- *p = '\0';
- return 1;
- }
- count++;
- }
- }
- /* TODO: bug in syslogd? That all doesn't make so much sense... rger 2007-07-16 */
- if (LocalHosts) {
- count=0;
- while (LocalHosts[count]) {
- if (!strcmp((char*)pszHost, LocalHosts[count])) {
- *p = '\0';
- return 1;
- }
- count++;
- }
- }
- }
- }
- return 1;
-}
-
/* This method writes mark messages and - some time later - flushes reapeat
* messages.
diff --git a/syslogd.h b/syslogd.h
index 55ed1547..38127c5f 100644
--- a/syslogd.h
+++ b/syslogd.h
@@ -22,6 +22,40 @@
#include "syslogd-types.h"
+#ifdef USE_NETZIP
+#include <zlib.h>
+/* config param: minimum message size to try compression. The smaller
+ * the message, the less likely is any compression gain. We check for
+ * gain before we submit the message. But to do so we still need to
+ * do the (costly) compress() call. The following setting sets a size
+ * for which no call to compress() is done at all. This may result in
+ * a few more bytes being transmited but better overall performance.
+ * Note: I have not yet checked the minimum UDP packet size. It might be
+ * that we do not save anything by compressing very small messages, because
+ * UDP might need to pad ;)
+ * rgerhards, 2006-11-30
+ */
+#define MIN_SIZE_FOR_COMPRESS 60
+#endif
+
+#define MAXLINE 2048 /* maximum line length */
+
+#ifdef SYSLOG_INET
+#define INET_SUSPEND_TIME 60 /* equal to 1 minute
+ * rgerhards, 2005-07-26: This was 3 minutes. As the
+ * same timer is used for tcp based syslog, we have
+ * reduced it. However, it might actually be worth
+ * thinking about a buffered tcp sender, which would be
+ * a much better alternative. When that happens, this
+ * time here can be re-adjusted to 3 minutes (or,
+ * even better, made configurable).
+ */
+#define INET_RETRY_MAX 30 /* maximum of retries for gethostbyname() */
+ /* was 10, changed to 30 because we reduced INET_SUSPEND_TIME by one third. So
+ * this "fixes" some of implications of it (see comment on INET_SUSPEND_TIME).
+ * rgerhards, 2005-07-26
+ */
+#endif
#if defined(__GLIBC__)
#define dprintf mydprintf
#endif /* __GLIBC__ */
@@ -31,6 +65,10 @@ void logerror(char *type);
void logerrorSz(char *type, char *errMsg);
void logerrorInt(char *type, int iErr);
+void printchopped(char *hname, char *msg, int len, int fd, int iSourceType);
+void freeAllSockets(int **socks);
+int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost);
+char *getFwdSyslogPt(selector_t *f);
void getCurrTime(struct syslogTime *t);
int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst);
int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf);
@@ -41,6 +79,18 @@ char *iovAsString(selector_t *f);
extern int glblHadMemShortage; /* indicates if we had memory shortage some time during the run */
extern syslogCODE rs_prioritynames[];
extern syslogCODE rs_facilitynames[];
+extern char LocalHostName[];
+extern int family;
+extern int NoHops;
+extern int *finet;
+extern int send_to_all;
+extern int option_DisallowWarning;
+extern int Debug;
+extern char**LocalHosts;
+extern int DisableDNS;
+extern char **StripDomains;
+extern char *LocalDomain;
+extern int bDropMalPTRMsgs;
+extern struct AllowedSenders *pAllowedSenders_TCP;
-#include "rsyslog.h"
#endif /* #ifndef SYSLOGD_H_INCLUDED */
diff --git a/tcpsyslog.c b/tcpsyslog.c
new file mode 100644
index 00000000..83204519
--- /dev/null
+++ b/tcpsyslog.c
@@ -0,0 +1,1062 @@
+/* tcpsyslog.c
+ * This is the implementation of TCP-based syslog.
+ *
+ * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c)
+ * This file is under development and has not yet arrived at being fully
+ * self-contained and a real object. So far, it is mostly an excerpt
+ * of the "old" message code without any modifications. However, it
+ * helps to have things at the right place one we go to the meat of it.
+ *
+ * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifdef USE_PTHREADS
+#include <pthread.h>
+#endif
+
+#include "rsyslog.h"
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "net.h"
+#include "tcpsyslog.h"
+/********************************************************************
+ * ### SYSLOG/TCP CODE ###
+ * This is code for syslog/tcp. This code would belong to a separate
+ * file - but I have put it here to avoid hassle with CVS. Over
+ * time, I expect rsyslog to utilize liblogging for actual network
+ * I/O. So the tcp code will be (re)moved some time. I don't like
+ * to add a new file to cvs that I will push to the attic in just
+ * a few weeks (month at most...). So I simply add the code here.
+ *
+ * Place no unrelated code between this comment and the
+ * END tcp comment!
+ *
+ * 2005-07-04 RGerhards (Happy independence day to our US friends!)
+ ********************************************************************/
+#ifdef SYSLOG_INET
+
+#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */
+
+static int iTCPSessMax = TCPSESS_MAX_DEFAULT; /* actual number of sessions */
+char *TCPLstnPort = "0"; /* read-only after startup */
+int bEnableTCP = 0; /* read-only after startup */
+int *sockTCPLstn = NULL; /* read-only after startup, modified by restart */
+struct TCPSession *pTCPSessions;
+/* The thread-safeness of the sesion table is doubtful */
+
+
+/* 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 dprintf() in here - the dprintf() 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);
+ bEnableTCP = -1; /* enable TCP listening */
+
+ /* extract port */
+ i = 0;
+ while(isdigit((int) *pArg)) {
+ i = i * 10 + *pArg++ - '0';
+ }
+
+ if( i >= 0 && i <= 65535) {
+ TCPLstnPort = cOptarg;
+ } else {
+ fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i);
+ TCPLstnPort = "514";
+ }
+
+ /* number of sessions */
+ if(*pArg == ','){
+ *pArg = '\0'; /* hack: terminates port (see a few lines above, same buffer!) */
+ ++pArg;
+ while(isspace((int) *pArg))
+ ++pArg;
+ /* ok, here should be the number... */
+ i = 0;
+ while(isdigit((int) *pArg)) {
+ i = i * 10 + *pArg++ - '0';
+ }
+ if(i > 1)
+ iTCPSessMax = i;
+ else {
+ /* too small, need to adjust */
+ fprintf(stderr,
+ "rsyslogd: TCP session max configured to %d [-t %s] - changing to 1.\n",
+ i, cOptarg);
+ iTCPSessMax = 1;
+ }
+ } else if(*pArg == '\0') {
+ /* use default for session number - that's already set...*/
+ /*EMPTY BY INTENSION*/
+ } else {
+ fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", cOptarg);
+ }
+}
+
+
+/* Initialize the session table
+ * returns 0 if OK, somewhat else otherwise
+ */
+static int TCPSessInit(void)
+{
+ register int i;
+
+ assert(pTCPSessions == NULL);
+ dprintf("Allocating buffer for %d TCP sessions.\n", iTCPSessMax);
+ if((pTCPSessions = (struct TCPSession *) malloc(sizeof(struct TCPSession) * iTCPSessMax))
+ == NULL) {
+ dprintf("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... */
+ }
+ 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)
+{
+ int iTCPSess;
+
+ assert(pTCPSessions != NULL);
+ /* close all TCP connections! */
+ iTCPSess = TCPSessGetNxtSess(-1);
+ while(iTCPSess != -1) {
+ int fd;
+ fd = pTCPSessions[iTCPSess].sock;
+ dprintf("Closing TCP Session %d\n", fd);
+ close(fd);
+ /* 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);
+}
+
+
+/* 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)
+{
+ 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
+ */
+ 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)
+ dprintf("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);
+ }
+
+ /* 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);
+}
+
+
+/* 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.
+ */
+void TCPSessAccept(int fd)
+{
+ 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;
+
+ newConn = accept(fd, (struct sockaddr*) &addr, &addrlen);
+ if (newConn < 0) {
+ logerror("tcp accept, ignoring error and connection request");
+ return;
+ }
+
+ /* Add to session list */
+ iSess = TCPSessFindFreeSpot();
+ if(iSess == -1) {
+ errno = 0;
+ logerror("too many tcp sessions - dropping incoming request");
+ close(newConn);
+ return;
+ }
+
+ /* OK, we have a "good" index... */
+ /* get the host name */
+ if(cvthname(&addr, fromHost, fromHostFQDN) == 0) {
+ /* 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;
+ }
+
+ /* 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(!isAllowedSender(pAllowedSenders_TCP, (struct sockaddr *)&addr, (char*)fromHostFQDN)) {
+ if(option_DisallowWarning) {
+ errno = 0;
+ logerrorSz("TCP message from disallowed sender %s discarded",
+ (char*)fromHost);
+ }
+ close(newConn);
+ return;
+ }
+
+ /* 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! */
+}
+
+
+/* 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)
+{
+ 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.
+ */
+ dprintf("Extra data at end of stream in legacy syslog/tcp message - processing\n");
+ printchopped(pTCPSessions[iTCPSess].fromHost, pTCPSessions[iTCPSess].msg,
+ pTCPSessions[iTCPSess].iMsg, pTCPSessions[iTCPSess].sock, 1);
+ pTCPSessions[iTCPSess].bAtStrtOfFram = 1;
+ }
+}
+
+
+/* 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)
+{
+ if(iSess < 0 || iSess > iTCPSessMax) {
+ errno = 0;
+ logerror("internal error, trying to close an invalid TCP session!");
+ return;
+ }
+
+ close(pTCPSessions[iSess].sock);
+ pTCPSessions[iSess].sock = -1;
+ free(pTCPSessions[iSess].fromHost);
+ pTCPSessions[iSess].fromHost = NULL; /* not really needed, but... */
+}
+
+
+/* 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;
+ }
+ dprintf("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! */
+ dprintf("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...
+ */
+ printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg,
+ pTCPSessions[iTCPSess].sock, 1);
+ 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? */
+ printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg,
+ pTCPSessions[iTCPSess].sock, 1);
+ 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! */
+ printchopped(pTCPSessions[iTCPSess].fromHost, pMsg, iMsg,
+ pTCPSessions[iTCPSess].sock, 1);
+ iMsg = 0;
+ pTCPSessions[iTCPSess].bAtStrtOfFram = 1;
+ }
+ }
+ }
+
+ pTCPSessions[iTCPSess].iMsg = iMsg; /* persist value */
+
+ return(1); /* successful return */
+}
+
+
+/* CODE FOR SENDING TCP MESSAGES */
+
+/* get send status
+ * rgerhards, 2005-10-24
+ */
+void TCPSendSetStatus(selector_t *f, enum TCPSendStatus iNewState)
+{
+ assert(f != NULL);
+ assert(f->f_type == F_FORW);
+ assert(f->f_un.f_forw.protocol == FORW_TCP);
+ assert( (iNewState == TCP_SEND_NOTCONNECTED)
+ || (iNewState == TCP_SEND_CONNECTING)
+ || (iNewState == TCP_SEND_READY));
+
+ /* there can potentially be a race condition, so guard by mutex */
+# ifdef USE_PTHREADS
+ pthread_mutex_lock(&f->f_un.f_forw.mtxTCPSend);
+# endif
+ f->f_un.f_forw.status = iNewState;
+# ifdef USE_PTHREADS
+ pthread_mutex_unlock(&f->f_un.f_forw.mtxTCPSend);
+# endif
+}
+
+
+/* set send status
+ * rgerhards, 2005-10-24
+ */
+enum TCPSendStatus TCPSendGetStatus(selector_t *f)
+{
+ enum TCPSendStatus eState;
+ assert(f != NULL);
+ assert(f->f_type == F_FORW);
+ assert(f->f_un.f_forw.protocol == FORW_TCP);
+
+ /* there can potentially be a race condition, so guard by mutex */
+# ifdef USE_PTHREADS
+ pthread_mutex_lock(&f->f_un.f_forw.mtxTCPSend);
+# endif
+ eState = f->f_un.f_forw.status;
+# ifdef USE_PTHREADS
+ pthread_mutex_unlock(&f->f_un.f_forw.mtxTCPSend);
+# endif
+
+ return eState;
+}
+
+
+/* Initialize TCP sockets (for sender)
+ * This is done once per selector line, if not yet initialized.
+ */
+static int TCPSendCreateSocket(selector_t *f)
+{
+ int fd;
+ struct addrinfo *r;
+
+ assert(f != NULL);
+
+ r = f->f_un.f_forw.f_addr;
+
+ while(r != NULL) {
+ fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (fd != -1) {
+ /* We can not allow the TCP sender to block syslogd, at least
+ * not in a single-threaded design. That would cause rsyslogd to
+ * loose input messages - which obviously also would affect
+ * other selector lines, too. So we do set it to non-blocking and
+ * handle the situation ourselfs (by discarding messages). IF we run
+ * dual-threaded, however, the situation is different: in this case,
+ * the receivers and the selector line processing are only loosely
+ * coupled via a memory buffer. Now, I think, we can afford the extra
+ * wait time. Thus, we enable blocking mode for TCP if we compile with
+ * pthreads.
+ * rgerhards, 2005-10-25
+ */
+# ifndef USE_PTHREADS
+ /* set to nonblocking - rgerhards 2005-07-20 */
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+# endif
+ if (connect (fd, r->ai_addr, r->ai_addrlen) != 0) {
+ if(errno == EINPROGRESS) {
+ /* this is normal - will complete during select */
+ TCPSendSetStatus(f, TCP_SEND_CONNECTING);
+ return fd;
+ } else {
+ dprintf("create tcp connection failed, reason %s",
+ strerror(errno));
+ }
+
+ }
+ else {
+ TCPSendSetStatus(f, TCP_SEND_READY);
+ return fd;
+ }
+ close(fd);
+ }
+ else {
+ dprintf("couldn't create send socket, reason %s", strerror(errno));
+ }
+ r = r->ai_next;
+ }
+
+ dprintf("no working socket could be obtained");
+
+ return -1;
+}
+
+
+/* Sends a TCP message. It is first checked if the
+ * session is open and, if not, it is opened. Then the send
+ * is tried. If it fails, one silent re-try is made. If the send
+ * fails again, an error status (-1) is returned. If all goes well,
+ * 0 is returned. The TCP session is NOT torn down.
+ * For now, EAGAIN is ignored (causing message loss) - but it is
+ * hard to do something intelligent in this case. With this
+ * implementation here, we can not block and/or defer. Things are
+ * probably a bit better when we move to liblogging. The alternative
+ * would be to enhance the current select server with buffering and
+ * write descriptors. This seems not justified, given the expected
+ * short life span of this code (and the unlikeliness of this event).
+ * rgerhards 2005-07-06
+ * This function is now expected to stay. Libloging won't be used for
+ * that purpose. I have added the param "len", because it is known by the
+ * caller and so safes us some time. Also, it MUST be given because there
+ * may be NULs inside msg so that we can not rely on strlen(). Please note
+ * that the restrictions outlined above do not existin in multi-threaded
+ * mode, which we assume will now be most often used. So there is no
+ * real issue with the potential message loss in single-threaded builds.
+ * rgerhards, 2006-11-30
+ *
+ * In order to support compressed messages via TCP, we must support an
+ * octet-counting based framing (LF may be part of the compressed message).
+ * We are now supporting the same mode that is available in IETF I-D
+ * syslog-transport-tls-05 (current at the time of this writing). This also
+ * eases things when we go ahead and implement that framing. I have now made
+ * available two cases where this framing is used: either by explitely
+ * specifying it in the config file or implicitely when sending a compressed
+ * message. In the later case, compressed and uncompressed messages within
+ * the same session have different framings. If it is explicitely set to
+ * octet-counting, only this framing mode is used within the session.
+ * rgerhards, 2006-12-07
+ */
+int TCPSend(selector_t *f, char *msg, size_t len)
+{
+ int retry = 0;
+ int done = 0;
+ int bIsCompressed;
+ int lenSend;
+ short f_type;
+ char *buf = NULL; /* if this is non-NULL, it MUST be freed before return! */
+ enum TCPSendStatus eState;
+ TCPFRAMINGMODE framingToUse;
+
+ assert(f != NULL);
+ assert(msg != NULL);
+ assert(len > 0);
+
+ bIsCompressed = *msg == 'z'; /* cache this, so that we can modify the message buffer */
+ /* select framing for this record. If we have a compressed record, we always need to
+ * use octet counting because the data potentially contains all control characters
+ * including LF.
+ */
+ framingToUse = bIsCompressed ? TCP_FRAMING_OCTET_COUNTING : f->f_un.f_forw.tcp_framing;
+
+ do { /* try to send message */
+ if(f->f_file <= 0) {
+ /* we need to open the socket first */
+ if((f->f_file = TCPSendCreateSocket(f)) <= 0) {
+ return -1;
+ }
+ }
+
+ eState = TCPSendGetStatus(f); /* cache info */
+
+ if(eState == TCP_SEND_CONNECTING) {
+ /* In this case, we save the buffer. If we have a
+ * system with few messages, that hopefully prevents
+ * message loss at all. However, we make no further attempts,
+ * just the first message is saved. So we only try this
+ * if there is not yet a saved message present.
+ * rgerhards 2005-07-20
+ */
+ if(f->f_un.f_forw.savedMsg == NULL) {
+ f->f_un.f_forw.savedMsg = malloc(len * sizeof(char));
+ if(f->f_un.f_forw.savedMsg == NULL)
+ return 0; /* nothing we can do... */
+ memcpy(f->f_un.f_forw.savedMsg, msg, len);
+ f->f_un.f_forw.savedMsgLen = len;
+ }
+ return 0;
+ } else if(eState != TCP_SEND_READY)
+ /* This here is debatable. For the time being, we
+ * accept the loss of a single message (e.g. during
+ * connection setup in favour of not messing with
+ * wait time and timeouts. The reason is that such
+ * things might otherwise cost us considerable message
+ * loss on the receiving side (even at a timeout set
+ * to just 1 second). - rgerhards 2005-07-20
+ */
+ return 0;
+
+ /* now check if we need to add a line terminator. We need to
+ * copy the string in memory in this case, this is probably
+ * quicker than using writev and definitely quicker than doing
+ * two socket calls.
+ * rgerhards 2005-07-22
+ *//*
+ * Some messages already contain a \n character at the end
+ * of the message. We append one only if we there is not
+ * already one. This seems the best fit, though this also
+ * means the message does not arrive unaltered at the final
+ * destination. But in the spirit of legacy syslog, this is
+ * probably the best to do...
+ * rgerhards 2005-07-20
+ */
+
+ /* Build frame based on selected framing */
+ if(framingToUse == TCP_FRAMING_OCTET_STUFFING) {
+ if((*(msg+len-1) != '\n')) {
+ if(buf != NULL)
+ free(buf);
+ /* in the malloc below, we need to add 2 to the length. The
+ * reason is that we a) add one character and b) len does
+ * not take care of the '\0' byte. Up until today, it was just
+ * +1 , which caused rsyslogd to sometimes dump core.
+ * I have added this comment so that the logic is not accidently
+ * changed again. rgerhards, 2005-10-25
+ */
+ if((buf = malloc((len + 2) * sizeof(char))) == NULL) {
+ /* extreme mem shortage, try to solve
+ * as good as we can. No point in calling
+ * any alarms, they might as well run out
+ * of memory (the risk is very high, so we
+ * do NOT risk that). If we have a message of
+ * more than 1 byte (what I guess), we simply
+ * overwrite the last character.
+ * rgerhards 2005-07-22
+ */
+ if(len > 1) {
+ *(msg+len-1) = '\n';
+ } else {
+ /* we simply can not do anything in
+ * this case (its an error anyhow...).
+ */
+ }
+ } else {
+ /* we got memory, so we can copy the message */
+ memcpy(buf, msg, len); /* do not copy '\0' */
+ *(buf+len) = '\n';
+ *(buf+len+1) = '\0';
+ msg = buf; /* use new one */
+ ++len; /* care for the \n */
+ }
+ }
+ } else {
+ /* Octect-Counting
+ * In this case, we need to always allocate a buffer. This is because
+ * we need to put a header in front of the message text
+ */
+ char szLenBuf[16];
+ int iLenBuf;
+
+ /* important: the printf-mask is "%d<sp>" because there must be a
+ * space after the len!
+ *//* 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
+ */
+ iLenBuf = snprintf(szLenBuf, sizeof(szLenBuf)/sizeof(char), "%d ", (int) len);
+ /* IETF20061218 iLenBuf =
+ snprintf(szLenBuf, sizeof(szLenBuf)/sizeof(char), "%d ", len + iLenBuf);*/
+
+ if((buf = malloc((len + iLenBuf) * sizeof(char))) == NULL) {
+ /* we are out of memory. This is an extreme situation. We do not
+ * call any alarm handlers because they most likely run out of mem,
+ * too. We are brave enough to call debug output, though. Other than
+ * that, there is nothing left to do. We can not sent the message (as
+ * in case of the other framing, because the message is incomplete.
+ * We could, however, send two chunks (header and text separate), but
+ * that would cause a lot of complexity in the code. So we think it
+ * is appropriate enough to just make sure we do not crash in this
+ * very unlikely case. For this, it is justified just to loose
+ * the message. Rgerhards, 2006-12-07
+ */
+ dprintf("Error: out of memory when building TCP octet-counted "
+ "frame. Message is lost, trying to continue.\n");
+ return 0;
+ }
+
+ memcpy(buf, szLenBuf, iLenBuf); /* header */
+ memcpy(buf + iLenBuf, msg, len); /* message */
+ len += iLenBuf; /* new message size */
+ msg = buf; /* set message buffer */
+ }
+
+ /* frame building complete, on to actual sending */
+
+ lenSend = send(f->f_file, msg, len, 0);
+ dprintf("TCP sent %d bytes, requested %d, msg: '%s'\n", lenSend, len,
+ bIsCompressed ? "***compressed***" : msg);
+ if((unsigned)lenSend == len) {
+ /* all well */
+ if(buf != NULL) {
+ free(buf);
+ }
+ return 0;
+ } else if(lenSend != -1) {
+ /* no real error, could "just" not send everything...
+ * For the time being, we ignore this...
+ * rgerhards, 2005-10-25
+ */
+ dprintf("message not completely (tcp)send, ignoring %d\n", lenSend);
+# if USE_PTHREADS
+ usleep(1000); /* experimental - might be benefitial in this situation */
+# endif
+ if(buf != NULL)
+ free(buf);
+ return 0;
+ }
+
+ switch(errno) {
+ case EMSGSIZE:
+ dprintf("message not (tcp)send, too large\n");
+ /* This is not a real error, so it is not flagged as one */
+ if(buf != NULL)
+ free(buf);
+ return 0;
+ break;
+ case EINPROGRESS:
+ case EAGAIN:
+ dprintf("message not (tcp)send, would block\n");
+# if USE_PTHREADS
+ usleep(1000); /* experimental - might be benefitial in this situation */
+# endif
+ /* we loose this message, but that's better than loosing
+ * all ;)
+ */
+ /* This is not a real error, so it is not flagged as one */
+ if(buf != NULL)
+ free(buf);
+ return 0;
+ break;
+ default:
+ f_type = f->f_type;
+ f->f_type = F_UNUSED;
+ logerror("message not (tcp)send");
+ f->f_type = f_type;
+ break;
+ }
+
+ if(retry == 0) {
+ ++retry;
+ /* try to recover */
+ close(f->f_file);
+ TCPSendSetStatus(f, TCP_SEND_NOTCONNECTED);
+ f->f_file = -1;
+ } else {
+ if(buf != NULL)
+ free(buf);
+ return -1;
+ }
+ } while(!done); /* warning: do ... while() */
+ /*NOT REACHED*/
+
+ if(buf != NULL)
+ free(buf);
+ return -1; /* only to avoid compiler warning! */
+}
+
+#endif
+/********************************************************************
+ * ### END OF SYSLOG/TCP CODE ###
+ ********************************************************************/
+
+
+/*
+ * vi:set ai:
+ */
diff --git a/tcpsyslog.h b/tcpsyslog.h
new file mode 100644
index 00000000..03975247
--- /dev/null
+++ b/tcpsyslog.h
@@ -0,0 +1,59 @@
+/* tcpsyslog.h
+ * These are the definitions for TCP-based syslog.
+ *
+ * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ */
+#ifndef TCPSYSLOG_H_INCLUDED
+#define TCPSYSLOG_H_INCLUDED 1
+
+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;
+};
+
+/* static data */
+extern int *sockTCPLstn;
+extern char *TCPLstnPort;
+extern int bEnableTCP;
+extern struct TCPSession *pTCPSessions;
+
+/* prototypes */
+int TCPSend(selector_t *f, char *msg, size_t len);
+void deinit_tcp_listener(void);
+int *create_tcp_socket(void);
+enum TCPSendStatus TCPSendGetStatus(selector_t *f);
+int TCPSessGetNxtSess(int iCurr);
+void TCPSessAccept(int fd);
+void TCPSessPrepareClose(int iTCPSess);
+void TCPSessClose(int iSess);
+int TCPSessDataRcvd(int iTCPSess, char *pData, int iLen);
+void configureTCPListen(char *cOptarg);
+void TCPSendSetStatus(selector_t *f, enum TCPSendStatus iNewState);
+
+#endif /* #ifndef TCPSYSLOG_H_INCLUDED */
+/*
+ * vi:set ai:
+ */