/* Copyright (C) 2007-2010 Open Information Security Foundation
 *
 * You can copy, redistribute or modify this Program under the terms of
 * the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/**
 * \file
 *
 *  \author Anoop Saldanha <anoopsaldanha@gmail.com>
 *
 *  Rule variable utility functions
 */

#include "suricata-common.h"
#include "conf.h"
#include "conf-yaml-loader.h"

#include "detect.h"
#include "detect-content.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "detect-engine-mpm.h"

#include "util-rule-vars.h"
#include "util-enum.h"
#include "util-debug.h"
#include "util-unittest.h"

/** An enum-string map, that maps the different vars type in the yaml conf
 *  type with the mapping path in the yaml conf file */
SCEnumCharMap sc_rule_vars_type_map[ ] = {
    { "vars.address-groups", SC_RULE_VARS_ADDRESS_GROUPS },
    { "vars.port-groups",    SC_RULE_VARS_PORT_GROUPS }
};

/**
 * \internal
 * \brief Retrieves a value for a yaml mapping.  The sequence from the yaml
 *        conf file, from which the conf value has to be retrieved can be
 *        specified by supplying a SCRuleVarsType enum.  The string mapping
 *        for each of the SCRuleVarsType is present in sc_rule_vars_type_map.
 *
 * \param conf_var_name  Pointer to a character string containing the conf var
 *                       name, whose value has to be retrieved from the yaml
 *                       conf file.
 * \param conf_vars_type Holds an enum value that indicates the kind of yaml
 *                       mapping that has to be retrieved.  Can be one of the
 *                       values in SCRuleVarsType.
 *
 * \retval conf_var_name_value Pointer to the string containing the conf value
 *                             on success; NULL on failure.
 */
const char *SCRuleVarsGetConfVar(const DetectEngineCtx *de_ctx,
                           const char *conf_var_name,
                           SCRuleVarsType conf_vars_type)
{
    SCEnter();

    const char *conf_var_type_name = NULL;
    char conf_var_full_name[2048];
    const char *conf_var_full_name_value = NULL;

    if (conf_var_name == NULL)
        goto end;

    while (conf_var_name[0] != '\0' && isspace((unsigned char)conf_var_name[0])) {
        conf_var_name++;
    }

    (conf_var_name[0] == '$') ? conf_var_name++ : conf_var_name;
    conf_var_type_name = SCMapEnumValueToName(conf_vars_type,
                                              sc_rule_vars_type_map);
    if (conf_var_type_name == NULL)
        goto end;

    if (de_ctx != NULL && strlen(de_ctx->config_prefix) > 0) {
        if (snprintf(conf_var_full_name, sizeof(conf_var_full_name), "%s.%s.%s",
                    de_ctx->config_prefix, conf_var_type_name, conf_var_name) < 0) {
            goto end;
        }
    } else {
        if (snprintf(conf_var_full_name, sizeof(conf_var_full_name), "%s.%s",
                    conf_var_type_name, conf_var_name) < 0) {
            goto end;
        }
    }

    if (ConfGet(conf_var_full_name, &conf_var_full_name_value) != 1) {
        SCLogError("Variable \"%s\" is not defined in "
                   "configuration file",
                conf_var_name);
        goto end;
    }

    SCLogDebug("Value obtained from the yaml conf file, for the var "
               "\"%s\" is \"%s\"", conf_var_name, conf_var_full_name_value);

 end:
    SCReturnCharPtr(conf_var_full_name_value);
}


/**********************************Unittests***********************************/
#ifdef UNITTESTS

static const char *dummy_conf_string =
    "%YAML 1.1\n"
    "---\n"
    "\n"
    "default-log-dir: /var/log/suricata\n"
    "\n"
    "logging:\n"
    "\n"
    "  default-log-level: debug\n"
    "\n"
    "  default-format: \"<%t> - <%l>\"\n"
    "\n"
    "  default-startup-message: Your IDS has started.\n"
    "\n"
    "  default-output-filter:\n"
    "\n"
    "  output:\n"
    "\n"
    "  - interface: console\n"
    "    log-level: info\n"
    "\n"
    "  - interface: file\n"
    "    filename: /var/log/suricata.log\n"
    "\n"
    "  - interface: syslog\n"
    "    facility: local5\n"
    "    format: \"%l\"\n"
    "\n"
    "pfring:\n"
    "\n"
    "  interface: eth0\n"
    "\n"
    "  clusterid: 99\n"
    "\n"
    "vars:\n"
    "\n"
    "  address-groups:\n"
    "\n"
    "    HOME_NET: \"[192.168.0.0/16,10.8.0.0/16,127.0.0.1,2001:888:"
    "13c5:5AFE::/64,2001:888:13c5:CAFE::/64]\"\n"
    "\n"
    "    EXTERNAL_NET: \"[!192.168.0.0/16,2000::/3]\"\n"
    "\n"
    "    HTTP_SERVERS: \"!192.168.0.0/16\"\n"
    "\n"
    "    SMTP_SERVERS: \"!192.168.0.0/16\"\n"
    "\n"
    "    SQL_SERVERS: \"!192.168.0.0/16\"\n"
    "\n"
    "    DNS_SERVERS: any\n"
    "\n"
    "    TELNET_SERVERS: any\n"
    "\n"
    "    AIM_SERVERS: any\n"
    "\n"
    "  port-groups:\n"
    "\n"
    "    HTTP_PORTS: \"80:81,88\"\n"
    "\n"
    "    SHELLCODE_PORTS: 80\n"
    "\n"
    "    ORACLE_PORTS: 1521\n"
    "\n"
    "    SSH_PORTS: 22\n"
    "\n";

