/* Editor Settings: expandtabs and use 4 spaces for indentation
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 */

/*
 * Copyright Likewise Software
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the license, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.  You should have received a copy
 * of the GNU Lesser 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
 * LESSER 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
 */

#include "includes.h"
#include <lsa/ad.h>


static
DWORD
RpctGuessServerRealmName(
    PCSTR ServerName,
    PSTR *RealmName
    );

static
BOOLEAN
IsCommentLine(
    PSTR Line
    );


#if !defined (HAVE_GETLINE)
static
ssize_t getline(char **lineptr, size_t *n, FILE *stream)
{
    size_t lineSize = 128;
    size_t length = 0;
    char *line = NULL;
    char *newLine = NULL;

    if (!lineptr || !n || ferror(stream))
    {
        errno = EINVAL;
        return -1;
    }

    if (feof(stream))
    {
        return -1;
    }

    if (*lineptr == NULL)
    {
        line = malloc(lineSize);
        if (!line)
        {
            errno = ENOMEM;
            return -1;
        }
    }
    else
    {
        lineSize = *n;
    }

    while (!feof(stream))
    {
        line[length] = (char)fgetc(stream);
        if (line[length] == '\n')
        {
            break;
        }

        if (length + 1 >= lineSize)
        {
            lineSize *= 2;
            newLine = realloc(line, lineSize);
            if (!newLine)
            {
                errno = ENOMEM;
                free(line);
                return -1;
            }

            line = newLine;
            newLine = NULL;
        }

        line[++length] = 0;
    }

    // Ensure terminating zero
    if (length + 1 >= lineSize)
    {
        lineSize *= 2;
        newLine = realloc(line, lineSize);
        if (!newLine)
        {
            errno = ENOMEM;
            free(line);
            return -1;
        }

        line = newLine;
        newLine = NULL;
    }

    line[++length] = 0;

    *lineptr = line;
    *n = lineSize;

    return (ssize_t)length;
}
#endif /* !defined(HAVE_GETLINE) */


