/* **********************************************************
 * Copyright (c) 2004-2008 VMware, Inc.  All rights reserved.
 * **********************************************************/

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of VMware, Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#include "share.h"
#include "policy.h"
#include "parser.h"
#include "config.h"
#include "processes.h"

#ifndef UNIT_TEST

WCHAR *msg_id_keys[] = {
#    define MSG_FIELD(x) L#    x,
    POLICY_DEF_KEYS
#    undef MSG_FIELD
    L"<invalid message field>"
};

msg_id
get_msgkey_id(WCHAR *msgk)
{
    msg_id id = -1;

    DO_DEBUG(DL_FINEST, printf("trying to ID %S\n", msgk););

    while (++id != MSGKEY_BAD_FIELD)
        if (0 == wcscmp(msg_id_keys[id], msgk))
            break;

    return id;
}

char *
parse_policy_line(char *start, BOOL *done, msg_id *mfield, WCHAR *param, WCHAR *value,
                  SIZE_T maxchars)
{
    char *next = parse_line(start, done, param, value, maxchars);

    if (mfield != NULL)
        *mfield = get_msgkey_id(param);

    return next;
}

#    define MAX_SUPPORTED_ENGINES 16

void
add_to_engines(int *engines, const WCHAR *neweng)
{
    int i;
    for (i = 0; engines[i] != 0; i++)
        ;
    engines[i] = _wtoi(neweng);
}

DWORD
parse_policy(char *policy_definition,
             /* OUT */ ConfigGroup **config,
             /* OUT */ ConfigGroup **options, BOOL validating)
{
    WCHAR namebuf[MAX_PARAM_LEN], valuebuf[MAX_PARAM_LEN];
    WCHAR app_name[MAX_PATH];
    msg_id mfield;
    DWORD res;
    char *polstr = policy_definition;
    ConfigGroup *policy, *app;
    char *modes_file;
    SIZE_T modes_file_size;
    BOOL parsing_done;
    int engines[MAX_SUPPORTED_ENGINES] = { 0 };

    DO_ASSERT(config != NULL);
    DO_ASSERT(options != NULL);

    *config = NULL;
    *options = NULL;

    DO_DEBUG(DL_VERB, printf("policy string received: %s\n ", polstr););

    res = read_config_group(config, L_PRODUCT_NAME, FALSE);
    if (res != ERROR_SUCCESS)
        return res;

    *options = new_config_group(L"options");

    /* alias the pointer for clarity */
    policy = *config;

    policy->should_clear = TRUE;

    /* starting parsing the polstr */
    polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
                               MAX_PARAM_LEN);

    /* first, load the options at the beginning
     *  (GLOBAL_PROTECT, VERSION, etc) */
    while (!parsing_done && mfield != MSGKEY_BEGIN_BLOCK) {
        if (0 == wcscmp(namebuf, L"ENGINE")) {
            add_to_engines(engines, valuebuf);
        } else {
            set_config_group_parameter(*options, namebuf, valuebuf);
        }
        polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
                                   MAX_PARAM_LEN);
    }

    /* now do all of the blocks */
    while (!parsing_done) {

        if (mfield != MSGKEY_BEGIN_BLOCK) {
            DO_DEBUG(DL_ERROR, printf("BEGIN_BLOCK not found, instead %S\n", namebuf););
            return ERROR_PARSE_ERROR;
        }

        polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
                                   MAX_PARAM_LEN);

        if (mfield == MSGKEY_GLOBAL) {
            app = policy;
        } else if (mfield == MSGKEY_APP_NAME) {
            wcsncpy(app_name, valuebuf, MAX_PATH);
            NULL_TERMINATE_BUFFER(app_name);
            app = new_config_group(app_name);
            add_config_group(policy, app);
        } else {
            DO_DEBUG(DL_ERROR, printf("bad appname token: %S\n", namebuf););
            return ERROR_PARSE_ERROR;
        }

        DO_DEBUG(DL_FINEST, printf("'%S' is the app being parsed\n", app->name););

        modes_file = NULL;
        modes_file_size = 0xffffffff;

        for (;;) {

            polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
                                       MAX_PARAM_LEN);

            if (parsing_done || mfield == MSGKEY_END_BLOCK)
                break;

            if (mfield == MSGKEY_BEGIN_MP_MODES) {
                polstr = next_token(polstr, &modes_file_size);
                modes_file = polstr;
                DO_DEBUG(DL_VERB, printf("mf=%s\n", modes_file););
                polstr = get_message_block_size(polstr, msg_id_keys[MSGKEY_END_MP_MODES],
                                                &modes_file_size);
                if (modes_file_size == 0xffffffff)
                    return ERROR_PARSE_ERROR;

                continue;
            }

            DO_DEBUG(DL_VERB,
                     printf("option setting: %S, %S=%S\n", app_name, namebuf, valuebuf););

            set_config_group_parameter(app, namebuf, valuebuf);

        } // for(;;)

        /* FIXME:
         * strictly speaking, this isn't parsing, and so it's
         *  kind of messy to be doing this here. (eg: notice that
         *  validate_policy at this point will write all of the
         *  modes files!) but it's ok for now..sort of... */
        if (!validating && modes_file != NULL) {
            char backup;
            WCHAR *modes_path;
            BOOL changed;
            WCHAR modes_filename[MAX_PATH];
            int j;

            modes_path = get_config_group_parameter(app, L_DYNAMORIO_VAR_HOT_PATCH_MODES);
            if (modes_path == NULL) {
                DO_DEBUG(DL_ERROR, printf("missing modes file name!\n"););
                return ERROR_PARSE_ERROR;
            }

            backup = modes_file[modes_file_size];
            modes_file[modes_file_size] = L'\0';

            /* need to write modes file for all supported engines */
            for (j = 0; engines[j] != 0; j++) {
                _snwprintf(modes_filename, MAX_PATH, L"%s\\%d\\%S", modes_path,
                           engines[j], HOTP_MODES_FILENAME);
                res = write_file_contents_if_different(modes_filename, modes_file,
                                                       &changed);
            }

            modes_file[modes_file_size] = backup;

            if (res != ERROR_SUCCESS)
                return res;

            /* FIXME: we have the 'changed' info, so we may want to nudge
             *  selectively based on that...but it gets kind of dangerous
             *  so just nudge_all for now. */
        } else {
            /* FIXME: should we delete old modes files? */
        }

        /* and prep the next line */
        polstr = parse_policy_line(polstr, &parsing_done, &mfield, namebuf, valuebuf,
                                   MAX_PARAM_LEN);

    } // while(!parsing_done)

    return ERROR_SUCCESS;
}

