1 /*
2  *    jnettop, network online traffic visualiser
3  *    Copyright (C) 2002-2005 Jakub Skopal
4  *
5  *    This program is free software; you can redistribute it and/or modify
6  *    it under the terms of the GNU General Public License as published by
7  *    the Free Software Foundation; either version 2 of the License, or
8  *    (at your option) any later version.
9  *
10  *    This program is distributed in the hope that it will be useful,
11  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *    GNU General Public License for more details.
14  *
15  *    You should have received a copy of the GNU General Public License
16  *    along with this program; if not, write to the Free Software
17  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  *    $Header: /cvsroot/jnettop/jnettop/jresolver.c,v 1.5 2006/04/12 07:47:01 merunka Exp $
20  *
21  */
22 
23 #include "jbase.h"
24 #include "jutil.h"
25 #include "jresolver.h"
26 
27 gboolean	jresolver_IsEnabled;
28 
29 GHashTable	*resolverCache;
30 GMutex		*resolverCacheMutex;
31 GThreadPool	*resolverThreadPool;
32 
33 GMutex		*gethostbyaddrMutex;
34 
35 GPtrArray	*resolverTypes;
36 
37 ResolvedNotifyFunc	jresolver_ResolvedNotifyFunc;
38 
jresolver_Lookup(int af,const jbase_mutableaddress * address)39 jbase_resolv_entry *jresolver_Lookup(int af, const jbase_mutableaddress *address) {
40 	jbase_resolv_entry key;
41 	jbase_resolv_entry *rentry;
42 
43 	memcpy(&key.addr, address, sizeof(jbase_mutableaddress));
44 	key.name = NULL;
45 	key.af = af;
46 	g_mutex_lock(resolverCacheMutex);
47 	rentry = g_hash_table_lookup(resolverCache, &key);
48 	if (rentry == NULL) {
49 		rentry = g_new0(jbase_resolv_entry, 1);
50 		memcpy(rentry, &key, sizeof(key));
51 		g_hash_table_insert(resolverCache, rentry, rentry);
52 		g_mutex_unlock(resolverCacheMutex);
53 		if (jresolver_IsEnabled)
54 			g_thread_pool_push(resolverThreadPool, rentry, NULL);
55 	} else {
56 		g_mutex_unlock(resolverCacheMutex);
57 	}
58 
59 	return rentry;
60 }
61 
resolveNormal(jbase_resolv_entry * entry)62 static gboolean resolveNormal(jbase_resolv_entry *entry) {
63 	gchar buffer[4096];
64 	struct hostent shentry, *hentry;
65 	int  e, ret, size;
66 	gchar *name;
67 	gboolean found = FALSE;
68 
69 #if !HAVE_GETHOSTBYADDR_R_8 && !HAVE_GETHOSTBYADDR_7
70 	g_mutex_lock(gethostbyaddrMutex);
71 #endif
72 	ret = 0; e=0;
73 	size = entry->af == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr);
74 #if HAVE_GETHOSTBYADDR_R_8
75 	ret = gethostbyaddr_r(&entry->addr, size, entry->af, &shentry, buffer, 4096, &hentry, &e);
76 #elif HAVE_GETHOSTBYADDR_R_7
77 	hentry = gethostbyaddr_r(&entry->addr, size, entry->af, &shentry, buffer, 4096, &e);
78 #else
79 	hentry = gethostbyaddr(&entry->addr, size, entry->af);
80 #endif
81 	if (!hentry || ret || e) {
82 		goto resolverfailed;
83 	}
84 	name = g_strdup(hentry->h_name);
85 	entry->name = name;
86 	found = TRUE;
87 resolverfailed:
88 #if !HAVE_GETHOSTBYADDR_R_8 && !HAVE_GETHOSTBYADDR_7
89 	g_mutex_unlock(gethostbyaddrMutex);
90 #else
91 	name = name; // dummy to avoid deprecated warning on linux
92 #endif
93 	return found;
94 }
95 
resolveExternal(char * lookupScriptPath,jbase_resolv_entry * entry)96 static gboolean resolveExternal(char *lookupScriptPath, jbase_resolv_entry *entry) {
97 	gchar *outname;
98 	gint outstatus;
99 	gchar **argv = (gchar **) g_new0(gchar *, 2);
100 	gchar *buffer = (gchar *) g_new0(gchar, 256);
101 	jutil_Address2String(entry->af, &entry->addr, buffer, 256);
102 	argv[0] = lookupScriptPath;
103 	argv[1] = buffer;
104 	if (g_spawn_sync(NULL, argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &outname, NULL, &outstatus, NULL)) {
105 		if (!WEXITSTATUS(outstatus)) {
106 			g_strdelimit(outname, "\r\n\t", '\0');
107 			if (strlen(outname) > 0) {
108 				entry->name = outname;
109 				return TRUE;
110 			}
111 			// strip name
112 		}
113 	}
114 	g_free(buffer);
115 	return FALSE;
116 }
117 
118 #ifdef SUPPORT_DB4
119 
120 DB *cacheDb = NULL;
121 
initializeCache()122 static gboolean initializeCache() {
123 	int err;
124 	if ((err = db_create(&cacheDb, NULL, 0))) {
125 		debug(LOG_NOTICE, "db_create: Cannot create database environment (%s)", db_strerror(err));
126 		goto initialize_cleanup;
127 	}
128 	if ((err = cacheDb->set_alloc(cacheDb, (void*(*)(size_t))g_malloc, (void*(*)(void*,size_t))g_realloc, (void(*)(void*))g_free))) {
129 		debug(LOG_NOTICE, "db->set_alloc: Cannot set allocation functions to DB4 (%s)", db_strerror(err));
130 		goto initialize_cleanup;
131 	}
132 	if ((err = cacheDb->open(cacheDb, NULL, "/var/cache/jnettop/dns.cache", NULL, DB_HASH, DB_AUTO_COMMIT || DB_CREATE || DB_THREAD, 0))) {
133 		debug(LOG_NOTICE, "db->open: Cannot open/create database (%s)", db_strerror(err));
134 		goto initialize_cleanup;
135 	}
136 
137 	return TRUE;
138 
139 initialize_cleanup:
140 	if (cacheDb != NULL) {
141 		cacheDb->close(cacheDb, 0);
142 		cacheDb = NULL;
143 	}
144 	return FALSE;
145 }
146 
resolveFromCache(jbase_resolv_entry * entry)147 static gboolean resolveFromCache(jbase_resolv_entry *entry) {
148 	int err;
149 	DBT keyThang, valueThang;
150 
151 	if (cacheDb == NULL)
152 		return FALSE;
153 
154 	memset(&keyThang, '\0', sizeof(DBT));
155 	memset(&valueThang, '\0', sizeof(DBT));
156 
157 	keyThang.data = &entry->addr;
158 	keyThang.size = JBASE_AF_SIZE(entry->af);
159 	keyThang.ulen = keyThang.size;
160 
161 	valueThang.data = NULL;
162 	valueThang.flags = DB_DBT_MALLOC;
163 
164 	err = cacheDb->get(cacheDb, NULL, &keyThang, &valueThang, 0);
165 
166 	if (err == DB_NOTFOUND) {
167 		return FALSE;
168 	}
169 
170 	if (err) {
171 		debug(LOG_NOTICE, "db->get: Cannot get record from database (%s)", db_strerror(err));
172 		return FALSE;
173 	}
174 
175 	entry->name = (gchar *)valueThang.data;
176 	debug(LOG_DEBUG, "resolved from cache: %s", entry->name);
177 	return TRUE;
178 }
179 
storeToCache(jbase_resolv_entry * entry)180 static void storeToCache(jbase_resolv_entry *entry) {
181 	int err;
182 	DBT keyThang, valueThang;
183 
184 	if (cacheDb == NULL)
185 		return;
186 
187 	memset(&keyThang, '\0', sizeof(DBT));
188 	memset(&valueThang, '\0', sizeof(DBT));
189 
190 	keyThang.data = &entry->addr;
191 	keyThang.size = JBASE_AF_SIZE(entry->af);
192 	keyThang.ulen = keyThang.size;
193 
194 	valueThang.data = g_strdup(entry->name);
195 	valueThang.size = strlen(entry->name) + 1;
196 	valueThang.ulen = valueThang.size;
197 
198 	if ((err = cacheDb->put(cacheDb, NULL, &keyThang, &valueThang, 0))) {
199 		debug(LOG_NOTICE, "db->put: Cannot put record to database (%s)", db_strerror(err));
200 	}
201 
202 	g_free(valueThang.data);
203 }
204 
shutdownCache()205 static void shutdownCache() {
206 	int err;
207 
208 	if (cacheDb == NULL)
209 		return;
210 
211 	if ((err = cacheDb->close(cacheDb, 0))) {
212 		debug(LOG_NOTICE, "db->close: Cannot close database (%s)", db_strerror(err));
213 	}
214 
215 	cacheDb = NULL;
216 }
217 
218 #else
219 
initializeCache()220 static gboolean initializeCache() {
221 	return FALSE;
222 }
223 
resolveFromCache(jbase_resolv_entry * entry)224 static gboolean resolveFromCache(jbase_resolv_entry *entry) {
225 	return FALSE;
226 }
227 
storeToCache(jbase_resolv_entry * entry)228 static void storeToCache(jbase_resolv_entry *entry) {
229 }
230 
shutdownCache()231 static void shutdownCache() {
232 }
233 
234 #endif
235 
resolverThreadFunc(gpointer task,gpointer user_data)236  void resolverThreadFunc(gpointer task, gpointer user_data) {
237 	int i;
238 	jbase_resolv_entry *entry = (jbase_resolv_entry *)task;
239 
240 	if (resolveFromCache(entry)) {
241 		goto resolver_done_nocache;
242 	}
243 
244 	for (i=0; i<resolverTypes->len; i++) {
245 		jresolver_resolvertype *type = (jresolver_resolvertype *)g_ptr_array_index(resolverTypes, i);
246 
247 		if (jutil_IsInNetwork(&entry->addr, entry->af, &type->value, &type->mask, type->af)) {
248 			switch (type->lookupType) {
249 				case LOOKUPTYPE_EXTERNAL:
250 					if (resolveExternal(type->externalLookupScript, entry))
251 						goto resolver_done;
252 					break;
253 				case LOOKUPTYPE_NORMAL:
254 					if (resolveNormal(entry))
255 						goto resolver_done;
256 					break;
257 			}
258 		}
259 	}
260 
261 	resolveNormal(entry);
262 
263 resolver_done:
264 	if (entry->name != NULL) {
265 		storeToCache(entry);
266 	}
267 
268 resolver_done_nocache:
269 	if (entry->name != NULL && jresolver_ResolvedNotifyFunc != NULL) {
270 		jresolver_ResolvedNotifyFunc(entry);
271 	}
272 }
273 
addZeroResolves()274 static void addZeroResolves() {
275 	jbase_resolv_entry *entry;
276 	entry = g_new0(jbase_resolv_entry, 1);
277 	entry->name = "UNKNOWNv4";
278 	entry->af = AF_INET;
279 	entry->addr.addr4.s_addr = 0x0;
280 	g_hash_table_insert(resolverCache, entry, entry);
281 	entry = g_new0(jbase_resolv_entry, 1);
282 	entry->name = "UNKNOWNv6";
283 	entry->af = AF_INET6;
284 	entry->addr.addr6.ntop_s6_addr32[0] = 0x0;
285 	entry->addr.addr6.ntop_s6_addr32[1] = 0x0;
286 	entry->addr.addr6.ntop_s6_addr32[2] = 0x0;
287 	entry->addr.addr6.ntop_s6_addr32[3] = 0x0;
288 	g_hash_table_insert(resolverCache, entry, entry);
289 	entry = g_new0(jbase_resolv_entry, 1);
290 	entry->name = "AGGREGATEDv4";
291 	entry->af = AF_INET;
292 	entry->addr.addr4.s_addr = htonl(0x01000000);
293 	g_hash_table_insert(resolverCache, entry, entry);
294 	entry = g_new0(jbase_resolv_entry, 1);
295 	entry->name = "AGGREGATEDv6";
296 	entry->af = AF_INET6;
297 	entry->addr.addr6.ntop_s6_addr32[0] = 0x0;
298 	entry->addr.addr6.ntop_s6_addr32[1] = 0x0;
299 	entry->addr.addr6.ntop_s6_addr32[2] = 0x0;
300 	entry->addr.addr6.ntop_s6_addr32[3] = htonl(0x01000000);
301 	g_hash_table_insert(resolverCache, entry, entry);
302 }
303 
hashResolvEntry(gconstpointer key)304 static guint hashResolvEntry(gconstpointer key) {
305 	const jbase_resolv_entry *resolv = key;
306 	guint hash = 0;
307 	hash = resolv->addr.addr6.ntop_s6_addr32[0];
308 	hash ^= resolv->addr.addr6.ntop_s6_addr32[1];
309 	hash ^= resolv->addr.addr6.ntop_s6_addr32[2];
310 	hash ^= resolv->addr.addr6.ntop_s6_addr32[3];
311 	return hash;
312 }
313 
compareResolvEntry(gconstpointer a,gconstpointer b)314 static gboolean compareResolvEntry(gconstpointer a, gconstpointer b) {
315 	jbase_resolv_entry	*aa, *bb;
316 	if(a == b) return 1;
317 	aa = (jbase_resolv_entry *) a;
318 	bb = (jbase_resolv_entry *) b;
319 	if(aa->af != bb->af)
320 		return 0;
321 	return !memcmp(&aa->addr, &bb->addr, sizeof(aa->addr));
322 }
323 
jresolver_AddExternalLookupScript(int af,const jbase_mutableaddress * mask,const jbase_mutableaddress * value,char * lookupScriptName)324 void jresolver_AddExternalLookupScript(int af, const jbase_mutableaddress *mask, const jbase_mutableaddress *value, char *lookupScriptName) {
325 	jresolver_resolvertype *type = g_new0(jresolver_resolvertype, 1);
326 	type->af = af;
327 	memcpy(&type->mask, mask, sizeof(jbase_mutableaddress));
328 	memcpy(&type->value, value, sizeof(jbase_mutableaddress));
329 	type->lookupType = LOOKUPTYPE_EXTERNAL;
330 	type->externalLookupScript = lookupScriptName;
331 	g_ptr_array_add(resolverTypes, type);
332 }
333 
jresolver_AddNormalLookup(int af,const jbase_mutableaddress * mask,const jbase_mutableaddress * value)334 void jresolver_AddNormalLookup(int af, const jbase_mutableaddress *mask, const jbase_mutableaddress *value) {
335 	jresolver_resolvertype *type = g_new0(jresolver_resolvertype, 1);
336 	type->af = af;
337 	memcpy(&type->mask, mask, sizeof(jbase_mutableaddress));
338 	memcpy(&type->value, value, sizeof(jbase_mutableaddress));
339 	type->lookupType = LOOKUPTYPE_NORMAL;
340 	g_ptr_array_add(resolverTypes, type);
341 }
342 
jresolver_SetEnabled(gboolean isEnabled)343 void jresolver_SetEnabled(gboolean isEnabled) {
344 	jresolver_IsEnabled = isEnabled;
345 }
346 
jresolver_Setup()347 gboolean jresolver_Setup() {
348 	resolverCache = g_hash_table_new((GHashFunc)hashResolvEntry, (GEqualFunc)compareResolvEntry);
349 	resolverCacheMutex = g_mutex_new();
350 	resolverThreadPool = g_thread_pool_new((GFunc)resolverThreadFunc, NULL, 5, FALSE, NULL);
351 	resolverTypes = g_ptr_array_new();
352 	gethostbyaddrMutex = g_mutex_new();
353 
354 	jresolver_IsEnabled = TRUE;
355 
356 	addZeroResolves();
357 	return TRUE;
358 }
359 
jresolver_SetResolvedNotifyFunc(ResolvedNotifyFunc resolvedNotifyFunction)360 void jresolver_SetResolvedNotifyFunc(ResolvedNotifyFunc resolvedNotifyFunction) {
361 	jresolver_ResolvedNotifyFunc = resolvedNotifyFunction;
362 }
363 
jresolver_Initialize()364 void jresolver_Initialize() {
365 	initializeCache();
366 }
367 
jresolver_Shutdown()368 void jresolver_Shutdown() {
369 	shutdownCache();
370 }
371