/**
 * \test Check that valid address and port group vars are correctly retrieved
 *       from the configuration.
 */
static int SCRuleVarsPositiveTest01(void)
{
    ConfCreateContextBackup();
    ConfInit();
    ConfYamlLoadString(dummy_conf_string, strlen(dummy_conf_string));

    /* check for address-groups */
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$HOME_NET", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$HOME_NET", SC_RULE_VARS_ADDRESS_GROUPS),
                        "[192.168.0.0/16,10.8.0.0/16,127.0.0.1,2001:888:13c5:"
                        "5AFE::/64,2001:888:13c5:CAFE::/64]") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$EXTERNAL_NET", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$EXTERNAL_NET", SC_RULE_VARS_ADDRESS_GROUPS),
                        "[!192.168.0.0/16,2000::/3]") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$HTTP_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$HTTP_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                        "!192.168.0.0/16") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$SMTP_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$SMTP_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                        "!192.168.0.0/16") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$SQL_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$SQL_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                        "!192.168.0.0/16") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$DNS_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$DNS_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                        "any") == 0);
    FAIL_IF_NOT(
            SCRuleVarsGetConfVar(NULL, "$TELNET_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
            strcmp(SCRuleVarsGetConfVar(NULL, "$TELNET_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                    "any") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$AIM_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$AIM_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                        "any") == 0);

    /* Test that a leading space is stripped. */
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, " $AIM_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, " $AIM_SERVERS", SC_RULE_VARS_ADDRESS_GROUPS),
                        "any") == 0);

    /* check for port-groups */
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$HTTP_PORTS", SC_RULE_VARS_PORT_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$HTTP_PORTS", SC_RULE_VARS_PORT_GROUPS),
                        "80:81,88") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$SHELLCODE_PORTS", SC_RULE_VARS_PORT_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$SHELLCODE_PORTS", SC_RULE_VARS_PORT_GROUPS),
                        "80") == 0);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$ORACLE_PORTS", SC_RULE_VARS_PORT_GROUPS) != NULL &&
                strcmp(SCRuleVarsGetConfVar(NULL, "$ORACLE_PORTS", SC_RULE_VARS_PORT_GROUPS),
                        "1521") == 0);
    FAIL_IF_NOT(
            SCRuleVarsGetConfVar(NULL, "$SSH_PORTS", SC_RULE_VARS_PORT_GROUPS) != NULL &&
            strcmp(SCRuleVarsGetConfVar(NULL, "$SSH_PORTS", SC_RULE_VARS_PORT_GROUPS), "22") == 0);

    ConfDeInit();
    ConfRestoreContextBackup();
    PASS;
}

/**
 * \test Check that invalid address and port groups are properly handled by the
 *       API.
 */
static int SCRuleVarsNegativeTest02(void)
{
    ConfCreateContextBackup();
    ConfInit();
    ConfYamlLoadString(dummy_conf_string, strlen(dummy_conf_string));

    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$HOME_NETW", SC_RULE_VARS_ADDRESS_GROUPS) == NULL);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$home_net", SC_RULE_VARS_ADDRESS_GROUPS) == NULL);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$TOMCAT_PORTSW", SC_RULE_VARS_PORT_GROUPS) == NULL);
    FAIL_IF_NOT(SCRuleVarsGetConfVar(NULL, "$tomcat_ports", SC_RULE_VARS_PORT_GROUPS) == NULL);

    ConfDeInit();
    ConfRestoreContextBackup();
    PASS;
}

/**
 * \test Check that Signatures with valid address and port groups are parsed
 *       without any errors by the Signature parsing API.
 */
static int SCRuleVarsPositiveTest03(void)
{
    ConfCreateContextBackup();
    ConfInit();
    ConfYamlLoadString(dummy_conf_string, strlen(dummy_conf_string));

    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
    FAIL_IF_NULL(de_ctx);
    de_ctx->flags |= DE_QUIET;

    Signature *s = DetectEngineAppendSig(de_ctx,
            "alert tcp [$HTTP_SERVERS,$HOME_NET,192.168.2.5] $HTTP_PORTS -> $EXTERNAL_NET "
            "[80,[!$HTTP_PORTS,$ORACLE_PORTS]] (msg:\"Rule Vars Test\"; sid:1;)");
    FAIL_IF_NULL(s);

    ConfDeInit();
    ConfRestoreContextBackup();
    DetectEngineCtxFree(de_ctx);
    PASS;
}

/**
 * \test Check that Signatures with invalid address and port groups, are
 *       are invalidated by the Signature parsing API.
 */