NTSTATUS
CreateRpcBinding(
    PVOID *phBinding,
    RPC_BINDING_TYPE eBindingType,
    PRPCSERVER Server,
    PCREDENTIALS pCredentials
    )
{
    PCSTR pszNtlmsspAuth = "ntlmssp";
    PCSTR gssSpnegoAuth = "gss-spnego";
    PCSTR gssKrb5Auth = "gss-krb5";
    PCSTR schannelAuth = "schannel";
    PCSTR connectOption = "connect";
    PCSTR signOption = "sign";
    PCSTR sealOption = "seal";
    PCSTR headerSignOption = "hdrsign";

    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD dwError = ERROR_SUCCESS;
    unsigned32 rpcStatus = RPC_S_OK;
    LSA_BINDING hBinding = NULL;
    LW_PIO_CREDS pIoCreds = NULL;
    PWSTR pwszBindingString = NULL;
    size_t sBindingStringLen = 0;
    PSTR pszBindingString = NULL;
    PSTR *ppszOptions = NULL;
    DWORD iOpt = 0;
    DWORD i = 0;
    DWORD dwNumValidOptions = 0;
    unsigned32 AuthType = 0;
    unsigned32 ProtectionLevel = rpc_c_protect_level_default;
    unsigned32 authFlags = 0;
    SEC_WINNT_AUTH_IDENTITY *pNtlmSspAuthInfo = NULL;
    PVOID pAuthInfo = NULL;
    unsigned char *pszUuid = NULL;
    unsigned char *pszProtSeq = NULL;
    unsigned char *pszNetworkAddr = NULL;
    unsigned char *pszEndpoint = NULL;
    unsigned char *pszOptions = NULL;
    PSTR pszHostname = NULL;
    PSTR realmName = NULL;
    PSTR principalName = NULL;
    PWSTR dnsDomainName = NULL;
    PWSTR domainName = NULL;
    PWSTR machineAccount = NULL;
    PWSTR machinePassword = NULL;
    PWSTR netlogonServer = NULL;
    PWSTR computerName = NULL;
    PVOID hSchannel = NULL;
    NetrCredentials Creds = {0};

    if (phBinding == NULL)
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    if (Server->BindingString)
    {
        dwError = LwAllocateWc16String(&pwszBindingString,
                                       Server->BindingString);
        BAIL_ON_WIN_ERROR(dwError);

        dwError = LwWc16sLen(pwszBindingString, &sBindingStringLen);
        BAIL_ON_WIN_ERROR(dwError);

        dwError = LwWc16sToMbs(pwszBindingString,
                               &pszBindingString);
        BAIL_ON_WIN_ERROR(dwError);

        ppszOptions = RpctGetStringList(pszBindingString, ':');
        if (ppszOptions == NULL)
        {
            ntStatus = STATUS_INVALID_PARAMETER;
            BAIL_ON_NT_STATUS(ntStatus);
        }

        /*
         * Find and identify valid options
         */
        while (ppszOptions[iOpt])
        {
            if (!strcasecmp(pszNtlmsspAuth, ppszOptions[iOpt]))
            {
                AuthType = rpc_c_authn_winnt;
                dwNumValidOptions++;
            }
            if (!strcasecmp(gssSpnegoAuth, ppszOptions[iOpt]))
            {
                AuthType = rpc_c_authn_gss_negotiate;
                dwNumValidOptions++;
            }
            if (!strcasecmp(gssKrb5Auth, ppszOptions[iOpt]))
            {
                AuthType = rpc_c_authn_gss_mskrb;
                dwNumValidOptions++;
            }
            if (!strcasecmp(schannelAuth, ppszOptions[iOpt]))
            {
                AuthType = rpc_c_authn_netlogon;
                dwNumValidOptions++;
            }
            else if (!strcasecmp(connectOption, ppszOptions[iOpt]))
            {
                ProtectionLevel = rpc_c_authn_level_connect;
                dwNumValidOptions++;
            }
            else if (!strcasecmp(signOption, ppszOptions[iOpt]))
            {
                ProtectionLevel = rpc_c_authn_level_pkt_integrity;
                dwNumValidOptions++;
            }
            else if (!strcasecmp(sealOption, ppszOptions[iOpt]))
            {
                ProtectionLevel = rpc_c_authn_level_pkt_privacy;
                dwNumValidOptions++;
            }
            else if (!strcasecmp(headerSignOption, ppszOptions[iOpt]))
            {
                authFlags |= rpc_c_protect_flags_header_sign;
                dwNumValidOptions++;
            }

            iOpt++;
        }
    }

    /*
     * Cut off the options from the binding string so it can
     * be passed to rpc routines
     */
    if (dwNumValidOptions > 0)
    {
        i = sBindingStringLen;
        while (dwNumValidOptions &&
               pwszBindingString[--i])
        {
            if (pwszBindingString[i] == (WCHAR)':')
            {
                dwNumValidOptions--;
            }
        }

        pwszBindingString[i] = (WCHAR)'\0';
        pszBindingString[i] = '\0';
    }

    if (Server->Hostname)
    {
        dwError = LwWc16sToMbs(Server->Hostname,
                               &pszHostname);
        BAIL_ON_WIN_ERROR(dwError);
    }
    else
    {
        rpc_string_binding_parse((unsigned char*)pszBindingString,
                                 &pszUuid,
                                 &pszProtSeq,
                                 &pszNetworkAddr,
                                 &pszEndpoint,
                                 &pszOptions,
                                 &rpcStatus);
        if (rpcStatus)
        {
            ntStatus = LwRpcStatusToNtStatus(rpcStatus);
            BAIL_ON_NT_STATUS(ntStatus);
        }

        dwError = LwAllocateString((PSTR)pszNetworkAddr,
                                   &pszHostname);
        BAIL_ON_WIN_ERROR(dwError);

        dwError = LwMbsToWc16s(
                          pszHostname,
                          &Server->Hostname);
        BAIL_ON_WIN_ERROR(dwError);
    }

    if (Server->Realm)
    {
        dwError = LwWc16sToMbs(
                         Server->Realm,
                         &realmName);
        BAIL_ON_WIN_ERROR(dwError);
    }

    ntStatus = LwIoGetActiveCreds(NULL, &pIoCreds);
    BAIL_ON_NT_STATUS(ntStatus);

    if (pwszBindingString)
    {
        if (AuthType == rpc_c_authn_netlogon)
        {
            DISPLAY_COMMENT(("Selected DCE/RPC authentication mechanism: "
                             "Netlogon SCHANNEL\n"));

            dwError = GetMachinePassword(
                            &dnsDomainName,
                            &domainName,
                            &machineAccount,
                            &machinePassword,
                            &computerName);
            BAIL_ON_WIN_ERROR(dwError);

            dwError = LwMbsToWc16s(
                            pszHostname,
                            &netlogonServer);
            BAIL_ON_WIN_ERROR(dwError);

            ntStatus = NetrInitBindingDefault(
                            &hSchannel,
                            netlogonServer,
                            pIoCreds);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = NetrOpenSchannel(
                            hSchannel,
                            machineAccount,
                            netlogonServer,
                            netlogonServer,
                            domainName,
                            dnsDomainName,
                            computerName,
                            machinePassword,
                            &Creds,
                            &hSchannel);
            BAIL_ON_NT_STATUS(ntStatus);

            hBinding  = hSchannel;
            hSchannel = NULL;
        }
        else
        {
            ntStatus = RpcInitBindingFromBindingString(
                                             (handle_t*)&hBinding,
                                             pwszBindingString,
                                             pIoCreds);
        }
    }
    else
    {
        switch (eBindingType)
        {
        case RPC_LSA_BINDING:
            ntStatus = LsaInitBindingDefault(&hBinding,
                                             Server->Hostname,
                                             pIoCreds);
            break;

        case RPC_SAMR_BINDING:
            ntStatus = SamrInitBindingDefault(&hBinding,
                                              Server->Hostname,
                                              pIoCreds);
            break;

        case RPC_NETLOGON_BINDING:
            ntStatus = NetrInitBindingDefault(&hBinding,
                                              Server->Hostname,
                                              pIoCreds);
            break;

        case RPC_DSSETUP_BINDING:
            ntStatus = DsrInitBindingDefault(&hBinding,
                                             Server->Hostname,
                                             pIoCreds);
            break;

        case RPC_WKSSVC_BINDING:
            ntStatus = WkssInitBindingDefault(&hBinding,
                                              Server->Hostname,
                                              pIoCreds);
            break;

        default:
            ntStatus = STATUS_NOT_IMPLEMENTED;
            break;
        }
    }
    BAIL_ON_NT_STATUS(ntStatus);

    switch (AuthType)
    {
    case rpc_c_authn_winnt:
        DISPLAY_COMMENT(("Selected DCE/RPC authentication mechanism: "
                         "NTLMSSP\n"));

        dwError = LwAllocateMemory(sizeof(*pNtlmSspAuthInfo),
                                   OUT_PPVOID(&pNtlmSspAuthInfo));
        BAIL_ON_WIN_ERROR(dwError);

        if (pCredentials->Ntlm.pwszDomain)
        {
            dwError = LwWc16sToMbs(pCredentials->Ntlm.pwszDomain,
                                   &pNtlmSspAuthInfo->Domain);
            BAIL_ON_WIN_ERROR(dwError);

            pNtlmSspAuthInfo->DomainLength = strlen(pNtlmSspAuthInfo->Domain);
        }

        dwError = LwWc16sToMbs(pCredentials->Ntlm.pwszUsername,
                               &pNtlmSspAuthInfo->User);
        BAIL_ON_WIN_ERROR(dwError);

        pNtlmSspAuthInfo->UserLength = strlen(pNtlmSspAuthInfo->User);

        dwError = LwWc16sToMbs(pCredentials->Ntlm.pwszPassword,
                               &pNtlmSspAuthInfo->Password);
        BAIL_ON_WIN_ERROR(dwError);

        pNtlmSspAuthInfo->PasswordLength = strlen(pNtlmSspAuthInfo->Password);

        pAuthInfo = (PVOID)pNtlmSspAuthInfo;
        break;

    default:
        pAuthInfo = NULL;
        break;
    }

    if (AuthType && AuthType != rpc_c_authn_netlogon)
    {
        if (AuthType == rpc_c_authn_gss_negotiate ||
            AuthType == rpc_c_authn_gss_mskrb)
        {
            DISPLAY_COMMENT(("Selected DCE/RPC authentication mechanism: %s\n",
                             (AuthType == rpc_c_authn_gss_negotiate) ?
                             "GSS-SPNEGO" : "GSS-KRB5"));

            if (!realmName)
            {
                dwError = RpctGuessServerRealmName(
                                      pszHostname,
                                      &realmName);
                if (dwError == ERROR_DC_NOT_FOUND)
                {
                    dwError = ERROR_SUCCESS;
                }
                BAIL_ON_WIN_ERROR(dwError);
            }

            if (realmName)
            {
                dwError = LwAllocateStringPrintf(
                                      &principalName,
                                      "host/%s@%s",
                                      pszHostname,
                                      realmName);
            }
            else
            {
                dwError = LwAllocateStringPrintf(
                                      &principalName,
                                      "host/%s",
                                      pszHostname);
            }
            BAIL_ON_WIN_ERROR(dwError);

            DISPLAY_COMMENT(("Server principal name [%s]\n", principalName));
        }
        else
        {
            principalName = pszHostname;
            pszHostname = NULL;
        }

        rpc_binding_set_auth_info_2(hBinding,
                                    (unsigned char*)principalName,
                                    ProtectionLevel,
                                    AuthType,
                                    authFlags,
                                    (rpc_auth_identity_handle_t)pAuthInfo,
                                    rpc_c_authz_name, /* authz_protocol */
                                    &rpcStatus);
        if (rpcStatus)
        {
            ntStatus = LwRpcStatusToNtStatus(rpcStatus);
            BAIL_ON_NT_STATUS(ntStatus);
        }
    }

    *phBinding = hBinding;

error:
    if (pIoCreds)
    {
        LwIoDeleteCreds(pIoCreds);
    }

    LW_SAFE_FREE_MEMORY(pwszBindingString);
    LW_SAFE_FREE_MEMORY(pszBindingString);
    LW_SAFE_FREE_MEMORY(pszHostname);
    LW_SAFE_FREE_MEMORY(principalName);
    LW_SAFE_FREE_MEMORY(realmName);

    rpc_string_free(&pszUuid, &rpcStatus);
    rpc_string_free(&pszProtSeq, &rpcStatus);
    rpc_string_free(&pszNetworkAddr, &rpcStatus);
    rpc_string_free(&pszEndpoint, &rpcStatus);
    rpc_string_free(&pszOptions, &rpcStatus);

    LW_SAFE_FREE_MEMORY(machineAccount);
    LW_SAFE_FREE_MEMORY(machinePassword);
    LW_SAFE_FREE_MEMORY(netlogonServer);
    LW_SAFE_FREE_MEMORY(domainName);
    LW_SAFE_FREE_MEMORY(dnsDomainName);
    LW_SAFE_FREE_MEMORY(computerName);

    if (ppszOptions)
    {
        RpctFreeStringList(ppszOptions);
    }

    if (ntStatus == STATUS_SUCCESS &&
        dwError != ERROR_SUCCESS)
    {
        ntStatus = LwWin32ErrorToNtStatus(dwError);
    }

    if (ntStatus)
    {
        *phBinding = NULL;
    }

    return ntStatus;
}


