new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / analysisd / format / json_extended.c
diff --git a/src/analysisd/format/json_extended.c b/src/analysisd/format/json_extended.c
new file mode 100644 (file)
index 0000000..d04e04a
--- /dev/null
@@ -0,0 +1,382 @@
+/* Copyright (C) 2015 Wazuh Inc\r
+ * All rights reserved.\r
+ *\r
+ */\r
+\r
+#include "json_extended.h"\r
+#include <stddef.h>\r
+\r
+#define MAX_MATCHES 10\r
+#define MAX_STRING 1024\r
+#define MAX_STRING_LESS 30\r
+\r
+void W_ParseJSON(cJSON* root, const Eventinfo* lf)\r
+{\r
+\r
+    // Parse hostname & Parse AGENTIP\r
+    if(lf->hostname) {\r
+        W_JSON_ParseHostname(root, lf->hostname);\r
+        W_JSON_ParseAgentIP(root, lf);\r
+    }\r
+    // Parse timestamp\r
+    if(lf->year && lf->mon && lf->day && lf->hour) {\r
+        W_JSON_ParseTimestamp(root, lf);\r
+    }\r
+    // Parse Location\r
+    if(lf->location) {\r
+        W_JSON_ParseLocation(root, lf, 0);\r
+    }\r
+    // Parse groups && Parse PCIDSS && Parse CIS\r
+    if(lf->generated_rule->group) {\r
+        W_JSON_ParseGroups(root, lf, 1);\r
+    }\r
+    // Parse CIS and PCIDSS rules from rootcheck .txt benchmarks\r
+    if(lf->full_log && W_isRootcheck(root, 1)) {\r
+        W_JSON_ParseRootcheck(root, lf, 1);\r
+    }\r
+}\r
+\r
+\r
+// Detect if the alert is coming from rootcheck controls.\r
+\r
+int W_isRootcheck(cJSON* root, int nested)\r
+{\r
+    cJSON* groups;\r
+    cJSON* group;\r
+    cJSON* rule;\r
+    char* group_json;\r
+\r
+    int totalGroups, i;\r
+\r
+    if(!nested)\r
+        rule = root;\r
+    else\r
+        rule = cJSON_GetObjectItem(root, "rule");\r
+\r
+    groups = cJSON_GetObjectItem(rule, "groups");\r
+    totalGroups = cJSON_GetArraySize(groups);\r
+    for(i = 0; i < totalGroups; i++) {\r
+        group = cJSON_GetArrayItem(groups, i);\r
+        group_json = cJSON_Print(group);\r
+        if(strcmp(group_json, "\"rootcheck\"") == 0) {\r
+            free(group_json);\r
+            return 1;\r
+        }\r
+        free(group_json);\r
+    }\r
+    return 0;\r
+}\r
+\r
+\r
+// Getting security compliance field from rootcheck rules benchmarks .txt\r
+void W_JSON_ParseRootcheck(cJSON* root, const Eventinfo* lf, int nested)\r
+{\r
+    regex_t r;\r
+    cJSON* rule;\r
+    cJSON* compliance;\r
+    const char* regex_text;\r
+    const char* find_text;\r
+    char* token;\r
+    char* token2;\r
+    char* results[MAX_MATCHES];\r
+    int matches, i, j;\r
+    const char delim[2] = ":";\r
+    const char delim2[2] = ",";\r
+    char fullog[MAX_STRING];\r
+\r
+    // Allocate memory\r
+    for(i = 0; i < MAX_MATCHES; i++)\r
+        results[i] = malloc((MAX_STRING_LESS) * sizeof(char));\r
+\r
+    // Getting groups object JSON\r
+    if(!nested)\r
+        rule = root;\r
+    else\r
+        rule = cJSON_GetObjectItem(root, "rule");\r
+\r
+    // Getting full log string\r
+    strncpy(fullog, lf->full_log, MAX_STRING - 1);\r
+    // Searching regex\r
+    regex_text = "\\{([A-Za-z0-9_]*: [A-Za-z0-9_., ]*)\\}";\r
+    find_text = fullog;\r
+    compile_regex(&r, regex_text);\r
+    matches = match_regex(&r, find_text, results);\r
+\r
+    if(matches > 0) {\r
+        for(i = 0; i < matches; i++) {\r
+            token = strtok(results[i], delim);\r
+\r
+            trim(token);\r
+            cJSON_AddItemToObject(rule, token, compliance = cJSON_CreateArray());\r
+            for(j = 0; token[j]; j++) {\r
+                token[j] = tolower(token[j]);\r
+            }\r
+            if(token) {\r
+                token = strtok(0, delim);\r
+                trim(token);\r
+                token2 = strtok(token, delim2);\r
+                while(token2) {\r
+\r
+                    trim(token2);\r
+                    cJSON_AddItemToArray(compliance, cJSON_CreateString(token2));\r
+                    token2 = strtok(0, delim2);\r
+                }\r
+            }\r
+        }\r
+     }\r
+    regfree(&r);\r
+    for(i = 0; i < MAX_MATCHES; i++)\r
+         free(results[i]);\r
+} \r
+\r
+\r
\r
+// STRTOK every "-" delimiter to get differents groups to our json array. \r
+void W_JSON_ParseGroups(cJSON* root, const Eventinfo* lf, int nested)\r
+{\r
+    cJSON* groups;\r
+    cJSON* rule;\r
+    int firstPCI, firstCIS, foundCIS, foundPCI;\r
+    char delim[2];\r
+    char buffer[MAX_STRING];\r
+    char* token;\r
+\r
+    firstPCI = firstCIS = 1;\r
+    foundPCI = foundCIS = 0;\r
+    delim[0] = ',';\r
+    delim[1] = 0;\r
+\r
+    if(!nested)\r
+        rule = root;\r
+    else\r
+        rule = cJSON_GetObjectItem(root, "rule");\r
+\r
+    cJSON_AddItemToObject(rule, "groups", groups = cJSON_CreateArray());\r
+    strncpy(buffer, lf->generated_rule->group, sizeof(buffer) - 1);\r
+\r
+    token = strtok(buffer, delim);\r
+    while(token) {\r
+        foundPCI = foundCIS = 0;\r
+        foundPCI = add_groupPCI(rule, token, firstPCI);\r
+        if(!foundPCI)\r
+            foundCIS = add_groupCIS(rule, token, firstCIS);\r
+\r
+        if(foundPCI && firstPCI)\r
+            firstPCI = 0;\r
+        if(foundCIS && firstCIS)\r
+            firstCIS = 0;\r
+\r
+        if(!foundPCI && !foundCIS) {\r
+            cJSON_AddItemToArray(groups, cJSON_CreateString(token));\r
+        }\r
+        token = strtok(0, delim);\r
+    }\r
+}\r
+\r
+\r
+ // Parse groups PCI\r
+int add_groupPCI(cJSON* rule, char* group, int firstPCI)\r
+{\r
+    //char* len = NULL;\r
+    cJSON* pci;\r
+    char aux[strlen(group)];\r
+    // If group begin with pci_dss_ we have a PCI group\r
+    if((startsWith("pci_dss_", group)) == 1) {\r
+        // Once we add pci_dss group and create array for PCI_DSS requirements\r
+        if(firstPCI == 1) {\r
+            pci = cJSON_CreateArray();\r
+            cJSON_AddItemToObject(rule, "PCI_DSS", pci);\r
+        } else {\r
+            pci = cJSON_GetObjectItem(rule, "PCI_DSS");\r
+        }\r
+        // Prepare string and add it to PCI dss array\r
+        strncpy(aux, group, strlen(group) - 1 );\r
+        str_cut(aux, 0, 8);\r
+        cJSON_AddItemToArray(pci, cJSON_CreateString(aux));\r
+        return 1;\r
+    }\r
+    return 0;\r
+}\r
+\r
+\r
+int add_groupCIS(cJSON* rule, char* group, int firstCIS)\r
+{\r
+    //char* len = NULL;\r
+    cJSON* cis;\r
+    char aux[strlen(group)];\r
+    if((startsWith("cis_", group)) == 1) {\r
+        if(firstCIS == 1) {\r
+            cis = cJSON_CreateArray();\r
+            cJSON_AddItemToObject(rule, "CIS", cis);\r
+        } else {\r
+            cis = cJSON_GetObjectItem(rule, "CIS");\r
+        }\r
+        strncpy(aux, group, strlen(group) - 1);\r
+        str_cut(aux, 0, 4);\r
+        cJSON_AddItemToArray(cis, cJSON_CreateString(aux));\r
+        return 1;\r
+    }\r
+    return 0;\r
+}\r
+\r
\r
+// If hostname being with "(" means that alerts came from an agent, so we will remove the brakets\r
+// ** TODO ** Regex instead str_cut\r
+void W_JSON_ParseHostname(cJSON* root, char* hostname)\r
+{\r
+    if(hostname[0] == '(') {\r
+        char* search;\r
+        char string[MAX_STRING];\r
+        strncpy(string, hostname, MAX_STRING - 1);\r
+        int index;\r
+        search = strchr(string, ')');\r
+        if(search) {\r
+            index = (int)(search - string);\r
+            str_cut(string, index, -1);\r
+            str_cut(string, 0, 1);\r
+            cJSON_AddStringToObject(root, "hostname", string);\r
+        }\r
+    } else {\r
+        cJSON_AddStringToObject(root, "hostname", hostname);\r
+    }\r
+}\r
+// Parse timestamp\r
+void W_JSON_ParseTimestamp(cJSON* root, const Eventinfo* lf)\r
+{\r
+    char* dateTimestamp = malloc(21);\r
+    sprintf(dateTimestamp, "%d %s %02d %s", lf->year, lf->mon, lf->day, lf->hour);\r
+    cJSON_AddStringToObject(root, "timestamp", dateTimestamp);\r
+    free(dateTimestamp);\r
+}\r
+\r
\r
+// The IP of an agent usually comes in "hostname" field, we will extract it.\r
+// ** TODO ** Regex instead str_cut\r
+void W_JSON_ParseAgentIP(cJSON* root, const Eventinfo* lf)\r
+{\r
+    if(lf->hostname[0] == '(') {\r
+        char* search;\r
+        char string[MAX_STRING];\r
+        strncpy(string, lf->hostname, MAX_STRING - 1);\r
+        int index;\r
+        search = strchr(string, ')');\r
+        if(search) {\r
+            index = (int)(search - string);\r
+            str_cut(string, 0, index);\r
+            str_cut(string, 0, 2);\r
+            search = strchr(string, '-');\r
+            index = (int)(search - string);\r
+            str_cut(string, index, -1);\r
+            cJSON_AddStringToObject(root, "agentip", string);\r
+        }\r
+        \r
+    }\r
+}\r
+ // The file location usually comes with more information about the alert (like hostname or ip) we will extract just the "/var/folder/file.log".\r
+void W_JSON_ParseLocation(cJSON* root, const Eventinfo* lf, int archives)\r
+{\r
+    if(lf->location[0] == '(') {\r
+        char* search;\r
+        char string[MAX_STRING];\r
+        strncpy(string, lf->location, MAX_STRING - 1);\r
+        int index;\r
+        search = strchr(string, '>');\r
+        if(search) {\r
+            index = (int)(search - string);\r
+            str_cut(string, 0, index);\r
+            str_cut(string, 0, 1);\r
+\r
+            if(archives == 1)\r
+                cJSON_AddStringToObject(root, "location_desc", string);\r
+            else\r
+                cJSON_AddStringToObject(root, "location", string);\r
+        }\r
+    } else {\r
+        if(archives == 1)\r
+            cJSON_AddStringToObject(root, "location_desc", lf->location);\r
+        else\r
+            cJSON_AddStringToObject(root, "location", lf->location);\r
+    }\r
+}\r
+\r
+\r
+#define MAX_ERROR_MSG 0x1000\r
+// Regex compilator\r
+int compile_regex(regex_t* r, const char* regex_text)\r
+{\r
+    int status = regcomp(r, regex_text, REG_EXTENDED | REG_NEWLINE);\r
+    if(status != 0) {\r
+        char error_message[MAX_ERROR_MSG];\r
+        regerror(status, r, error_message, MAX_ERROR_MSG);\r
+        debug1("Regex error compiling '%s': %s\n", regex_text, error_message);\r
+        return 1;\r
+    }\r
+    return 0;\r
+}\r
+\r
+int match_regex(regex_t* r, const char* to_match, char* results[MAX_MATCHES])\r
+{\r
+    const char* p = to_match;\r
+    const int n_matches = 10;\r
+    regmatch_t m[n_matches];\r
+    int totalResults = 0;\r
+    while(1) {\r
+        int i = 0;\r
+        int nomatch = regexec(r, p, n_matches, m, 0);\r
+        if(nomatch) {\r
+            //printf ("No more matches.\n");\r
+            return totalResults;\r
+        }\r
+        for(i = 0; i < n_matches; i++) {\r
+            int start;\r
+            int finish;\r
+            if(m[i].rm_so == -1) {\r
+\r
+                break;\r
+            }\r
+            start = m[i].rm_so + (p - to_match);\r
+            finish = m[i].rm_eo + (p - to_match);\r
+            if(i > 0) {\r
+                sprintf(results[totalResults], "%.*s", (finish - start), to_match + start);\r
+                totalResults = totalResults + 1;\r
+            }\r
+            \r
+        }\r
+        p += m[0].rm_eo;\r
+    }\r
+    return 0;\r
+}\r
+\r
+int str_cut(char* str, int begin, int len)\r
+{\r
+    int l = strlen(str);\r
+\r
+    if(len < 0)\r
+        len = l - begin;\r
+    if(begin + len > l)\r
+        len = l - begin;\r
+    memmove(str + begin, str + begin + len, l - len + 1);\r
+\r
+    return len;\r
+}\r
+\r
+void trim(char* s)\r
+{\r
+    char* p = s;\r
+    int l = strlen(p);\r
+\r
+    while(isspace(p[l - 1]))\r
+        p[--l] = 0;\r
+    while(*p && isspace(*p))\r
+        ++p, --l;\r
+\r
+    memmove(s, p, l + 1);\r
+}\r
+\r
+int startsWith(const char *pre, const char *str)\r
+{\r
+    size_t lenpre = strlen(pre),\r
+           lenstr = strlen(str);\r
+    return lenstr < lenpre ? 0 : strncmp(pre, str, lenpre) == 0;\r
+}\r
+\r