summaryrefslogtreecommitdiffstats
path: root/runtime/net.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:28:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:28:20 +0000
commitdcc721a95bef6f0d8e6d8775b8efe33e5aecd562 (patch)
tree66a2774cd0ee294d019efd71d2544c70f42b2842 /runtime/net.c
parentInitial commit. (diff)
downloadrsyslog-dcc721a95bef6f0d8e6d8775b8efe33e5aecd562.tar.xz
rsyslog-dcc721a95bef6f0d8e6d8775b8efe33e5aecd562.zip
Adding upstream version 8.2402.0.upstream/8.2402.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'runtime/net.c')
-rw-r--r--runtime/net.c1741
1 files changed, 1741 insertions, 0 deletions
diff --git a/runtime/net.c b/runtime/net.c
new file mode 100644
index 0000000..ff46cbc
--- /dev/null
+++ b/runtime/net.c
@@ -0,0 +1,1741 @@
+/* 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" networking code without any modifications. However, it
+ * helps to have things at the right place one we go to the meat of it.
+ *
+ * Starting 2007-12-24, I have begun to shuffle more network-related code
+ * from syslogd.c to over here. I am not sure if it will stay here in the
+ * long term, but it is good to have it out of syslogd.c. Maybe this here is
+ * an interim location ;)
+ *
+ * Copyright 2007-2018 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#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 <fnmatch.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#else
+#include "compat/ifaddrs.h"
+#endif /* HAVE_GETIFADDRS */
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include "rsyslog.h"
+#include "syslogd-types.h"
+#include "module-template.h"
+#include "parse.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "net.h"
+#include "dnscache.h"
+#include "prop.h"
+#include "rsconf.h"
+
+#ifdef OS_SOLARIS
+#include <arpa/nameser_compat.h>
+# define s6_addr32 _S6_un._S6_u32
+ typedef unsigned int u_int32_t;
+#endif
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(prop)
+
+#ifndef HAVE_STRUCT_SOCKADDR_SA_LEN
+extern size_t SALEN(struct sockaddr *sa);
+#endif
+/* support for defining allowed TCP and UDP senders. We use the same
+ * structure to implement this (a linked list), but we define two different
+ * list roots, one for UDP and one for TCP.
+ * rgerhards, 2005-09-26
+ */
+/* All of the five below are read-only after startup */
+struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */
+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 */
+#ifdef USE_GSSAPI
+struct AllowedSenders *pAllowedSenders_GSS = NULL;
+static struct AllowedSenders *pLastAllowedSenders_GSS = NULL;
+#endif
+
+/* ------------------------------ begin permitted peers code ------------------------------ */
+
+
+/* sets the correct allow root pointer based on provided type
+ * rgerhards, 2008-12-01
+ */
+static rsRetVal
+setAllowRoot(struct AllowedSenders **ppAllowRoot, uchar *pszType)
+{
+ DEFiRet;
+
+ if(!strcmp((char*)pszType, "UDP"))
+ *ppAllowRoot = pAllowedSenders_UDP;
+ else if(!strcmp((char*)pszType, "TCP"))
+ *ppAllowRoot = pAllowedSenders_TCP;
+#ifdef USE_GSSAPI
+ else if(!strcmp((char*)pszType, "GSS"))
+ *ppAllowRoot = pAllowedSenders_GSS;
+#endif
+ else {
+ dbgprintf("program error: invalid allowed sender ID '%s', denying...\n", pszType);
+ ABORT_FINALIZE(RS_RET_CODE_ERR); /* everything is invalid for an invalid type */
+ }
+
+finalize_it:
+ RETiRet;
+}
+/* re-initializes (sets to NULL) the correct allow root pointer
+ * rgerhards, 2009-01-12
+ */
+static rsRetVal
+reinitAllowRoot(uchar *pszType)
+{
+ DEFiRet;
+
+ if(!strcmp((char*)pszType, "UDP"))
+ pAllowedSenders_UDP = NULL;
+ else if(!strcmp((char*)pszType, "TCP"))
+ pAllowedSenders_TCP = NULL;
+#ifdef USE_GSSAPI
+ else if(!strcmp((char*)pszType, "GSS"))
+ pAllowedSenders_GSS = NULL;
+#endif
+ else {
+ dbgprintf("program error: invalid allowed sender ID '%s', denying...\n", pszType);
+ ABORT_FINALIZE(RS_RET_CODE_ERR); /* everything is invalid for an invalid type */
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* add a wildcard entry to this permitted peer. Entries are always
+ * added at the tail of the list. pszStr and lenStr identify the wildcard
+ * entry to be added. Note that the string is NOT \0 terminated, so
+ * we must rely on lenStr for when it is finished.
+ * rgerhards, 2008-05-27
+ */
+static rsRetVal
+AddPermittedPeerWildcard(permittedPeers_t *pPeer, uchar* pszStr, size_t lenStr)
+{
+ permittedPeerWildcard_t *pNew = NULL;
+ size_t iSrc;
+ size_t iDst;
+ DEFiRet;
+
+ assert(pPeer != NULL);
+ assert(pszStr != NULL);
+
+ CHKmalloc(pNew = calloc(1, sizeof(*pNew)));
+
+ if(lenStr == 0) { /* empty domain components are permitted */
+ pNew->wildcardType = PEER_WILDCARD_EMPTY_COMPONENT;
+ FINALIZE;
+ } else {
+ /* alloc memory for the domain component. We may waste a byte or
+ * two, but that's ok.
+ */
+ CHKmalloc(pNew->pszDomainPart = malloc(lenStr +1 ));
+ }
+
+ if(pszStr[0] == '*') {
+ pNew->wildcardType = PEER_WILDCARD_AT_START;
+ iSrc = 1; /* skip '*' */
+ } else {
+ iSrc = 0;
+ }
+
+ for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) {
+ pNew->pszDomainPart[iDst] = pszStr[iSrc];
+ }
+
+ if(iSrc < lenStr) {
+ if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') {
+ if(pNew->wildcardType == PEER_WILDCARD_AT_START) {
+ ABORT_FINALIZE(RS_RET_INVALID_WILDCARD);
+ } else {
+ pNew->wildcardType = PEER_WILDCARD_AT_END;
+ }
+ } else {
+ /* we have an invalid wildcard, something follows the asterisk! */
+ ABORT_FINALIZE(RS_RET_INVALID_WILDCARD);
+ }
+ }
+
+ if(lenStr == 1 && pNew->wildcardType == PEER_WILDCARD_AT_START) {
+ pNew->wildcardType = PEER_WILDCARD_MATCH_ALL;
+ }
+
+ /* if we reach this point, we had a valid wildcard. We now need to
+ * properly terminate the domain component string.
+ */
+ pNew->pszDomainPart[iDst] = '\0';
+ pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL) {
+ if(pNew->pszDomainPart != NULL)
+ free(pNew->pszDomainPart);
+ free(pNew);
+ }
+ } else {
+ /* enqueue the element */
+ if(pPeer->pWildcardRoot == NULL) {
+ pPeer->pWildcardRoot = pNew;
+ pPeer->pWildcardLast = pNew;
+ } else {
+ pPeer->pWildcardLast->pNext = pNew;
+ }
+ pPeer->pWildcardLast = pNew;
+ }
+
+ RETiRet;
+}
+
+
+/* Destruct a permitted peer's wildcard list -- rgerhards, 2008-05-27 */
+static rsRetVal
+DestructPermittedPeerWildcards(permittedPeers_t *pPeer)
+{
+ permittedPeerWildcard_t *pCurr;
+ permittedPeerWildcard_t *pDel;
+ DEFiRet;
+
+ assert(pPeer != NULL);
+
+ for(pCurr = pPeer->pWildcardRoot ; pCurr != NULL ; /*EMPTY*/) {
+ pDel = pCurr;
+ pCurr = pCurr->pNext;
+ free(pDel->pszDomainPart);
+ free(pDel);
+ }
+
+ pPeer->pWildcardRoot = NULL;
+ pPeer->pWildcardLast = NULL;
+
+ RETiRet;
+}
+
+
+/* add a permitted peer. PermittedPeers is an interim solution until we can provide
+ * access control via enhanced RainerScript methods.
+ * Note: the provided string is handed over to this function, caller must
+ * no longer access it. -- rgerhards, 2008-05-19
+ */
+static rsRetVal
+AddPermittedPeer(permittedPeers_t **ppRootPeer, uchar* pszID)
+{
+ permittedPeers_t *pNew = NULL;
+ DEFiRet;
+
+ assert(ppRootPeer != NULL);
+ assert(pszID != NULL);
+
+ CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); /* we use calloc() for consistency with "real" objects */
+ CHKmalloc(pNew->pszID = (uchar*)strdup((char*)pszID));
+
+ if(*ppRootPeer != NULL) {
+ pNew->pNext = *ppRootPeer;
+ }
+ *ppRootPeer = pNew;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL)
+ free(pNew);
+ }
+ RETiRet;
+}
+
+
+/* Destruct a permitted peers list -- rgerhards, 2008-05-19 */
+static rsRetVal
+DestructPermittedPeers(permittedPeers_t **ppRootPeer)
+{
+ permittedPeers_t *pCurr;
+ permittedPeers_t *pDel;
+ DEFiRet;
+
+ assert(ppRootPeer != NULL);
+
+ for(pCurr = *ppRootPeer ; pCurr != NULL ; /*EMPTY*/) {
+ pDel = pCurr;
+ pCurr = pCurr->pNext;
+ DestructPermittedPeerWildcards(pDel);
+ free(pDel->pszID);
+ free(pDel);
+ }
+
+ *ppRootPeer = NULL;
+
+ RETiRet;
+}
+
+
+/* Compile a wildcard. The function first checks if there is a wildcard
+ * present and compiles it only if so ;) It sets the etryType status
+ * accordingly.
+ * rgerhards, 2008-05-27
+ */
+static rsRetVal
+PermittedPeerWildcardCompile(permittedPeers_t *pPeer)
+{
+ uchar *pC;
+ uchar *pStart;
+ DEFiRet;
+
+ assert(pPeer != NULL);
+ assert(pPeer->pszID != NULL);
+
+ /* first check if we have a wildcard */
+ for(pC = pPeer->pszID ; *pC != '\0' && *pC != '*' ; ++pC)
+ /*EMPTY, just skip*/;
+
+ if(*pC == '\0') {
+ /* no wildcard found, we are mostly done */
+ pPeer->etryType = PERM_PEER_TYPE_PLAIN;
+ FINALIZE;
+ }
+
+ /* if we reach this point, the string contains wildcards. So let's
+ * compile the structure. To do so, we must parse from dot to dot
+ * and create a wildcard entry for each domain component we find.
+ * We must also flag problems if we have an asterisk in the middle
+ * of the text (it is supported at the start or end only).
+ */
+ pPeer->etryType = PERM_PEER_TYPE_WILDCARD;
+ pC = pPeer->pszID;
+ while(*pC != '\0') {
+ pStart = pC;
+ /* find end of domain component */
+ for( ; *pC != '\0' && *pC != '.' ; ++pC)
+ /*EMPTY, just skip*/;
+ CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, pC - pStart));
+ /* now check if we have an empty component at end of string */
+ if(*pC == '.' && *(pC + 1) == '\0') {
+ /* pStart is a dummy, it is not used if length is 0 */
+ CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, 0));
+ }
+ if(*pC != '\0')
+ ++pC;
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ LogError(0, iRet, "error compiling wildcard expression '%s'",
+ pPeer->pszID);
+ }
+ RETiRet;
+}
+
+
+/* Do a (potential) wildcard match. The function first checks if the wildcard
+ * has already been compiled and, if not, compiles it. If the peer entry in
+ * question does NOT contain a wildcard, a simple strcmp() is done.
+ * *pbIsMatching is set to 0 if there is no match and something else otherwise.
+ * rgerhards, 2008-05-27 */
+static rsRetVal
+PermittedPeerWildcardMatch(permittedPeers_t *const pPeer,
+ const uchar *pszNameToMatch,
+ int *const pbIsMatching)
+{
+ const permittedPeerWildcard_t *pWildcard;
+ const uchar *pC;
+ size_t iWildcard, iName; /* work indexes for backward comparisons */
+ DEFiRet;
+
+ assert(pPeer != NULL);
+ assert(pszNameToMatch != NULL);
+ assert(pbIsMatching != NULL);
+
+ if(pPeer->etryType == PERM_PEER_TYPE_UNDECIDED) {
+ PermittedPeerWildcardCompile(pPeer);
+ }
+
+ if(pPeer->etryType == PERM_PEER_TYPE_PLAIN) {
+ *pbIsMatching = !strcmp((char*)pPeer->pszID, (char*)pszNameToMatch);
+ FINALIZE;
+ }
+
+ /* we have a wildcard, so we need to extract the domain components and
+ * check then against the provided wildcards.
+ */
+ pWildcard = pPeer->pWildcardRoot;
+ pC = pszNameToMatch;
+ while(*pC != '\0') {
+ if(pWildcard == NULL) {
+ /* we have more domain components than we have wildcards --> no match */
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ const uchar *const pStart = pC; /* start of current domain component */
+ while(*pC != '\0' && *pC != '.') {
+ ++pC;
+ }
+
+ /* got the component, now do the match */
+ switch(pWildcard->wildcardType) {
+ case PEER_WILDCARD_NONE:
+ if( pWildcard->lenDomainPart != (size_t) (pC - pStart)
+ || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ break;
+ case PEER_WILDCARD_AT_START:
+ /* we need to do the backwards-matching manually */
+ if(pWildcard->lenDomainPart > (size_t) (pC - pStart)) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart;
+ iWildcard = 0;
+ while(iWildcard < pWildcard->lenDomainPart) {
+ // I give up, I see now way to remove false positive -- rgerhards, 2017-10-23
+ #ifndef __clang_analyzer__
+ if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ #endif
+ ++iName;
+ ++iWildcard;
+ }
+ break;
+ case PEER_WILDCARD_AT_END:
+ if( pWildcard->lenDomainPart > (size_t) (pC - pStart)
+ || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart,
+ pWildcard->lenDomainPart)) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ break;
+ case PEER_WILDCARD_MATCH_ALL:
+ /* everything is OK, just continue */
+ break;
+ case PEER_WILDCARD_EMPTY_COMPONENT:
+ if(pC - pStart > 0) {
+ /* if it is not empty, it is no match... */
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ break;
+ }
+ pWildcard = pWildcard->pNext; /* we processed this entry */
+
+ /* skip '.' if we had it and so prepare for next iteration */
+ if(*pC == '.')
+ ++pC;
+ }
+
+ if(pWildcard != NULL) {
+ /* we have more domain components than in the name to be
+ * checked. So this is no match.
+ */
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+
+ *pbIsMatching = 1; /* finally... it matches ;) */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* ------------------------------ end permitted peers code ------------------------------ */
+
+
+/* Code for handling allowed/disallowed senders
+ */
+static void MaskIP6 (struct in6_addr *addr, uint8_t bits) {
+ register uint8_t i;
+
+ assert (addr != NULL);
+ assert (bits <= 128);
+
+ i = bits/32;
+ if (bits%32)
+ addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32)));
+ for (; i < (sizeof addr->s6_addr32)/4; i++)
+ addr->s6_addr32[i] = 0;
+}
+
+static void MaskIP4 (struct in_addr *addr, uint8_t bits) {
+
+ assert (addr != NULL);
+ assert (bits <=32 );
+
+ addr->s_addr &= htonl(0xffffffff << (32 - bits));
+}
+
+#define SIN(sa) ((struct sockaddr_in *)(void*)(sa))
+#define SIN6(sa) ((struct sockaddr_in6 *)(void*)(sa))
+
+
+/* This is a cancel-safe getnameinfo() version, because we learned
+ * (via drd/valgrind) that getnameinfo() seems to have some issues
+ * when being cancelled, at least if the module was dlloaded.
+ * rgerhards, 2008-09-30
+ */
+static int
+mygetnameinfo(const struct sockaddr *sa, socklen_t salen,
+ char *host, size_t hostlen,
+ char *serv, size_t servlen, int flags)
+{
+ int iCancelStateSave;
+ int i;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
+ pthread_setcancelstate(iCancelStateSave, NULL);
+ return i;
+}
+
+
+/* This function adds an allowed sender entry to the ACL linked list.
+ * In any case, a single entry is added. If an error occurs, the
+ * function does its error reporting itself. All validity checks
+ * must already have been done by the caller.
+ * This is a helper to AddAllowedSender().
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast,
+ struct NetAddr *iAllow, uint8_t iSignificantBits)
+{
+ struct AllowedSenders *pEntry = NULL;
+
+ assert(ppRoot != NULL);
+ assert(ppLast != NULL);
+ assert(iAllow != NULL);
+
+ if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) {
+ return RS_RET_OUT_OF_MEMORY; /* no options left :( */
+ }
+
+ memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr));
+ pEntry->pNext = NULL;
+ pEntry->SignificantBits = iSignificantBits;
+
+ /* enqueue */
+ if(*ppRoot == NULL) {
+ *ppRoot = pEntry;
+ } else {
+ (*ppLast)->pNext = pEntry;
+ }
+ *ppLast = pEntry;
+
+ return RS_RET_OK;
+}
+
+/* function to clear the allowed sender structure in cases where
+ * it must be freed (occurs most often when HUPed).
+ * rgerhards, 2008-12-02: revamped this code when we fixed the interface
+ * definition. Now an iterative algorithm is used.
+ */
+static void
+clearAllowedSenders(uchar *pszType)
+{
+ struct AllowedSenders *pPrev;
+ struct AllowedSenders *pCurr = NULL;
+
+ if(setAllowRoot(&pCurr, pszType) != RS_RET_OK)
+ return; /* if something went wrong, so let's leave */
+
+ while(pCurr != NULL) {
+ pPrev = pCurr;
+ pCurr = pCurr->pNext;
+ /* now delete the entry we are right now processing */
+ if(F_ISSET(pPrev->allowedSender.flags, ADDR_NAME))
+ free(pPrev->allowedSender.addr.HostWildcard);
+ else
+ free(pPrev->allowedSender.addr.NetAddr);
+ free(pPrev);
+ }
+
+ /* indicate root pointer is de-init (was forgotten previously, resulting in
+ * all kinds of interesting things) -- rgerhards, 2009-01-12
+ */
+ reinitAllowRoot(pszType);
+}
+
+
+/* function to add an allowed sender to the allowed sender list. The
+ * root of the list is caller-provided, so it can be used for all
+ * supported lists. The caller must provide a pointer to the root,
+ * as it eventually needs to be updated. Also, a pointer to the
+ * pointer to the last element must be provided (to speed up adding
+ * list elements).
+ * rgerhards, 2005-09-26
+ * If a hostname is given there are possible multiple entries
+ * added (all addresses from that host).
+ */
+static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast,
+ struct NetAddr *iAllow, uint8_t iSignificantBits)
+{
+ struct addrinfo *restmp = NULL;
+ DEFiRet;
+
+ assert(ppRoot != NULL);
+ assert(ppLast != NULL);
+ assert(iAllow != NULL);
+
+ if (!F_ISSET(iAllow->flags, ADDR_NAME)) {
+ if(iSignificantBits == 0)
+ /* we handle this seperatly just to provide a better
+ * error message.
+ */
+ LogError(0, NO_ERRCODE, "You can not specify 0 bits of the netmask, this would "
+ "match ALL systems. If you really intend to do that, "
+ "remove all $AllowedSender directives.");
+
+ switch (iAllow->addr.NetAddr->sa_family) {
+ case AF_INET:
+ if((iSignificantBits < 1) || (iSignificantBits > 32)) {
+ LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv4 address - adjusted to 32",
+ (int)iSignificantBits);
+ iSignificantBits = 32;
+ }
+
+ MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits);
+ break;
+ case AF_INET6:
+ if((iSignificantBits < 1) || (iSignificantBits > 128)) {
+ LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv6 address - adjusted to 128",
+ iSignificantBits);
+ iSignificantBits = 128;
+ }
+
+ MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits);
+ break;
+ default:
+ /* rgerhards, 2007-07-16: We have an internal program error in this
+ * case. However, there is not much we can do against it right now. Of
+ * course, we could abort, but that would probably cause more harm
+ * than good. So we continue to run. We simply do not add this line - the
+ * worst thing that happens is that one host will not be allowed to
+ * log.
+ */
+ LogError(0, NO_ERRCODE, "Internal error caused AllowedSender to be ignored, AF = %d",
+ iAllow->addr.NetAddr->sa_family);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ /* OK, entry constructed, now lets add it to the ACL list */
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ } else {
+ /* we need to process a hostname ACL */
+ if(glbl.GetDisableDNS(loadConf)) {
+ LogError(0, NO_ERRCODE, "Ignoring hostname based ACLs because DNS is disabled.");
+ ABORT_FINALIZE(RS_RET_OK);
+ }
+
+ if (!strchr (iAllow->addr.HostWildcard, '*') &&
+ !strchr (iAllow->addr.HostWildcard, '?') &&
+ loadConf->globals.ACLDontResolve == 0) {
+ /* single host - in this case, we pull its IP addresses from DNS
+ * and add IP-based ACLs.
+ */
+ struct addrinfo hints, *res;
+ struct NetAddr allowIP;
+
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+# ifdef AI_ADDRCONFIG /* seems not to be present on all systems */
+ hints.ai_flags = AI_ADDRCONFIG;
+# endif
+
+ if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) {
+ LogError(0, NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard);
+
+ if (loadConf->globals.ACLAddHostnameOnFail) {
+ LogError(0, NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard "
+ "entry.", iAllow->addr.HostWildcard);
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ FINALIZE;
+ } else {
+ LogError(0, NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.",
+ iAllow->addr.HostWildcard);
+ ABORT_FINALIZE(RS_RET_NOENTRY);
+ }
+ }
+
+ restmp = res;
+ for ( ; res != NULL ; res = res->ai_next) {
+ switch (res->ai_family) {
+ case AF_INET: /* add IPv4 */
+ iSignificantBits = 32;
+ allowIP.flags = 0;
+ if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen);
+
+ if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits))
+ != RS_RET_OK) {
+ free(allowIP.addr.NetAddr);
+ FINALIZE;
+ }
+ break;
+ case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */
+ if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) {
+ /* extract & add IPv4 */
+
+ iSignificantBits = 32;
+ allowIP.flags = 0;
+ if((allowIP.addr.NetAddr = (struct sockaddr *)
+ malloc(sizeof(struct sockaddr))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ SIN(allowIP.addr.NetAddr)->sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in);
+#endif
+ SIN(allowIP.addr.NetAddr)->sin_port = 0;
+ memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr),
+ &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]),
+ sizeof (in_addr_t));
+
+ if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP,
+ iSignificantBits))
+ != RS_RET_OK) {
+ free(allowIP.addr.NetAddr);
+ FINALIZE;
+ }
+ } else {
+ /* finally add IPv6 */
+
+ iSignificantBits = 128;
+ allowIP.flags = 0;
+ if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen);
+
+ if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP,
+ iSignificantBits))
+ != RS_RET_OK) {
+ free(allowIP.addr.NetAddr);
+ FINALIZE;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ /* wildcards in hostname - we need to add a text-based ACL.
+ * For this, we already have everything ready and just need
+ * to pass it along...
+ */
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ }
+ }
+
+finalize_it:
+ if(restmp != NULL) {
+ freeaddrinfo (restmp);
+ }
+ RETiRet;
+}
+
+
+static const char *SENDER_TEXT[4] = { "", "UDP", "TCP", "GSS" };
+/* Print an allowed sender list. The caller must tell us which one.
+ * iListToPrint = 1 means UDP, 2 means TCP
+ * rgerhards, 2005-09-27
+ */
+static void
+PrintAllowedSenders(int iListToPrint)
+{
+ struct AllowedSenders *pSender;
+ uchar szIP[64];
+#ifdef USE_GSSAPI
+#define iListToPrint_MAX 3
+#else
+#define iListToPrint_MAX 2
+#endif
+ assert((iListToPrint > 0) && (iListToPrint <= iListToPrint_MAX));
+
+ dbgprintf("Allowed %s Senders:\n", SENDER_TEXT[iListToPrint]);
+
+ pSender = (iListToPrint == 1) ? pAllowedSenders_UDP :
+#ifdef USE_GSSAPI
+ (iListToPrint == 3) ? pAllowedSenders_GSS :
+#endif
+ pAllowedSenders_TCP;
+ if(pSender == NULL) {
+ dbgprintf("\tNo restrictions set.\n");
+ } else {
+ while(pSender != NULL) {
+ if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME))
+ dbgprintf ("\t%s\n", pSender->allowedSender.addr.HostWildcard);
+ else {
+ if(mygetnameinfo (pSender->allowedSender.addr.NetAddr,
+ SALEN(pSender->allowedSender.addr.NetAddr),
+ (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) {
+ dbgprintf ("\t%s/%u\n", szIP, pSender->SignificantBits);
+ } else {
+ /* getnameinfo() failed - but as this is only a
+ * debug function, we simply spit out an error and do
+ * not care much about it.
+ */
+ dbgprintf("\tERROR in getnameinfo() - something may be wrong "
+ "- ignored for now\n");
+ }
+ }
+ pSender = pSender->pNext;
+ }
+ }
+}
+
+
+/* parse an allowed sender config line and add the allowed senders
+ * (if the line is correct).
+ * rgerhards, 2005-09-27
+ */
+static rsRetVal
+addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine)
+{
+ struct AllowedSenders **ppRoot;
+ struct AllowedSenders **ppLast;
+ rsParsObj *pPars;
+ rsRetVal iRet;
+ struct NetAddr *uIP = NULL;
+ int iBits;
+
+ assert(pName != NULL);
+ assert(ppRestOfConfLine != NULL);
+ assert(*ppRestOfConfLine != NULL);
+
+ if(!strcasecmp(pName, "udp")) {
+ ppRoot = &pAllowedSenders_UDP;
+ ppLast = &pLastAllowedSenders_UDP;
+ } else if(!strcasecmp(pName, "tcp")) {
+ ppRoot = &pAllowedSenders_TCP;
+ ppLast = &pLastAllowedSenders_TCP;
+#ifdef USE_GSSAPI
+ } else if(!strcasecmp(pName, "gss")) {
+ ppRoot = &pAllowedSenders_GSS;
+ ppLast = &pLastAllowedSenders_GSS;
+#endif
+ } else {
+ LogError(0, RS_RET_ERR, "Invalid protocol '%s' in allowed sender "
+ "list, line ignored", pName);
+ return RS_RET_ERR;
+ }
+
+ /* OK, we now know the protocol and have valid list pointers.
+ * So let's process the entries. We are using the parse class
+ * for this.
+ */
+ /* create parser object starting with line string without leading colon */
+ if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) {
+ LogError(0, iRet, "Error %d constructing parser object - ignoring allowed sender list", iRet);
+ return(iRet);
+ }
+
+ while(!parsIsAtEndOfParseString(pPars)) {
+ if(parsPeekAtCharAtParsPtr(pPars) == '#')
+ break; /* a comment-sign stops processing of line */
+ /* now parse a single IP address */
+ if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) {
+ LogError(0, iRet, "Error %d parsing address in allowed sender"
+ "list - ignoring.", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+ if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) != RS_RET_OK) {
+ if(iRet == RS_RET_NOENTRY) {
+ LogError(0, iRet, "Error %d adding allowed sender entry "
+ "- ignoring.", iRet);
+ } else {
+ LogError(0, iRet, "Error %d adding allowed sender entry "
+ "- terminating, nothing more will be added.", iRet);
+ rsParsDestruct(pPars);
+ free(uIP);
+ return(iRet);
+ }
+ }
+ free (uIP); /* copy stored in AllowedSenders list */
+ }
+
+ /* cleanup */
+ *ppRestOfConfLine += parsGetCurrentPosition(pPars);
+ return rsParsDestruct(pPars);
+}
+
+
+
+/* compares a host to an allowed sender list entry. Handles all subleties
+ * including IPv4/v6 as well as domain name wildcards.
+ * This is a helper to isAllowedSender.
+ * Returns 0 if they do not match, 1 if they match and 2 if a DNS name would have been required.
+ * contributed 2007-07-16 by mildew@gmail.com
+ */
+static int
+MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS)
+{
+ assert(pAllow != NULL);
+ assert(pFrom != NULL);
+
+ if(F_ISSET(pAllow->flags, ADDR_NAME)) {
+ if(bChkDNS == 0)
+ return 2;
+ dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard);
+
+# if !defined(FNM_CASEFOLD)
+ /* TODO: I don't know if that then works, seen on HP UX, what I have not in lab... ;) */
+ return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE) == 0);
+# else
+ return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0);
+# endif
+ } else {/* We need to compare an IP address */
+ switch (pFrom->sa_family) {
+ case AF_INET:
+ if (AF_INET == pAllow->addr.NetAddr->sa_family)
+ return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) )
+ == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr);
+ else
+ return 0;
+ break;
+ case AF_INET6:
+ switch (pAllow->addr.NetAddr->sa_family) {
+ case AF_INET6: {
+ struct in6_addr ip, net;
+ register uint8_t i;
+
+ memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr));
+ memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr));
+
+ i = bits/32;
+ if (bits % 32)
+ ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32)));
+ for (; i < (sizeof ip.s6_addr32)/4; i++)
+ ip.s6_addr32[i] = 0;
+
+ return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 &&
+ (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ?
+ SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1));
+ }
+ case AF_INET: {
+ struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr;
+ struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr;
+
+ if ((ip6->s6_addr32[3] & (u_int32_t)
+ htonl((0xffffffff << (32 - bits)))) == net->s_addr &&
+#if BYTE_ORDER == LITTLE_ENDIAN
+ (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) &&
+#else
+ (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) &&
+#endif
+ (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0))
+ return 1;
+ else
+ return 0;
+ }
+ default:
+ /* Unsupported AF */
+ return 0;
+ }
+ /* fallthrough */
+ default:
+ /* Unsupported AF */
+ return 0;
+ }
+ }
+}
+
+
+/* check if a sender is allowed. The root of the the allowed sender.
+ * list must be proveded by the caller. As such, this function can be
+ * used to check both UDP and TCP allowed sender lists.
+ * returns 1, if the sender is allowed, 0 if not and 2 if we could not
+ * obtain a result because we would need a dns name, which we don't have
+ * (2 was added rgerhards, 2009-11-16).
+ * rgerhards, 2005-09-26
+ */
+static int isAllowedSender2(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS)
+{
+ struct AllowedSenders *pAllow;
+ struct AllowedSenders *pAllowRoot = NULL;
+ int bNeededDNS = 0; /* partial check because we could not resolve DNS? */
+ int ret;
+
+ assert(pFrom != NULL);
+
+ if(setAllowRoot(&pAllowRoot, pszType) != RS_RET_OK)
+ return 0; /* if something went wrong, we deny access - that's the better choice... */
+
+ if(pAllowRoot == NULL)
+ return 1; /* checking disabled, everything is valid! */
+
+ /* now we loop through the list of allowed senders. As soon as
+ * we find a match, we return back (indicating allowed). We loop
+ * until we are out of allowed senders. If so, we fall through the
+ * loop and the function's terminal return statement will indicate
+ * that the sender is disallowed.
+ */
+ for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) {
+ ret = MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost, bChkDNS);
+ if(ret == 1)
+ return 1;
+ else if(ret == 2)
+ bNeededDNS = 2;
+ }
+ return bNeededDNS;
+}
+
+
+/* legacy API, not to be used any longer */
+static int
+isAllowedSender(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost) {
+ return isAllowedSender2(pszType, pFrom, pszFromHost, 1);
+}
+
+
+/* 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
+ */
+#include <sys/utsname.h>
+static int
+should_use_so_bsdcompat(void)
+{
+#ifndef OS_BSD
+ static int use_so_bsdcompat = -1;
+
+ /* we guard the init code just by an atomic fetch to local variable. This still
+ * is racy, but the worst that can happen is that we do the very same init twice.
+ * This does hurt less than locking a mutex.
+ */
+ const int local_use_so_bsdcompat = PREFER_FETCH_32BIT(use_so_bsdcompat);
+ if(local_use_so_bsdcompat == -1) {
+ struct utsname myutsname;
+ unsigned int version, patchlevel;
+
+ if (uname(&myutsname) < 0) {
+ char errStr[1024];
+ dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr)));
+ PREFER_STORE_1_TO_INT(&use_so_bsdcompat);
+ FINALIZE;
+ }
+ /* 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(myutsname.release, "%u.%u", &version, &patchlevel) != 2) {
+ dbgprintf("uname: unexpected release '%s'\r\n",
+ myutsname.release);
+ PREFER_STORE_1_TO_INT(&use_so_bsdcompat);
+ FINALIZE;
+ }
+ /* 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)) {
+ PREFER_STORE_0_TO_INT(&use_so_bsdcompat);
+ FINALIZE;
+ }
+ }
+finalize_it:
+ return PREFER_FETCH_32BIT(use_so_bsdcompat);
+#else /* #ifndef OS_BSD */
+ return 1;
+#endif /* #ifndef OS_BSD */
+}
+#ifndef SO_BSDCOMPAT
+/* this shall prevent compiler errors due to undfined name */
+#define SO_BSDCOMPAT 0
+#endif
+
+
+/* print out which socket we are listening on. This is only
+ * a debug aid. rgerhards, 2007-07-02
+ */
+static void
+debugListenInfo(int fd, char *type)
+{
+ const char *szFamily;
+ int port;
+ union {
+ struct sockaddr_storage sa;
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+ } sockaddr;
+ socklen_t saLen = sizeof(sockaddr.sa);
+
+ if(getsockname(fd, (struct sockaddr *) &sockaddr.sa, &saLen) == 0) {
+ switch(sockaddr.sa.ss_family) {
+ case PF_INET:
+ szFamily = "IPv4";
+ port = ntohs(sockaddr.sa4.sin_port);
+ break;
+ case PF_INET6:
+ szFamily = "IPv6";
+ port = ntohs(sockaddr.sa6.sin6_port);
+ break;
+ default:
+ szFamily = "other";
+ port = -1;
+ break;
+ }
+ dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n",
+ type, fd, szFamily, port);
+ return;
+ }
+
+ /* we can not obtain peer info. We are just providing
+ * debug info, so this is no reason to break the program
+ * or do any serious error reporting.
+ */
+ dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd);
+}
+
+
+/* Return a printable representation of a host addresses. If
+ * a parameter is NULL, it is not set. rgerhards, 2013-01-22
+ */
+static rsRetVal
+cvthname(struct sockaddr_storage *f, prop_t **localName, prop_t **fqdn, prop_t **ip)
+{
+ DEFiRet;
+ assert(f != NULL);
+ iRet = dnscacheLookup(f, fqdn, NULL, localName, ip);
+ RETiRet;
+}
+
+
+/* get the name of the local host. A pointer to a character pointer is passed
+ * in, which on exit points to the local hostname. This buffer is dynamically
+ * allocated and must be free()ed by the caller. If the functions returns an
+ * error, the pointer is NULL.
+ * This function always tries to return a FQDN, even so be quering DNS. So it
+ * is safe to assume for the caller that when the function does not return
+ * a FQDN, it simply is not available. The domain part of that string is
+ * normalized to lower case. The hostname is kept in mixed case for historic
+ * reasons.
+ */
+#define EMPTY_HOSTNAME_REPLACEMENT "localhost-empty-hostname"
+static rsRetVal
+getLocalHostname(rsconf_t *const pConf, uchar **ppName)
+{
+ DEFiRet;
+ char hnbuf[8192];
+ uchar *fqdn = NULL;
+ int empty_hostname = 1;
+
+ if(gethostname(hnbuf, sizeof(hnbuf)) != 0) {
+ strcpy(hnbuf, EMPTY_HOSTNAME_REPLACEMENT);
+ } else {
+ /* now guard against empty hostname
+ * see https://github.com/rsyslog/rsyslog/issues/1040
+ */
+ if(hnbuf[0] == '\0') {
+ strcpy(hnbuf, EMPTY_HOSTNAME_REPLACEMENT);
+ } else {
+ empty_hostname = 0;
+ hnbuf[sizeof(hnbuf)-1] = '\0'; /* be on the safe side... */
+ }
+ }
+
+ char *dot = strstr(hnbuf, ".");
+ struct addrinfo *res = NULL;
+ if(!empty_hostname && dot == NULL && pConf != NULL && !glbl.GetDisableDNS(pConf)) {
+ /* we need to (try) to find the real name via resolver */
+ struct addrinfo flags;
+ memset(&flags, 0, sizeof(flags));
+ flags.ai_flags = AI_CANONNAME;
+ int error = getaddrinfo((char*)hnbuf, NULL, &flags, &res);
+ if (error != 0 &&
+ error != EAI_NONAME && error != EAI_AGAIN && error != EAI_FAIL) {
+ /* If we get one of errors above, network is probably
+ * not working yet, so we fall back to local hostname below
+ */
+ LogError(0, RS_RET_ERR, "getaddrinfo failed obtaining local "
+ "hostname - using '%s' instead; error: %s",
+ hnbuf, gai_strerror(error));
+ }
+ if (res != NULL) {
+ /* When AI_CANONNAME is set first member of res linked-list */
+ /* should contain what we need */
+ if (res->ai_canonname != NULL && res->ai_canonname[0] != '\0') {
+ CHKmalloc(fqdn = (uchar*)strdup(res->ai_canonname));
+ dot = strstr((char*)fqdn, ".");
+ }
+ }
+ }
+
+ if(fqdn == NULL) {
+ /* already was FQDN or we could not obtain a better one */
+ CHKmalloc(fqdn = (uchar*) strdup(hnbuf));
+ }
+
+ if(dot != NULL)
+ for(char *p = dot+1 ; *p ; ++p)
+ *p = tolower(*p);
+
+ *ppName = fqdn;
+finalize_it:
+ if (res != NULL) {
+ freeaddrinfo(res);
+ }
+ RETiRet;
+}
+
+
+/* closes the UDP listen sockets (if they exist) and frees
+ * all dynamically assigned memory.
+ */
+static void
+closeUDPListenSockets(int *pSockArr)
+{
+ register int i;
+
+ assert(pSockArr != NULL);
+ if(pSockArr != NULL) {
+ for (i = 0; i < *pSockArr; i++)
+ close(pSockArr[i+1]);
+ free(pSockArr);
+ }
+}
+
+
+/* create a single UDP socket and bail out if an error occurs.
+ * This is called from a loop inside create_udp_socket which
+ * iterates through potentially multiple sockets. NOT to be
+ * used elsewhere.
+ */
+static rsRetVal ATTR_NONNULL(1, 2)
+create_single_udp_socket(int *const s, /* socket */
+ struct addrinfo *const r,
+ const uchar *const hostname,
+ const int bIsServer,
+ const int rcvbuf,
+ const int sndbuf,
+ const int ipfreebind,
+ const char *const device
+ )
+{
+ const int on = 1;
+ int sockflags;
+ int actrcvbuf;
+ int actsndbuf;
+ socklen_t optlen;
+ char errStr[1024];
+ DEFiRet;
+
+ assert(r != NULL); // does NOT work with -O2 or higher due to ATTR_NONNULL!
+ assert(s != NULL);
+
+# if defined (_AIX)
+ /* AIXPORT : socktype will be SOCK_DGRAM, as set in hints before */
+ *s = socket(r->ai_family, SOCK_DGRAM, r->ai_protocol);
+# else
+ *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+# endif
+ if (*s < 0) {
+ if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) {
+ LogError(errno, NO_ERRCODE, "create_udp_socket(), socket");
+ /* it is debateble if PF_INET with EAFNOSUPPORT should
+ * also be ignored...
+ */
+ }
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+# 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(errno, RS_RET_ERR, "error creating UDP socket - setsockopt");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+# endif
+
+ if(device) {
+# if defined(SO_BINDTODEVICE)
+ if(setsockopt(*s, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) < 0)
+# endif
+ {
+ LogError(errno, RS_RET_ERR, "create UDP socket bound to device failed");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+
+ if(setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
+ LogError(errno, RS_RET_ERR, "create UDP socket failed to set REUSEADDR");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ /* We need to enable BSD compatibility. Otherwise an attacker
+ * could flood our log files by sending us tons of ICMP errors.
+ */
+ /* AIXPORT : SO_BSDCOMPAT socket option is deprecated, and its usage
+ * has been discontinued on most unixes, AIX does not support this option,
+ * hence avoid the call.
+ */
+# if !defined(OS_BSD) && !defined(__hpux) && !defined(_AIX)
+ if (should_use_so_bsdcompat()) {
+ if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, (char *) &on, sizeof(on)) < 0) {
+ LogError(errno, RS_RET_ERR, "create UDP socket failed to set BSDCOMPAT");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+# endif
+ if(bIsServer) {
+ DBGPRINTF("net.c: trying to set server socket %d to non-blocking mode\n", *s);
+ if ((sockflags = fcntl(*s, F_GETFL)) != -1) {
+ sockflags |= O_NONBLOCK;
+ /* SETFL could fail too, so get it caught by the subsequent
+ * error check.
+ */
+ sockflags = fcntl(*s, F_SETFL, sockflags);
+ }
+ if (sockflags == -1) {
+ LogError(errno, RS_RET_ERR, "net.c: socket %d fcntl(O_NONBLOCK)", *s);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+
+ if(sndbuf != 0) {
+# if defined(SO_SNDBUFFORCE)
+ if(setsockopt(*s, SOL_SOCKET, SO_SNDBUFFORCE, &sndbuf, sizeof(sndbuf)) < 0)
+# endif
+ {
+ /* if we fail, try to do it the regular way. Experiments show that at
+ * least some platforms do not return an error here, but silently set
+ * it to the max permitted value. So we do our error check a bit
+ * differently by querying the size below.
+ */
+ if(setsockopt(*s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) != 0) {
+ /* keep Coverity happy */
+ DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
+ "handled at later stages\n", __FILE__, __LINE__);
+ }
+ }
+ /* report socket buffer sizes */
+ optlen = sizeof(actsndbuf);
+ if(getsockopt(*s, SOL_SOCKET, SO_SNDBUF, &actsndbuf, &optlen) == 0) {
+ LogMsg(0, NO_ERRCODE, LOG_INFO,
+ "socket %d, actual os socket sndbuf size is %d", *s, actsndbuf);
+ if(sndbuf != 0 && actsndbuf/2 != sndbuf) {
+ LogError(errno, NO_ERRCODE,
+ "could not set os socket sndbuf size %d for socket %d, "
+ "value now is %d", sndbuf, *s, actsndbuf/2);
+ }
+ } else {
+ DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
+ *s, rs_strerror_r(errno, errStr, sizeof(errStr)));
+ }
+ }
+
+ if(rcvbuf != 0) {
+# if defined(SO_RCVBUFFORCE)
+ if(setsockopt(*s, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf)) < 0)
+# endif
+ {
+ /* if we fail, try to do it the regular way. Experiments show that at
+ * least some platforms do not return an error here, but silently set
+ * it to the max permitted value. So we do our error check a bit
+ * differently by querying the size below.
+ */
+ if(setsockopt(*s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) != 0) {
+ /* keep Coverity happy */
+ DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
+ "handled at later stages\n", __FILE__, __LINE__);
+ }
+ }
+ optlen = sizeof(actrcvbuf);
+ if(getsockopt(*s, SOL_SOCKET, SO_RCVBUF, &actrcvbuf, &optlen) == 0) {
+ LogMsg(0, NO_ERRCODE, LOG_INFO,
+ "socket %d, actual os socket rcvbuf size %d\n", *s, actrcvbuf);
+ if(rcvbuf != 0 && actrcvbuf/2 != rcvbuf) {
+ LogError(errno, NO_ERRCODE,
+ "cannot set os socket rcvbuf size %d for socket %d, value now is %d",
+ rcvbuf, *s, actrcvbuf/2);
+ }
+ } else {
+ DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
+ *s, rs_strerror_r(errno, errStr, sizeof(errStr)));
+ }
+ }
+
+ if(bIsServer) {
+ /* rgerhards, 2007-06-22: if we run on a kernel that does not support
+ * the IPV6_V6ONLY socket option, we need to use a work-around. On such
+ * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4
+ * socket can not listen on the same port as an IPv6 socket. The only
+ * workaround is to ignore the "socket in use" error. This is what we
+ * do if we have to.
+ */
+ if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0)
+# ifndef IPV6_V6ONLY
+ && (errno != EADDRINUSE)
+# endif
+ ) {
+ if (errno == EADDRNOTAVAIL && ipfreebind != IPFREEBIND_DISABLED) {
+ if (setsockopt(*s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on)) < 0) {
+ LogError(errno, RS_RET_ERR, "setsockopt(IP_FREEBIND)");
+ } else if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
+ LogError(errno, RS_RET_ERR, "bind with IP_FREEBIND");
+ } else {
+ if (ipfreebind >= IPFREEBIND_ENABLED_WITH_LOG)
+ LogMsg(0, RS_RET_OK_WARN, LOG_WARNING,
+ "bound address %s IP free", hostname);
+ FINALIZE;
+ }
+ }
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(*s != -1) {
+ close(*s);
+ *s = -1;
+ }
+ }
+ RETiRet;
+}
+
+/* creates the UDP listen sockets
+ * hostname and/or pszPort may be NULL, but not both!
+ * bIsServer indicates if a server socket should be created
+ * 1 - server, 0 - client
+ * Note: server sockets are created in non-blocking mode, client ones
+ * are blocking.
+ * param rcvbuf indicates desired rcvbuf size; 0 means OS default,
+ * similar for sndbuf.
+ */
+static int *
+create_udp_socket(uchar *hostname,
+ uchar *pszPort,
+ const int bIsServer,
+ const int rcvbuf,
+ const int sndbuf,
+ const int ipfreebind,
+ char *device)
+{
+ struct addrinfo hints, *res, *r;
+ int error, maxs, *s, *socks;
+ rsRetVal localRet;
+
+ assert(!((pszPort == NULL) && (hostname == NULL))); /* one of them must be non-NULL */
+ memset(&hints, 0, sizeof(hints));
+ if(bIsServer)
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+ else
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = glbl.GetDefPFFamily(runConf);
+ hints.ai_socktype = SOCK_DGRAM;
+# if defined (_AIX)
+ /* AIXPORT : SOCK_DGRAM has the protocol IPPROTO_UDP
+ * getaddrinfo needs this hint on AIX
+ */
+ hints.ai_protocol = IPPROTO_UDP;
+# endif
+ error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res);
+ if(error) {
+ LogError(0, NO_ERRCODE, "%s", gai_strerror(error));
+ LogError(0, NO_ERRCODE, "UDP message reception disabled due to error logged in last message.\n");
+ 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(0, RS_RET_OUT_OF_MEMORY, "couldn't allocate memory for UDP "
+ "sockets, suspending UDP 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) {
+ localRet = create_single_udp_socket(s, r, hostname, bIsServer, rcvbuf,
+ sndbuf, ipfreebind, device);
+ if(localRet == RS_RET_OK) {
+ (*socks)++;
+ s++;
+ }
+ }
+
+ if(res != NULL)
+ freeaddrinfo(res);
+
+ if(Debug && *socks != maxs)
+ dbgprintf("We could initialize %d UDP listen sockets out of %d we received "
+ "- this may or may not be an error indication.\n", *socks, maxs);
+
+ if(*socks == 0) {
+ LogError(0, NO_ERRCODE, "No UDP socket could successfully be initialized, "
+ "some functionality may be disabled.\n");
+ /* we do NOT need to close any sockets, because there were none... */
+ free(socks);
+ return(NULL);
+ }
+
+ return(socks);
+}
+
+
+/* check if two provided socket addresses point to the same host. Note that the
+ * length of the sockets must be provided as third parameter. This is necessary to
+ * compare non IPv4/v6 hosts, in which case we do a simple memory compare of the
+ * address structure (in that case, the same host may not reliably be detected).
+ * Note that we need to do the comparison not on the full structure, because it contains things
+ * like the port, which we do not need to look at when thinking about hostnames. So we look
+ * at the relevant fields, what means a somewhat more complicated processing.
+ * Also note that we use a non-standard calling interface, as this is much more natural and
+ * it looks extremely unlikely that we get an exception of any kind here. What we
+ * return is mimiced after memcmp(), and as such useful for building binary trees
+ * (the order relation may be a bit arbritrary, but at least it is consistent).
+ * rgerhards, 2009-09-03
+ */
+static int CmpHost(struct sockaddr_storage *s1, struct sockaddr_storage* s2, size_t socklen)
+{
+ int ret;
+
+ if(((struct sockaddr*) s1)->sa_family != ((struct sockaddr*) s2)->sa_family) {
+ ret = memcmp(s1, s2, socklen);
+ goto finalize_it;
+ }
+
+ if(((struct sockaddr*) s1)->sa_family == AF_INET) {
+ if(((struct sockaddr_in *) s1)->sin_addr.s_addr == ((struct sockaddr_in*)s2)->sin_addr.s_addr) {
+ ret = 0;
+ } else if(((struct sockaddr_in *) s1)->sin_addr.s_addr < ((struct sockaddr_in*)s2)->sin_addr.s_addr) {
+ ret = -1;
+ } else {
+ ret = 1;
+ }
+ } else if(((struct sockaddr*) s1)->sa_family == AF_INET6) {
+ /* IPv6 addresses are always 16 octets long */
+ ret = memcmp(((struct sockaddr_in6 *)s1)->sin6_addr.s6_addr,
+ ((struct sockaddr_in6*)s2)->sin6_addr.s6_addr, 16);
+ } else {
+ ret = memcmp(s1, s2, socklen);
+ }
+
+finalize_it:
+ return ret;
+}
+
+
+
+/* check if restrictions (ALCs) exists. The goal of this function is to disable the
+ * somewhat time-consuming ACL checks if no restrictions are defined (the usual case).
+ * This also permits to gain some speedup by using firewall-based ACLs instead of
+ * rsyslog ACLs (the recommended method.
+ * rgerhards, 2009-11-16
+ */
+static rsRetVal
+HasRestrictions(uchar *pszType, int *bHasRestrictions) {
+ struct AllowedSenders *pAllowRoot = NULL;
+ DEFiRet;
+
+ CHKiRet(setAllowRoot(&pAllowRoot, pszType));
+
+ *bHasRestrictions = (pAllowRoot == NULL) ? 0 : 1;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ *bHasRestrictions = 1; /* in this case it is better to check individually */
+ DBGPRINTF("Error %d trying to obtain ACL restriction state of '%s'\n", iRet, pszType);
+ }
+ RETiRet;
+}
+
+
+/* return the IP address (IPv4/6) for the provided interface. Returns
+ * RS_RET_NOT_FOUND if interface can not be found in interface list.
+ * The family must be correct (AF_INET vs. AF_INET6, AF_UNSPEC means
+ * either of *these two*).
+ * The function re-queries the interface list (at least in theory).
+ * However, it caches entries in order to avoid too-frequent requery.
+ * rgerhards, 2012-03-06
+ */
+static rsRetVal
+getIFIPAddr(uchar *szif, int family, uchar *pszbuf, int lenBuf)
+{
+#ifdef _AIX
+ struct ifaddrs_rsys * ifaddrs = NULL;
+ struct ifaddrs_rsys * ifa;
+#else
+ struct ifaddrs * ifaddrs = NULL;
+ struct ifaddrs * ifa;
+#endif
+ union {
+ struct sockaddr *sa;
+ struct sockaddr_in *ipv4;
+ struct sockaddr_in6 *ipv6;
+ } savecast;
+ void * pAddr;
+ DEFiRet;
+
+ if(getifaddrs(&ifaddrs) != 0) {
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ if(strcmp(ifa->ifa_name, (char*)szif))
+ continue;
+ savecast.sa = ifa->ifa_addr;
+ if( (family == AF_INET6 || family == AF_UNSPEC)
+ && ifa->ifa_addr->sa_family == AF_INET6) {
+ pAddr = &(savecast.ipv6->sin6_addr);
+ inet_ntop(AF_INET6, pAddr, (char*)pszbuf, lenBuf);
+ break;
+ } else if(/* (family == AF_INET || family == AF_UNSPEC)
+ &&*/ ifa->ifa_addr->sa_family == AF_INET) {
+ pAddr = &(savecast.ipv4->sin_addr);
+ inet_ntop(AF_INET, pAddr, (char*)pszbuf, lenBuf);
+ break;
+ }
+ }
+
+ if(ifaddrs != NULL)
+ freeifaddrs(ifaddrs);
+
+ if(ifa == NULL)
+ iRet = RS_RET_NOT_FOUND;
+
+finalize_it:
+ RETiRet;
+
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(net)
+CODESTARTobjQueryInterface(net)
+ if(pIf->ifVersion != netCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->cvthname = cvthname;
+ /* things to go away after proper modularization */
+ pIf->addAllowedSenderLine = addAllowedSenderLine;
+ pIf->PrintAllowedSenders = PrintAllowedSenders;
+ pIf->clearAllowedSenders = clearAllowedSenders;
+ pIf->debugListenInfo = debugListenInfo;
+ pIf->create_udp_socket = create_udp_socket;
+ pIf->closeUDPListenSockets = closeUDPListenSockets;
+ pIf->isAllowedSender = isAllowedSender;
+ pIf->isAllowedSender2 = isAllowedSender2;
+ pIf->should_use_so_bsdcompat = should_use_so_bsdcompat;
+ pIf->getLocalHostname = getLocalHostname;
+ pIf->AddPermittedPeer = AddPermittedPeer;
+ pIf->DestructPermittedPeers = DestructPermittedPeers;
+ pIf->PermittedPeerWildcardMatch = PermittedPeerWildcardMatch;
+ pIf->CmpHost = CmpHost;
+ pIf->HasRestrictions = HasRestrictions;
+ pIf->GetIFIPAddr = getIFIPAddr;
+finalize_it:
+ENDobjQueryInterface(net)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(net, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(net)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ENDObjClassExit(net)
+
+
+/* Initialize the net class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(net, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(net)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ netClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(netClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
+/* vi:set ai:
+ */