static
DWORD
RpctGuessServerRealmName(
    PCSTR ServerName,
    PSTR *RealmName
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR server = NULL;
    PSTR dnsName = NULL;
    PSTR forestName = NULL;
    PLWNET_DC_INFO dcInfo = NULL;

    error = LwAllocateString(ServerName, &server);
    BAIL_ON_WIN_ERROR(error);

    dnsName = server;
    while (dnsName && strlen(dnsName) > 0)
    {
        DISPLAY_COMMENT(("Guessing realm name for server %s\n",
                         dnsName));

        error = LWNetGetDCName(dnsName,
                               NULL,
                               NULL,
                               0,
                               &dcInfo);
        if (error)
        {
            DISPLAY_COMMENT(("Guessing realm name for domain %s\n",
                             dnsName));

            error = LWNetGetDCName(NULL,
                                   dnsName,
                                   NULL,
                                   0,
                                   &dcInfo);
        }

        if (error == ERROR_SUCCESS &&
            dcInfo->pszDnsForestName)
        {
            error = LwAllocateString(
                              dcInfo->pszDnsForestName,
                              &forestName);
            BAIL_ON_WIN_ERROR(error);

            // Ensure uppercased name because it's a realm name
            LwStrToUpper(forestName);

            DISPLAY_COMMENT(("Found forest name %s\n", forestName));

            break;
        }

        LwStrChr(dnsName, '.', &dnsName);
        if (dnsName)
        {
            dnsName[0] = 0;
            dnsName++;
        }
    }

    if (!dnsName)
    {
        error = ERROR_DC_NOT_FOUND;
    }

error:
    if (error)
    {
        LW_SAFE_FREE_MEMORY(forestName);
    }

    if (dcInfo)
    {
        LWNetFreeDCInfo(dcInfo);
    }

    LW_SAFE_FREE_MEMORY(server);

    *RealmName = forestName;

    return error;
}


