/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright (c) 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@likewise.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *     lwkeytab.c
 *
 * Abstract:
 *
 *     Kerberos Keytab Helper API
 *
 * Authors: Danilo Almeida (dalmeida@likewise.com)
 */

#include "includes.h"
#include <lw/rtlgoto.h>


#define PTHREAD_CALL_MUST_SUCCEED(Call) \
    do { \
        int localError = Call; \
        LSA_ASSERT(localError == 0); \
    } while (0)

#define LOG_LEAVE_ON_WINERROR_EE(winError, EE) \
    do { \
        if (winError || EE) \
        { \
            LW_RTL_LOG_DEBUG("LEAVE: -> winError = %u (%s) (EE = %d) ", \
                             winError, LwWin32ErrorToName(winError), EE); \
        } \
    } while (0)

// In an ideal world, we would use PTHREAD_MUTEX_INITIALIZER.
// However, on some platforms, pthread_mutex_lock can still
// fail because the initializer requires subsequent memory allocation.
// Therefore, we rely on pthread_once instead.

#ifndef BROKEN_ONCE_INIT
#if defined(sun) || defined(_AIX)
#define BROKEN_ONCE_INIT 1
#else
#define BROKEN_ONCE_INIT 0
#endif
#endif

#if BROKEN_ONCE_INIT
#define ONCE_INIT {PTHREAD_ONCE_INIT}
#else
#define ONCE_INIT PTHREAD_ONCE_INIT
#endif

typedef struct _LW_KEYTAB_STATE {
    struct {
        pthread_once_t OnceControl;
        DWORD Error;
        BOOLEAN Done;
    } Init;
    pthread_mutex_t Mutex;
    pthread_mutex_t* pMutex;
    LONG RefCount;
} LW_KEYTAB_STATE, *PLW_KEYTAB_STATE;

static LW_KEYTAB_STATE LwpKeytabState = { { ONCE_INIT } };

static
VOID
LwpKeytabCleanupLibraryInternal(
    VOID
    )
{
    PLW_KEYTAB_STATE pState = &LwpKeytabState;
    pthread_once_t onceControl = ONCE_INIT;

    if (pState->pMutex)
    {
        pthread_mutex_destroy(pState->pMutex);
        pState->pMutex = NULL;
    }

    pState->Init.OnceControl = onceControl;
    pState->Init.Error = 0;
    pState->Init.Done = FALSE;
}

