/*
 * Copyright (c) Likewise Software.  All rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * 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 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Module Name:
 *
 *        registry.c
 *
 * Abstract:
 *
 *        Registry logic
 *
 * Authors: Brian Koropoff (bkoropoff@likewise.com)
 *
 */

#include "includes.h"

static
DWORD
LwSmRegistryReadDword(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PDWORD pdwValue
    );

static
DWORD
LwSmRegistryReadString(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PWSTR* ppwszValue
    );

static
DWORD
LwSmRegistryReadStringList(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PWSTR** pppwszValues
    );

static
size_t
LwSmCountTokens(
    PWSTR pwszString
    );

DWORD
LwSmRegistryEnumServices(
    HANDLE hReg,
    PWSTR** pppwszNames
    )
{
    DWORD dwError = 0;
    DWORD dwKeyCount = 0;
    DWORD dwMaxKeyLen = 0;
    DWORD dwKeyLen = 0;
    HKEY pRootKey = NULL;
    HKEY pParentKey = NULL;
    PWSTR* ppwszNames = NULL;
    PWSTR pwszParentPath = NULL;
    DWORD i = 0;
    DWORD o = 0;
    static const PSTR pszServicesKeyName = "\\Services";
    static const WCHAR lwsmServiceName[] = {'l','w','s','m',0};
    PWSTR pName = NULL;

    dwError = LwMbsToWc16s(pszServicesKeyName + 1, &pwszParentPath);
    BAIL_ON_ERROR(dwError);

    dwError = RegOpenKeyExA(
            hReg,
            NULL,
            HKEY_THIS_MACHINE,
            0,
            KEY_READ,
            &pRootKey);
    BAIL_ON_ERROR(dwError);

    dwError = RegOpenKeyExW(hReg, pRootKey, pwszParentPath, 0, KEY_READ, &pParentKey);
    BAIL_ON_ERROR(dwError);

    dwError = RegQueryInfoKeyW(
        hReg,
        pParentKey,
        NULL,
        NULL,
        NULL,
        &dwKeyCount,
        &dwMaxKeyLen,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL);
    BAIL_ON_ERROR(dwError);

    dwError = LwAllocateMemory(
        sizeof(*ppwszNames) * (dwKeyCount + 1),
        OUT_PPVOID(&ppwszNames));
    BAIL_ON_ERROR(dwError);

    for (i = o = 0; i < dwKeyCount; i++)
    {
        dwKeyLen = dwMaxKeyLen + 1;

        dwError = LwAllocateMemory(
            sizeof(*pName) * dwKeyLen,
            OUT_PPVOID(&pName));
        BAIL_ON_ERROR(dwError);

        dwError = RegEnumKeyExW(hReg, pParentKey, i, pName, &dwKeyLen, NULL, NULL, NULL, NULL);
        BAIL_ON_ERROR(dwError);

        if (LwRtlWC16StringIsEqual(pName, lwsmServiceName, FALSE))
        {
            LW_SAFE_FREE_MEMORY(pName);
            continue;
        }
        else
        {
            ppwszNames[o++] = pName;
        }
    }

    *pppwszNames = ppwszNames;

cleanup:

    LW_SAFE_FREE_MEMORY(pwszParentPath);

    if (pParentKey)
    {
        RegCloseKey(hReg, pParentKey);
    }

    if (pRootKey)
    {
        RegCloseKey(hReg, pRootKey);
    }

    return dwError;

error:

    *pppwszNames = NULL;

    if (ppwszNames)
    {
        LwSmFreeStringList(ppwszNames);
    }

    goto cleanup;
}

