1 /* dnscache.c
2 * Implementation of a real DNS cache
3 *
4 * File begun on 2011-06-06 by RGerhards
5 * The initial implementation is far from being optimal. The idea is to
6 * first get somethting that'S functionally OK, and then evolve the algorithm.
7 * In any case, even the initial implementaton is far faster than what we had
8 * before. -- rgerhards, 2011-06-06
9 *
10 * Copyright 2011-2019 by Rainer Gerhards and Adiscon GmbH.
11 *
12 * This file is part of the rsyslog runtime library.
13 *
14 * Licensed under the Apache License, Version 2.0 (the "License");
15 * you may not use this file except in compliance with the License.
16 * You may obtain a copy of the License at
17 *
18 * http://www.apache.org/licenses/LICENSE-2.0
19 * -or-
20 * see COPYING.ASL20 in the source distribution
21 *
22 * Unless required by applicable law or agreed to in writing, software
23 * distributed under the License is distributed on an "AS IS" BASIS,
24 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25 * See the License for the specific language governing permissions and
26 * limitations under the License.
27 */
28 #include "config.h"
29
30 #include "rsyslog.h"
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <signal.h>
34 #include <netdb.h>
35 #include <unistd.h>
36 #include <ctype.h>
37
38 #include "syslogd-types.h"
39 #include "glbl.h"
40 #include "errmsg.h"
41 #include "obj.h"
42 #include "unicode-helper.h"
43 #include "net.h"
44 #include "hashtable.h"
45 #include "prop.h"
46 #include "dnscache.h"
47
48 /* module data structures */
49 struct dnscache_entry_s {
50 struct sockaddr_storage addr;
51 prop_t *fqdn;
52 prop_t *fqdnLowerCase;
53 prop_t *localName; /* only local name, without domain part (if configured so) */
54 prop_t *ip;
55 time_t validUntil;
56 struct dnscache_entry_s *next;
57 unsigned nUsed;
58 };
59 typedef struct dnscache_entry_s dnscache_entry_t;
60 struct dnscache_s {
61 pthread_rwlock_t rwlock;
62 struct hashtable *ht;
63 unsigned nEntries;
64 };
65 typedef struct dnscache_s dnscache_t;
66
67 unsigned dnscacheDefaultTTL = 24 * 60 * 60; /* 24 hrs default TTL */
68 int dnscacheEnableTTL = 0; /* expire entries or not (0) ? */
69
70
71 /* static data */
72 DEFobjStaticHelpers
73 DEFobjCurrIf(glbl)
74 DEFobjCurrIf(prop)
75 static dnscache_t dnsCache;
76 static prop_t *staticErrValue;
77
78
79 /* Our hash function.
80 */
81 static unsigned int
hash_from_key_fn(void * k)82 hash_from_key_fn(void *k)
83 {
84 int len = 0;
85 uchar *rkey; /* we treat this as opaque bytes */
86 unsigned hashval = 1;
87
88 switch (((struct sockaddr *)k)->sa_family) {
89 case AF_INET:
90 len = sizeof (struct in_addr);
91 rkey = (uchar*) &(((struct sockaddr_in *)k)->sin_addr);
92 break;
93 case AF_INET6:
94 len = sizeof (struct in6_addr);
95 rkey = (uchar*) &(((struct sockaddr_in6 *)k)->sin6_addr);
96 break;
97 default:
98 dbgprintf("hash_from_key_fn: unknown address family!\n");
99 len = 0;
100 rkey = NULL;
101 }
102 while(len--)
103 hashval = hashval * 33 + *rkey++;
104
105 return hashval;
106 }
107
108
109 static int
key_equals_fn(void * key1,void * key2)110 key_equals_fn(void *key1, void *key2)
111 {
112 int RetVal = 0;
113
114 if(((struct sockaddr *)key1)->sa_family != ((struct sockaddr *)key2)->sa_family) {
115 return 0;
116 }
117 switch (((struct sockaddr *)key1)->sa_family) {
118 case AF_INET:
119 RetVal = !memcmp(&((struct sockaddr_in *)key1)->sin_addr,
120 &((struct sockaddr_in *)key2)->sin_addr, sizeof (struct in_addr));
121 break;
122 case AF_INET6:
123 RetVal = !memcmp(&((struct sockaddr_in6 *)key1)->sin6_addr,
124 &((struct sockaddr_in6 *)key2)->sin6_addr, sizeof (struct in6_addr));
125 break;
126 }
127
128 return RetVal;
129 }
130
131 /* destruct a cache entry.
132 * Precondition: entry must already be unlinked from list
133 */
ATTR_NONNULL()134 static void ATTR_NONNULL()
135 entryDestruct(dnscache_entry_t *const etry)
136 {
137 if(etry->fqdn != NULL)
138 prop.Destruct(&etry->fqdn);
139 if(etry->fqdnLowerCase != NULL)
140 prop.Destruct(&etry->fqdnLowerCase);
141 if(etry->localName != NULL)
142 prop.Destruct(&etry->localName);
143 if(etry->ip != NULL)
144 prop.Destruct(&etry->ip);
145 free(etry);
146 }
147
148 /* init function (must be called once) */
149 rsRetVal
dnscacheInit(void)150 dnscacheInit(void)
151 {
152 DEFiRet;
153 if((dnsCache.ht = create_hashtable(100, hash_from_key_fn, key_equals_fn,
154 (void(*)(void*))entryDestruct)) == NULL) {
155 DBGPRINTF("dnscache: error creating hash table!\n");
156 ABORT_FINALIZE(RS_RET_ERR); // TODO: make this degrade, but run!
157 }
158 dnsCache.nEntries = 0;
159 pthread_rwlock_init(&dnsCache.rwlock, NULL);
160 CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */
161 CHKiRet(objUse(glbl, CORE_COMPONENT));
162 CHKiRet(objUse(prop, CORE_COMPONENT));
163
164 prop.Construct(&staticErrValue);
165 prop.SetString(staticErrValue, (uchar*)"???", 3);
166 prop.ConstructFinalize(staticErrValue);
167 finalize_it:
168 RETiRet;
169 }
170
171 /* deinit function (must be called once) */
172 rsRetVal
dnscacheDeinit(void)173 dnscacheDeinit(void)
174 {
175 DEFiRet;
176 prop.Destruct(&staticErrValue);
177 hashtable_destroy(dnsCache.ht, 1); /* 1 => free all values automatically */
178 pthread_rwlock_destroy(&dnsCache.rwlock);
179 objRelease(glbl, CORE_COMPONENT);
180 objRelease(prop, CORE_COMPONENT);
181 RETiRet;
182 }
183
184
185 /* This is a cancel-safe getnameinfo() version, because we learned
186 * (via drd/valgrind) that getnameinfo() seems to have some issues
187 * when being cancelled, at least if the module was dlloaded.
188 * rgerhards, 2008-09-30
189 */
190 static int
mygetnameinfo(const struct sockaddr * sa,socklen_t salen,char * host,size_t hostlen,char * serv,size_t servlen,int flags)191 mygetnameinfo(const struct sockaddr *sa, socklen_t salen,
192 char *host, size_t hostlen,
193 char *serv, size_t servlen, int flags)
194 {
195 int iCancelStateSave;
196 int i;
197
198 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
199 i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
200 pthread_setcancelstate(iCancelStateSave, NULL);
201 return i;
202 }
203
204
205 /* get only the local part of the hostname and set it in cache entry */
206 static void
setLocalHostName(dnscache_entry_t * etry)207 setLocalHostName(dnscache_entry_t *etry)
208 {
209 uchar *fqdnLower;
210 uchar *p;
211 int i;
212 uchar hostbuf[NI_MAXHOST];
213
214 if(glbl.GetPreserveFQDN()) {
215 prop.AddRef(etry->fqdnLowerCase);
216 etry->localName = etry->fqdnLowerCase;
217 goto done;
218 }
219
220 /* strip domain, if configured for this entry */
221 fqdnLower = propGetSzStr(etry->fqdnLowerCase);
222 p = (uchar*)strchr((char*)fqdnLower, '.'); /* find start of domain name "machine.example.com" */
223 if(p == NULL) { /* do we have a domain part? */
224 prop.AddRef(etry->fqdnLowerCase); /* no! */
225 etry->localName = etry->fqdnLowerCase;
226 goto done;
227 }
228
229 i = p - fqdnLower; /* length of hostname */
230 memcpy(hostbuf, fqdnLower, i);
231 hostbuf[i] = '\0';
232
233 /* at this point, we have not found anything, so we again use the
234 * already-created complete full name property.
235 */
236 prop.AddRef(etry->fqdnLowerCase);
237 etry->localName = etry->fqdnLowerCase;
238 done: return;
239 }
240
241
242 /* resolve an address.
243 *
244 * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
245 * for some explanation of the code found below. We do by default not
246 * discard message where we detected malicouos DNS PTR records. However,
247 * there is a user-configurabel option that will tell us if
248 * we should abort. For this, the return value tells the caller if the
249 * message should be processed (1) or discarded (0).
250 */
ATTR_NONNULL()251 static rsRetVal ATTR_NONNULL()
252 resolveAddr(struct sockaddr_storage *addr, dnscache_entry_t *etry)
253 {
254 DEFiRet;
255 int error;
256 sigset_t omask, nmask;
257 struct addrinfo hints, *res;
258 char szIP[80]; /* large enough for IPv6 */
259 char fqdnBuf[NI_MAXHOST];
260 rs_size_t fqdnLen;
261 rs_size_t i;
262
263 error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *)addr),
264 (char*) szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST);
265 if(error) {
266 dbgprintf("Malformed from address %s\n", gai_strerror(error));
267 ABORT_FINALIZE(RS_RET_INVALID_SOURCE);
268 }
269
270 if(!glbl.GetDisableDNS()) {
271 sigemptyset(&nmask);
272 sigaddset(&nmask, SIGHUP);
273 pthread_sigmask(SIG_BLOCK, &nmask, &omask);
274
275 error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *) addr),
276 fqdnBuf, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
277
278 if(error == 0) {
279 memset (&hints, 0, sizeof (struct addrinfo));
280 hints.ai_flags = AI_NUMERICHOST;
281
282 /* we now do a lookup once again. This one should fail,
283 * because we should not have obtained a non-numeric address. If
284 * we got a numeric one, someone messed with DNS!
285 */
286 if(getaddrinfo (fqdnBuf, NULL, &hints, &res) == 0) {
287 freeaddrinfo (res);
288 /* OK, we know we have evil. The question now is what to do about
289 * it. One the one hand, the message might probably be intended
290 * to harm us. On the other hand, losing the message may also harm us.
291 * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords
292 * option. If it tells us we should discard, we do so, else we proceed,
293 * but log an error message together with it.
294 * time being, we simply drop the name we obtained and use the IP - that one
295 * is OK in any way. We do also log the error message. rgerhards, 2007-07-16
296 */
297 if(glbl.GetDropMalPTRMsgs() == 1) {
298 LogError(0, RS_RET_MALICIOUS_ENTITY,
299 "Malicious PTR record, message dropped "
300 "IP = \"%s\" HOST = \"%s\"",
301 szIP, fqdnBuf);
302 pthread_sigmask(SIG_SETMASK, &omask, NULL);
303 ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY);
304 }
305
306 /* Please note: we deal with a malicous entry. Thus, we have crafted
307 * the snprintf() below so that all text is in front of the entry - maybe
308 * it contains characters that make the message unreadable
309 * (OK, I admit this is more or less impossible, but I am paranoid...)
310 * rgerhards, 2007-07-16
311 */
312 LogError(0, NO_ERRCODE,
313 "Malicious PTR record (message accepted, but used IP "
314 "instead of PTR name: IP = \"%s\" HOST = \"%s\"",
315 szIP, fqdnBuf);
316
317 error = 1; /* that will trigger using IP address below. */
318 } else {/* we have a valid entry, so let's create the respective properties */
319 fqdnLen = strlen(fqdnBuf);
320 prop.CreateStringProp(&etry->fqdn, (uchar*)fqdnBuf, fqdnLen);
321 for(i = 0 ; i < fqdnLen ; ++i)
322 fqdnBuf[i] = tolower(fqdnBuf[i]);
323 prop.CreateStringProp(&etry->fqdnLowerCase, (uchar*)fqdnBuf, fqdnLen);
324 }
325 }
326 pthread_sigmask(SIG_SETMASK, &omask, NULL);
327 }
328
329
330 finalize_it:
331 if(iRet != RS_RET_OK) {
332 strcpy(szIP, "?error.obtaining.ip?");
333 error = 1; /* trigger hostname copies below! */
334 }
335
336 prop.CreateStringProp(&etry->ip, (uchar*)szIP, strlen(szIP));
337
338 if(error || glbl.GetDisableDNS()) {
339 dbgprintf("Host name for your address (%s) unknown\n", szIP);
340 prop.AddRef(etry->ip);
341 etry->fqdn = etry->ip;
342 prop.AddRef(etry->ip);
343 etry->fqdnLowerCase = etry->ip;
344 }
345
346 setLocalHostName(etry);
347
348 RETiRet;
349 }
350
351
ATTR_NONNULL()352 static rsRetVal ATTR_NONNULL()
353 addEntry(struct sockaddr_storage *const addr, dnscache_entry_t **const pEtry)
354 {
355 int r;
356 dnscache_entry_t *etry = NULL;
357 DEFiRet;
358
359 /* entry still does not exist, so add it */
360 struct sockaddr_storage *const keybuf = malloc(sizeof(struct sockaddr_storage));
361 CHKmalloc(keybuf);
362 CHKmalloc(etry = malloc(sizeof(dnscache_entry_t)));
363 resolveAddr(addr, etry);
364 assert(etry != NULL);
365 memcpy(&etry->addr, addr, SALEN((struct sockaddr*) addr));
366 etry->nUsed = 0;
367 if(dnscacheEnableTTL) {
368 etry->validUntil = time(NULL) + dnscacheDefaultTTL;
369 }
370
371 memcpy(keybuf, addr, sizeof(struct sockaddr_storage));
372
373 r = hashtable_insert(dnsCache.ht, keybuf, etry);
374 if(r == 0) {
375 DBGPRINTF("dnscache: inserting element failed\n");
376 }
377 *pEtry = etry;
378
379 finalize_it:
380 if(iRet != RS_RET_OK) {
381 free(keybuf);
382 }
383 RETiRet;
384 }
385
386
387 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)388 findEntry(struct sockaddr_storage *const addr,
389 prop_t **const fqdn, prop_t **const fqdnLowerCase,
390 prop_t **const localName, prop_t **const ip)
391 {
392 DEFiRet;
393
394 pthread_rwlock_rdlock(&dnsCache.rwlock);
395 dnscache_entry_t * etry = hashtable_search(dnsCache.ht, addr);
396 DBGPRINTF("findEntry: 1st lookup found %p\n", etry);
397
398 if(etry == NULL || (dnscacheEnableTTL && (etry->validUntil <= time(NULL)))) {
399 pthread_rwlock_unlock(&dnsCache.rwlock);
400 pthread_rwlock_wrlock(&dnsCache.rwlock);
401 etry = hashtable_search(dnsCache.ht, addr); /* re-query, might have changed */
402 DBGPRINTF("findEntry: 2nd lookup found %p\n", etry);
403 if(etry == NULL || (dnscacheEnableTTL && (etry->validUntil <= time(NULL)))) {
404 if(etry != NULL) {
405 DBGPRINTF("hashtable: entry timed out, discarding it; "
406 "valid until %lld, now %lld\n",
407 (long long) etry->validUntil, (long long) time(NULL));
408 dnscache_entry_t *const deleted = hashtable_remove(dnsCache.ht, addr);
409 if(deleted != etry) {
410 LogError(0, RS_RET_INTERNAL_ERROR, "dnscache %d: removed different "
411 "hashtable entry than expected - please report issue; "
412 "rsyslog version is %s", __LINE__, VERSION);
413 }
414 entryDestruct(etry);
415 }
416 /* now entry doesn't exist in any case, so let's (re)create it */
417 CHKiRet(addEntry(addr, &etry));
418 }
419 }
420
421 prop.AddRef(etry->ip);
422 *ip = etry->ip;
423 if(fqdn != NULL) {
424 prop.AddRef(etry->fqdn);
425 *fqdn = etry->fqdn;
426 }
427 if(fqdnLowerCase != NULL) {
428 prop.AddRef(etry->fqdnLowerCase);
429 *fqdnLowerCase = etry->fqdnLowerCase;
430 }
431 if(localName != NULL) {
432 prop.AddRef(etry->localName);
433 *localName = etry->localName;
434 }
435
436 finalize_it:
437 pthread_rwlock_unlock(&dnsCache.rwlock);
438 RETiRet;
439 }
440
441
442 /* This is the main function: it looks up an entry and returns it's name
443 * and IP address. If the entry is not yet inside the cache, it is added.
444 * If the entry can not be resolved, an error is reported back. If fqdn
445 * or fqdnLowerCase are NULL, they are not set.
446 */
447 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)448 dnscacheLookup(struct sockaddr_storage *const addr,
449 prop_t **const fqdn, prop_t **const fqdnLowerCase,
450 prop_t **const localName, prop_t **const ip)
451 {
452 DEFiRet;
453
454 iRet = findEntry(addr, fqdn, fqdnLowerCase, localName, ip);
455
456 if(iRet != RS_RET_OK) {
457 DBGPRINTF("dnscacheLookup failed with iRet %d\n", iRet);
458 prop.AddRef(staticErrValue);
459 *ip = staticErrValue;
460 if(fqdn != NULL) {
461 prop.AddRef(staticErrValue);
462 *fqdn = staticErrValue;
463 }
464 if(fqdnLowerCase != NULL) {
465 prop.AddRef(staticErrValue);
466 *fqdnLowerCase = staticErrValue;
467 }
468 if(localName != NULL) {
469 prop.AddRef(staticErrValue);
470 *localName = staticErrValue;
471 }
472 }
473 RETiRet;
474 }
475