/* the amount of time to wait after startup before doing
 * a nudge reset on all processes. */
#    define DEFAULT_RESET_INTERVAL_MS 2 * 60 * 1000

/* timeout for nudge reset operation */
#    define DEFAULT_RESET_TIMEOUT_MS 30 * 1000

/* to mitigate the possibility of bringing the system to a halt, wait
 * 2 seconds between process resets. */
#    define DEFAULT_RESET_DELAY_MS 2 * 1000

DWORD
policy_import(char *policy_definition, BOOL synchronize_system, BOOL *inject_flag,
              DWORD *warning)
{
    ConfigGroup *policy;
    ConfigGroup *options;
    DWORD res;
    WCHAR *global_protect;

    DO_ASSERT(policy_definition != NULL);

    if (warning != NULL)
        *warning = ERROR_SUCCESS;

    res = parse_policy(policy_definition, &policy, &options, FALSE);

    if (res != ERROR_SUCCESS)
        return res;

    res = write_config_group(policy);

    if (res != ERROR_SUCCESS)
        return res;

    /* note global protect is optional */
    global_protect =
        get_config_group_parameter(options, msg_id_keys[MSGKEY_GLOBAL_PROTECT]);

    if (NULL != global_protect) {

        if (_wtoi(global_protect)) {
            if (inject_flag != NULL)
                *inject_flag = TRUE;
            else
                set_autoinjection();
        } else {
            if (inject_flag != NULL) {
                *inject_flag = FALSE;
            } else {
                if (!using_system32_for_preinject(NULL)) {
                    /* NOTE: on NT the appinit value is cached per-boot;
                     *  so instead of clearing it, we just leave our
                     *  preinject in permanently, and only remove it at
                     *  uninstall.  this is ok for global protect = OFF
                     *  since that message will come with an empty list of
                     *  apps, so nothing will be run under dr.
                     * but for slightly saner platforms we prefer to take
                     *  the safe route of clearing out the appinit value. */
                    unset_autoinjection();
                }
            }
            res = detach_all(DETACH_RECOMMENDED_TIMEOUT);
            if (res != ERROR_SUCCESS && warning != NULL)
                *warning = res;
        }
    }

    if (res != ERROR_SUCCESS)
        return res;

    if (synchronize_system) {
        /* FIXME: This is a ugly hack to fix case 9436; reasonable because 4.2
         * release is close and this is the minimal change.  The better fix
         * would be to make the core flag thin_client via drmarker and use it in
         * system_info_cb.  Though that is better, it still isn't great because
         * node manager will still be issuing detach to all that aren't in the
         * config set.  The best would be either to make EV send an incremental
         * policy update rather than a full one or have node manager identify
         * which ones to nudge by maintaining the prior policy update. */
        wchar_t *global_options;
        global_options = get_config_group_parameter(policy, L_DYNAMORIO_VAR_OPTIONS);
        if (global_options == NULL || wcsstr(global_options, L"-thin_client") == NULL) {
            res = detach_all_not_in_config_group(policy, DETACH_RECOMMENDED_TIMEOUT);
        }
        if (res != ERROR_SUCCESS) {
            DO_DEBUG(DL_WARN, printf("error %d doing consistency detach!\n", res););

            if (warning && *warning != ERROR_SUCCESS)
                *warning = res;
        }

        /* FIXME: very inefficient to issue a process control nudge each time
         * there is an generic update; ev should split update messages into
         * regular, process control, hotpatch mode, hotpatch defs, etc and use
         * them only as needed - will prevent needless performance bottlenecks,
         * esp. with hotpatch nudges.  02-Nov-06: Bharath. */
        /* FIXME: reset_threadproc() just seems to do a generic nudge with
         * sleep - parameterize the sleep and check its uses to see if it can
         * be made generic.  02-Nov-06: Bharath. */
        /* FIXME: Also, there are two layers of delaying/sleeping one at
         * reset_threadproc() level and one at the process_walk level for
         * nudges.  Why wasn't one enough?  02-Nov-06e: Bharath */
        /* FIXME: return value from generic_nudge_all and
         * hotp_notify_all_modes_update (and hotp_notify_all_defs_update else
         * where) haven't been checked.  Don't even know what that means for a
         * nudge all.  Is it supposed to indicate that the whole thing failed
         * or just one nudge?  If so what is the intended action.  02-Nov-06:
         * Bharath. */
        /* FIXME: make both nodemanager and core use the generic nudge
         * interface to do hotpatch and detach nudges and do away with the
         * nudge-specific code.  02-Nov-06: Bharath. */
        generic_nudge_all(NUDGE_GENERIC(process_control), NULL, DEFAULT_RESET_TIMEOUT_MS,
                          0);

        /* FIXME: for now we do this at every policy update, but
         *  maybe should be more efficient? */
        res = hotp_notify_all_modes_update(DETACH_RECOMMENDED_TIMEOUT);

        if (res != ERROR_SUCCESS) {

            DO_DEBUG(DL_WARN, printf("Hotpatch nudge failed! %d\n", res););

            if (warning && *warning != ERROR_SUCCESS)
                *warning = res;
        }
    }

    free_config_group(options);
    free_config_group(policy);

    DO_DEBUG(DL_INFO, printf("Processed policy update.\n"););

    return ERROR_SUCCESS;
}