DWORD
LwSmRegistryReadServiceInfo(
    HANDLE hReg,
    PCWSTR pwszName,
    PLW_SERVICE_INFO* ppInfo
    )
{
    DWORD dwError = 0;
    PLW_SERVICE_INFO pInfo = NULL;
    HKEY pRootKey = NULL;
    PWSTR pwszParentKey = NULL;
    PSTR pszName = NULL;
    PSTR pszParentKey = NULL;

    static const WCHAR wszDescription[] =
        {'D', 'e', 's', 'c', 'r', 'i', 'p', 't', 'i', 'o', 'n', 0};
    static const WCHAR wszPath[] =
        {'P', 'a', 't', 'h', 0};
    static const WCHAR wszArguments[] =
        {'A', 'r', 'g', 'u', 'm', 'e', 'n', 't', 's', 0};
    static const WCHAR wszDependencies[] =
        {'D', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's', 0};
#ifndef SERVICE_DIRECT
    static const WCHAR wszGroup[] =
        {'S', 'e', 'r', 'v', 'i', 'c', 'e', 'G', 'r', 'o', 'u', 'p', 0};
#endif
    static const WCHAR wszFdLimit[] =
        {'F', 'd', 'L', 'i', 'm', 'i', 't', 0};
    static const WCHAR wszDefaultLogType[] =
        {'D', 'e', 'f', 'a', 'u', 'l', 't', 'L', 'o', 'g', 'T', 'y', 'p', 'e', 0};
    static const WCHAR wszDefaultLogTarget[] =
        {'D', 'e', 'f', 'a', 'u', 'l', 't', 'L', 'o', 'g', 'T', 'a', 'r', 'g', 'e', 't', 0};
    static const WCHAR wszDefaultLogLevel[] =
        {'D', 'e', 'f', 'a', 'u', 'l', 't', 'L', 'o', 'g', 'L', 'e', 'v', 'e', 'l', 0};
    static const WCHAR categories[] =
        {'C', 'a', 't', 'e', 'g', 'o', 'r', 'i', 'e', 's', 0};
    static const WCHAR checkCommand[] =
        {'C', 'h', 'e', 'c', 'k', 'C', 'o', 'm', 'm', 'a', 'n', 'd', 0};
    static WCHAR const serviceCat[] = {'s','e','r','v','i','c','e','\0'};
    static WCHAR const * defaultCategories[] = {serviceCat, NULL};

    dwError = LwWc16sToMbs(pwszName, &pszName);
    BAIL_ON_ERROR(dwError);

    dwError = LwAllocateStringPrintf(&pszParentKey, "Services\\%s", pszName);
    BAIL_ON_ERROR(dwError);

    dwError = LwMbsToWc16s(pszParentKey, &pwszParentKey);
    BAIL_ON_ERROR(dwError);

    dwError = RegOpenKeyExA(
            hReg,
            NULL,
            HKEY_THIS_MACHINE,
            0,
            KEY_READ,
            &pRootKey);
    BAIL_ON_ERROR(dwError);

    dwError = LwAllocateMemory(sizeof(*pInfo), OUT_PPVOID(&pInfo));
    BAIL_ON_ERROR(dwError);

    dwError = LwAllocateWc16String(&pInfo->pwszName, pwszName);
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadString(
        hReg,
        pRootKey,
        pwszParentKey,
        wszDescription,
        &pInfo->pwszDescription);
    BAIL_ON_ERROR(dwError);

   dwError = LwSmRegistryReadString(
        hReg,
        pRootKey,
        pwszParentKey,
        wszPath,
        &pInfo->pwszPath);
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadStringList(
        hReg,
        pRootKey,
        pwszParentKey,
        wszArguments,
        &pInfo->ppwszArgs);
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadStringList(
        hReg,
        pRootKey,
        pwszParentKey,
        wszDependencies,
        &pInfo->ppwszDependencies);
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadStringList(
        hReg,
        pRootKey,
        pwszParentKey,
        categories,
        &pInfo->ppCategories);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = LwSmCopyStringList((PWSTR*)(PVOID) defaultCategories, &pInfo->ppCategories);
    }
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        pwszParentKey,
        wszFdLimit,
        &pInfo->dwFdLimit);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = 0;
        pInfo->dwFdLimit = 0;
    }
    BAIL_ON_ERROR(dwError);

#ifdef SERVICE_DIRECT
    dwError = LwMbsToWc16s("direct", &pInfo->pwszGroup);
#else
    dwError = LwSmRegistryReadString(
        hReg,
        pRootKey,
        pwszParentKey,
        wszGroup,
        &pInfo->pwszGroup);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = LwAllocateWc16String(&pInfo->pwszGroup, pInfo->pwszName);
    }
