1 /*
2  * Copyright (c) 2017 Balabit
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License version 2 as published
6  * by the Free Software Foundation, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16  *
17  * As an additional exemption you are allowed to compile & link against the
18  * OpenSSL libraries as published by the OpenSSL project. See the file
19  * COPYING for details.
20  *
21  */
22 
23 #include "maxminddb-helper.h"
24 #include "scratch-buffers.h"
25 #include <logmsg/logmsg.h>
26 #include <messages.h>
27 
28 #define return_and_set_error_if(predicate, status)                     \
29   if (predicate)                                                       \
30     {                                                                  \
31       *status = MMDB_INVALID_DATA_ERROR;                               \
32       return NULL;                                                     \
33     }
34 
35 gchar *
mmdb_default_database(void)36 mmdb_default_database(void)
37 {
38   static const gchar *possible_paths[] =
39   {
40     "/usr/share/GeoIP/GeoLite2-City.mmdb", //centos, arch, fedora
41     "/var/lib/GeoIP/GeoLite2-City.mmdb",   //ubuntu
42   };
43 
44   for (gint i = 0; i <  G_N_ELEMENTS(possible_paths); ++i)
45     {
46       if (g_file_test(possible_paths[i], G_FILE_TEST_EXISTS))
47         return g_strdup(possible_paths[i]);
48     }
49 
50   return NULL;
51 }
52 
53 gboolean
mmdb_open_database(const gchar * path,MMDB_s * database)54 mmdb_open_database(const gchar *path, MMDB_s *database)
55 {
56   int mmdb_status = MMDB_open(path, MMDB_MODE_MMAP, database);
57   if (mmdb_status != MMDB_SUCCESS)
58     {
59       msg_error("MMDB_open",
60                 evt_tag_str("error", MMDB_strerror(mmdb_status)));
61       return FALSE;
62     }
63   return TRUE;
64 }
65 
66 void
append_mmdb_entry_data_to_gstring(GString * target,MMDB_entry_data_s * entry_data)67 append_mmdb_entry_data_to_gstring(GString *target, MMDB_entry_data_s *entry_data)
68 {
69 
70   switch (entry_data->type)
71     {
72     case MMDB_DATA_TYPE_UTF8_STRING:
73       g_string_append_printf(target, "%.*s", entry_data->data_size, entry_data->utf8_string);
74       break;
75     case MMDB_DATA_TYPE_DOUBLE:
76       g_string_append_printf(target, "%f", entry_data->double_value);
77       break;
78     case MMDB_DATA_TYPE_FLOAT:
79       g_string_append_printf(target, "%f", (double)entry_data->float_value);
80       break;
81     case MMDB_DATA_TYPE_UINT16:
82       g_string_append_printf(target, "%u", entry_data->uint16);
83       break;
84     case MMDB_DATA_TYPE_UINT32:
85       g_string_append_printf(target, "%u", entry_data->uint32);
86       break;
87     case MMDB_DATA_TYPE_INT32:
88       g_string_append_printf(target, "%d", entry_data->int32);
89       break;
90     case MMDB_DATA_TYPE_UINT64:
91       g_string_append_printf(target, "%"G_GUINT64_FORMAT, entry_data->uint64);
92       break;
93     case MMDB_DATA_TYPE_BOOLEAN:
94       g_string_append_printf(target, "%s", entry_data->boolean ? "true" : "false");
95       break;
96     case MMDB_DATA_TYPE_UINT128:
97     case MMDB_DATA_TYPE_MAP:
98     case MMDB_DATA_TYPE_BYTES:
99     case MMDB_DATA_TYPE_ARRAY:
100       g_assert_not_reached();
101     default:
102       g_assert_not_reached();
103     }
104 }
105 
106 static MMDB_entry_data_list_s *
107 mmdb_skip_tree(MMDB_entry_data_list_s *entry_data_list, gint *status);
108 
109 static MMDB_entry_data_list_s *
mmdb_skip_map(MMDB_entry_data_list_s * entry_data_list,gint * status)110 mmdb_skip_map(MMDB_entry_data_list_s *entry_data_list, gint *status)
111 {
112   guint32 size = entry_data_list->entry_data.data_size;
113 
114   for (entry_data_list = entry_data_list->next;
115        size && entry_data_list; size--)
116     {
117 
118       return_and_set_error_if(MMDB_DATA_TYPE_UTF8_STRING != entry_data_list->entry_data.type, status);
119 
120       entry_data_list = entry_data_list->next;
121       entry_data_list = mmdb_skip_tree(entry_data_list, status);
122       if (MMDB_SUCCESS != *status)
123         {
124           return NULL;
125         }
126     }
127 
128   return entry_data_list;
129 }
130 
131 static MMDB_entry_data_list_s *
mmdb_skip_array(MMDB_entry_data_list_s * entry_data_list,gint * status)132 mmdb_skip_array(MMDB_entry_data_list_s *entry_data_list, gint *status)
133 {
134   uint32_t size = entry_data_list->entry_data.data_size;
135 
136   for (entry_data_list = entry_data_list->next;
137        size && entry_data_list; size--)
138     {
139       entry_data_list = mmdb_skip_tree(entry_data_list, status);
140 
141       if (MMDB_SUCCESS != *status)
142         {
143           return NULL;
144         }
145     }
146   return entry_data_list;
147 }
148 
149 static MMDB_entry_data_list_s *
mmdb_skip_tree(MMDB_entry_data_list_s * entry_data_list,gint * status)150 mmdb_skip_tree(MMDB_entry_data_list_s *entry_data_list, gint *status)
151 {
152 
153   switch (entry_data_list->entry_data.type)
154     {
155     case MMDB_DATA_TYPE_MAP:
156       entry_data_list = mmdb_skip_map(entry_data_list, status);
157       break;
158     case MMDB_DATA_TYPE_ARRAY:
159       entry_data_list = mmdb_skip_array(entry_data_list, status);
160       break;
161     case MMDB_DATA_TYPE_BYTES:
162     case MMDB_DATA_TYPE_BOOLEAN:
163     case MMDB_DATA_TYPE_UINT128:
164     case MMDB_DATA_TYPE_UTF8_STRING:
165     case MMDB_DATA_TYPE_DOUBLE:
166     case MMDB_DATA_TYPE_FLOAT:
167     case MMDB_DATA_TYPE_UINT16:
168     case MMDB_DATA_TYPE_UINT32:
169     case MMDB_DATA_TYPE_UINT64:
170     case MMDB_DATA_TYPE_INT32:
171       entry_data_list = entry_data_list->next;
172       break;
173 
174     default:
175       *status = MMDB_INVALID_DATA_ERROR;
176       return NULL;
177     }
178 
179   *status = MMDB_SUCCESS;
180   return entry_data_list;
181 }
182 
183 static void
_geoip_log_msg_add_value(LogMessage * msg,GArray * path,GString * value)184 _geoip_log_msg_add_value(LogMessage *msg, GArray *path, GString *value)
185 {
186   gchar *path_string = g_strjoinv(".", (gchar **)path->data);
187   log_msg_set_value_by_name(msg, path_string, value->str, value->len);
188   g_free(path_string);
189 }
190 
191 static void
_print_preferred_string_for_lang(LogMessage * msg,MMDB_entry_data_s * entry_data,GArray * path,gchar * preferred_language)192 _print_preferred_string_for_lang(LogMessage *msg, MMDB_entry_data_s *entry_data, GArray *path,
193                                  gchar *preferred_language)
194 {
195   g_array_append_val(path, preferred_language);
196 
197   GString *value = scratch_buffers_alloc();
198   g_string_printf(value, "%.*s",
199                   entry_data->data_size,
200                   entry_data->utf8_string);
201   _geoip_log_msg_add_value(msg, path, value);
202   g_array_remove_index(path, path->len-1);
203 }
204 
205 static MMDB_entry_data_list_s *
check_language_and_maybe_insert(GString * key,gchar * preferred_language,LogMessage * msg,MMDB_entry_data_list_s * entry_data_list,GArray * path,gint * status)206 check_language_and_maybe_insert(GString *key, gchar *preferred_language, LogMessage *msg,
207                                 MMDB_entry_data_list_s *entry_data_list, GArray *path, gint *status)
208 {
209   if (!strcmp(key->str, preferred_language))
210     {
211       return_and_set_error_if(entry_data_list->entry_data.type != MMDB_DATA_TYPE_UTF8_STRING, status);
212 
213       _print_preferred_string_for_lang(msg, &entry_data_list->entry_data, path, preferred_language);
214       entry_data_list = entry_data_list->next;
215     }
216   else
217     {
218       entry_data_list = mmdb_skip_tree(entry_data_list, status);
219       if (MMDB_SUCCESS != *status)
220         return NULL;
221     }
222 
223   return entry_data_list;
224 }
225 
226 static MMDB_entry_data_list_s *
select_language(gchar * preferred_language,LogMessage * msg,MMDB_entry_data_list_s * entry_data_list,GArray * path,gint * status)227 select_language(gchar *preferred_language, LogMessage *msg,
228                 MMDB_entry_data_list_s *entry_data_list, GArray *path, gint *status)
229 {
230 
231   return_and_set_error_if(entry_data_list->entry_data.type != MMDB_DATA_TYPE_MAP, status)
232 
233   guint32 size = entry_data_list->entry_data.data_size;
234 
235   for (entry_data_list = entry_data_list->next;
236        size && entry_data_list; size--)
237     {
238       return_and_set_error_if(entry_data_list->entry_data.type != MMDB_DATA_TYPE_UTF8_STRING, status);
239 
240       GString *key = scratch_buffers_alloc();
241       g_string_printf(key, "%.*s",
242                       entry_data_list->entry_data.data_size,
243                       entry_data_list->entry_data.utf8_string);
244 
245       entry_data_list = entry_data_list->next;
246       entry_data_list = check_language_and_maybe_insert(key, preferred_language, msg,
247                                                         entry_data_list, path, status);
248       if (MMDB_SUCCESS != *status)
249         return NULL;
250 
251     }
252   return entry_data_list;
253 }
254 
255 static void
_index_array_in_path(GArray * path,guint32 _index,GString * indexer)256 _index_array_in_path(GArray *path, guint32 _index, GString *indexer)
257 {
258   g_string_printf(indexer, "%d", _index);
259   ((gchar **)path->data)[path->len-1] = indexer->str;
260 }
261 
262 MMDB_entry_data_list_s *
dump_geodata_into_msg_map(LogMessage * msg,MMDB_entry_data_list_s * entry_data_list,GArray * path,gint * status)263 dump_geodata_into_msg_map(LogMessage *msg, MMDB_entry_data_list_s *entry_data_list, GArray *path, gint *status)
264 {
265   guint32 size = entry_data_list->entry_data.data_size;
266 
267   for (entry_data_list = entry_data_list->next;
268        size && entry_data_list; size--)
269     {
270       return_and_set_error_if(MMDB_DATA_TYPE_UTF8_STRING != entry_data_list->entry_data.type, status);
271 
272       GString *key = scratch_buffers_alloc();
273       g_string_printf(key, "%.*s",
274                       entry_data_list->entry_data.data_size,
275                       entry_data_list->entry_data.utf8_string);
276 
277       g_array_append_val(path, key->str);
278       entry_data_list = entry_data_list->next;
279 
280       if (!strcmp(key->str, "names"))
281         entry_data_list = select_language("en", msg, entry_data_list, path, status);
282       else
283         entry_data_list = dump_geodata_into_msg(msg, entry_data_list, path, status);
284 
285       if (MMDB_SUCCESS != *status)
286         return NULL;
287 
288       g_array_remove_index(path, path->len-1);
289     }
290 
291   return entry_data_list;
292 }
293 
294 MMDB_entry_data_list_s *
dump_geodata_into_msg_array(LogMessage * msg,MMDB_entry_data_list_s * entry_data_list,GArray * path,gint * status)295 dump_geodata_into_msg_array(LogMessage *msg, MMDB_entry_data_list_s *entry_data_list, GArray *path, gint *status)
296 {
297   guint32 size = entry_data_list->entry_data.data_size;
298   guint32 _index = 0;
299   GString *indexer = scratch_buffers_alloc();
300   g_array_append_val(path, indexer->str);
301 
302   for (entry_data_list = entry_data_list->next;
303        (_index < size) && entry_data_list;
304        _index++)
305     {
306       _index_array_in_path(path, _index, indexer);
307       entry_data_list = dump_geodata_into_msg(msg, entry_data_list, path, status);
308 
309       if (MMDB_SUCCESS != *status)
310         return NULL;
311     }
312   g_array_remove_index(path, path->len-1);
313 
314   return entry_data_list;
315 }
316 
317 static void G_GNUC_PRINTF(3, 4)
dump_geodata_into_msg_data(LogMessage * msg,GArray * path,gchar * fmt,...)318 dump_geodata_into_msg_data(LogMessage *msg, GArray *path, gchar *fmt, ...)
319 {
320   GString *value = scratch_buffers_alloc();
321   va_list va;
322 
323   va_start(va, fmt);
324   g_string_vprintf(value, fmt, va);
325   va_end(va);
326 
327   _geoip_log_msg_add_value(msg, path, value);
328 }
329 
330 MMDB_entry_data_list_s *
dump_geodata_into_msg(LogMessage * msg,MMDB_entry_data_list_s * entry_data_list,GArray * path,gint * status)331 dump_geodata_into_msg(LogMessage *msg, MMDB_entry_data_list_s *entry_data_list, GArray *path, gint *status)
332 {
333   switch (entry_data_list->entry_data.type)
334     {
335     case MMDB_DATA_TYPE_MAP:
336       entry_data_list = dump_geodata_into_msg_map(msg, entry_data_list, path, status);
337       if (MMDB_SUCCESS != *status)
338         return NULL;
339       break;
340 
341     case MMDB_DATA_TYPE_BYTES:
342     case MMDB_DATA_TYPE_UINT128:
343       g_assert_not_reached();
344 
345     case MMDB_DATA_TYPE_ARRAY:
346       entry_data_list = dump_geodata_into_msg_array(msg, entry_data_list, path, status);
347       if (MMDB_SUCCESS != *status)
348         return NULL;
349       break;
350     case MMDB_DATA_TYPE_UTF8_STRING:
351       dump_geodata_into_msg_data(msg, path, "%.*s", entry_data_list->entry_data.data_size,
352                                  entry_data_list->entry_data.utf8_string);
353       entry_data_list = entry_data_list->next;
354       break;
355 
356     case MMDB_DATA_TYPE_DOUBLE:
357       dump_geodata_into_msg_data(msg, path, "%f", entry_data_list->entry_data.double_value);
358       entry_data_list = entry_data_list->next;
359       break;
360 
361     case MMDB_DATA_TYPE_FLOAT:
362       dump_geodata_into_msg_data(msg, path, "%f", (double)entry_data_list->entry_data.float_value);
363       entry_data_list = entry_data_list->next;
364       break;
365 
366     case MMDB_DATA_TYPE_UINT16:
367       dump_geodata_into_msg_data(msg, path, "%u", entry_data_list->entry_data.uint16);
368       entry_data_list = entry_data_list->next;
369       break;
370 
371     case MMDB_DATA_TYPE_UINT32:
372       dump_geodata_into_msg_data(msg, path, "%u", entry_data_list->entry_data.uint32);
373       entry_data_list = entry_data_list->next;
374       break;
375 
376     case MMDB_DATA_TYPE_UINT64:
377       dump_geodata_into_msg_data(msg, path, "%" PRIu64, entry_data_list->entry_data.uint64);
378       entry_data_list = entry_data_list->next;
379       break;
380 
381     case MMDB_DATA_TYPE_INT32:
382       dump_geodata_into_msg_data(msg, path, "%d", entry_data_list->entry_data.int32);
383       entry_data_list = entry_data_list->next;
384       break;
385     case MMDB_DATA_TYPE_BOOLEAN:
386       dump_geodata_into_msg_data(msg, path, "%s", entry_data_list->entry_data.boolean ? "true" : "false");
387       entry_data_list = entry_data_list->next;
388       break;
389     default:
390       *status = MMDB_INVALID_DATA_ERROR;
391       return NULL;
392     }
393 
394   *status = MMDB_SUCCESS;
395   return entry_data_list;
396 }
397