/src/suricata/src/util-classification-config.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2010 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Anoop Saldanha <anoopsaldanha@gmail.com> |
22 | | * |
23 | | * Used for parsing a classification.config file |
24 | | */ |
25 | | |
26 | | #include "suricata-common.h" |
27 | | #include "detect.h" |
28 | | #include "detect-engine.h" |
29 | | #include "util-hash.h" |
30 | | |
31 | | #include "conf.h" |
32 | | #include "util-classification-config.h" |
33 | | #include "util-error.h" |
34 | | #include "util-debug.h" |
35 | | #include "util-fmemopen.h" |
36 | | #include "util-byte.h" |
37 | | |
38 | | /* Regex to parse the classtype argument from a Signature. The first substring |
39 | | * holds the classtype name, the second substring holds the classtype the |
40 | | * classtype description, and the third argument holds the priority */ |
41 | 148k | #define DETECT_CLASSCONFIG_REGEX "^\\s*config\\s*classification\\s*:\\s*([a-zA-Z][a-zA-Z0-9-_]*)\\s*,\\s*(.+)\\s*,\\s*(\\d+)\\s*$" |
42 | | |
43 | | /* Default path for the classification.config file */ |
44 | | #if defined OS_WIN32 || defined __CYGWIN__ |
45 | | #define SC_CLASS_CONF_DEF_CONF_FILEPATH CONFIG_DIR "\\\\classification.config" |
46 | | #else |
47 | 148k | #define SC_CLASS_CONF_DEF_CONF_FILEPATH CONFIG_DIR "/classification.config" |
48 | | #endif |
49 | | |
50 | | uint32_t SCClassConfClasstypeHashFunc(HashTable *ht, void *data, uint16_t datalen); |
51 | | char SCClassConfClasstypeHashCompareFunc(void *data1, uint16_t datalen1, |
52 | | void *data2, uint16_t datalen2); |
53 | | void SCClassConfClasstypeHashFree(void *ch); |
54 | | static const char *SCClassConfGetConfFilename(const DetectEngineCtx *de_ctx); |
55 | | |
56 | | static SCClassConfClasstype *SCClassConfAllocClasstype(uint16_t classtype_id, |
57 | | const char *classtype, const char *classtype_desc, int priority); |
58 | | static void SCClassConfDeAllocClasstype(SCClassConfClasstype *ct); |
59 | | |
60 | | void SCClassSCConfInit(DetectEngineCtx *de_ctx) |
61 | 148k | { |
62 | 148k | int en; |
63 | 148k | PCRE2_SIZE eo; |
64 | 148k | int opts = 0; |
65 | | |
66 | 148k | de_ctx->class_conf_regex = pcre2_compile( |
67 | 148k | (PCRE2_SPTR8)DETECT_CLASSCONFIG_REGEX, PCRE2_ZERO_TERMINATED, opts, &en, &eo, NULL); |
68 | 148k | if (de_ctx->class_conf_regex == NULL) { |
69 | 0 | PCRE2_UCHAR errbuffer[256]; |
70 | 0 | pcre2_get_error_message(en, errbuffer, sizeof(errbuffer)); |
71 | 0 | SCLogWarning("pcre2 compile of \"%s\" failed at " |
72 | 0 | "offset %d: %s", |
73 | 0 | DETECT_CLASSCONFIG_REGEX, (int)eo, errbuffer); |
74 | 0 | return; |
75 | 0 | } |
76 | 148k | de_ctx->class_conf_regex_match = |
77 | 148k | pcre2_match_data_create_from_pattern(de_ctx->class_conf_regex, NULL); |
78 | 148k | } |
79 | | |
80 | | void SCClassConfDeinit(DetectEngineCtx *de_ctx) |
81 | 148k | { |
82 | 148k | if (de_ctx->class_conf_regex != NULL) { |
83 | 148k | pcre2_code_free(de_ctx->class_conf_regex); |
84 | 148k | de_ctx->class_conf_regex = NULL; |
85 | 148k | } |
86 | 148k | if (de_ctx->class_conf_regex_match != NULL) { |
87 | 148k | pcre2_match_data_free(de_ctx->class_conf_regex_match); |
88 | 148k | de_ctx->class_conf_regex_match = NULL; |
89 | 148k | } |
90 | 148k | } |
91 | | |
92 | | |
93 | | /** |
94 | | * \brief Inits the context to be used by the Classification Config parsing API. |
95 | | * |
96 | | * This function initializes the hash table to be used by the Detection |
97 | | * Engine Context to hold the data from the classification.config file, |
98 | | * obtains the file desc to parse the classification.config file, and |
99 | | * inits the regex used to parse the lines from classification.config |
100 | | * file. |
101 | | * |
102 | | * \param de_ctx Pointer to the Detection Engine Context. |
103 | | * \param fd Pointer to already opened file |
104 | | * |
105 | | * \note even if the file open fails we will keep the de_ctx->class_conf_ht |
106 | | * initialized. |
107 | | * |
108 | | * \retval fp NULL on error |
109 | | */ |
110 | | static FILE *SCClassConfInitContextAndLocalResources(DetectEngineCtx *de_ctx, FILE *fd) |
111 | 148k | { |
112 | | /* init the hash table to be used by the classification config Classtypes */ |
113 | 148k | de_ctx->class_conf_ht = HashTableInit(128, SCClassConfClasstypeHashFunc, |
114 | 148k | SCClassConfClasstypeHashCompareFunc, |
115 | 148k | SCClassConfClasstypeHashFree); |
116 | 148k | if (de_ctx->class_conf_ht == NULL) { |
117 | 0 | SCLogError("Error initializing the hash " |
118 | 0 | "table"); |
119 | 0 | return NULL; |
120 | 0 | } |
121 | | |
122 | | /* if it is not NULL, use the file descriptor. The hack so that we can |
123 | | * avoid using a dummy classification file for testing purposes and |
124 | | * instead use an input stream against a buffer containing the |
125 | | * classification strings */ |
126 | 148k | if (fd == NULL) { |
127 | 148k | const char *filename = SCClassConfGetConfFilename(de_ctx); |
128 | 148k | if ( (fd = fopen(filename, "r")) == NULL) { |
129 | | #ifdef UNITTESTS |
130 | | if (RunmodeIsUnittests()) |
131 | | return NULL; // silently fail |
132 | | #endif |
133 | 148k | SCLogWarning("could not open: \"%s\": %s", filename, strerror(errno)); |
134 | 148k | return NULL; |
135 | 148k | } |
136 | 148k | } |
137 | | |
138 | 0 | DEBUG_VALIDATE_MARK_SANITIZED(fd); |
139 | 0 | return fd; |
140 | 148k | } |
141 | | |
142 | | |
143 | | /** |
144 | | * \brief Returns the path for the Classification Config file. We check if we |
145 | | * can retrieve the path from the yaml conf file. If it is not present, |
146 | | * return the default path for the classification file which is |
147 | | * "./classification.config". |
148 | | * |
149 | | * \retval log_filename Pointer to a string containing the path for the |
150 | | * Classification Config file. |
151 | | */ |
152 | | static const char *SCClassConfGetConfFilename(const DetectEngineCtx *de_ctx) |
153 | 148k | { |
154 | 148k | const char *log_filename = NULL; |
155 | | |
156 | 148k | if (de_ctx != NULL && strlen(de_ctx->config_prefix) > 0) { |
157 | 0 | char config_value[256]; |
158 | 0 | snprintf(config_value, sizeof(config_value), |
159 | 0 | "%s.classification-file", de_ctx->config_prefix); |
160 | | |
161 | | /* try loading prefix setting, fall back to global if that |
162 | | * fails. */ |
163 | 0 | if (SCConfGetNonNull(config_value, &log_filename) != 1) { |
164 | 0 | if (SCConfGetNonNull("classification-file", &log_filename) != 1) { |
165 | 0 | log_filename = (char *)SC_CLASS_CONF_DEF_CONF_FILEPATH; |
166 | 0 | } |
167 | 0 | } |
168 | 148k | } else { |
169 | 148k | if (SCConfGetNonNull("classification-file", &log_filename) != 1) { |
170 | 148k | log_filename = (char *)SC_CLASS_CONF_DEF_CONF_FILEPATH; |
171 | 148k | } |
172 | 148k | } |
173 | | |
174 | 148k | return log_filename; |
175 | 148k | } |
176 | | |
177 | | /** |
178 | | * \brief Releases resources used by the Classification Config API. |
179 | | */ |
180 | | static void SCClassConfDeInitLocalResources(FILE *fd) |
181 | 0 | { |
182 | 0 | if (fd != NULL) { |
183 | 0 | fclose(fd); |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | | /** |
188 | | * \brief Releases resources used by the Classification Config API. |
189 | | */ |
190 | | void SCClassConfDeInitContext(DetectEngineCtx *de_ctx) |
191 | 148k | { |
192 | 148k | if (de_ctx->class_conf_ht != NULL) |
193 | 148k | HashTableFree(de_ctx->class_conf_ht); |
194 | | |
195 | 148k | de_ctx->class_conf_ht = NULL; |
196 | 148k | } |
197 | | |
198 | | /** |
199 | | * \brief Converts a string to lowercase. |
200 | | * |
201 | | * \param str Pointer to the string to be converted. |
202 | | */ |
203 | | static char *SCClassConfStringToLowercase(const char *str) |
204 | 25.8k | { |
205 | 25.8k | char *new_str = NULL; |
206 | 25.8k | char *temp_str = NULL; |
207 | | |
208 | 25.8k | if ( (new_str = SCStrdup(str)) == NULL) { |
209 | 0 | SCLogError("Error allocating memory"); |
210 | 0 | return NULL; |
211 | 0 | } |
212 | | |
213 | 25.8k | temp_str = new_str; |
214 | 448k | while (*temp_str != '\0') { |
215 | 422k | *temp_str = u8_tolower((unsigned char)*temp_str); |
216 | 422k | temp_str++; |
217 | 422k | } |
218 | | |
219 | 25.8k | return new_str; |
220 | 25.8k | } |
221 | | |
222 | | /** |
223 | | * \brief Parses a line from the classification file and adds it to Classtype |
224 | | * hash table in DetectEngineCtx, i.e. DetectEngineCtx->class_conf_ht. |
225 | | * |
226 | | * \param rawstr Pointer to the string to be parsed. |
227 | | * \param index Relative index of the string to be parsed. |
228 | | * \param de_ctx Pointer to the Detection Engine Context. |
229 | | * |
230 | | * \retval 0 On success. |
231 | | * \retval -1 On failure. |
232 | | */ |
233 | | int SCClassConfAddClasstype(DetectEngineCtx *de_ctx, char *rawstr, uint16_t index) |
234 | 25.8k | { |
235 | 25.8k | char ct_name[CLASSTYPE_NAME_MAX_LEN]; |
236 | 25.8k | char ct_desc[CLASSTYPE_DESC_MAX_LEN]; |
237 | 25.8k | char ct_priority_str[16]; |
238 | 25.8k | uint32_t ct_priority = 0; |
239 | 25.8k | uint16_t ct_id = index; |
240 | | |
241 | 25.8k | SCClassConfClasstype *ct_new = NULL; |
242 | 25.8k | SCClassConfClasstype *ct_lookup = NULL; |
243 | | |
244 | 25.8k | int ret = 0; |
245 | | |
246 | 25.8k | ret = pcre2_match(de_ctx->class_conf_regex, (PCRE2_SPTR8)rawstr, strlen(rawstr), 0, 0, |
247 | 25.8k | de_ctx->class_conf_regex_match, NULL); |
248 | 25.8k | if (ret < 0) { |
249 | 0 | SCLogError("Invalid Classtype in " |
250 | 0 | "classification.config file %s: \"%s\"", |
251 | 0 | SCClassConfGetConfFilename(de_ctx), rawstr); |
252 | 0 | goto error; |
253 | 0 | } |
254 | | |
255 | 25.8k | size_t copylen = sizeof(ct_name); |
256 | | /* retrieve the classtype name */ |
257 | 25.8k | ret = pcre2_substring_copy_bynumber( |
258 | 25.8k | de_ctx->class_conf_regex_match, 1, (PCRE2_UCHAR8 *)ct_name, ©len); |
259 | 25.8k | if (ret < 0) { |
260 | 0 | SCLogInfo("pcre2_substring_copy_bynumber() failed"); |
261 | 0 | goto error; |
262 | 0 | } |
263 | | |
264 | | /* retrieve the classtype description */ |
265 | 25.8k | copylen = sizeof(ct_desc); |
266 | 25.8k | ret = pcre2_substring_copy_bynumber( |
267 | 25.8k | de_ctx->class_conf_regex_match, 2, (PCRE2_UCHAR8 *)ct_desc, ©len); |
268 | 25.8k | if (ret < 0) { |
269 | 0 | SCLogInfo("pcre2_substring_copy_bynumber() failed"); |
270 | 0 | goto error; |
271 | 0 | } |
272 | | |
273 | | /* retrieve the classtype priority */ |
274 | 25.8k | copylen = sizeof(ct_priority_str); |
275 | 25.8k | ret = pcre2_substring_copy_bynumber( |
276 | 25.8k | de_ctx->class_conf_regex_match, 3, (PCRE2_UCHAR8 *)ct_priority_str, ©len); |
277 | 25.8k | if (ret < 0) { |
278 | 0 | SCLogInfo("pcre2_substring_copy_bynumber() failed"); |
279 | 0 | goto error; |
280 | 0 | } |
281 | 25.8k | if (StringParseUint32(&ct_priority, 10, 0, (const char *)ct_priority_str) < 0) { |
282 | 0 | goto error; |
283 | 0 | } |
284 | | |
285 | | /* Create a new instance of the parsed Classtype string */ |
286 | 25.8k | ct_new = SCClassConfAllocClasstype(ct_id, ct_name, ct_desc, ct_priority); |
287 | 25.8k | if (ct_new == NULL) |
288 | 0 | goto error; |
289 | | |
290 | | /* Check if the Classtype is present in the HashTable. In case it's present |
291 | | * ignore it, as it is a duplicate. If not present, add it to the table */ |
292 | 25.8k | ct_lookup = HashTableLookup(de_ctx->class_conf_ht, ct_new, 0); |
293 | 25.8k | if (ct_lookup == NULL) { |
294 | 25.8k | if (HashTableAdd(de_ctx->class_conf_ht, ct_new, 0) < 0) |
295 | 0 | SCLogDebug("HashTable Add failed"); |
296 | 25.8k | } else { |
297 | 0 | SCLogDebug("Duplicate classtype found inside classification.config"); |
298 | 0 | if (ct_new->classtype_desc) SCFree(ct_new->classtype_desc); |
299 | 0 | if (ct_new->classtype) SCFree(ct_new->classtype); |
300 | 0 | SCFree(ct_new); |
301 | 0 | } |
302 | | |
303 | 25.8k | return 0; |
304 | | |
305 | 0 | error: |
306 | 0 | return -1; |
307 | 25.8k | } |
308 | | |
309 | | /** |
310 | | * \brief Checks if a string is a comment or a blank line. |
311 | | * |
312 | | * Comments lines are lines of the following format - |
313 | | * "# This is a comment string" or |
314 | | * " # This is a comment string". |
315 | | * |
316 | | * \param line String that has to be checked |
317 | | * |
318 | | * \retval 1 On the argument string being a comment or blank line |
319 | | * \retval 0 Otherwise |
320 | | */ |
321 | | static int SCClassConfIsLineBlankOrComment(char *line) |
322 | 0 | { |
323 | 0 | while (*line != '\0') { |
324 | | /* we have a comment */ |
325 | 0 | if (*line == '#') |
326 | 0 | return 1; |
327 | | |
328 | | /* this line is neither a comment line, nor a blank line */ |
329 | 0 | if (!isspace((unsigned char)*line)) |
330 | 0 | return 0; |
331 | | |
332 | 0 | line++; |
333 | 0 | } |
334 | | |
335 | | /* we have a blank line */ |
336 | 0 | return 1; |
337 | 0 | } |
338 | | |
339 | | /** |
340 | | * \brief Parses the Classification Config file and updates the |
341 | | * DetectionEngineCtx->class_conf_ht with the Classtype information. |
342 | | * |
343 | | * \param de_ctx Pointer to the Detection Engine Context. |
344 | | */ |
345 | | static bool SCClassConfParseFile(DetectEngineCtx *de_ctx, FILE *fd) |
346 | 0 | { |
347 | 0 | char line[1024]; |
348 | 0 | uint16_t i = 1; |
349 | 0 | int errors = 0; |
350 | |
|
351 | 0 | while (fgets(line, sizeof(line), fd) != NULL) { |
352 | 0 | if (SCClassConfIsLineBlankOrComment(line)) |
353 | 0 | continue; |
354 | | |
355 | 0 | if (SCClassConfAddClasstype(de_ctx, line, i) == -1) { |
356 | 0 | errors++; |
357 | 0 | } else { |
358 | 0 | i++; |
359 | 0 | } |
360 | 0 | } |
361 | |
|
362 | | #ifdef UNITTESTS |
363 | | if (de_ctx != NULL && strlen(de_ctx->config_prefix) > 0) |
364 | | SCLogInfo("tenant id %d: Added \"%d\" classification types from the classification file", |
365 | | de_ctx->tenant_id, de_ctx->class_conf_ht->count); |
366 | | else |
367 | | SCLogInfo("Added \"%d\" classification types from the classification file", |
368 | | de_ctx->class_conf_ht->count); |
369 | | #endif |
370 | |
|
371 | 0 | return errors == 0; |
372 | 0 | } |
373 | | |
374 | | /** |
375 | | * \internal |
376 | | * \brief Returns a new SCClassConfClasstype instance. The classtype string |
377 | | * is converted into lowercase, before being assigned to the instance. |
378 | | * |
379 | | * \param classtype Pointer to the classification type. |
380 | | * \param classtype_desc Pointer to the classification type description. |
381 | | * \param priority Holds the priority for the classification type. |
382 | | * |
383 | | * \retval ct Pointer to the new instance of SCClassConfClasstype on success; |
384 | | * NULL on failure. |
385 | | */ |
386 | | static SCClassConfClasstype *SCClassConfAllocClasstype(uint16_t classtype_id, |
387 | | const char *classtype, |
388 | | const char *classtype_desc, |
389 | | int priority) |
390 | 25.8k | { |
391 | 25.8k | SCClassConfClasstype *ct = NULL; |
392 | | |
393 | 25.8k | if (classtype == NULL) |
394 | 0 | return NULL; |
395 | | |
396 | 25.8k | if ((ct = SCCalloc(1, sizeof(SCClassConfClasstype))) == NULL) |
397 | 0 | return NULL; |
398 | | |
399 | 25.8k | if ((ct->classtype = SCClassConfStringToLowercase(classtype)) == NULL) { |
400 | 0 | SCClassConfDeAllocClasstype(ct); |
401 | 0 | return NULL; |
402 | 0 | } |
403 | | |
404 | 25.8k | if (classtype_desc != NULL && |
405 | 25.8k | (ct->classtype_desc = SCStrdup(classtype_desc)) == NULL) { |
406 | 0 | SCLogError("Error allocating memory"); |
407 | |
|
408 | 0 | SCClassConfDeAllocClasstype(ct); |
409 | 0 | return NULL; |
410 | 0 | } |
411 | | |
412 | 25.8k | ct->classtype_id = classtype_id; |
413 | 25.8k | ct->priority = priority; |
414 | | |
415 | 25.8k | return ct; |
416 | 25.8k | } |
417 | | |
418 | | /** |
419 | | * \internal |
420 | | * \brief Frees a SCClassConfClasstype instance |
421 | | * |
422 | | * \param Pointer to the SCClassConfClasstype instance that has to be freed |
423 | | */ |
424 | | static void SCClassConfDeAllocClasstype(SCClassConfClasstype *ct) |
425 | 25.7k | { |
426 | 25.7k | if (ct != NULL) { |
427 | 25.7k | if (ct->classtype != NULL) |
428 | 25.7k | SCFree(ct->classtype); |
429 | | |
430 | 25.7k | if (ct->classtype_desc != NULL) |
431 | 25.7k | SCFree(ct->classtype_desc); |
432 | | |
433 | 25.7k | SCFree(ct); |
434 | 25.7k | } |
435 | 25.7k | } |
436 | | |
437 | | /** |
438 | | * \brief Hashing function to be used to hash the Classtype name. Would be |
439 | | * supplied as an argument to the HashTableInit function for |
440 | | * DetectEngineCtx->class_conf_ht. |
441 | | * |
442 | | * \param ht Pointer to the HashTable. |
443 | | * \param data Pointer to the data to be hashed. In this case, the data |
444 | | * would be a pointer to a SCClassConfClasstype instance. |
445 | | * \param datalen Not used by this function. |
446 | | */ |
447 | | uint32_t SCClassConfClasstypeHashFunc(HashTable *ht, void *data, uint16_t datalen) |
448 | 487k | { |
449 | 487k | SCClassConfClasstype *ct = (SCClassConfClasstype *)data; |
450 | 487k | uint32_t hash = 0; |
451 | 487k | size_t i = 0; |
452 | | |
453 | 487k | size_t len = strlen(ct->classtype); |
454 | | |
455 | 8.73M | for (i = 0; i < len; i++) |
456 | 8.24M | hash += u8_tolower((unsigned char)(ct->classtype)[i]); |
457 | | |
458 | 487k | hash = hash % ht->array_size; |
459 | | |
460 | 487k | return hash; |
461 | 487k | } |
462 | | |
463 | | /** |
464 | | * \brief Used to compare two Classtypes that have been stored in the HashTable. |
465 | | * This function is supplied as an argument to the HashTableInit function |
466 | | * for DetectionEngineCtx->class_conf_ct. |
467 | | * |
468 | | * \param data1 Pointer to the first SCClassConfClasstype to be compared. |
469 | | * \param len1 Not used by this function. |
470 | | * \param data2 Pointer to the second SCClassConfClasstype to be compared. |
471 | | * \param len2 Not used by this function. |
472 | | * |
473 | | * \retval 1 On data1 and data2 being equal. |
474 | | * \retval 0 On data1 and data2 not being equal. |
475 | | */ |
476 | | char SCClassConfClasstypeHashCompareFunc(void *data1, uint16_t datalen1, |
477 | | void *data2, uint16_t datalen2) |
478 | 202k | { |
479 | 202k | SCClassConfClasstype *ct1 = (SCClassConfClasstype *)data1; |
480 | 202k | SCClassConfClasstype *ct2 = (SCClassConfClasstype *)data2; |
481 | | |
482 | 202k | if (ct1 == NULL || ct2 == NULL) |
483 | 0 | return 0; |
484 | | |
485 | 202k | if (ct1->classtype == NULL || ct2->classtype == NULL) |
486 | 0 | return 0; |
487 | | |
488 | 202k | if (strcmp(ct1->classtype, ct2->classtype) == 0) { |
489 | 188k | SCLogDebug("Match found inside Classification-Config hash function"); |
490 | 188k | return 1; |
491 | 188k | } |
492 | | |
493 | 13.4k | return 0; |
494 | 202k | } |
495 | | |
496 | | /** |
497 | | * \brief Used to free the Classification Config Hash Data that was stored in |
498 | | * DetectEngineCtx->class_conf_ht Hashtable. |
499 | | * |
500 | | * \param ch Pointer to the data that has to be freed. |
501 | | */ |
502 | | void SCClassConfClasstypeHashFree(void *ch) |
503 | 25.7k | { |
504 | 25.7k | SCClassConfDeAllocClasstype(ch); |
505 | 25.7k | } |
506 | | |
507 | | /** |
508 | | * \brief Loads the Classtype info from the classification.config file. |
509 | | * |
510 | | * The classification.config file contains the different classtypes, |
511 | | * that can be used to label Signatures. Each line of the file should |
512 | | * have the following format - |
513 | | * classtype_name, classtype_description, priority |
514 | | * None of the above parameters should hold a quote inside the file. |
515 | | * |
516 | | * \param de_ctx Pointer to the Detection Engine Context that should be updated |
517 | | * with Classtype information. |
518 | | */ |
519 | | bool SCClassConfLoadClassificationConfigFile(DetectEngineCtx *de_ctx, FILE *fd) |
520 | 148k | { |
521 | 148k | fd = SCClassConfInitContextAndLocalResources(de_ctx, fd); |
522 | 148k | if (fd == NULL) { |
523 | | #ifdef UNITTESTS |
524 | | if (RunmodeIsUnittests()) { |
525 | | return false; |
526 | | } |
527 | | #endif |
528 | 148k | SCLogError("please check the \"classification-file\" " |
529 | 148k | "option in your suricata.yaml file"); |
530 | 148k | return false; |
531 | 148k | } |
532 | | |
533 | 148k | bool ret = true; |
534 | 0 | if (!SCClassConfParseFile(de_ctx, fd)) { |
535 | 0 | SCLogWarning("Error loading classification configuration from %s", |
536 | 0 | SCClassConfGetConfFilename(de_ctx)); |
537 | 0 | ret = false; |
538 | 0 | } |
539 | |
|
540 | 0 | SCClassConfDeInitLocalResources(fd); |
541 | |
|
542 | 0 | return ret; |
543 | 148k | } |
544 | | |
545 | | /** |
546 | | * \brief Gets the classtype from the corresponding hash table stored |
547 | | * in the Detection Engine Context's class conf ht, given the |
548 | | * classtype name. |
549 | | * |
550 | | * \param ct_name Pointer to the classtype name that has to be looked up. |
551 | | * \param de_ctx Pointer to the Detection Engine Context. |
552 | | * |
553 | | * \retval lookup_ct_info Pointer to the SCClassConfClasstype instance from |
554 | | * the hash table on success; NULL on failure. |
555 | | */ |
556 | | SCClassConfClasstype *SCClassConfGetClasstype(const char *ct_name, |
557 | | DetectEngineCtx *de_ctx) |
558 | 199k | { |
559 | 199k | if (strlen(ct_name) > CLASSTYPE_NAME_MAX_LEN) { |
560 | 0 | DEBUG_VALIDATE_BUG_ON(strlen(ct_name) > CLASSTYPE_NAME_MAX_LEN); |
561 | 0 | return NULL; |
562 | 0 | } |
563 | 199k | char name[strlen(ct_name) + 1]; |
564 | 199k | size_t s; |
565 | 3.92M | for (s = 0; s < strlen(ct_name); s++) |
566 | 3.73M | name[s] = u8_tolower((unsigned char)ct_name[s]); |
567 | 199k | name[s] = '\0'; |
568 | | |
569 | | SCClassConfClasstype ct_lookup = {0, 0, name, NULL }; |
570 | 199k | SCClassConfClasstype *lookup_ct_info = HashTableLookup(de_ctx->class_conf_ht, |
571 | 199k | &ct_lookup, 0); |
572 | 199k | return lookup_ct_info; |
573 | 199k | } |
574 | | |
575 | | /*----------------------------------Unittests---------------------------------*/ |
576 | | |
577 | | |
578 | | #ifdef UNITTESTS |
579 | | |
580 | | /** |
581 | | * \brief Creates a dummy classification file, with all valid Classtypes, for |
582 | | * testing purposes. |
583 | | * |
584 | | * \file_path Pointer to the file_path for the dummy classification file. |
585 | | */ |
586 | | FILE *SCClassConfGenerateValidDummyClassConfigFD01(void) |
587 | | { |
588 | | const char *buffer = |
589 | | "config classification: nothing-wrong,Nothing Wrong With Us,3\n" |
590 | | "config classification: unknown,Unknown are we,3\n" |
591 | | "config classification: bad-unknown,We think it's bad, 2\n"; |
592 | | |
593 | | FILE *fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); |
594 | | if (fd == NULL) |
595 | | SCLogDebug("Error with SCFmemopen() called by Classification Config test code"); |
596 | | |
597 | | return fd; |
598 | | } |
599 | | |
600 | | /** |
601 | | * \brief Creates a dummy classification file, with some valid Classtypes and a |
602 | | * couple of invalid Classtypes, for testing purposes. |
603 | | * |
604 | | * \file_path Pointer to the file_path for the dummy classification file. |
605 | | */ |
606 | | FILE *SCClassConfGenerateInvalidDummyClassConfigFD02(void) |
607 | | { |
608 | | const char *buffer = |
609 | | "config classification: not-suspicious,Not Suspicious Traffic,3\n" |
610 | | "onfig classification: unknown,Unknown Traffic,3\n" |
611 | | "config classification: _badunknown,Potentially Bad Traffic, 2\n" |
612 | | "config classification: bamboola1,Unknown Traffic,3\n" |
613 | | "config classification: misc-activity,Misc activity,-1\n" |
614 | | "config classification: policy-violation,Potential Corporate " |
615 | | "config classification: bamboola,Unknown Traffic,3\n"; |
616 | | |
617 | | FILE *fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); |
618 | | if (fd == NULL) |
619 | | SCLogDebug("Error with SCFmemopen() called by Classification Config test code"); |
620 | | |
621 | | return fd; |
622 | | } |
623 | | |
624 | | /** |
625 | | * \brief Creates a dummy classification file, with all invalid Classtypes, for |
626 | | * testing purposes. |
627 | | * |
628 | | * \file_path Pointer to the file_path for the dummy classification file. |
629 | | */ |
630 | | FILE *SCClassConfGenerateInvalidDummyClassConfigFD03(void) |
631 | | { |
632 | | const char *buffer = |
633 | | "conig classification: not-suspicious,Not Suspicious Traffic,3\n" |
634 | | "onfig classification: unknown,Unknown Traffic,3\n" |
635 | | "config classification: _badunknown,Potentially Bad Traffic, 2\n" |
636 | | "config classification: misc-activity,Misc activity,-1\n"; |
637 | | |
638 | | FILE *fd = SCFmemopen((void *)buffer, strlen(buffer), "r"); |
639 | | if (fd == NULL) |
640 | | SCLogDebug("Error with SCFmemopen() called by Classification Config test code"); |
641 | | |
642 | | return fd; |
643 | | } |
644 | | |
645 | | /** |
646 | | * \test Check that the classification file is loaded and the detection engine |
647 | | * content class_conf_hash_table loaded with the classtype data. |
648 | | */ |
649 | | static int SCClassConfTest01(void) |
650 | | { |
651 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
652 | | int result = 0; |
653 | | |
654 | | if (de_ctx == NULL) |
655 | | return result; |
656 | | |
657 | | SCClassConfDeInitContext(de_ctx); |
658 | | FILE *fd = SCClassConfGenerateValidDummyClassConfigFD01(); |
659 | | SCClassConfLoadClassificationConfigFile(de_ctx, fd); |
660 | | |
661 | | if (de_ctx->class_conf_ht == NULL) |
662 | | return result; |
663 | | |
664 | | result = (de_ctx->class_conf_ht->count == 3); |
665 | | if (result == 0) printf("de_ctx->class_conf_ht->count %u: ", de_ctx->class_conf_ht->count); |
666 | | |
667 | | DetectEngineCtxFree(de_ctx); |
668 | | |
669 | | return result; |
670 | | } |
671 | | |
672 | | /** |
673 | | * \test Check that invalid classtypes present in the classification config file |
674 | | * aren't loaded. |
675 | | */ |
676 | | static int SCClassConfTest02(void) |
677 | | { |
678 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
679 | | int result = 0; |
680 | | |
681 | | if (de_ctx == NULL) |
682 | | return result; |
683 | | |
684 | | SCClassConfDeInitContext(de_ctx); |
685 | | FILE *fd = SCClassConfGenerateInvalidDummyClassConfigFD03(); |
686 | | SCClassConfLoadClassificationConfigFile(de_ctx, fd); |
687 | | |
688 | | if (de_ctx->class_conf_ht == NULL) |
689 | | return result; |
690 | | |
691 | | result = (de_ctx->class_conf_ht->count == 0); |
692 | | |
693 | | DetectEngineCtxFree(de_ctx); |
694 | | |
695 | | return result; |
696 | | } |
697 | | |
698 | | /** |
699 | | * \test Check that only valid classtypes are loaded into the hash table from |
700 | | * the classification.config file. |
701 | | */ |
702 | | static int SCClassConfTest03(void) |
703 | | { |
704 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
705 | | |
706 | | FAIL_IF_NULL(de_ctx); |
707 | | |
708 | | SCClassConfDeInitContext(de_ctx); |
709 | | FILE *fd = SCClassConfGenerateInvalidDummyClassConfigFD02(); |
710 | | FAIL_IF(SCClassConfLoadClassificationConfigFile(de_ctx, fd)); |
711 | | |
712 | | DetectEngineCtxFree(de_ctx); |
713 | | |
714 | | PASS; |
715 | | } |
716 | | |
717 | | /** |
718 | | * \test Check if the classtype info from the classification.config file have |
719 | | * been loaded into the hash table. |
720 | | */ |
721 | | static int SCClassConfTest04(void) |
722 | | { |
723 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
724 | | FAIL_IF_NULL(de_ctx); |
725 | | |
726 | | SCClassConfDeInitContext(de_ctx); |
727 | | FILE *fd = SCClassConfGenerateValidDummyClassConfigFD01(); |
728 | | SCClassConfLoadClassificationConfigFile(de_ctx, fd); |
729 | | |
730 | | FAIL_IF_NULL(de_ctx->class_conf_ht); |
731 | | FAIL_IF_NOT(de_ctx->class_conf_ht->count == 3); |
732 | | |
733 | | FAIL_IF_NOT(SCClassConfGetClasstype("unknown", de_ctx) != NULL); |
734 | | FAIL_IF_NOT(SCClassConfGetClasstype("unKnoWn", de_ctx) != NULL); |
735 | | FAIL_IF_NOT(SCClassConfGetClasstype("bamboo", de_ctx) == NULL); |
736 | | FAIL_IF_NOT(SCClassConfGetClasstype("bad-unknown", de_ctx) != NULL); |
737 | | FAIL_IF_NOT(SCClassConfGetClasstype("BAD-UNKnOWN", de_ctx) != NULL); |
738 | | FAIL_IF_NOT(SCClassConfGetClasstype("bed-unknown", de_ctx) == NULL); |
739 | | |
740 | | DetectEngineCtxFree(de_ctx); |
741 | | PASS; |
742 | | } |
743 | | |
744 | | /** |
745 | | * \test Check if the classtype info from the invalid classification.config file |
746 | | * have not been loaded into the hash table, and cross verify to check |
747 | | * that the hash table contains no classtype data. |
748 | | */ |
749 | | static int SCClassConfTest05(void) |
750 | | { |
751 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
752 | | FAIL_IF_NULL(de_ctx); |
753 | | |
754 | | SCClassConfDeInitContext(de_ctx); |
755 | | FILE *fd = SCClassConfGenerateInvalidDummyClassConfigFD03(); |
756 | | SCClassConfLoadClassificationConfigFile(de_ctx, fd); |
757 | | |
758 | | FAIL_IF_NULL(de_ctx->class_conf_ht); |
759 | | FAIL_IF_NOT(de_ctx->class_conf_ht->count == 0); |
760 | | |
761 | | FAIL_IF_NOT(SCClassConfGetClasstype("unknown", de_ctx) == NULL); |
762 | | FAIL_IF_NOT(SCClassConfGetClasstype("unKnoWn", de_ctx) == NULL); |
763 | | FAIL_IF_NOT(SCClassConfGetClasstype("bamboo", de_ctx) == NULL); |
764 | | FAIL_IF_NOT(SCClassConfGetClasstype("bad-unknown", de_ctx) == NULL); |
765 | | FAIL_IF_NOT(SCClassConfGetClasstype("BAD-UNKnOWN", de_ctx) == NULL); |
766 | | FAIL_IF_NOT(SCClassConfGetClasstype("bed-unknown", de_ctx) == NULL); |
767 | | |
768 | | DetectEngineCtxFree(de_ctx); |
769 | | PASS; |
770 | | } |
771 | | |
772 | | /** |
773 | | * \brief This function registers unit tests for Classification Config API. |
774 | | */ |
775 | | void SCClassConfRegisterTests(void) |
776 | | { |
777 | | UtRegisterTest("SCClassConfTest01", SCClassConfTest01); |
778 | | UtRegisterTest("SCClassConfTest02", SCClassConfTest02); |
779 | | UtRegisterTest("SCClassConfTest03", SCClassConfTest03); |
780 | | UtRegisterTest("SCClassConfTest04", SCClassConfTest04); |
781 | | UtRegisterTest("SCClassConfTest05", SCClassConfTest05); |
782 | | } |
783 | | #endif /* UNITTESTS */ |