#endif
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        pwszParentKey,
        wszDefaultLogType,
        &pInfo->DefaultLogType);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = 0;
        pInfo->DefaultLogType = LW_SM_LOGGER_DEFAULT;
    }
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        pwszParentKey,
        wszDefaultLogLevel,
        &pInfo->DefaultLogLevel);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = 0;
        pInfo->DefaultLogLevel = 0;
    }
    BAIL_ON_ERROR(dwError);


    dwError = LwSmRegistryReadString(
        hReg,
        pRootKey,
        pwszParentKey,
        wszDefaultLogTarget,
        &pInfo->pDefaultLogTarget);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        switch (pInfo->DefaultLogType)
        {
        case LW_SM_LOGGER_SYSLOG:
            dwError = LwMbsToWc16s("LOG_DAEMON", &pInfo->pDefaultLogTarget);
            break;
        default:
            dwError = 0;
            break;
        }
    }
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadStringList(
        hReg,
        pRootKey,
        pwszParentKey,
        checkCommand,
        &pInfo->ppCheckCommand);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = LwAllocateMemory(sizeof(*pInfo->ppCheckCommand), OUT_PPVOID(&pInfo->ppCheckCommand));
    }
    BAIL_ON_ERROR(dwError);

    *ppInfo = pInfo;

cleanup:

    LW_SAFE_FREE_MEMORY(pszName);
    LW_SAFE_FREE_MEMORY(pszParentKey);
    LW_SAFE_FREE_MEMORY(pwszParentKey);

    if (pRootKey)
    {
        RegCloseKey(hReg, pRootKey);
    }

    return dwError;

error:

    *ppInfo = NULL;

    if (pInfo)
    {
        LwSmCommonFreeServiceInfo(pInfo);
    }

    goto cleanup;
}

DWORD
LwSmRegistryReadConfig(
    HANDLE hReg,
    PSM_GLOBAL_STATE pState
    )
{
    DWORD dwError = 0;
    HKEY pRootKey = NULL;

    static const WCHAR paramsKey[] =
    {'S','e','r','v','i','c','e','s','\\','l','w','s','m','\\','P','a','r','a','m','e','t','e','r','s',0};
    static const WCHAR paramsWatchdogKey[] =
    {'S','e','r','v','i','c','e','s','\\','l','w','s','m','\\','P','a','r','a','m','e','t','e','r','s','\\','W','a','t','c','h','d','o','g',0};
    static const WCHAR stuckTimeoutValue[] =
    {'S','t','u','c','k','T','i','m','e','o','u','t',0};
    static const WCHAR enabledValue[] =
    {'E','n','a','b','l','e','d',0};
    static const WCHAR restartPeriodValue[] =
    {'R','e','s','t','a','r','t','P','e','r','i','o','d',0};
    static const WCHAR restartLimitValue[] =
    {'R','e','s','t','a','r','t','L','i','m','i','t',0};
    DWORD value = 0;

    dwError = RegOpenKeyExA(
            hReg,
            NULL,
            HKEY_THIS_MACHINE,
            0,
            KEY_READ,
            &pRootKey);
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        paramsKey,
        stuckTimeoutValue,
        &pState->StuckTimeout);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = ERROR_SUCCESS;
        pState->StuckTimeout = 15000;
    }
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        paramsWatchdogKey,
        enabledValue,
        &value);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = ERROR_SUCCESS;
        value = 1;
    }
    BAIL_ON_ERROR(dwError);
    pState->bWatchdog = value ? TRUE : FALSE;

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        paramsWatchdogKey,
        restartPeriodValue,
        &pState->RestartPeriod);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = ERROR_SUCCESS;
        pState->RestartPeriod = 30;
    }
    BAIL_ON_ERROR(dwError);

    dwError = LwSmRegistryReadDword(
        hReg,
        pRootKey,
        paramsWatchdogKey,
        restartLimitValue,
        &pState->RestartLimit);
    if (dwError == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        dwError = ERROR_SUCCESS;
        pState->RestartLimit = 2;
    }
    BAIL_ON_ERROR(dwError);

cleanup:

    if (pRootKey)
    {
        RegCloseKey(hReg, pRootKey);
    }

    return dwError;

error:

    goto cleanup;
}

static
DWORD
LwSmRegistryReadDword(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PDWORD pdwValue
    )
{
    DWORD dwError = 0;
    DWORD dwSize = sizeof(*pdwValue) + 1;
    DWORD dwType = 0;

    dwError = RegGetValueW(
        hReg,
        pRootKey,
        pwszParentKey,
        pwszValueName,
        RRF_RT_REG_DWORD,
        &dwType,
        (PBYTE) pdwValue,
        &dwSize);
    BAIL_ON_ERROR(dwError);

cleanup:

    return dwError;

error:

    goto cleanup;
}

