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(¤t_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