static
DWORD
LwpKeytabInitializeLibraryInternal(
    VOID
    )
{
    DWORD winError = 0;
    int EE = 0;
    PLW_KEYTAB_STATE pState = &LwpKeytabState;
    int error = 0;

    if (pState->Init.Done)
    {
        LwInterlockedIncrement(&pState->RefCount);

        winError = pState->Init.Error;
        GOTO_CLEANUP_EE(EE);
    }

    error = pthread_mutex_init(&pState->Mutex, NULL);
    winError = LwErrnoToWin32Error(error);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    pState->pMutex = &pState->Mutex;

cleanup:
    if (winError)
    {
        LwpKeytabCleanupLibraryInternal();
    }

    pState->Init.Error = winError;
    pState->Init.Done = TRUE;

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
VOID
LwpKeytabInitializeLibraryOnce(
    VOID
    )
{
    LwpKeytabInitializeLibraryInternal();
}

DWORD
LwpKeytabEnsureInitialized(
    VOID
    )
{
    DWORD winError = 0;
    int EE = 0;
    PLW_KEYTAB_STATE pState = &LwpKeytabState;

    if (pState->RefCount)
    {
        pthread_once(&pState->Init.OnceControl, LwpKeytabInitializeLibraryOnce);
        winError = pState->Init.Error;
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
    }
    else
    {
        winError = ERROR_DLL_INIT_FAILED;
        GOTO_CLEANUP_EE(EE);
    }

cleanup:
    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

__attribute__((constructor))
VOID
LwpKeytabInitializeLibrary(
    VOID
    )
{
    LwInterlockedIncrement(&LwpKeytabState.RefCount);
}

__attribute__((destructor))
VOID
LwpKeytabCleanupLibrary(
    VOID
    )
{
    if (0 == LwInterlockedDecrement(&LwpKeytabState.RefCount))
    {
        LwpKeytabCleanupLibraryInternal();
    }
}

static
inline
VOID
LwpKrbFreeContext(
    IN krb5_context Context
    )
{
    krb5_free_context(Context);
}

static
DWORD
LwpKrbInitContext(
    OUT krb5_context* Context
    )
{
    DWORD winError = ERROR_SUCCESS;
    krb5_error_code krbError = 0;
    krb5_context context = NULL;

    krbError = krb5_init_context(&context);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR(winError);

cleanup:
    if (winError)
    {
        LwpKrbFreeContext(context);
        context = NULL;
    }

    *Context = context;

    return winError;
}

static
DWORD
LwpKrbFixEmptyKeytabFile(
    IN PCSTR pPath
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    struct stat statBuffer = { 0 };
    int fd = -1;

    // Fix up a "zero"-length keytab file (technically anything too small).
    if ((stat(pPath, &statBuffer) == 0) &&
        (statBuffer.st_size < 2))
    {
        // The file is empty. Add the version number to the file so kerberos
        // will accept it.
        char bytes[] = { 0x05, 0x02 };

        fd = open(pPath, O_WRONLY, 0);
        if (fd < 0)
        {
            winError = LwMapErrnoToLwError(errno);
            GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
        }

        if (write(fd, &bytes, sizeof(bytes)) < 0)
        {
            winError = LwMapErrnoToLwError(errno);
            GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
        }
    }

cleanup:
    if (fd >= 0)
    {
        close(fd);
        fd = -1;
    }

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
DWORD
LwpKrbGetDefaultKeytabPath(
    IN krb5_context Context,
    OUT PSTR* ppPath
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    krb5_error_code krbError = 0;
    PSTR pPath = NULL;
    PSTR pDefaultName = NULL;
    PSTR pNewDefaultName = NULL;
    // Do not free
    PSTR pPathPart = NULL;
    int size = 32;
    const int maxSize = 1024;

    for (;;)
    {
        size += size;
        if (size > maxSize)
        {
            winError = ERROR_BUFFER_OVERFLOW;
            GOTO_CLEANUP_EE(EE);
        }

        winError = LwReallocMemory(
                        pDefaultName,
                        OUT_PPVOID(&pNewDefaultName),
                        size);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

        pDefaultName = pNewDefaultName;
        pNewDefaultName = NULL;

        krbError = krb5_kt_default_name(Context, pDefaultName, size);
        if (krbError == KRB5_CONFIG_NOTENUFSPACE)
        {
            continue;
        }
        winError = LwMapKrb5ErrorToLwError(krbError);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

        LwStrChr(pDefaultName, ':', &pPathPart);

        if (!pPathPart)
        {
            pPathPart = pDefaultName;
        }
        else
        {
            if (!strncmp("WRFILE:", pDefaultName, strlen("WRFILE:")) ||
                !strncmp("FILE:", pDefaultName, strlen("FILE:")))
            {
                pPathPart++;
            }
            else
            {
                winError = ERROR_INVALID_PARAMETER;
                GOTO_CLEANUP_EE(EE);
            }
        }
        break;
    }

    if (strlen(pPathPart) < 1)
    {
        winError = ERROR_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }

    winError = LwAllocateString(pPathPart, &pPath);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

cleanup:
    if (winError)
    {
        LW_SAFE_FREE_STRING(pPath);
    }

    LW_SAFE_FREE_STRING(pDefaultName);
    LW_SAFE_FREE_STRING(pNewDefaultName);

    *ppPath = pPath;

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
inline
VOID
LwpKrbCloseKeytab(
    IN krb5_context Context,
    IN krb5_keytab Keytab
    )
{
    if (Keytab)
    {
        krb5_kt_close(Context, Keytab);
    }
}


static
DWORD
LwpKrbResolveKeytab(
    IN krb5_context Context,
    OUT krb5_keytab* Keytab,
    IN PCSTR OPTIONAL KeytabName,
    IN BOOLEAN IsWrite
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    krb5_error_code krbError = 0;
    krb5_keytab keytab = NULL;
    PCSTR keytabName = KeytabName;
    PSTR freeKeytabName = NULL;

    if (!keytabName)
    {
        winError = LwpKrbGetDefaultKeytabPath(Context, &freeKeytabName);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

        keytabName = freeKeytabName;
    }

    winError = LwpKrbFixEmptyKeytabFile(keytabName);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    krbError = krb5_kt_resolve(Context, keytabName, &keytab);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

cleanup:
    if (winError)
    {
        LwpKrbCloseKeytab(Context, keytab);
        keytab = NULL;
    }

    LW_SAFE_FREE_MEMORY(freeKeytabName);

    *Keytab = keytab;

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
VOID
LwpKrbFreeKeytabEntries(
    IN krb5_context Context,
    IN krb5_keytab_entry* Entries,
    IN ULONG Count
    )
{
    ULONG i = 0;

    for (i = 0; i < Count; i++)
    {
        krb5_free_principal(Context, Entries[i].principal);
        krb5_free_keyblock_contents(Context, &Entries[i].key);
    }

    LW_SAFE_FREE_MEMORY(Entries);
}

static
DWORD
LwpKrbSearchKeytabKeys(
    IN krb5_context Context,
    IN krb5_keytab Keytab,
    IN PCSTR Principal,
    OUT krb5_keytab_entry** Entries,
    OUT PULONG EntriesCount
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    krb5_error_code krbError = 0;
    krb5_principal principal = NULL;
    krb5_kt_cursor cursor = NULL;
    krb5_keytab_entry entry = { 0 };
    krb5_keytab_entry* freeEntryContents = { 0 };
    krb5_keytab_entry* entries = NULL;
    krb5_keytab_entry* newEntries = NULL;
    ULONG count = 0;

    krbError = krb5_parse_name(Context, Principal, &principal);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    krbError = krb5_kt_start_seq_get(Context, Keytab, &cursor);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    for (;;)
    {
        krbError = krb5_kt_next_entry(Context, Keytab, &entry, &cursor);
        if (krbError == KRB5_KT_END)
        {
            break;
        }
        winError = LwMapKrb5ErrorToLwError(krbError);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

        freeEntryContents = &entry;

        if (krb5_principal_compare(Context, entry.principal, principal))
        {
            winError = LwReallocMemory(entries,
                                       OUT_PPVOID(&newEntries),
                                       (count + 1) * sizeof(entries[0]));
            GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

            entries = newEntries;
            newEntries = NULL;

            memset(&entries[count], 0, sizeof(entries[0]));
            count++;

            entries[count - 1].magic     = entry.magic;
            entries[count - 1].timestamp = entry.timestamp;
            entries[count - 1].vno  = entry.vno;

            krbError = krb5_copy_principal(
                            Context,
                            entry.principal,
                            &entries[count - 1].principal);
            winError = LwMapKrb5ErrorToLwError(krbError);
            GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

            krbError = krb5_copy_keyblock_contents(
                            Context,
                            &entry.key,
                            &entries[count - 1].key);
            winError = LwMapKrb5ErrorToLwError(krbError);
            GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
        }

        krb5_free_keytab_entry_contents(Context, &entry);
        freeEntryContents = NULL;
    }

    if (count == 0)
    {
        winError = ERROR_FILE_NOT_FOUND;
        GOTO_CLEANUP_EE(EE);
    }

cleanup:
    if (winError)
    {
        LwpKrbFreeKeytabEntries(Context, entries, count);
        entries = NULL;
        count = 0;
    }

    if (freeEntryContents)
    {
        krb5_free_keytab_entry_contents(Context, freeEntryContents);
    }
    if (cursor)
    {
        krb5_kt_end_seq_get(Context, Keytab, &cursor);
    }
    krb5_free_principal(Context, principal);

    *Entries = entries;
    *EntriesCount = count;

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
DWORD
LwpKrbRemoveStaleKeytabEntries(
    IN krb5_context Context,
    IN krb5_keytab Keytab,
    IN PCSTR Principal,
    IN ULONG KeyVersionNumber
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    krb5_error_code krbError = 0;
    krb5_kvno kvno = KeyVersionNumber;
    krb5_octet previousKvno = 0;
    krb5_keytab_entry* entries = NULL;
    ULONG entriesCount = 0;
    ULONG i = 0;

    // Search fo existing keys for this principal.
    winError = LwpKrbSearchKeytabKeys(
                    Context,
                    Keytab,
                    Principal,
                    &entries,
                    &entriesCount);
    if ((winError == ERROR_FILE_NOT_FOUND) ||
        (winError == LW_ERROR_KRB5_NO_KEYS_FOUND))
    {
        // No key has been found or the file does not exist,
        // so there is nothing to remove.
        winError = ERROR_SUCCESS;
        GOTO_CLEANUP_EE(EE);
    }
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    // Find the latest version of this key and remove old ones.
    // Key versions are stored as a single byte.
    previousKvno = (krb5_octet)(kvno - 1);
    for (i = 0; i < entriesCount; i++)
    {
        if (previousKvno == entries[i].vno)
        {
            // Don't remove the keys with just one version number older.
            continue;
        }
        else
        {
            krbError = krb5_kt_remove_entry(Context, Keytab, &entries[i]);
            winError = LwMapKrb5ErrorToLwError(krbError);
            GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
        }
    }

cleanup:
    if (entries)
    {
        LwpKrbFreeKeytabEntries(Context, entries, entriesCount);
    }

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
DWORD
LwpKrbAddKeytabKeys(
    IN krb5_context Context,
    IN krb5_keytab Keytab,
    IN PCSTR Principal,
    IN ULONG KeyVersionNumber,
    IN PCSTR Password,
    IN PCSTR SaltPrincipal,
    IN OPTIONAL PCSTR KeytabPath
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    krb5_error_code krbError = 0;
    const krb5_enctype encTypes[] = {
        ENCTYPE_DES_CBC_CRC,
        ENCTYPE_DES_CBC_MD5,
        ENCTYPE_ARCFOUR_HMAC };
    krb5_principal principal = NULL;
    krb5_principal saltPrincipal = NULL;
    krb5_kvno kvno = KeyVersionNumber;
    krb5_data salt = { 0 };
    // Do not free
    krb5_data password = { 0 };
    krb5_keyblock key = { 0 };
    DWORD i = 0;

    winError = LwpKrbRemoveStaleKeytabEntries(
                    Context,
                    Keytab,
                    Principal,
                    KeyVersionNumber);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    // Prepare new key for this principal
    krbError = krb5_parse_name(Context, Principal, &principal);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    krbError = krb5_parse_name(Context, SaltPrincipal, &saltPrincipal);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    krbError = krb5_principal2salt(Context, saltPrincipal, &salt);
    winError = LwMapKrb5ErrorToLwError(krbError);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    password.data = (PVOID) Password;
    password.length = strlen(Password);

    // Add key entry for each encryption type
    for (i = 0; i < sizeof(encTypes)/sizeof(encTypes[0]); i++)
    {
        krb5_keytab_entry entry = { 0 };

        krbError = krb5_c_string_to_key(Context,
                                        encTypes[i],
                                        &password,
                                        &salt,
                                        &key);
        winError = LwMapKrb5ErrorToLwError(krbError);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

        entry.principal = principal;
        entry.vno = kvno;
        entry.key = key;

        krbError = krb5_kt_add_entry(Context, Keytab, &entry);
        winError = LwMapKrb5ErrorToLwError(krbError);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

        krb5_free_keyblock_contents(Context, &key);
    }

cleanup:
    krb5_free_principal(Context, principal);
    krb5_free_principal(Context, saltPrincipal);
    krb5_free_keyblock_contents(Context, &key);
    krb5_free_data_contents(Context, &salt);

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

static
DWORD
LwpKrbCreateComputerSaltPrincipal(
    OUT PSTR* SaltPrincipal,
    IN PCSTR SamAccountName,
    IN PCSTR DnsDomainName
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    PSTR realm = NULL;
    PSTR suffix = NULL;
    PSTR computerName = NULL;
    size_t length = 0;
    PSTR saltPrincipal = NULL;

    winError = LwAllocateString(DnsDomainName, &realm);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    LwStrToUpper(realm);

    winError = LwAllocateString(DnsDomainName, &suffix);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    LwStrToLower(suffix);

    winError = LwAllocateString(SamAccountName, &computerName);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    length = strlen(computerName);
    if (length < 1)
    {
        LW_RTL_LOG_DEBUG("Computer name is empty");
        winError = ERROR_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }

    if (computerName[length-1] != '$')
    {
        LW_RTL_LOG_DEBUG("Computer name is missing trailing $ (%s)",
                         computerName);
        winError = ERROR_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }

    computerName[length-1] = 0;
    LwStrToLower(computerName);

    winError = LwAllocateStringPrintf(
                    &saltPrincipal,
                    "host/%s.%s@%s",
                    computerName,
                    suffix,
                    realm);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

cleanup:
    if (winError)
    {
        LW_SAFE_FREE_MEMORY(saltPrincipal);
    }

    LW_SAFE_FREE_MEMORY(realm);
    LW_SAFE_FREE_MEMORY(suffix);
    LW_SAFE_FREE_MEMORY(computerName);

    *SaltPrincipal = saltPrincipal;

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

DWORD
LwKrbGetKeytabDefaultComputerPrincipals(
    IN PCSTR SamAccountName,
    IN PCSTR DnsDomainName,
    IN PCSTR Fqdn,
    OUT PSTR** PrincipalNames,
    OUT PULONG PrincipalNamesCount
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    PSTR* principals = NULL;
    ULONG count = 0;
    ULONG i = 0;
    PSTR samAccountNameUpper = NULL;
    PSTR realm = NULL;
    PSTR fqdnHostLower = NULL;
    PSTR fqdnHostUpper = NULL;
    PSTR fqdnSuffixLower = NULL;
    PSTR fqdnSuffixUpper = NULL;
    // Do not free
    PSTR period = NULL;
    PCSTR services[] = { "host", "cifs" };
    PSTR* hosts[] = { &fqdnHostLower, &fqdnHostUpper };
    PSTR* suffixes[] = { &fqdnSuffixLower, &fqdnSuffixUpper };
    ULONG serviceIndex = 0;
    ULONG hostIndex = 0;
    ULONG suffixIndex = 0;

    winError = LwAllocateString(SamAccountName, &samAccountNameUpper);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    winError = LwAllocateString(DnsDomainName, &realm);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    winError = LwAllocateString(Fqdn, &fqdnHostLower);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    period = strchr(fqdnHostLower, '.');
    if (!period)
    {
        winError = ERROR_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }

    if (strlen(period+1) < 1)
    {
        winError = ERROR_INVALID_PARAMETER;
        GOTO_CLEANUP_EE(EE);
    }

    period[0] = 0;

    winError = LwAllocateString(fqdnHostLower, &fqdnHostUpper);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    winError = LwAllocateString(period+1, &fqdnSuffixLower);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    winError = LwAllocateString(period+1, &fqdnSuffixUpper);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    LwStrToUpper(samAccountNameUpper);
    LwStrToUpper(realm);

    LwStrToLower(fqdnHostLower);
    LwStrToLower(fqdnSuffixLower);

    LwStrToUpper(fqdnHostUpper);
    LwStrToUpper(fqdnSuffixUpper);

    // 11 principals:
    //
    // MACHINE$@REALM
    //
    // NOTE: Should the below be FQDN_HOST/fqdn_host or
    //       just MACHINE/machine.
    //
    // host/FQDN_HOST@REALM
    // host/fqdn_host@REALM
    //
    // host/FQDN@REALM
    // host/fqdn@REALM
    // host/FQDN_HOST.fqdn_suffix@REALM
    // host/fqdn_host.FQDN_SUFFIX@REALM
    //
    // cifs/FQDN@REALM
    // cifs/fqdn@REALM
    // cifs/FQDN_HOST.fqdn_suffix@REALM
    // cifs/fqdn_host.FQDN_SUFFIX@REALM
    //

    // NOTE: Why not cifs/MACHINE@REALM
    // NOTE: Why not cifs/machine@REALM

    count = 11;
    winError = LwAllocateMemory(count * sizeof(principals[0]),
                                OUT_PPVOID(&principals));
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    assert(i < count);
    winError = LwAllocateStringPrintf(
                    &principals[i++],
                    "%s@%s",
                    samAccountNameUpper,
                    realm);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    assert(i < count);
    winError = LwAllocateStringPrintf(
                    &principals[i++],
                    "host/%s@%s",
                    fqdnHostUpper,
                    realm);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    assert(i < count);
    winError = LwAllocateStringPrintf(
                    &principals[i++],
                    "host/%s@%s",
                    fqdnHostLower,
                    realm);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    for (serviceIndex = 0;
         serviceIndex < sizeof(services)/sizeof(services[0]);
         serviceIndex++)
    {
        PCSTR service = services[serviceIndex];
        for (hostIndex = 0;
             hostIndex < sizeof(hosts)/sizeof(hosts[0]);
             hostIndex++)
        {
            PCSTR host = *hosts[hostIndex];
            for (suffixIndex = 0;
                 suffixIndex < sizeof(suffixes)/sizeof(suffixes[0]);
                 suffixIndex++)
            {
                PCSTR suffix = *suffixes[suffixIndex];

                assert(i < count);
                winError = LwAllocateStringPrintf(
                                &principals[i++],
                                "%s/%s.%s@%s",
                                service,
                                host,
                                suffix,
                                realm);
                GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
            }
        }
    }


cleanup:
    if (winError)
    {
        LwFreeStringArray(principals, count);
        principals = NULL;
        count = 0;
    }

    LW_SAFE_FREE_MEMORY(samAccountNameUpper);
    LW_SAFE_FREE_MEMORY(realm);
    LW_SAFE_FREE_MEMORY(fqdnHostLower);
    LW_SAFE_FREE_MEMORY(fqdnHostUpper);
    LW_SAFE_FREE_MEMORY(fqdnSuffixLower);
    LW_SAFE_FREE_MEMORY(fqdnSuffixUpper);

    *PrincipalNames = principals;
    *PrincipalNamesCount = count;

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}

DWORD
LwKrbAddKeytabComputerPrincipals(
    IN PCSTR SamAccountName,
    IN PCSTR DnsDomainName,
    IN ULONG KeyVersionNumber,
    IN PCSTR Password,
    IN PCSTR OPTIONAL KeytabName,
    IN PSTR* PrincipalNames,
    IN ULONG PrincipalNamesCount
    )
{
    DWORD winError = ERROR_SUCCESS;
    int EE = 0;
    BOOLEAN isLocked = FALSE;
    PSTR saltPrincipal = NULL;
    krb5_context context = NULL;
    krb5_keytab keytab = NULL;
    ULONG i = 0;

    winError = LwpKeytabEnsureInitialized();
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    winError = LwpKrbCreateComputerSaltPrincipal(
                    &saltPrincipal,
                    SamAccountName,
                    DnsDomainName);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    winError = LwpKrbInitContext(&context);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    PTHREAD_CALL_MUST_SUCCEED(pthread_mutex_lock(LwpKeytabState.pMutex));
    isLocked = TRUE;

    winError = LwpKrbResolveKeytab(context, &keytab, KeytabName, TRUE);
    GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);

    for (i = 0; i < PrincipalNamesCount; i++)
    {
        winError = LwpKrbAddKeytabKeys(
                        context,
                        keytab,
                        PrincipalNames[i],
                        KeyVersionNumber,
                        Password,
                        saltPrincipal,
                        NULL);
        GOTO_CLEANUP_ON_WINERROR_EE(winError, EE);
    }

cleanup:
    LW_SAFE_FREE_MEMORY(saltPrincipal);
    LwpKrbCloseKeytab(context, keytab);
    LwpKrbFreeContext(context);

    if (isLocked)
    {
        PTHREAD_CALL_MUST_SUCCEED(pthread_mutex_unlock(LwpKeytabState.pMutex));
    }

    LOG_LEAVE_ON_WINERROR_EE(winError, EE);
    return winError;
}