VOID
FreeRpcBinding(
    PVOID            *phBinding,
    RPC_BINDING_TYPE  eBindingType
    )
{
    unsigned32 rpcStatus = RPC_S_OK;
    PVOID hBinding = NULL;

    if (phBinding) return;

    hBinding = *phBinding;

    switch (eBindingType)
    {
    case RPC_LSA_BINDING:
        LsaFreeBinding(hBinding);
        break;

    case RPC_SAMR_BINDING:
        SamrFreeBinding(hBinding);
        break;

    case RPC_NETLOGON_BINDING:
        NetrFreeBinding(hBinding);
        break;

    case RPC_DSSETUP_BINDING:
        DsrFreeBinding(hBinding);
        break;

    case RPC_WKSSVC_BINDING:
        WkssFreeBinding(hBinding);
        break;

    default:
        if (phBinding && *phBinding)
        {
            rpc_binding_free((handle_t*)phBinding, &rpcStatus);
        }
        break;
    }

    *phBinding = NULL;
}


DWORD
GetMachinePassword(
    OUT OPTIONAL PWSTR* ppwszDnsDomainName,
    OUT OPTIONAL PWSTR* ppwszDomainName,
    OUT OPTIONAL PWSTR* ppwszMachineSamAccountName,
    OUT OPTIONAL PWSTR* ppwszMachinePassword,
    OUT OPTIONAL PWSTR* ppwszComputerName
    )
{
    DWORD dwError = 0;
    PWSTR pwszDnsDomainName = NULL;
    PWSTR pwszDomainName = NULL;
    PWSTR pwszMachineSamAccountName = NULL;
    PWSTR pwszMachinePassword = NULL;
    PWSTR pwszComputerName = NULL;
    HANDLE hLsaConnection = NULL;
    PLSA_MACHINE_PASSWORD_INFO_A pPasswordInfo = NULL;

    dwError = LsaOpenServer(&hLsaConnection);
    BAIL_ON_WIN_ERROR(dwError);

    dwError = LsaAdGetMachinePasswordInfo(hLsaConnection,
                                          NULL,
                                          &pPasswordInfo);
    BAIL_ON_WIN_ERROR(dwError);

    if (ppwszDnsDomainName)
    {
        dwError = LwMbsToWc16s(pPasswordInfo->Account.DnsDomainName,
                               &pwszDnsDomainName);
        BAIL_ON_WIN_ERROR(dwError);
    }

    if (ppwszDomainName)
    {
        dwError = LwMbsToWc16s(pPasswordInfo->Account.NetbiosDomainName,
                               &pwszDomainName);
        BAIL_ON_WIN_ERROR(dwError);
    }

    if (ppwszMachineSamAccountName)
    {
        dwError = LwMbsToWc16s(pPasswordInfo->Account.SamAccountName,
                               &pwszMachineSamAccountName);
        BAIL_ON_WIN_ERROR(dwError);
    }

    if (ppwszMachinePassword)
    {
        dwError = LwMbsToWc16s(pPasswordInfo->Password,
                               &pwszMachinePassword);
        BAIL_ON_WIN_ERROR(dwError);
    }

    if (ppwszComputerName)
    {
        dwError = LwMbsToWc16s(pPasswordInfo->Account.SamAccountName,
                               &pwszComputerName);
        BAIL_ON_WIN_ERROR(dwError);

        // Remove $ from account name
        pwszComputerName[wc16slen(pwszComputerName) - 1] = 0;
    }

error:
    if (dwError)
    {
        LW_SAFE_FREE_MEMORY(pwszDnsDomainName);
        LW_SAFE_FREE_MEMORY(pwszDomainName);
        LW_SAFE_FREE_MEMORY(pwszMachineSamAccountName);
        LW_SECURE_FREE_WSTRING(pwszMachinePassword);
        LW_SAFE_FREE_MEMORY(pwszComputerName);
    }

    if (hLsaConnection)
    {
        LsaCloseServer(hLsaConnection);
    }

    if (pPasswordInfo)
    {
        LsaAdFreeMachinePasswordInfo(pPasswordInfo);
    }

    if (ppwszDnsDomainName)
    {
        LW_SAFE_FREE_MEMORY(*ppwszDnsDomainName);
        *ppwszDnsDomainName = pwszDnsDomainName;
    }
    if (ppwszDomainName)
    {
        LW_SAFE_FREE_MEMORY(*ppwszDomainName);
        *ppwszDomainName = pwszDomainName;
    }
    if (ppwszMachineSamAccountName)
    {
        LW_SAFE_FREE_MEMORY(*ppwszMachineSamAccountName);
        *ppwszMachineSamAccountName = pwszMachineSamAccountName;
    }
    if (ppwszMachinePassword)
    {
        LW_SAFE_FREE_MEMORY(*ppwszMachinePassword);
        *ppwszMachinePassword = pwszMachinePassword;
    }
    if (ppwszComputerName)
    {
        LW_SAFE_FREE_MEMORY(*ppwszComputerName);
        *ppwszComputerName = pwszComputerName;
    }

    return dwError;
}