DWORD
clear_policy()
{
    ConfigGroup *config;
    DWORD res;

    /* wipe out the registry by writing an empty policy. */
    res = read_config_group(&config, L_PRODUCT_NAME, FALSE);
    if (res != ERROR_SUCCESS)
        return res;

    config->should_clear = TRUE;
    res = write_config_group(config);
    free_config_group(config);

    return res;
}

/* returns ERROR_SUCCESS unless the policy_buffer is invalid. */
DWORD
validate_policy(char *policy_definition)
{
    ConfigGroup *policy;
    ConfigGroup *options;
    DWORD res;

    DO_ASSERT(policy_definition != NULL);

    res = parse_policy(policy_definition, &policy, &options, TRUE);

    if (res != ERROR_SUCCESS)
        return res;

    free_config_group(policy);
    free_config_group(options);

    return ERROR_SUCCESS;
}

void
append_policy_block(char *policy_buffer, SIZE_T maxchars, SIZE_T *accumlen,
                    ConfigGroup *cfg)
{
    NameValuePairNode *nvpn = NULL;

    msg_append(policy_buffer, maxchars, msg_id_keys[MSGKEY_BEGIN_BLOCK], accumlen);
    msg_append(policy_buffer, maxchars, L_NEWLINE, accumlen);

    if (0 == wcscmp(cfg->name, L_PRODUCT_NAME)) {
        msg_append(policy_buffer, maxchars, msg_id_keys[MSGKEY_GLOBAL], accumlen);
        msg_append(policy_buffer, maxchars, L_NEWLINE, accumlen);
    } else {
        msg_append_nvp(policy_buffer, maxchars, accumlen, msg_id_keys[MSGKEY_APP_NAME],
                       cfg->name);
    }

    for (nvpn = cfg->params; NULL != nvpn; nvpn = nvpn->next) {
        msg_append_nvp(policy_buffer, maxchars, accumlen, nvpn->name, nvpn->value);
    }

    msg_append(policy_buffer, maxchars, msg_id_keys[MSGKEY_END_BLOCK], accumlen);
    msg_append(policy_buffer, maxchars, L_NEWLINE, accumlen);
}

