Get rid of TestBigEndian and AC_C_BIGENDIAN.
[metze/wireshark/wip.git] / epan / maxmind_db.c
1 /* maxmind_db.c
2  * GeoIP database support
3  *
4  * Copyright 2018, Gerald Combs <gerald@wireshark.org>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13 #include "config.h"
14
15 #include <glib.h>
16
17 #include <epan/maxmind_db.h>
18
19 static mmdb_lookup_t mmdb_not_found;
20
21 #ifdef HAVE_MAXMINDDB
22
23 #include <stdio.h>
24 #include <errno.h>
25
26 #include <epan/wmem/wmem.h>
27
28 #include <epan/addr_resolv.h>
29 #include <epan/uat.h>
30 #include <epan/prefs.h>
31
32 #include <wsutil/report_message.h>
33 #include <wsutil/file_util.h>
34 #include <wsutil/filesystem.h>
35 #include <wsutil/ws_pipe.h>
36 #include <wsutil/strtoi.h>
37
38 // To do:
39 // - If we can't reliably do non-blocking reads, move process_mmdbr_stdout to a worker thread.
40 // - Add RBL lookups? Along with the "is this a spammer" information that most RBL databases
41 //   provide, you can also fetch AS information: http://www.team-cymru.org/IP-ASN-mapping.html
42 // - Switch to a different format? I was going to use g_key_file_* to parse
43 //   the mmdbresolve output, but it was easier to just parse it directly.
44
45 // Hashes of mmdb_lookup_t
46 static wmem_map_t *mmdb_ipv4_map;
47 static wmem_map_t *mmdb_ipv6_map;
48
49 // Interned strings
50 static wmem_map_t *mmdb_str_chunk;
51 static wmem_map_t *mmdb_ipv6_chunk;
52
53 /* Child mmdbresolve process */
54 static char cur_addr[WS_INET6_ADDRSTRLEN];
55 static mmdb_lookup_t cur_lookup;
56 static ws_pipe_t mmdbr_pipe;
57 static FILE *mmdbr_stdout;
58
59 /* UAT definitions. Copied from oids.c */
60 typedef struct _maxmind_db_path_t {
61     char* path;
62 } maxmind_db_path_t;
63
64 static maxmind_db_path_t *maxmind_db_paths;
65 static guint num_maxmind_db_paths;
66 static const maxmind_db_path_t maxmind_db_system_paths[] = {
67 #ifdef _WIN32
68     // XXX Properly expand "%ProgramData%\GeoIP".
69     { "C:\\ProgramData\\GeoIP" },
70     { "C:\\GeoIP" },
71 #else
72     { "/usr/share/GeoIP" },
73     { "/var/lib/GeoIP" },
74 #endif
75     { NULL }
76 };
77 static uat_t *maxmind_db_paths_uat;
78 UAT_DIRECTORYNAME_CB_DEF(maxmind_mod, path, maxmind_db_path_t)
79
80 static GPtrArray *mmdb_file_arr; // .mmdb files
81
82 #if 0
83 #define MMDB_DEBUG(...) { \
84     char *MMDB_DEBUG_MSG = g_strdup_printf(__VA_ARGS__); \
85     g_warning("mmdb: %s:%d %s", G_STRFUNC, __LINE__, MMDB_DEBUG_MSG); \
86     g_free(MMDB_DEBUG_MSG); \
87 }
88 #else
89 #define MMDB_DEBUG(...)
90 #endif
91
92 static void mmdb_resolve_stop(void);
93
94 // Hopefully scanning a few lines asynchronously has less overhead than
95 // reading in a child thread.
96 #define RES_STATUS_ERROR        "mmdbresolve.status: false"
97 #define RES_COUNTRY_ISO_CODE    "country.iso_code"
98 #define RES_COUNTRY_NAMES_EN    "country.names.en"
99 #define RES_CITY_NAMES_EN       "city.names.en"
100 #define RES_ASN_ORG             "autonomous_system_organization"
101 #define RES_ASN_NUMBER          "autonomous_system_number"
102 #define RES_LOCATION_LATITUDE   "location.latitude"
103 #define RES_LOCATION_LONGITUDE  "location.longitude"
104 #define RES_END                 "# End "
105
106 // Interned strings and v6 addresses, similar to GLib's string chunks.
107 static const char *chunkify_string(char *key) {
108     key = g_strstrip(key);
109     char *chunk_string = (char *) wmem_map_lookup(mmdb_str_chunk, key);
110
111     if (!chunk_string) {
112         chunk_string = wmem_strdup(wmem_epan_scope(), key);
113         wmem_map_insert(mmdb_str_chunk, chunk_string, chunk_string);
114     }
115
116     return chunk_string;
117 }
118
119 static const void *chunkify_v6_addr(const ws_in6_addr *addr) {
120     void *chunk_v6_bytes = (char *) wmem_map_lookup(mmdb_ipv6_chunk, addr->bytes);
121
122     if (!chunk_v6_bytes) {
123         chunk_v6_bytes = wmem_memdup(wmem_epan_scope(), addr->bytes, sizeof(ws_in6_addr));
124         wmem_map_insert(mmdb_ipv6_chunk, chunk_v6_bytes, chunk_v6_bytes);
125     }
126
127     return chunk_v6_bytes;
128 }
129
130 static gboolean
131 process_mmdbr_stdout(void) {
132
133     int read_buf_size = 2048;
134     char *read_buf = (char *) g_malloc(read_buf_size);
135     gboolean new_entries = FALSE;
136
137     MMDB_DEBUG("start %d", ws_pipe_data_available(mmdbr_pipe.stdout_fd));
138
139     while (ws_pipe_data_available(mmdbr_pipe.stdout_fd)) {
140         read_buf[0] = '\0';
141         char *line = fgets(read_buf, read_buf_size, mmdbr_stdout);
142         if (!line || ferror(mmdbr_stdout)) {
143             MMDB_DEBUG("read error %s", g_strerror(errno));
144             mmdb_resolve_stop();
145             break;
146         }
147
148         line = g_strstrip(line);
149         size_t line_len = strlen(line);
150         MMDB_DEBUG("read %zd bytes, feof %d: %s", line_len, feof(mmdbr_stdout), line);
151         if (line_len < 1) continue;
152
153         char *val_start = strchr(line, ':');
154         if (val_start) val_start++;
155
156         if (line[0] == '[' && line_len > 2) {
157             // [init] or resolved address in square brackets.
158             line[line_len - 1] = '\0';
159             g_strlcpy(cur_addr, line + 1, WS_INET6_ADDRSTRLEN);
160             memset(&cur_lookup, 0, sizeof(cur_lookup));
161         } else if (strcmp(line, RES_STATUS_ERROR) == 0) {
162             // Error during init.
163             cur_addr[0] = '\0';
164             memset(&cur_lookup, 0, sizeof(cur_lookup));
165             mmdb_resolve_stop();
166         } else if (val_start && g_str_has_prefix(line, RES_COUNTRY_ISO_CODE)) {
167             cur_lookup.found = TRUE;
168             cur_lookup.country_iso = chunkify_string(val_start);
169         } else if (val_start && g_str_has_prefix(line, RES_COUNTRY_NAMES_EN)) {
170             cur_lookup.found = TRUE;
171             cur_lookup.country = chunkify_string(val_start);
172         } else if (val_start && g_str_has_prefix(line, RES_CITY_NAMES_EN)) {
173             cur_lookup.found = TRUE;
174             cur_lookup.city = chunkify_string(val_start);
175         } else if (val_start && g_str_has_prefix(line, RES_ASN_ORG)) {
176             cur_lookup.found = TRUE;
177             cur_lookup.as_org = chunkify_string(val_start);
178         } else if (val_start && g_str_has_prefix(line, RES_ASN_NUMBER)) {
179             if (ws_strtou32(val_start, NULL, &cur_lookup.as_number)) {
180                 cur_lookup.found = TRUE;
181             } else {
182                 MMDB_DEBUG("Invalid as number: %s", val_start);
183             }
184         } else if (val_start && g_str_has_prefix(line, RES_LOCATION_LATITUDE)) {
185             cur_lookup.found = TRUE;
186             cur_lookup.latitude = g_ascii_strtod(val_start, NULL);
187         } else if (val_start && g_str_has_prefix(line, RES_LOCATION_LONGITUDE)) {
188             cur_lookup.found = TRUE;
189             cur_lookup.longitude = g_ascii_strtod(val_start, NULL);
190         } else if (g_str_has_prefix(line, RES_END)) {
191             if (cur_lookup.found) {
192                 mmdb_lookup_t *mmdb_val = (mmdb_lookup_t *) wmem_memdup(wmem_epan_scope(), &cur_lookup, sizeof(cur_lookup));
193                 if (strstr(cur_addr, ".")) {
194                     MMDB_DEBUG("inserting v4 %p %s: city %s country %s", (void *) mmdb_val, cur_addr, mmdb_val->city, mmdb_val->country);
195                     guint32 addr;
196                     ws_inet_pton4(cur_addr, &addr);
197                     wmem_map_insert(mmdb_ipv4_map, GUINT_TO_POINTER(addr), mmdb_val);
198                     new_entries = TRUE;
199                 } else if (strstr(cur_addr, ":")) {
200                     MMDB_DEBUG("inserting v6 %p %s: city %s country %s", (void *) mmdb_val, cur_addr, mmdb_val->city, mmdb_val->country);
201                     ws_in6_addr addr;
202                     ws_inet_pton6(cur_addr, &addr);
203                     wmem_map_insert(mmdb_ipv6_map, chunkify_v6_addr(&addr), mmdb_val);
204                     new_entries = TRUE;
205                 }
206             }
207             cur_addr[0] = '\0';
208             memset(&cur_lookup, 0, sizeof(cur_lookup));
209         }
210     }
211
212     g_free(read_buf);
213     return new_entries;
214 }
215
216 /**
217  * Stop our mmdbresolve process.
218  */
219 static void mmdb_resolve_stop(void) {
220     if (!ws_pipe_valid(&mmdbr_pipe)) {
221         MMDB_DEBUG("not cleaning up, invalid PID %d", mmdbr_pipe.pid);
222         return;
223     }
224
225     ws_close(mmdbr_pipe.stdin_fd);
226     fclose(mmdbr_stdout);
227     MMDB_DEBUG("closing pid %d", mmdbr_pipe.pid);
228     g_spawn_close_pid(mmdbr_pipe.pid);
229     mmdbr_pipe.pid = WS_INVALID_PID;
230     mmdbr_stdout = NULL;
231 }
232
233 /**
234  * Start an mmdbresolve process.
235  */
236 static void mmdb_resolve_start(void) {
237     if (!mmdb_ipv4_map) {
238         mmdb_ipv4_map = wmem_map_new(wmem_epan_scope(), g_direct_hash, g_direct_equal);
239     }
240     if (!mmdb_ipv6_map) {
241         mmdb_ipv6_map = wmem_map_new(wmem_epan_scope(), ipv6_oat_hash, ipv6_equal);
242     }
243
244     if (!mmdb_str_chunk) {
245         mmdb_str_chunk = wmem_map_new(wmem_epan_scope(), wmem_str_hash, g_str_equal);
246     }
247
248     if (!mmdb_ipv6_chunk) {
249         mmdb_ipv6_chunk = wmem_map_new(wmem_epan_scope(), ipv6_oat_hash, ipv6_equal);
250     }
251
252     if (!mmdb_file_arr) {
253         MMDB_DEBUG("unexpected mmdb_file_arr == NULL");
254         return;
255     }
256
257     mmdb_resolve_stop();
258
259     if (mmdb_file_arr->len == 0) {
260         MMDB_DEBUG("no GeoIP databases found");
261         return;
262     }
263
264     GPtrArray *args = g_ptr_array_new();
265     char *mmdbresolve = g_strdup_printf("%s%c%s", get_progfile_dir(), G_DIR_SEPARATOR, "mmdbresolve");
266     g_ptr_array_add(args, mmdbresolve);
267     for (guint i = 0; i < mmdb_file_arr->len; i++) {
268         g_ptr_array_add(args, g_strdup("-f"));
269         g_ptr_array_add(args, g_strdup((const gchar *)g_ptr_array_index(mmdb_file_arr, i)));
270     }
271     g_ptr_array_add(args, NULL);
272
273     ws_pipe_init(&mmdbr_pipe);
274     mmdbr_stdout = NULL;
275     GPid pipe_pid = ws_pipe_spawn_async(&mmdbr_pipe, args);
276     MMDB_DEBUG("spawned %s pid %d", mmdbresolve, pipe_pid);
277
278     for (guint i = 0; i < args->len; i++) {
279         char *arg = (char *)g_ptr_array_index(args, i);
280         MMDB_DEBUG("args: %s", arg);
281         g_free(arg);
282     }
283     g_ptr_array_free(args, TRUE);
284
285     if (pipe_pid == WS_INVALID_PID) {
286         ws_pipe_init(&mmdbr_pipe);
287         return;
288     }
289
290     // XXX Should we set O_NONBLOCK similar to dumpcap?
291     mmdbr_stdout = ws_fdopen(mmdbr_pipe.stdout_fd, "r");
292     setvbuf(mmdbr_stdout, NULL, _IONBF, 0);
293
294     // [init]
295     process_mmdbr_stdout();
296 }
297
298 /**
299  * Scan a directory for GeoIP databases and load them
300  */
301 static void
302 maxmind_db_scan_dir(const char *dirname) {
303     WS_DIR *dir;
304     WS_DIRENT *file;
305
306     if ((dir = ws_dir_open(dirname, 0, NULL)) != NULL) {
307         while ((file = ws_dir_read_name(dir)) != NULL) {
308             const char *name = ws_dir_get_name(file);
309             if (g_str_has_suffix(file, ".mmdb")) {
310                 char *datname = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dirname, name);
311                 FILE *mmdb_f = ws_fopen(datname, "r");
312                 if (mmdb_f) {
313                     g_ptr_array_add(mmdb_file_arr, datname);
314                     fclose(mmdb_f);
315                 } else {
316                     g_free(datname);
317                 }
318             }
319         }
320         ws_dir_close (dir);
321     }
322 }
323
324 /* UAT callbacks */
325 static void* maxmind_db_path_copy_cb(void* dest, const void* orig, size_t len _U_) {
326     const maxmind_db_path_t *m = (const maxmind_db_path_t *)orig;
327     maxmind_db_path_t *d = (maxmind_db_path_t *)dest;
328
329     d->path = g_strdup(m->path);
330
331     return d;
332 }
333
334 static void maxmind_db_path_free_cb(void* p) {
335     maxmind_db_path_t *m = (maxmind_db_path_t *)p;
336     g_free(m->path);
337 }
338
339 static void maxmind_db_cleanup(void) {
340     guint i;
341
342     mmdb_resolve_stop();
343
344     /* If we have old data, clear out the whole thing
345      * and start again. TODO: Just update the ones that
346      * have changed for efficiency's sake. */
347     if (mmdb_file_arr) {
348         for (i = 0; i < mmdb_file_arr->len; i++) {
349             g_free(g_ptr_array_index(mmdb_file_arr, i));
350         }
351         /* finally, free the array itself */
352         g_ptr_array_free(mmdb_file_arr, TRUE);
353         mmdb_file_arr = NULL;
354     }
355 }
356
357 /* called every time the user presses "Apply" or "OK in the list of
358  * GeoIP directories, and also once on startup */
359 static void maxmind_db_post_update_cb(void) {
360     guint i;
361
362     maxmind_db_cleanup();
363
364     /* allocate the array */
365     mmdb_file_arr = g_ptr_array_new();
366
367     /* First try the system paths */
368     for (i = 0; maxmind_db_system_paths[i].path != NULL; i++) {
369         maxmind_db_scan_dir(maxmind_db_system_paths[i].path);
370     }
371
372     /* Walk all the directories */
373     for (i = 0; i < num_maxmind_db_paths; i++) {
374         if (maxmind_db_paths[i].path) {
375             maxmind_db_scan_dir(maxmind_db_paths[i].path);
376         }
377     }
378
379     mmdb_resolve_start();
380 }
381
382 /**
383  * Initialize GeoIP lookups
384  */
385 void
386 maxmind_db_pref_init(module_t *nameres)
387 {
388     static uat_field_t maxmind_db_paths_fields[] = {
389         UAT_FLD_DIRECTORYNAME(maxmind_mod, path, "MaxMind Database Directory", "The MaxMind database directory path"),
390         UAT_END_FIELDS
391     };
392
393     maxmind_db_paths_uat = uat_new("MaxMind Database Paths",
394             sizeof(maxmind_db_path_t),
395             "maxmind_db_paths",
396             FALSE, // Global, not per-profile
397             (void**)&maxmind_db_paths,
398             &num_maxmind_db_paths,
399             UAT_AFFECTS_DISSECTION, // Affects IP4 and IPv6 packets.
400             "ChMaxMindDbPaths",
401             maxmind_db_path_copy_cb,
402             NULL, // update_cb
403             maxmind_db_path_free_cb,
404             maxmind_db_post_update_cb,
405             maxmind_db_cleanup,
406             maxmind_db_paths_fields);
407
408     prefs_register_uat_preference(nameres,
409             "maxmind_db_paths",
410             "MaxMind database directories",
411             "Search paths for MaxMind address mapping databases."
412             " Wireshark will look in each directory for files ending"
413             " with \".mmdb\".",
414             maxmind_db_paths_uat);
415 }
416
417 void maxmind_db_pref_cleanup(void)
418 {
419     mmdb_resolve_stop();
420 }
421
422 /**
423  * Public API
424  */
425
426 gboolean maxmind_db_lookup_process(void)
427 {
428     if (!ws_pipe_valid(&mmdbr_pipe)) return FALSE;
429
430     return process_mmdbr_stdout();
431 }
432
433 const mmdb_lookup_t *
434 maxmind_db_lookup_ipv4(guint32 addr) {
435     mmdb_lookup_t *result = (mmdb_lookup_t *) wmem_map_lookup(mmdb_ipv4_map, GUINT_TO_POINTER(addr));
436
437     // XXX Should we call maxmind_db_lookup_process first?
438     if (!result) {
439         if (ws_pipe_valid(&mmdbr_pipe)) {
440             char addr_str[WS_INET_ADDRSTRLEN + 1];
441             ws_inet_ntop4(&addr, addr_str, WS_INET_ADDRSTRLEN);
442             MMDB_DEBUG("looking up %s", addr_str);
443             g_strlcat(addr_str, "\n", (gsize) sizeof(addr_str));
444             ssize_t write_status = ws_write(mmdbr_pipe.stdin_fd, addr_str, (unsigned int)strlen(addr_str));
445             if (write_status < 0) {
446                 MMDB_DEBUG("write error %s", g_strerror(errno));
447                 mmdb_resolve_stop();
448             }
449         }
450
451         result = &mmdb_not_found;
452         wmem_map_insert(mmdb_ipv4_map, GUINT_TO_POINTER(addr), result);
453     }
454
455     return result;
456 }
457
458 const mmdb_lookup_t *
459 maxmind_db_lookup_ipv6(const ws_in6_addr *addr) {
460     mmdb_lookup_t * result = (mmdb_lookup_t *) wmem_map_lookup(mmdb_ipv6_map, addr->bytes);
461
462     // XXX Should we call maxmind_db_lookup_process first?
463     if (!result) {
464         if (ws_pipe_valid(&mmdbr_pipe)) {
465             char addr_str[WS_INET6_ADDRSTRLEN + 1];
466             ws_inet_ntop6(addr, addr_str, WS_INET6_ADDRSTRLEN);
467             MMDB_DEBUG("looking up %s", addr_str);
468             g_strlcat(addr_str, "\n", (gsize) sizeof(addr_str));
469             ssize_t write_status = ws_write(mmdbr_pipe.stdin_fd, addr_str, (unsigned int)strlen(addr_str));
470             if (write_status < 0) {
471                 MMDB_DEBUG("write error %s", g_strerror(errno));
472                 mmdb_resolve_stop();
473             }
474         }
475
476         result = &mmdb_not_found;
477         wmem_map_insert(mmdb_ipv6_map, chunkify_v6_addr(addr), result);
478     }
479
480     return result;
481 }
482
483 gchar *
484 maxmind_db_get_paths(void) {
485     GString* path_str = NULL;
486     guint i;
487
488     path_str = g_string_new("");
489
490     for (i = 0; maxmind_db_system_paths[i].path != NULL; i++) {
491         g_string_append_printf(path_str,
492                 "%s" G_SEARCHPATH_SEPARATOR_S, maxmind_db_system_paths[i].path);
493     }
494
495     for (i = 0; i < num_maxmind_db_paths; i++) {
496         if (maxmind_db_paths[i].path) {
497             g_string_append_printf(path_str,
498                     "%s" G_SEARCHPATH_SEPARATOR_S, maxmind_db_paths[i].path);
499         }
500     }
501
502     g_string_truncate(path_str, path_str->len-1);
503
504     return g_string_free(path_str, FALSE);
505 }
506
507 #else // HAVE_MAXMINDDB
508
509 void
510 maxmind_db_pref_init(module_t *nameres _U_) {}
511
512 void
513 maxmind_db_pref_cleanup(void) {}
514
515
516 gboolean
517 maxmind_db_lookup_process(void)
518 {
519     return FALSE;
520 }
521
522 const mmdb_lookup_t *
523 maxmind_db_lookup_ipv4(guint32 addr _U_) {
524     return &mmdb_not_found;
525 }
526
527 const mmdb_lookup_t *
528 maxmind_db_lookup_ipv6(const ws_in6_addr *addr _U_) {
529     return &mmdb_not_found;
530 }
531
532 gchar *
533 maxmind_db_get_paths(void) {
534     return g_strdup("");
535 }
536 #endif // HAVE_MAXMINDDB
537
538
539 /*
540  * Editor modelines
541  *
542  * Local Variables:
543  * c-basic-offset: 4
544  * tab-width: 8
545  * indent-tabs-mode: nil
546  * End:
547  *
548  * ex: set shiftwidth=4 tabstop=8 expandtab:
549  * :indentSize=4:tabSize=8:noTabs=true:
550  */