DWORD
RpctReadTestSetFile(
    PCSTR TestSetFileName,
    PRPCSERVER Server,
    PARSE_TEST_SET_LINE_FN ParseLineFn,
    PVOID Context,
    PDWORD pTestItemsCount,
    PVOID TestItems
    )
{
    DWORD error = ERROR_SUCCESS;
    FILE *testFile = NULL;
    ssize_t lineSize = 0;
    PSTR itemLine = NULL;
    size_t itemLineSize = 0;
    DWORD testItemsCount = 0;
    PSTR* items = NULL;
    CHAR separator = ':';
    DWORD testItemIndex = 0;

    testFile = fopen(TestSetFileName, "r");
    if (testFile == NULL)
    {
        if (errno == ENOENT)
        {
            DISPLAY_ERROR(("File %s not found", TestSetFileName));
            error = ERROR_FILE_NOT_FOUND;
        }
        else
        {
            error = LwErrnoToWin32Error(errno);
        }
        BAIL_ON_WIN_ERROR(error);
    }

    do
    {
        LW_SAFE_FREE_MEMORY(itemLine);
        itemLineSize = 0;

        lineSize = getline(&itemLine, &itemLineSize, testFile);
        if (lineSize == -1 && !feof(testFile))
        {
            error = LwErrnoToWin32Error(errno);
            BAIL_ON_WIN_ERROR(error);
        }

        if (lineSize <= 1 || IsCommentLine(itemLine))
        {
            continue;
        }

        LW_SAFE_FREE_MEMORY(itemLine);
        itemLineSize = 0;

        testItemsCount++;

    } while (!feof(testFile));

    if (TestItems == NULL)
    {
        //
        // Just return how many items are expected so caller
        // can allocate the memory
        //
        goto error;
    }

    if (fseek(testFile, 0, SEEK_SET))
    {
        error = LwErrnoToWin32Error(errno);
        BAIL_ON_WIN_ERROR(error);
    }

    do
    {
        LW_SAFE_FREE_MEMORY(itemLine);
        itemLineSize = 0;

        lineSize = getline(&itemLine, &itemLineSize, testFile);
        if (lineSize == -1 && !feof(testFile))
        {
            error = LwErrnoToWin32Error(errno);
            BAIL_ON_WIN_ERROR(error);
        }

        if (lineSize <= 1 || IsCommentLine(itemLine))
        {
            continue;
        }

        items = RpctGetStringList(itemLine, separator);

        error = ParseLineFn(
                     items,
                     Server,
                     Context,
                     testItemIndex,
                     TestItems);
        BAIL_ON_WIN_ERROR(error);

        RpctFreeStringList(items);

        testItemIndex++;

    } while (!feof(testFile));

error:
    if (error)
    {
        DISPLAY_ERROR(("Error when parsing line:\n%s\n", itemLine));
    }

    if (testFile)
    {
        fclose(testFile);
    }

    *pTestItemsCount = testItemsCount;

    return error;
}


static
BOOLEAN
IsCommentLine(
    PSTR Line
    )
{
    LwStripLeadingWhitespace(Line);
    return (*Line == '#');
}


/*
local variables:
mode: c
c-basic-offset: 4
indent-tabs-mode: nil
tab-width: 4
end:
*/