/* FIXME: does not export modes! */
DWORD
policy_export(char *policy_buffer, SIZE_T maxchars, SIZE_T *needed)
{
    ConfigGroup *config, *chld;
    DWORD res;
    SIZE_T accumlen = 0;

    res = read_config_group(&config, L_PRODUCT_NAME, TRUE);
    if (res != ERROR_SUCCESS)
        return res;

    /* NOTE: we don't specify global protect when exporting */

    /* FIXME: hardcoded ID and version */
    msg_append_nvp(policy_buffer, maxchars, &accumlen, L"POLICY_VERSION", L"30000");

    append_policy_block(policy_buffer, maxchars, &accumlen, config);

    for (chld = config->children; chld != NULL; chld = chld->next) {
        append_policy_block(policy_buffer, maxchars, &accumlen, chld);
    }

    /* for the null terminator */
    accumlen++;

    if (needed != NULL)
        *needed = accumlen;

    if (policy_buffer == NULL || maxchars < accumlen)
        return ERROR_MORE_DATA;
    else
        return ERROR_SUCCESS;
}

DWORD
load_policy(WCHAR *filename, BOOL synchronize_system, DWORD *warning)
{
    DWORD res;
    SIZE_T len;
    char *policy;

    DO_ASSERT(filename != NULL);

    res = read_file_contents(filename, NULL, 0, &len);
    DO_ASSERT(res == ERROR_MORE_DATA);

    policy = (char *)malloc(len);
    res = read_file_contents(filename, policy, len, NULL);
    if (res != ERROR_SUCCESS)
        return res;

    res = policy_import(policy, synchronize_system, NULL, warning);

    free(policy);

    return res;
}

DWORD
save_policy(WCHAR *filename)
{
    DWORD res;
    SIZE_T len;
    char *policy;

    res = policy_export(NULL, 0, &len);
    if (res != ERROR_MORE_DATA && res != ERROR_SUCCESS)
        return res;

    policy = (char *)malloc(len);
    policy[0] = '\0';

    res = policy_export(policy, len, NULL);

    if (res == ERROR_SUCCESS) {
        res = write_file_contents(filename, policy, TRUE);
    }

    free(policy);

    return res;
}

#else // ifdef UNIT_TEST

void
test_sample_mfp(ConfigGroup *globals, ConfigGroup *config)
{
    ConfigGroup *chld;

    if (globals != NULL) {
        DO_ASSERT_WSTR_EQ(L"77777",
                          get_config_group_parameter(globals, L"POLICY_VERSION"));
    }

    DO_ASSERT_WSTR_EQ(L"1", get_config_group_parameter(config, L"DYNAMORIO_RUNUNDER"));
    DO_ASSERT_WSTR_EQ(L"", get_config_group_parameter(config, L"DYNAMORIO_OPTIONS"));
    DO_ASSERT(NULL !=
              wcsstr(get_config_group_parameter(config, L"DYNAMORIO_AUTOINJECT"),
                     L"\\lib\\77777\\dynamorio.dll"));

    chld = get_child(L"svchost.exe-bitsgroup", config);
    DO_ASSERT(chld != NULL);

    DO_ASSERT_WSTR_EQ(L"17", get_config_group_parameter(chld, L"DYNAMORIO_RUNUNDER"));
    DO_ASSERT_WSTR_EQ(L"-report_max 0 -kill_thread -kill_thread_max 1000",
                      get_config_group_parameter(chld, L"DYNAMORIO_OPTIONS"));
    DO_ASSERT(NULL !=
              wcsstr(get_config_group_parameter(chld, L"DYNAMORIO_AUTOINJECT"),
                     L"\\lib\\77777\\dynamorio.dll"));
}

