1 /*
2  * Copyright (c) 2016 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 "context-info-db.h"
24 #include "atomic.h"
25 #include "messages.h"
26 #include <string.h>
27 #include <stdio.h>
28 #include <sys/types.h>
29 
30 struct _ContextInfoDB
31 {
32   GAtomicCounter ref_cnt;
33   GArray *data;
34   GHashTable *index;
35   gboolean is_data_indexed;
36   gboolean is_ordering_enabled;
37   GList *ordered_selectors;
38   gboolean ignore_case;
39 };
40 
41 typedef struct _element_range
42 {
43   gsize offset;
44   gsize length;
45 } element_range;
46 
47 static gint
_contextual_data_record_cmp(gconstpointer k1,gconstpointer k2)48 _contextual_data_record_cmp(gconstpointer k1, gconstpointer k2)
49 {
50   ContextualDataRecord *r1 = (ContextualDataRecord *) k1;
51   ContextualDataRecord *r2 = (ContextualDataRecord *) k2;
52 
53   return strcmp(r1->selector->str, r2->selector->str);
54 }
55 
56 static gint
_g_strcmp(gconstpointer a,gconstpointer b)57 _g_strcmp(gconstpointer a, gconstpointer b)
58 {
59   return g_strcmp0((const gchar *) a, (const gchar *) b);
60 }
61 
62 static gint
_g_strcasecmp(gconstpointer a,gconstpointer b)63 _g_strcasecmp(gconstpointer a, gconstpointer b)
64 {
65   if (!a || !b)
66     return 1;
67   return g_ascii_strcasecmp((const gchar *)a, (const gchar *)b);
68 }
69 
70 static gint
_contextual_data_record_case_cmp(gconstpointer k1,gconstpointer k2)71 _contextual_data_record_case_cmp(gconstpointer k1, gconstpointer k2)
72 {
73   ContextualDataRecord *r1 = (ContextualDataRecord *) k1;
74   ContextualDataRecord *r2 = (ContextualDataRecord *) k2;
75 
76   return _g_strcasecmp(r1->selector->str, r2->selector->str);
77 }
78 
79 void
context_info_db_enable_ordering(ContextInfoDB * self)80 context_info_db_enable_ordering(ContextInfoDB *self)
81 {
82   self->is_ordering_enabled = TRUE;
83 }
84 
85 GList *
context_info_db_ordered_selectors(ContextInfoDB * self)86 context_info_db_ordered_selectors(ContextInfoDB *self)
87 {
88   return self->ordered_selectors;
89 }
90 
91 void
context_info_db_index(ContextInfoDB * self)92 context_info_db_index(ContextInfoDB *self)
93 {
94   GCompareFunc record_cmp = self->ignore_case ? _contextual_data_record_case_cmp : _contextual_data_record_cmp;
95 
96   if (self->data->len > 0)
97     {
98       g_array_sort(self->data, record_cmp);
99       gsize range_start = 0;
100       ContextualDataRecord *range_start_record =
101         &g_array_index(self->data, ContextualDataRecord, 0);
102 
103       for (gsize i = 1; i < self->data->len; ++i)
104         {
105           ContextualDataRecord *current_record =
106             &g_array_index(self->data, ContextualDataRecord, i);
107 
108           if (record_cmp(range_start_record, current_record))
109             {
110               element_range *current_range = g_new(element_range, 1);
111               current_range->offset = range_start;
112               current_range->length = i - range_start;
113 
114               g_hash_table_insert(self->index, range_start_record->selector->str,
115                                   current_range);
116 
117               range_start_record = current_record;
118               range_start = i;
119             }
120         }
121 
122       {
123         element_range *last_range = g_new(element_range, 1);
124         last_range->offset = range_start;
125         last_range->length = self->data->len - range_start;
126         g_hash_table_insert(self->index, range_start_record->selector->str,
127                             last_range);
128       }
129       self->is_data_indexed = TRUE;
130     }
131 }
132 
133 static void
_ensure_indexed_db(ContextInfoDB * self)134 _ensure_indexed_db(ContextInfoDB *self)
135 {
136   if (!self->is_data_indexed)
137     context_info_db_index(self);
138 }
139 
140 static void
_record_free(gpointer p)141 _record_free(gpointer p)
142 {
143   ContextualDataRecord *rec = (ContextualDataRecord *) p;
144   contextual_data_record_clean(rec);
145 }
146 
147 static gboolean
_strcase_eq(gconstpointer a,gconstpointer b)148 _strcase_eq(gconstpointer a, gconstpointer b)
149 {
150   return _g_strcasecmp(a, b) == 0;
151 }
152 
153 static guint
_str_case_insensitive_djb2_hash(const gchar * str)154 _str_case_insensitive_djb2_hash(const gchar *str)
155 {
156   guint hash = 5381;
157   int c;
158 
159   while ((c = *str++))
160     hash = ((hash << 5) + hash) + g_ascii_toupper(c);
161 
162   return hash;
163 }
164 
165 static guint
_strcase_hash(gconstpointer value)166 _strcase_hash(gconstpointer value)
167 {
168   return _str_case_insensitive_djb2_hash((const gchar *)value);
169 }
170 
171 static void
_free_array(GArray * array)172 _free_array(GArray *array)
173 {
174   for (gsize i = 0; i < array->len; ++i)
175     {
176       ContextualDataRecord current_record =
177         g_array_index(array, ContextualDataRecord, i);
178       _record_free(&current_record);
179     }
180   g_array_free(array, TRUE);
181 }
182 
183 static void
_free(ContextInfoDB * self)184 _free(ContextInfoDB *self)
185 {
186   if (self->index)
187     {
188       g_hash_table_unref(self->index);
189     }
190   if (self->data)
191     {
192       _free_array(self->data);
193     }
194   if (self->ordered_selectors)
195     {
196       g_list_free(self->ordered_selectors);
197     }
198 }
199 
200 
201 static element_range *
_get_range_of_records(ContextInfoDB * self,const gchar * selector)202 _get_range_of_records(ContextInfoDB *self, const gchar *selector)
203 {
204   _ensure_indexed_db(self);
205   return (element_range *) g_hash_table_lookup(self->index, selector);
206 }
207 
208 void
context_info_db_purge(ContextInfoDB * self)209 context_info_db_purge(ContextInfoDB *self)
210 {
211   g_hash_table_remove_all(self->index);
212   if (self->data->len > 0)
213     self->data = g_array_remove_range(self->data, 0, self->data->len);
214 }
215 
216 
217 void
context_info_db_insert(ContextInfoDB * self,const ContextualDataRecord * record)218 context_info_db_insert(ContextInfoDB *self,
219                        const ContextualDataRecord *record)
220 {
221   g_array_append_val(self->data, *record);
222   self->is_data_indexed = FALSE;
223   if (self->is_ordering_enabled && !g_list_find_custom(self->ordered_selectors, record->selector->str, _g_strcmp))
224     self->ordered_selectors = g_list_append(self->ordered_selectors, record->selector->str);
225 }
226 
227 gboolean
context_info_db_contains(ContextInfoDB * self,const gchar * selector)228 context_info_db_contains(ContextInfoDB *self, const gchar *selector)
229 {
230   if (!selector)
231     return FALSE;
232 
233   _ensure_indexed_db(self);
234   return (_get_range_of_records(self, selector) != NULL);
235 }
236 
237 gsize
context_info_db_number_of_records(ContextInfoDB * self,const gchar * selector)238 context_info_db_number_of_records(ContextInfoDB *self,
239                                   const gchar *selector)
240 {
241   _ensure_indexed_db(self);
242 
243   gsize n = 0;
244   element_range *range = _get_range_of_records(self, selector);
245 
246   if (range)
247     n = range->length;
248 
249   return n;
250 }
251 
252 void
context_info_db_foreach_record(ContextInfoDB * self,const gchar * selector,ADD_CONTEXT_INFO_CB callback,gpointer arg)253 context_info_db_foreach_record(ContextInfoDB *self, const gchar *selector,
254                                ADD_CONTEXT_INFO_CB callback, gpointer arg)
255 {
256   _ensure_indexed_db(self);
257 
258   element_range *record_range = _get_range_of_records(self, selector);
259 
260   if (!record_range)
261     return;
262 
263   for (gsize i = record_range->offset;
264        i < record_range->offset + record_range->length; ++i)
265     {
266       ContextualDataRecord *record =
267         &g_array_index(self->data, ContextualDataRecord, i);
268       callback(arg, record);
269     }
270 }
271 
272 gboolean
context_info_db_is_indexed(const ContextInfoDB * self)273 context_info_db_is_indexed(const ContextInfoDB *self)
274 {
275   return self->is_data_indexed;
276 }
277 
278 gboolean
context_info_db_is_loaded(const ContextInfoDB * self)279 context_info_db_is_loaded(const ContextInfoDB *self)
280 {
281   return (self->data != NULL && self->data->len > 0);
282 }
283 
284 GList *
context_info_db_get_selectors(ContextInfoDB * self)285 context_info_db_get_selectors(ContextInfoDB *self)
286 {
287   _ensure_indexed_db(self);
288   return g_hash_table_get_keys(self->index);
289 }
290 
291 static void
_truncate_eol(gchar * line,gsize line_len)292 _truncate_eol(gchar *line, gsize line_len)
293 {
294   if (line_len >= 2 && line[line_len - 2] == '\r' && line[line_len - 1] == '\n')
295     line[line_len - 2] = '\0';
296   else if (line_len >= 1 && line[line_len - 1] == '\n')
297     line[line_len - 1] = '\0';
298 }
299 
300 static gboolean
_get_line_without_eol(gchar ** line_buf,gsize * line_buf_len,FILE * fp)301 _get_line_without_eol(gchar **line_buf, gsize *line_buf_len, FILE *fp)
302 {
303   gssize n;
304   if ((n = getline(line_buf, line_buf_len, fp)) == -1)
305     return FALSE;
306 
307   _truncate_eol(*line_buf, n);
308   *line_buf_len = strlen(*line_buf);
309   return TRUE;
310 }
311 
312 gboolean
context_info_db_import(ContextInfoDB * self,FILE * fp,const gchar * filename,ContextualDataRecordScanner * scanner)313 context_info_db_import(ContextInfoDB *self, FILE *fp, const gchar *filename,
314                        ContextualDataRecordScanner *scanner)
315 {
316   size_t line_buf_len;
317   gchar *line_buf = NULL;
318   gint lineno = 0;
319   const ContextualDataRecord *next_record;
320 
321   while (_get_line_without_eol(&line_buf, &line_buf_len, fp))
322     {
323       lineno++;
324       if (line_buf_len == 0)
325         continue;
326       next_record = contextual_data_record_scanner_get_next(scanner, line_buf, filename, lineno);
327       if (!next_record)
328         {
329           context_info_db_purge(self);
330           g_free(line_buf);
331           return FALSE;
332         }
333       msg_trace("add-contextual-data(): adding database entry",
334                 evt_tag_str("selector", next_record->selector->str),
335                 evt_tag_str("name", log_msg_get_value_name(next_record->value_handle, NULL)),
336                 evt_tag_str("value", next_record->value->template));
337       context_info_db_insert(self, next_record);
338     }
339 
340   g_free(line_buf);
341   context_info_db_index(self);
342 
343   return TRUE;
344 }
345 
346 ContextInfoDB *
context_info_db_new(gboolean ignore_case)347 context_info_db_new(gboolean ignore_case)
348 {
349   ContextInfoDB *self = g_new0(ContextInfoDB, 1);
350 
351   g_atomic_counter_set(&self->ref_cnt, 1);
352 
353   self->ignore_case = ignore_case;
354   GEqualFunc str_eq = self->ignore_case ? _strcase_eq : g_str_equal;
355   GHashFunc str_hash = self->ignore_case ? _strcase_hash : g_str_hash;
356   self->data = g_array_new(FALSE, FALSE, sizeof(ContextualDataRecord));
357   self->index = g_hash_table_new_full(str_hash, str_eq, NULL, g_free);
358   return self;
359 }
360 
361 ContextInfoDB *
context_info_db_ref(ContextInfoDB * self)362 context_info_db_ref(ContextInfoDB *self)
363 {
364   if (self)
365     {
366       g_assert(g_atomic_counter_get(&self->ref_cnt) > 0);
367       g_atomic_counter_inc(&self->ref_cnt);
368     }
369 
370   return self;
371 }
372 
373 void
context_info_db_unref(ContextInfoDB * self)374 context_info_db_unref(ContextInfoDB *self)
375 {
376   if (self)
377     {
378       g_assert(g_atomic_counter_get(&self->ref_cnt));
379       if (g_atomic_counter_dec_and_test(&self->ref_cnt))
380         {
381           _free(self);
382           g_free(self);
383         }
384     }
385 }
386