diff options
Diffstat (limited to 'runtime/dnscache.c')
-rw-r--r-- | runtime/dnscache.c | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/runtime/dnscache.c b/runtime/dnscache.c new file mode 100644 index 0000000..7dd0aab --- /dev/null +++ b/runtime/dnscache.c @@ -0,0 +1,472 @@ +/* dnscache.c + * Implementation of a real DNS cache + * + * File begun on 2011-06-06 by RGerhards + * The initial implementation is far from being optimal. The idea is to + * first get somethting that'S functionally OK, and then evolve the algorithm. + * In any case, even the initial implementaton is far faster than what we had + * before. -- rgerhards, 2011-06-06 + * + * Copyright 2011-2019 by Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * 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 "rsyslog.h" +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <netdb.h> +#include <unistd.h> +#include <ctype.h> + +#include "syslogd-types.h" +#include "glbl.h" +#include "errmsg.h" +#include "obj.h" +#include "unicode-helper.h" +#include "net.h" +#include "hashtable.h" +#include "prop.h" +#include "dnscache.h" +#include "rsconf.h" + +/* module data structures */ +struct dnscache_entry_s { + struct sockaddr_storage addr; + prop_t *fqdn; + prop_t *fqdnLowerCase; + prop_t *localName; /* only local name, without domain part (if configured so) */ + prop_t *ip; + time_t validUntil; + struct dnscache_entry_s *next; + unsigned nUsed; +}; +typedef struct dnscache_entry_s dnscache_entry_t; +struct dnscache_s { + pthread_rwlock_t rwlock; + struct hashtable *ht; + unsigned nEntries; +}; +typedef struct dnscache_s dnscache_t; + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(glbl) +DEFobjCurrIf(prop) +static dnscache_t dnsCache; +static prop_t *staticErrValue; + + +/* Our hash function. + */ +static unsigned int +hash_from_key_fn(void *k) +{ + int len = 0; + uchar *rkey; /* we treat this as opaque bytes */ + unsigned hashval = 1; + + switch (((struct sockaddr *)k)->sa_family) { + case AF_INET: + len = sizeof (struct in_addr); + rkey = (uchar*) &(((struct sockaddr_in *)k)->sin_addr); + break; + case AF_INET6: + len = sizeof (struct in6_addr); + rkey = (uchar*) &(((struct sockaddr_in6 *)k)->sin6_addr); + break; + default: + dbgprintf("hash_from_key_fn: unknown address family!\n"); + len = 0; + rkey = NULL; + } + while(len--) + hashval = hashval * 33 + *rkey++; + + return hashval; +} + + +static int +key_equals_fn(void *key1, void *key2) +{ + int RetVal = 0; + + if(((struct sockaddr *)key1)->sa_family != ((struct sockaddr *)key2)->sa_family) { + return 0; + } + switch (((struct sockaddr *)key1)->sa_family) { + case AF_INET: + RetVal = !memcmp(&((struct sockaddr_in *)key1)->sin_addr, + &((struct sockaddr_in *)key2)->sin_addr, sizeof (struct in_addr)); + break; + case AF_INET6: + RetVal = !memcmp(&((struct sockaddr_in6 *)key1)->sin6_addr, + &((struct sockaddr_in6 *)key2)->sin6_addr, sizeof (struct in6_addr)); + break; + } + + return RetVal; +} + +/* destruct a cache entry. + * Precondition: entry must already be unlinked from list + */ +static void ATTR_NONNULL() +entryDestruct(dnscache_entry_t *const etry) +{ + if(etry->fqdn != NULL) + prop.Destruct(&etry->fqdn); + if(etry->fqdnLowerCase != NULL) + prop.Destruct(&etry->fqdnLowerCase); + if(etry->localName != NULL) + prop.Destruct(&etry->localName); + if(etry->ip != NULL) + prop.Destruct(&etry->ip); + free(etry); +} + +/* init function (must be called once) */ +rsRetVal +dnscacheInit(void) +{ + DEFiRet; + if((dnsCache.ht = create_hashtable(100, hash_from_key_fn, key_equals_fn, + (void(*)(void*))entryDestruct)) == NULL) { + DBGPRINTF("dnscache: error creating hash table!\n"); + ABORT_FINALIZE(RS_RET_ERR); // TODO: make this degrade, but run! + } + dnsCache.nEntries = 0; + pthread_rwlock_init(&dnsCache.rwlock, NULL); + CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + + prop.Construct(&staticErrValue); + prop.SetString(staticErrValue, (uchar*)"???", 3); + prop.ConstructFinalize(staticErrValue); +finalize_it: + RETiRet; +} + +/* deinit function (must be called once) */ +rsRetVal +dnscacheDeinit(void) +{ + DEFiRet; + prop.Destruct(&staticErrValue); + hashtable_destroy(dnsCache.ht, 1); /* 1 => free all values automatically */ + pthread_rwlock_destroy(&dnsCache.rwlock); + objRelease(glbl, CORE_COMPONENT); + objRelease(prop, CORE_COMPONENT); + RETiRet; +} + + +/* 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; +} + + +/* get only the local part of the hostname and set it in cache entry */ +static void +setLocalHostName(dnscache_entry_t *etry) +{ + uchar *fqdnLower; + uchar *p; + int i; + uchar hostbuf[NI_MAXHOST]; + + if(glbl.GetPreserveFQDN()) { + prop.AddRef(etry->fqdnLowerCase); + etry->localName = etry->fqdnLowerCase; + goto done; + } + + /* strip domain, if configured for this entry */ + fqdnLower = propGetSzStr(etry->fqdnLowerCase); + p = (uchar*)strchr((char*)fqdnLower, '.'); /* find start of domain name "machine.example.com" */ + if(p == NULL) { /* do we have a domain part? */ + prop.AddRef(etry->fqdnLowerCase); /* no! */ + etry->localName = etry->fqdnLowerCase; + goto done; + } + + i = p - fqdnLower; /* length of hostname */ + memcpy(hostbuf, fqdnLower, i); + hostbuf[i] = '\0'; + + /* at this point, we have not found anything, so we again use the + * already-created complete full name property. + */ + prop.AddRef(etry->fqdnLowerCase); + etry->localName = etry->fqdnLowerCase; +done: return; +} + + +/* resolve an address. + * + * 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 rsRetVal ATTR_NONNULL() +resolveAddr(struct sockaddr_storage *addr, dnscache_entry_t *etry) +{ + DEFiRet; + int error; + sigset_t omask, nmask; + struct addrinfo hints, *res; + char szIP[80]; /* large enough for IPv6 */ + char fqdnBuf[NI_MAXHOST]; + rs_size_t fqdnLen; + rs_size_t i; + + error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *)addr), + (char*) szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST); + if(error) { + dbgprintf("Malformed from address %s\n", gai_strerror(error)); + ABORT_FINALIZE(RS_RET_INVALID_SOURCE); + } + + if(!glbl.GetDisableDNS(runConf)) { + sigemptyset(&nmask); + sigaddset(&nmask, SIGHUP); + pthread_sigmask(SIG_BLOCK, &nmask, &omask); + + error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *) addr), + fqdnBuf, NI_MAXHOST, NULL, 0, NI_NAMEREQD); + + if(error == 0) { + memset (&hints, 0, sizeof (struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; + + /* we now do a lookup once again. This one should fail, + * because we should not have obtained a non-numeric address. If + * we got a numeric one, someone messed with DNS! + */ + if(getaddrinfo (fqdnBuf, NULL, &hints, &res) == 0) { + 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(glbl.GetDropMalPTRMsgs(runConf) == 1) { + LogError(0, RS_RET_MALICIOUS_ENTITY, + "Malicious PTR record, message dropped " + "IP = \"%s\" HOST = \"%s\"", + szIP, fqdnBuf); + pthread_sigmask(SIG_SETMASK, &omask, NULL); + ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY); + } + + /* 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 contains characters that make the message unreadable + * (OK, I admit this is more or less impossible, but I am paranoid...) + * rgerhards, 2007-07-16 + */ + LogError(0, NO_ERRCODE, + "Malicious PTR record (message accepted, but used IP " + "instead of PTR name: IP = \"%s\" HOST = \"%s\"", + szIP, fqdnBuf); + + error = 1; /* that will trigger using IP address below. */ + } else {/* we have a valid entry, so let's create the respective properties */ + fqdnLen = strlen(fqdnBuf); + prop.CreateStringProp(&etry->fqdn, (uchar*)fqdnBuf, fqdnLen); + for(i = 0 ; i < fqdnLen ; ++i) + fqdnBuf[i] = tolower(fqdnBuf[i]); + prop.CreateStringProp(&etry->fqdnLowerCase, (uchar*)fqdnBuf, fqdnLen); + } + } + pthread_sigmask(SIG_SETMASK, &omask, NULL); + } + + +finalize_it: + if(iRet != RS_RET_OK) { + strcpy(szIP, "?error.obtaining.ip?"); + error = 1; /* trigger hostname copies below! */ + } + + prop.CreateStringProp(&etry->ip, (uchar*)szIP, strlen(szIP)); + + if(error || glbl.GetDisableDNS(runConf)) { + dbgprintf("Host name for your address (%s) unknown\n", szIP); + prop.AddRef(etry->ip); + etry->fqdn = etry->ip; + prop.AddRef(etry->ip); + etry->fqdnLowerCase = etry->ip; + } + + setLocalHostName(etry); + + RETiRet; +} + + +static rsRetVal ATTR_NONNULL() +addEntry(struct sockaddr_storage *const addr, dnscache_entry_t **const pEtry) +{ + int r; + dnscache_entry_t *etry = NULL; + DEFiRet; + + /* entry still does not exist, so add it */ + struct sockaddr_storage *const keybuf = malloc(sizeof(struct sockaddr_storage)); + CHKmalloc(keybuf); + CHKmalloc(etry = malloc(sizeof(dnscache_entry_t))); + resolveAddr(addr, etry); + assert(etry != NULL); + memcpy(&etry->addr, addr, SALEN((struct sockaddr*) addr)); + etry->nUsed = 0; + if(runConf->globals.dnscacheEnableTTL) { + etry->validUntil = time(NULL) + runConf->globals.dnscacheDefaultTTL; + } + + memcpy(keybuf, addr, sizeof(struct sockaddr_storage)); + + r = hashtable_insert(dnsCache.ht, keybuf, etry); + if(r == 0) { + DBGPRINTF("dnscache: inserting element failed\n"); + } + *pEtry = etry; + +finalize_it: + if(iRet != RS_RET_OK) { + free(keybuf); + } + RETiRet; +} + + +static rsRetVal ATTR_NONNULL(1, 5) +findEntry(struct sockaddr_storage *const addr, + prop_t **const fqdn, prop_t **const fqdnLowerCase, + prop_t **const localName, prop_t **const ip) +{ + DEFiRet; + + pthread_rwlock_rdlock(&dnsCache.rwlock); + dnscache_entry_t * etry = hashtable_search(dnsCache.ht, addr); + DBGPRINTF("findEntry: 1st lookup found %p\n", etry); + + if(etry == NULL || (runConf->globals.dnscacheEnableTTL && (etry->validUntil <= time(NULL)))) { + pthread_rwlock_unlock(&dnsCache.rwlock); + pthread_rwlock_wrlock(&dnsCache.rwlock); + etry = hashtable_search(dnsCache.ht, addr); /* re-query, might have changed */ + DBGPRINTF("findEntry: 2nd lookup found %p\n", etry); + if(etry == NULL || (runConf->globals.dnscacheEnableTTL && (etry->validUntil <= time(NULL)))) { + if(etry != NULL) { + DBGPRINTF("hashtable: entry timed out, discarding it; " + "valid until %lld, now %lld\n", + (long long) etry->validUntil, (long long) time(NULL)); + dnscache_entry_t *const deleted = hashtable_remove(dnsCache.ht, addr); + if(deleted != etry) { + LogError(0, RS_RET_INTERNAL_ERROR, "dnscache %d: removed different " + "hashtable entry than expected - please report issue; " + "rsyslog version is %s", __LINE__, VERSION); + } + entryDestruct(etry); + } + /* now entry doesn't exist in any case, so let's (re)create it */ + CHKiRet(addEntry(addr, &etry)); + } + } + + prop.AddRef(etry->ip); + *ip = etry->ip; + if(fqdn != NULL) { + prop.AddRef(etry->fqdn); + *fqdn = etry->fqdn; + } + if(fqdnLowerCase != NULL) { + prop.AddRef(etry->fqdnLowerCase); + *fqdnLowerCase = etry->fqdnLowerCase; + } + if(localName != NULL) { + prop.AddRef(etry->localName); + *localName = etry->localName; + } + +finalize_it: + pthread_rwlock_unlock(&dnsCache.rwlock); + RETiRet; +} + + +/* This is the main function: it looks up an entry and returns it's name + * and IP address. If the entry is not yet inside the cache, it is added. + * If the entry can not be resolved, an error is reported back. If fqdn + * or fqdnLowerCase are NULL, they are not set. + */ +rsRetVal ATTR_NONNULL(1, 5) +dnscacheLookup(struct sockaddr_storage *const addr, + prop_t **const fqdn, prop_t **const fqdnLowerCase, + prop_t **const localName, prop_t **const ip) +{ + DEFiRet; + + iRet = findEntry(addr, fqdn, fqdnLowerCase, localName, ip); + + if(iRet != RS_RET_OK) { + DBGPRINTF("dnscacheLookup failed with iRet %d\n", iRet); + prop.AddRef(staticErrValue); + *ip = staticErrValue; + if(fqdn != NULL) { + prop.AddRef(staticErrValue); + *fqdn = staticErrValue; + } + if(fqdnLowerCase != NULL) { + prop.AddRef(staticErrValue); + *fqdnLowerCase = staticErrValue; + } + if(localName != NULL) { + prop.AddRef(staticErrValue); + *localName = staticErrValue; + } + } + RETiRet; +} |