int
main()
{
    char *testline = "GLOBAL_PROTECT=1\r\nBEGIN_BLOCK\r\nAPP_NAME=inetinfo."
                     "exe\r\nDYNAMORIO_OPTIONS=\r\nFOO=\\bar.dll\r\n";
    WCHAR *sample = L"sample.mfp";
    SIZE_T len;
    DWORD res;
    char *policy;

    set_debuglevel(DL_INFO);
    set_abortlevel(DL_WARN);

    /* load the sample policy file for testing */
    res = read_file_contents(sample, NULL, 0, &len);
    DO_ASSERT(res == ERROR_MORE_DATA);
    DO_ASSERT(len > 1000);

    policy = (char *)malloc(len);
    res = read_file_contents(sample, policy, len, NULL);
    DO_ASSERT(res == ERROR_SUCCESS);

    /* parse_policy_line tests */
    {
        WCHAR param[MAX_PATH], value[MAX_PATH];
        BOOL done;
        msg_id mfield;
        char *ptr, *line = testline;

        ptr = parse_policy_line(line, &done, &mfield, param, value, MAX_PATH);
        DO_ASSERT(!done);
        DO_ASSERT(mfield == MSGKEY_GLOBAL_PROTECT);
        DO_ASSERT_WSTR_EQ(param, L"GLOBAL_PROTECT");
        DO_ASSERT_WSTR_EQ(value, L"1");

        ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
        DO_ASSERT(!done);
        DO_ASSERT(mfield == MSGKEY_BEGIN_BLOCK);
        DO_ASSERT_WSTR_EQ(param, L"BEGIN_BLOCK");
        DO_ASSERT_WSTR_EQ(value, L"");

        ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
        DO_ASSERT(!done);
        DO_ASSERT(mfield == MSGKEY_APP_NAME);
        DO_ASSERT_WSTR_EQ(param, L"APP_NAME");
        DO_ASSERT_WSTR_EQ(value, L"inetinfo.exe");

        ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
        DO_ASSERT(!done);
        DO_ASSERT(mfield == MSGKEY_BAD_FIELD);
        DO_ASSERT_WSTR_EQ(param, L"DYNAMORIO_OPTIONS");
        DO_ASSERT_WSTR_EQ(value, L"");

        ptr = parse_policy_line(ptr, &done, &mfield, param, value, MAX_PATH);
        DO_ASSERT(!done);
        DO_ASSERT(mfield == MSGKEY_BAD_FIELD);
        DO_ASSERT_WSTR_EQ(param, L"FOO");
        DO_ASSERT(NULL != wcsstr(value, L"bar"));
        DO_ASSERT(NULL != wcsstr(value, get_dynamorio_home()));
    }

    /* parse policy tests */
    {
        ConfigGroup *config, *globals;

        res = parse_policy(policy, &config, &globals, FALSE);
        DO_ASSERT(res == ERROR_SUCCESS);
        DO_ASSERT(globals != NULL);
        DO_ASSERT(config != NULL);

        test_sample_mfp(globals, config);

        free_config_group(globals);
        free_config_group(config);
    }

    /* validate policy tests */
    {}

    /* import policy tests */
    {
        DWORD warning;
        ConfigGroup *config;

        warning = ERROR_SUCCESS;
        res = policy_import(policy, FALSE, NULL, &warning);
        DO_ASSERT(res == ERROR_SUCCESS);

        res = read_config_group(&config, L_PRODUCT_NAME, TRUE);
        DO_ASSERT(res == ERROR_SUCCESS);
        test_sample_mfp(NULL, config);

        free_config_group(config);
    }

    /* export policy tests */
    {
        WCHAR *outfn = L"test.mfp";
        char *outpol;
        DWORD warning;

        warning = ERROR_SUCCESS;
        res = policy_import(policy, FALSE, NULL, &warning);
        DO_ASSERT(res == ERROR_SUCCESS);

        /* load the sample policy file for testing */
        res = policy_export(NULL, 0, &len);
        DO_ASSERT(res == ERROR_MORE_DATA);
        DO_ASSERT(len > 1000);

        outpol = (char *)malloc(len);
        outpol[0] = '\0';
        res = policy_export(outpol, len, NULL);
        DO_ASSERT(res == ERROR_SUCCESS);
        DO_ASSERT(len == 1 + strlen(outpol));

        res = write_file_contents(outfn, outpol, TRUE);
        DO_ASSERT(res == ERROR_SUCCESS);
        // DO_ASSERT(0 == strcmp(policy, outpol));
    }

    /* load/save */
    {
        DWORD warning = ERROR_SUCCESS;
        ConfigGroup *c;

        /* TODO: better */

        CHECKED_OPERATION(clear_policy());
        CHECKED_OPERATION(load_policy(sample, FALSE, &warning));
        DO_ASSERT(warning == ERROR_SUCCESS);

        CHECKED_OPERATION(read_config_group(&c, L_PRODUCT_NAME, TRUE));
        test_sample_mfp(NULL, c);
        free_config_group(c);

        delete_file_rename_in_use(L"test2.mfp");
        CHECKED_OPERATION(save_policy(L"test2.mfp"));
    }

    /* cleanup */
    CHECKED_OPERATION(clear_policy());

    printf("All Test Passed\n");

    return 0;
}

#endif