static
DWORD
LwSmRegistryReadStringRaw(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PWSTR* ppwszValue,
    PDWORD pType
    )
{
    DWORD dwError = 0;
    WCHAR wszValue[MAX_VALUE_LENGTH];
    DWORD dwSize = 0;
    DWORD dwType = 0;
    PWSTR pwszValue = NULL;

    memset(wszValue, 0, sizeof(wszValue));
    dwSize = sizeof(wszValue);

    dwError = RegGetValueW(
        hReg,
        pRootKey,
        pwszParentKey,
        pwszValueName,
        RRF_RT_REG_SZ,
        &dwType,
        wszValue,
        &dwSize);
    BAIL_ON_ERROR(dwError);

    if (dwType != REG_SZ && dwType != REG_MULTI_SZ)
    {
        dwError = LW_ERROR_INVALID_CONFIG;
        BAIL_ON_ERROR(dwError);
    }

    dwError = LwAllocateMemory(dwSize + sizeof(WCHAR), OUT_PPVOID(&pwszValue));
    BAIL_ON_ERROR(dwError);

    memcpy(pwszValue, wszValue, dwSize);
    *ppwszValue = pwszValue;
    if (pType)
    {
        *pType = dwType;
    }

cleanup:

    return dwError;

error:

    *ppwszValue = NULL;

    goto cleanup;
}

static
DWORD
LwSmRegistryReadString(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PWSTR* ppwszValue
    )
{
    return LwSmRegistryReadStringRaw(hReg, pRootKey, pwszParentKey, pwszValueName, ppwszValue, NULL);
}

static
DWORD
LwSmRegistryReadStringList(
    HANDLE hReg,
    HKEY pRootKey,
    PCWSTR pwszParentKey,
    PCWSTR pwszValueName,
    PWSTR** pppwszValues
    )
{
    DWORD dwError = 0;
    DWORD dwType = 0;
    PWSTR pwszValuesString = NULL;
    PWSTR pwszCursor = NULL;
    PWSTR pwszSpace = NULL;
    PWSTR* ppwszValues = NULL;
    size_t count = 0;
    size_t i = 0;

    dwError = LwSmRegistryReadStringRaw(hReg, pRootKey, pwszParentKey, pwszValueName, &pwszValuesString, &dwType);
    BAIL_ON_ERROR(dwError);

    if (dwType == REG_SZ)
    {
        if (*pwszValuesString)
        {
            count = LwSmCountTokens(pwszValuesString);
        }
        else
        {
            count = 0;
        }

        dwError = LwAllocateMemory((count + 1) * sizeof(*ppwszValues), OUT_PPVOID(&ppwszValues));
        BAIL_ON_ERROR(dwError);

        for (pwszCursor = pwszValuesString, i = 0; i < count; i++)
        {
            for (pwszSpace = pwszCursor; *pwszSpace && *pwszSpace != ' '; pwszSpace++);

            *pwszSpace = '\0';

            dwError = LwAllocateWc16String(&ppwszValues[i], pwszCursor);
            BAIL_ON_ERROR(dwError);

            for (pwszCursor = pwszSpace + 1; *pwszCursor == ' '; pwszCursor++);
        }
    }
    else
    {
        for (pwszCursor = pwszValuesString; *pwszCursor; pwszCursor += LwRtlWC16StringNumChars(pwszCursor) + 1)
        {
            count++;
        }

        dwError = LwAllocateMemory((count + 1) * sizeof(*ppwszValues), OUT_PPVOID(&ppwszValues));
        BAIL_ON_ERROR(dwError);

        i = 0;
        for (pwszCursor = pwszValuesString; *pwszCursor; pwszCursor += LwRtlWC16StringNumChars(pwszCursor) + 1)
        {
            dwError = LwAllocateWc16String(&ppwszValues[i++], pwszCursor);
            BAIL_ON_ERROR(dwError);
        }
    }

    *pppwszValues = ppwszValues;

cleanup:

    LW_SAFE_FREE_MEMORY(pwszValuesString);

    return dwError;

error:

    *pppwszValues = NULL;

    if (ppwszValues)
    {
        LwSmFreeStringList(ppwszValues);
    }

    goto cleanup;
}

static
size_t
LwSmCountTokens(
    PWSTR pwszString
    )
{
    PWSTR pwszCursor = NULL;
    size_t count = 0;
    BOOLEAN inSpace = TRUE;

    for (pwszCursor = pwszString; *pwszCursor; pwszCursor++)
    {
        if (*pwszCursor == ' ')
        {
            inSpace = TRUE;
        }
        else
        {
            if (inSpace)
            {
                count++;
            }
            inSpace = FALSE;
        }
    }

    return count;
}
