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