static int SCRuleVarsNegativeTest04(void)
{
    ConfCreateContextBackup();
    ConfInit();
    ConfYamlLoadString(dummy_conf_string, strlen(dummy_conf_string));
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
    FAIL_IF_NULL(de_ctx);
    de_ctx->flags |= DE_QUIET;

    Signature *s = DetectEngineAppendSig(
            de_ctx, "alert tcp $HTTP_SERVER any -> any any (msg:\"Rule Vars Test\"; sid:1;)");
    FAIL_IF_NOT_NULL(s);
    s = DetectEngineAppendSig(
            de_ctx, "alert tcp $http_servers any -> any any (msg:\"Rule Vars Test\"; sid:1;)");
    FAIL_IF_NOT_NULL(s);
    s = DetectEngineAppendSig(de_ctx,
            "alert tcp $http_servers any -> any $HTTP_PORTS (msg:\"Rule Vars Test\"; sid:1;)");
    FAIL_IF_NOT_NULL(s);
    s = DetectEngineAppendSig(de_ctx,
            "alert tcp !$TELNET_SERVERS !80 -> any !$SSH_PORTS (msg:\"Rule Vars Test\"; sid:1;)");
    FAIL_IF_NOT_NULL(s);

    DetectEngineCtxFree(de_ctx);
    ConfDeInit();
    ConfRestoreContextBackup();
    PASS;
}

static const char *dummy_mt_conf_string =
    "%YAML 1.1\n"
    "---\n"
    "vars:\n"
    "\n"
    "  address-groups:\n"
    "\n"
    "    HOME_NET: \"[1.2.3.4]\"\n"
    "  port-groups:\n"
    "    HTTP_PORTS: \"12345\"\n"
    "multi-detect:\n"
    "  0:\n"
    "    vars:\n"
    "\n"
    "      address-groups:\n"
    "\n"
    "        HOME_NET: \"[8.8.8.8]\"\n"
    "      port-groups:\n"
    "        HTTP_PORTS: \"54321\"\n"
    "\n";

/**
 * \test Check that valid address and port group vars are correctly retrieved
 *       from the configuration.
 */
static int SCRuleVarsMTest01(void)
{
    int result = 0;
    DetectEngineCtx *de_ctx = NULL;

    ConfCreateContextBackup();
    ConfInit();
    ConfYamlLoadString(dummy_mt_conf_string, strlen(dummy_mt_conf_string));

    if ( (de_ctx = DetectEngineCtxInit()) == NULL)
        return 0;
    de_ctx->flags |= DE_QUIET;
    snprintf(de_ctx->config_prefix, sizeof(de_ctx->config_prefix),
                "multi-detect.0");

    /* check for address-groups */
    result = (SCRuleVarsGetConfVar(de_ctx,"$HOME_NET", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
               strcmp(SCRuleVarsGetConfVar(de_ctx,"$HOME_NET", SC_RULE_VARS_ADDRESS_GROUPS),
                      "[8.8.8.8]") == 0);
    if (result == 0)
        goto end;

    result = (SCRuleVarsGetConfVar(NULL,"$HOME_NET", SC_RULE_VARS_ADDRESS_GROUPS) != NULL &&
               strcmp(SCRuleVarsGetConfVar(NULL,"$HOME_NET", SC_RULE_VARS_ADDRESS_GROUPS),
                      "[1.2.3.4]") == 0);
    if (result == 0)
        goto end;

    /* check for port-groups */
    result = (SCRuleVarsGetConfVar(de_ctx,"$HTTP_PORTS", SC_RULE_VARS_PORT_GROUPS) != NULL &&
               strcmp(SCRuleVarsGetConfVar(de_ctx,"$HTTP_PORTS", SC_RULE_VARS_PORT_GROUPS),
                      "54321") == 0);
    if (result == 0)
        goto end;

    result = (SCRuleVarsGetConfVar(NULL,"$HTTP_PORTS", SC_RULE_VARS_PORT_GROUPS) != NULL &&
               strcmp(SCRuleVarsGetConfVar(NULL,"$HTTP_PORTS", SC_RULE_VARS_PORT_GROUPS),
                      "12345") == 0);
    if (result == 0)
        goto end;

end:
    ConfDeInit();
    ConfRestoreContextBackup();

    if (de_ctx != NULL)
        DetectEngineCtxFree(de_ctx);
    return result;
}

#endif /* UNITTESTS */

void SCRuleVarsRegisterTests(void)
{
#ifdef UNITTESTS
    UtRegisterTest("SCRuleVarsPositiveTest01", SCRuleVarsPositiveTest01);
    UtRegisterTest("SCRuleVarsNegativeTest02", SCRuleVarsNegativeTest02);
    UtRegisterTest("SCRuleVarsPositiveTest03", SCRuleVarsPositiveTest03);
    UtRegisterTest("SCRuleVarsNegativeTest04", SCRuleVarsNegativeTest04);

    UtRegisterTest("SCRuleVarsMTest01", SCRuleVarsMTest01);
#endif

    return;
}
