/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * * -*- mode: c, c-basic-offset: 4 -*- */ /* * 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 . * * 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 */ /* * Copyright (C) Likewise Software. All rights reserved. * * Module Name: * * lwkrb5.c * * Abstract: * * Likewise Advanced API (lwadvapi) * * Kerberos 5 API * * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com) * Sriram Nambakam (snambakam@likewisesoftware.com) * Rafal Szczesniak * Kyle Stemen (kstemen@likewisesoftware.com) * */ #include "includes.h" DWORD LwKrb5GetDefaultRealm( PSTR* ppszRealm ) { DWORD dwError = 0; krb5_context ctx = NULL; PSTR pszKrb5Realm = NULL; PSTR pszRealm = NULL; krb5_init_context(&ctx); krb5_get_default_realm(ctx, &pszKrb5Realm); if (LW_IS_NULL_OR_EMPTY_STR(pszKrb5Realm)) { dwError = LW_ERROR_NO_DEFAULT_REALM; BAIL_ON_LW_ERROR(dwError); } dwError = LwAllocateString(pszKrb5Realm, &pszRealm); BAIL_ON_LW_ERROR(dwError); *ppszRealm = pszRealm; cleanup: if (pszKrb5Realm) { krb5_free_default_realm(ctx, pszKrb5Realm); } krb5_free_context(ctx); return(dwError); error: *ppszRealm = NULL; LW_SAFE_FREE_STRING(pszRealm); goto cleanup; } DWORD LwKrb5GetSystemCachePath( PSTR* ppszCachePath ) { DWORD dwError = 0; PSTR pszCachePath = NULL; krb5_context ctx = NULL; const char *pszKrbDefault = NULL; krb5_error_code ret = 0; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); pszKrbDefault = krb5_cc_default_name(ctx); dwError = LwAllocateString( pszKrbDefault, &pszCachePath); BAIL_ON_LW_ERROR(dwError); *ppszCachePath = pszCachePath; cleanup: if (ctx) { krb5_free_context(ctx); } return dwError; error: *ppszCachePath = NULL; goto cleanup; } DWORD LwKrb5GetUserCachePath( uid_t uid, Krb5CacheType cacheType, PSTR* ppszCachePath ) { DWORD dwError = 0; PSTR pszCachePath = NULL; switch (cacheType) { case KRB5_InMemory_Cache: dwError = LwAllocateStringPrintf( &pszCachePath, "MEMORY:krb5cc_%ld", (long)uid); BAIL_ON_LW_ERROR(dwError); break; case KRB5_File_Cache: dwError = LwAllocateStringPrintf( &pszCachePath, "FILE:/tmp/krb5cc_%ld", (long)uid); BAIL_ON_LW_ERROR(dwError); break; default: dwError = LW_ERROR_INVALID_PARAMETER; BAIL_ON_LW_ERROR(dwError); break; } *ppszCachePath = pszCachePath; cleanup: return dwError; error: *ppszCachePath = NULL; goto cleanup; } DWORD LwKrb5SetDefaultCachePath( PCSTR pszCachePath, PSTR* ppszOrigCachePath ) { DWORD dwError = 0; DWORD dwMajorStatus = 0; DWORD dwMinorStatus = 0; PSTR pszOrigCachePath = NULL; // Set the default for gss dwMajorStatus = gss_krb5_ccache_name( (OM_uint32 *)&dwMinorStatus, pszCachePath, (ppszOrigCachePath) ? (const char**)&pszOrigCachePath : NULL); BAIL_ON_SEC_ERROR(dwMajorStatus); if (ppszOrigCachePath) { if (!LW_IS_NULL_OR_EMPTY_STR(pszOrigCachePath)) { dwError = LwAllocateString(pszOrigCachePath, ppszOrigCachePath); BAIL_ON_LW_ERROR(dwError); } else { *ppszOrigCachePath = NULL; } } cleanup: return dwError; error: if (ppszOrigCachePath) { *ppszOrigCachePath = NULL; } goto cleanup; } DWORD LwKrb5GetSystemKeytabPath( PSTR* ppszKeytabPath ) { DWORD dwError = LW_ERROR_SUCCESS; krb5_error_code ret = 0; krb5_context ctx = NULL; PSTR pszPath = NULL; size_t size = 64; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); do { LW_SAFE_FREE_STRING(pszPath); size *= 2; dwError = LwAllocateMemory(size, OUT_PPVOID(&pszPath)); BAIL_ON_LW_ERROR(dwError); ret = krb5_kt_default_name(ctx, pszPath, size); } while (ret == KRB5_CONFIG_NOTENUFSPACE); BAIL_ON_KRB_ERROR(ctx, ret); *ppszKeytabPath = pszPath; cleanup: if (ctx) { krb5_free_context(ctx); } return dwError; error: LW_SAFE_FREE_STRING(pszPath); *ppszKeytabPath = NULL; goto cleanup; } DWORD LwKrb5SetProcessDefaultCachePath( PCSTR pszCachePath ) { DWORD dwError = 0; PSTR pszEnvironmentEntry = NULL; static volatile PSTR pszSavedEnvironmentEntry = NULL; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; BOOLEAN bLocked = FALSE; dwError = pthread_mutex_lock(&lock); if (dwError) { dwError = LwMapErrnoToLwError(dwError); BAIL_ON_LW_ERROR(dwError); } bLocked = TRUE; dwError = LwAllocateStringPrintf(&pszEnvironmentEntry, "KRB5CCNAME=%s", pszCachePath); BAIL_ON_LW_ERROR(dwError); /* * putenv requires that the buffer not be free'd. */ if (putenv(pszEnvironmentEntry) < 0) { dwError = LwMapErrnoToLwError(errno); BAIL_ON_LW_ERROR(dwError); } LW_SAFE_FREE_STRING(pszSavedEnvironmentEntry); pszSavedEnvironmentEntry = pszEnvironmentEntry; pszEnvironmentEntry = NULL; error: LW_SAFE_FREE_STRING(pszEnvironmentEntry); if (bLocked) { pthread_mutex_unlock(&lock); } return dwError; } DWORD LwSetupMachineSession( PCSTR pszSamAccountName, PCSTR pszPassword, PCSTR pszRealm, PCSTR pszDomain, PDWORD pdwGoodUntilTime ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszHostKeytabFile = NULL; PSTR pszKrb5CcPath = NULL; PSTR pszDomname = NULL; PSTR pszRealmCpy = NULL; PSTR pszMachPrincipal = NULL; DWORD dwGoodUntilTime = 0; dwError = LwKrb5GetSystemKeytabPath(&pszHostKeytabFile); BAIL_ON_LW_ERROR(dwError); dwError = LwKrb5GetSystemCachePath(&pszKrb5CcPath); BAIL_ON_LW_ERROR(dwError); dwError = LwAllocateString(pszRealm, &pszRealmCpy); BAIL_ON_LW_ERROR(dwError); LwStrToUpper(pszRealmCpy); dwError = LwAllocateStringPrintf(&pszMachPrincipal, "%s@%s", pszSamAccountName, pszRealm); BAIL_ON_LW_ERROR(dwError); dwError = LwAllocateString(pszDomain, &pszDomname); BAIL_ON_LW_ERROR(dwError); LwStrToLower(pszDomname); dwError = LwKrb5GetTgt( pszMachPrincipal, pszPassword, pszKrb5CcPath, &dwGoodUntilTime); BAIL_ON_LW_ERROR(dwError); if (pdwGoodUntilTime) { *pdwGoodUntilTime = dwGoodUntilTime; } cleanup: LW_SAFE_FREE_STRING(pszMachPrincipal); LW_SAFE_FREE_STRING(pszDomname); LW_SAFE_FREE_STRING(pszRealmCpy); LW_SAFE_FREE_STRING(pszKrb5CcPath); LW_SAFE_FREE_STRING(pszHostKeytabFile); return (dwError); error: if (pdwGoodUntilTime) { *pdwGoodUntilTime = 0; } goto cleanup; } DWORD LwKrb5CleanupMachineSession( VOID ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszKrb5CcPath = NULL; krb5_error_code ret = 0; krb5_context ctx = NULL; krb5_ccache cc = NULL; dwError = LwKrb5GetSystemCachePath(&pszKrb5CcPath); BAIL_ON_LW_ERROR(dwError); ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_resolve(ctx, pszKrb5CcPath, &cc); if (KRB5_FCC_NOFILE == ret) { goto cleanup; } BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_destroy(ctx, cc); // This always frees the cc reference, even on error. cc = NULL; if (KRB5_FCC_NOFILE == ret) { goto cleanup; } BAIL_ON_KRB_ERROR(ctx, ret); cleanup: LW_SAFE_FREE_STRING(pszKrb5CcPath); if (cc) { // ctx must be valid. krb5_cc_close(ctx, cc); } if (ctx) { krb5_free_context(ctx); } return dwError; error: goto cleanup; } #define BAIL_ON_DCE_ERROR(dest, status) \ if ((status) != 0) { \ LW_LOG_ERROR("DCE Error [Code:%d]", (status)); \ (dest) = LW_ERROR_DCE_CALL_FAILED; \ goto error; \ } DWORD LwKrb5CopyFromUserCache( krb5_context ctx, krb5_ccache destCC, uid_t uid ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszCachePath = NULL; krb5_ccache srcCC = NULL; krb5_cc_cursor srcPos = NULL; krb5_cc_cursor destPos = NULL; // Free with krb5_free_cred_contents krb5_creds srcCreds = {0}; // Free with krb5_free_cred_contents krb5_creds destCreds = {0}; krb5_error_code ret = 0; krb5_principal destClient = 0; BOOLEAN bIncludeTicket = TRUE; DWORD dwTime = 0; ret = krb5_cc_get_principal( ctx, destCC, &destClient); BAIL_ON_KRB_ERROR(ctx, ret); dwError = LwKrb5GetUserCachePath( uid, KRB5_File_Cache, &pszCachePath); BAIL_ON_LW_ERROR(dwError); ret = krb5_cc_resolve( ctx, pszCachePath, &srcCC); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_start_seq_get( ctx, srcCC, &srcPos); if (ret == KRB5_FCC_NOFILE) { // The cache file does not exist ret = 0; goto cleanup; } if (ret == KRB5_CC_FORMAT) { // Some other user put a bad cc in place - don't copy anything // from it. ret = 0; goto cleanup; } BAIL_ON_KRB_ERROR(ctx, ret); dwTime = time(NULL); while (1) { krb5_free_cred_contents( ctx, &srcCreds); ret = krb5_cc_next_cred( ctx, srcCC, &srcPos, &srcCreds); if (ret == KRB5_CC_FORMAT) { break; } else if (ret == KRB5_CC_END) { break; } else { BAIL_ON_KRB_ERROR(ctx, ret); } if (!krb5_principal_compare(ctx, destClient, srcCreds.client)) { /* Can't keep these creds. The client principal doesn't * match. */ continue; } if ( srcCreds.times.endtime < dwTime ) { /* Credentials are too old. */ continue; } if (destPos != NULL) { krb5_cc_end_seq_get( ctx, destCC, &destPos); destPos = NULL; } ret = krb5_cc_start_seq_get( ctx, destCC, &destPos); BAIL_ON_KRB_ERROR(ctx, ret); bIncludeTicket = TRUE; while(bIncludeTicket) { krb5_free_cred_contents( ctx, &destCreds); ret = krb5_cc_next_cred( ctx, destCC, &destPos, &destCreds); if (ret == KRB5_CC_END) { break; } else { BAIL_ON_KRB_ERROR(ctx, ret); } if (krb5_principal_compare( ctx, destCreds.server, srcCreds.server)) { /* These credentials are already in the dest cache */ bIncludeTicket = FALSE; } } if (bIncludeTicket) { // These creds can go in the new cache ret = krb5_cc_store_cred(ctx, destCC, &srcCreds); BAIL_ON_KRB_ERROR(ctx, ret); } } cleanup: LW_SAFE_FREE_STRING(pszCachePath); if (ctx != NULL) { if (srcPos != NULL) { krb5_cc_end_seq_get( ctx, srcCC, &srcPos); } if (destPos != NULL) { krb5_cc_end_seq_get( ctx, destCC, &destPos); } if (srcCC != NULL) { krb5_cc_close(ctx, srcCC); } krb5_free_cred_contents(ctx, &srcCreds); krb5_free_cred_contents(ctx, &destCreds); if (destClient != NULL) { krb5_free_principal(ctx, destClient); } } return dwError; error: goto cleanup; } DWORD LwKrb5MoveCCacheToUserPath( krb5_context ctx, PCSTR pszNewCacheName, uid_t uid, gid_t gid ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszCachePath = NULL; PCSTR pszCachePathReal = NULL; dwError = LwKrb5GetUserCachePath( uid, KRB5_File_Cache, &pszCachePath); BAIL_ON_LW_ERROR(dwError); if (strncasecmp(pszCachePath, "FILE:", sizeof("FILE:")-1)) { dwError = LW_ERROR_INTERNAL; BAIL_ON_LW_ERROR(dwError); } else { pszCachePathReal = pszCachePath + sizeof("FILE:") - 1; } dwError = LwMoveFile(pszNewCacheName, pszCachePathReal); BAIL_ON_LW_ERROR(dwError); /* Let the user read and write to their cache file (before this, only * root was allowed to read and write the file). */ dwError = LwChangeOwnerAndPermissions( pszCachePathReal, uid, gid, S_IRWXU); BAIL_ON_LW_ERROR(dwError); cleanup: LW_SAFE_FREE_STRING(pszCachePath); return dwError; error: goto cleanup; } static void __attribute__((destructor)) LwKrb5Shutdown( VOID ) { pthread_mutex_destroy(&gLwKrb5State.ExistingClientLock); pthread_mutex_destroy(&gLwKrb5State.UserCacheMutex); } DWORD LwKrb5GetMachineCreds( PSTR* ppszUsername, PSTR* ppszPassword, PSTR* ppszDomainDnsName, PSTR* ppszHostDnsDomain ) { DWORD dwError = 0; PSTR pszUsername = NULL; PSTR pszPassword = NULL; PSTR pszDomainDnsName = NULL; PSTR pszHostDnsDomain = NULL; PLWPS_PASSWORD_INFO pMachineAcctInfo = NULL; HANDLE hPasswordStore = (HANDLE)NULL; dwError = LwpsOpenPasswordStore( LWPS_PASSWORD_STORE_DEFAULT, &hPasswordStore); BAIL_ON_LW_ERROR(dwError); dwError = LwpsGetPasswordByCurrentHostName( hPasswordStore, &pMachineAcctInfo); if (dwError) { LW_LOG_ERROR("Unable to read machine password for hostname"); BAIL_ON_LW_ERROR(dwError); } if (ppszUsername) { dwError = LwWc16sToMbs( pMachineAcctInfo->pwszMachineAccount, &pszUsername); BAIL_ON_LW_ERROR(dwError); if (LW_IS_NULL_OR_EMPTY_STR(pszUsername)) { dwError = LW_ERROR_INVALID_ACCOUNT; BAIL_ON_LW_ERROR(dwError); } } if (ppszPassword) { dwError = LwWc16sToMbs( pMachineAcctInfo->pwszMachinePassword, &pszPassword); BAIL_ON_LW_ERROR(dwError); if (LW_IS_NULL_OR_EMPTY_STR(pszPassword)) { dwError = LW_ERROR_INVALID_PASSWORD; BAIL_ON_LW_ERROR(dwError); } } if (ppszDomainDnsName) { dwError = LwWc16sToMbs( pMachineAcctInfo->pwszDnsDomainName, &pszDomainDnsName); BAIL_ON_LW_ERROR(dwError); if (LW_IS_NULL_OR_EMPTY_STR(pszDomainDnsName)) { dwError = LW_ERROR_INVALID_DOMAIN; BAIL_ON_LW_ERROR(dwError); } } if (ppszHostDnsDomain) { dwError = LwWc16sToMbs( pMachineAcctInfo->pwszHostDnsDomain, &pszHostDnsDomain); BAIL_ON_LW_ERROR(dwError); } if (ppszUsername) { *ppszUsername = pszUsername; } if (ppszPassword) { *ppszPassword = pszPassword; } if (ppszDomainDnsName) { *ppszDomainDnsName = pszDomainDnsName; } if (ppszHostDnsDomain) { *ppszHostDnsDomain = pszHostDnsDomain; } cleanup: if (pMachineAcctInfo) { LwpsFreePasswordInfo(hPasswordStore, pMachineAcctInfo); } if (hPasswordStore != (HANDLE)NULL) { LwpsClosePasswordStore(hPasswordStore); } return dwError; error: if (ppszUsername) { *ppszUsername = NULL; } if (ppszPassword) { *ppszPassword = NULL; } if (ppszDomainDnsName) { *ppszDomainDnsName = NULL; } if (ppszHostDnsDomain) { *ppszHostDnsDomain = NULL; } LW_SAFE_FREE_STRING(pszUsername); LW_SAFE_FREE_STRING(pszPassword); LW_SAFE_FREE_STRING(pszDomainDnsName); LW_SAFE_FREE_STRING(pszHostDnsDomain); goto cleanup; } DWORD LwKrb5RefreshMachineTGT( PDWORD pdwGoodUntilTime ) { DWORD dwError = 0; DWORD dwGoodUntilTime = 0; PSTR pszUsername = NULL; PSTR pszPassword = NULL; PSTR pszDomainDnsName = NULL; PSTR pszHostDnsDomain = NULL; LW_LOG_VERBOSE("Refreshing machine TGT"); dwError = LwKrb5GetMachineCreds( &pszUsername, &pszPassword, &pszDomainDnsName, &pszHostDnsDomain); BAIL_ON_LW_ERROR(dwError); dwError = LwSetupMachineSession( pszUsername, pszPassword, pszDomainDnsName, pszHostDnsDomain, &dwGoodUntilTime); BAIL_ON_LW_ERROR(dwError); if (pdwGoodUntilTime != NULL) { *pdwGoodUntilTime = dwGoodUntilTime; } cleanup: LW_SAFE_FREE_STRING(pszUsername); LW_SAFE_FREE_STRING(pszPassword); LW_SAFE_FREE_STRING(pszDomainDnsName); LW_SAFE_FREE_STRING(pszHostDnsDomain); return dwError; error: if (pdwGoodUntilTime != NULL) { *pdwGoodUntilTime = 0; } goto cleanup; } DWORD LwTranslateKrb5Error( krb5_context ctx, krb5_error_code krbError, PCSTR pszFunction, PCSTR pszFile, DWORD dwLine ) { DWORD dwError = LW_ERROR_SUCCESS; PCSTR pszKrb5Error = NULL; unsigned int i = 0; if (ctx) { pszKrb5Error = krb5_get_error_message(ctx, krbError); } if (pszKrb5Error) { LW_LOG_WARNING("[%s %s:%d] KRB5 Error code: %d (Message: %s)", pszFunction, pszFile, dwLine, krbError, pszKrb5Error); } else { LW_LOG_WARNING("[%s %s:%d] KRB5 Error code: %d", pszFunction, pszFile, dwLine, krbError); } switch (krbError) { case ENOENT: dwError = LW_ERROR_KRB5_NO_KEYS_FOUND; break; default: for (i = 0; krb5err_lwerr_map[i].pszKrb5errStr; i++) { if (krb5err_lwerr_map[i].krb5err == krbError) { dwError = krb5err_lwerr_map[i].lwerr; break; } } if (!dwError) { dwError = LW_ERROR_KRB5_CALL_FAILED; } break; } if (pszKrb5Error) { krb5_free_error_message(ctx, pszKrb5Error); } return dwError; } DWORD LwKrb5VerifyPac( krb5_context ctx, const krb5_ticket *pTgsTicket, const struct berval *pPacBerVal, const krb5_keyblock *serviceKey, char** ppchLogonInfo, size_t* psLogonInfo ) { krb5_error_code ret = 0; PAC_DATA *pPacData = NULL; DWORD i; char *pchPacCopy = NULL; //Do not free krb5_data krbPacData = {0}; //Do not free krb5_checksum checksum = {0}; //Do not free PAC_SIGNATURE_DATA *pServerSig = NULL; PAC_LOGON_NAME *pLogonName = NULL; size_t sServerSig = 0; //Do not free char *pchLogonInfoStart = NULL; size_t sLogonInfoLen = 0; krb5_boolean bHasGoodChecksum = FALSE; uint64_t qwNtAuthTime; DWORD dwError = LW_ERROR_SUCCESS; //Free with krb5_free_unparsed_name PSTR pszClientPrincipal = NULL; PSTR pszLogonName = NULL; char* pchLogonInfo = NULL; #if defined(WORDS_BIGENDIAN) WORD * pwNameLocal = NULL; DWORD dwCount = 0; #endif dwError = LwAllocateMemory( pPacBerVal->bv_len, OUT_PPVOID(&pPacData)); BAIL_ON_LW_ERROR(dwError); memcpy(pPacData, pPacBerVal->bv_val, pPacBerVal->bv_len); #if defined(WORDS_BIGENDIAN) pPacData->dwBufferCount = LW_ENDIAN_SWAP32(pPacData->dwBufferCount); pPacData->dwVersion = LW_ENDIAN_SWAP32(pPacData->dwVersion); #endif // We only know about version 0 if (pPacData->dwVersion != 0) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } // Make sure that the last buffer in the pac data doesn't go out of bounds // of the parent buffer if ((void *)&pPacData->buffers[pPacData->dwBufferCount] - (void *)pPacData > pPacBerVal->bv_len) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } // Make sure the data associated with each buffer doesn't go out of // bounds for (i = 0; i < pPacData->dwBufferCount; i++) { #if defined(WORDS_BIGENDIAN) pPacData->buffers[i].dwType = LW_ENDIAN_SWAP32(pPacData->buffers[i].dwType); pPacData->buffers[i].dwSize = LW_ENDIAN_SWAP32(pPacData->buffers[i].dwSize); pPacData->buffers[i].qwOffset = LW_ENDIAN_SWAP64(pPacData->buffers[i].qwOffset); #endif if (pPacData->buffers[i].qwOffset + pPacData->buffers[i].dwSize < pPacData->buffers[i].qwOffset) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } if (pPacData->buffers[i].qwOffset + pPacData->buffers[i].dwSize > pPacBerVal->bv_len) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } } dwError = LwAllocateMemory( pPacBerVal->bv_len, OUT_PPVOID(&pchPacCopy)); BAIL_ON_LW_ERROR(dwError); memcpy(pchPacCopy, pPacBerVal->bv_val, pPacBerVal->bv_len); krbPacData.magic = KV5M_DATA; krbPacData.length = pPacBerVal->bv_len; krbPacData.data = pchPacCopy; for (i = 0; i < pPacData->dwBufferCount; i++) { switch (pPacData->buffers[i].dwType) { case PAC_TYPE_LOGON_INFO: pchLogonInfoStart = (char *)pPacData + pPacData->buffers[i].qwOffset; sLogonInfoLen = pPacData->buffers[i].dwSize; break; case PAC_TYPE_SRV_CHECKSUM: pServerSig = (PAC_SIGNATURE_DATA *)((char *)pPacData + pPacData->buffers[i].qwOffset); #if defined(WORDS_BIGENDIAN) pServerSig->dwType = LW_ENDIAN_SWAP32(pServerSig->dwType); #endif sServerSig = pPacData->buffers[i].dwSize - (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature; /* The checksum is calculated with the signatures zeroed out. */ memset(pchPacCopy + pPacData->buffers[i].qwOffset + (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature, 0, pPacData->buffers[i].dwSize - (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature); break; case PAC_TYPE_KDC_CHECKSUM: /* The checksum is calculated with the signatures zeroed out. */ memset(pchPacCopy + pPacData->buffers[i].qwOffset + (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature, 0, pPacData->buffers[i].dwSize - (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature); break; case PAC_TYPE_LOGON_NAME: pLogonName = (PAC_LOGON_NAME *)((char *)pPacData + pPacData->buffers[i].qwOffset); #if defined(WORDS_BIGENDIAN) pLogonName->ticketTime = LW_ENDIAN_SWAP64(pLogonName->ticketTime); pLogonName->wAccountNameLen = LW_ENDIAN_SWAP16(pLogonName->wAccountNameLen); pwNameLocal = pLogonName->pwszName; for ( dwCount = 0 ; dwCount < pLogonName->wAccountNameLen / 2 ; dwCount++ ) { pwNameLocal[dwCount] = LW_ENDIAN_SWAP16(pwNameLocal[dwCount]); } #endif if ((char *)&pLogonName->pwszName + pLogonName->wAccountNameLen > (char *)pPacData + pPacData->buffers[i].qwOffset + pPacData->buffers[i].dwSize) { // The message is invalid because the terminating null // of the name lands outside of the buffer. dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } break; default: break; } } if (pServerSig == NULL) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } if (pLogonName == NULL) { //We need the logon name to verify the pac is for the right user dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } if (pchLogonInfoStart == NULL) { /* The buffer we really care about isn't in the pac. */ dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } checksum.magic = KV5M_CHECKSUM; checksum.checksum_type = pServerSig->dwType; checksum.length = sServerSig; checksum.contents = (unsigned char *)pServerSig->pchSignature; ret = krb5_c_verify_checksum( ctx, serviceKey, KRB5_KEYUSAGE_APP_DATA_CKSUM, &krbPacData, &checksum, &bHasGoodChecksum); BAIL_ON_KRB_ERROR(ctx, ret); if (!bHasGoodChecksum) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } // Make sure the pac was issued with this ticket, not an old ticket qwNtAuthTime = pTgsTicket->enc_part2->times.authtime; qwNtAuthTime += 11644473600LL; qwNtAuthTime *= 1000*1000*10; if (pLogonName->ticketTime != qwNtAuthTime) { dwError = LW_ERROR_CLOCK_SKEW; BAIL_ON_LW_ERROR(dwError); } ret = krb5_unparse_name( ctx, pTgsTicket->enc_part2->client, &pszClientPrincipal); BAIL_ON_KRB_ERROR(ctx, ret); // Strip off the domain name if (strchr(pszClientPrincipal, '@') != NULL) { strchr(pszClientPrincipal, '@')[0] = '\0'; } dwError = LwWc16snToMbs( pLogonName->pwszName, &pszLogonName, pLogonName->wAccountNameLen / 2); BAIL_ON_LW_ERROR(dwError); if (strcasecmp(pszClientPrincipal, pszLogonName)) { // The pac belongs to a different user dwError = LW_ERROR_INVALID_LOGIN_ID; BAIL_ON_LW_ERROR(dwError); } dwError = LwAllocateMemory( sLogonInfoLen, OUT_PPVOID(&pchLogonInfo)); BAIL_ON_LW_ERROR(dwError); memcpy(pchLogonInfo, pchLogonInfoStart, sLogonInfoLen); *ppchLogonInfo = pchLogonInfo; *psLogonInfo = sLogonInfoLen; cleanup: LW_SAFE_FREE_STRING(pszLogonName); LW_SAFE_FREE_MEMORY(pPacData); LW_SAFE_FREE_MEMORY(pchPacCopy); if (pszClientPrincipal != NULL) { krb5_free_unparsed_name(ctx, pszClientPrincipal); } return dwError; error: LW_SAFE_FREE_MEMORY(pchLogonInfo); *ppchLogonInfo = NULL; goto cleanup; } DWORD LwKrb5FindPac( krb5_context ctx, const krb5_ticket *pTgsTicket, const krb5_keyblock *serviceKey, char** ppchLogonInfo, size_t* psLogonInfo ) { DWORD dwError = LW_ERROR_SUCCESS; //Do not free struct berval bv = {0}; struct berval contents = {0}; //Do not free krb5_authdata **ppCur = NULL; //Do not free associated buffer BerElement *ber = NULL; ber_tag_t tag = 0; ber_len_t len = 0; // Do not free char *cookie = NULL; int adType; ber_tag_t seqTag, context0Tag, context1Tag; char* pchLogonInfo = NULL; size_t sLogonInfo = 0; ber = ber_alloc_t(0); if (pTgsTicket && pTgsTicket->enc_part2) { ppCur = pTgsTicket->enc_part2->authorization_data; } while (ppCur && (*ppCur != NULL)) { if (ppCur[0]->ad_type == AD_IF_RELEVANT_TYPE) { // This auth data contains a DER encoded sequence of more // auth data. One of them could be a pac. bv.bv_len = ppCur[0]->length; bv.bv_val = (char *)ppCur[0]->contents; ber_init2(ber, &bv, 0); tag = ber_first_element(ber, &len, &cookie); while (tag != LBER_ERROR) { // Free does nothing if pointer is NULL ber_memfree(contents.bv_val); contents.bv_val = NULL; tag = ber_scanf(ber, "t{t[i]t[", &seqTag, &context0Tag, &adType, &context1Tag); if (tag == LBER_ERROR) { // This auth data is invalid. Skip it and try // the next one break; } tag = ber_scanf(ber, "o]}", &contents); if (tag == LBER_ERROR) { // This auth data is invalid. Skip it and try // the next one break; } if (adType == AD_WIN2K_PAC) { dwError = LwKrb5VerifyPac( ctx, pTgsTicket, &contents, serviceKey, &pchLogonInfo, &sLogonInfo); if (dwError == LW_ERROR_INVALID_MESSAGE) { dwError = LW_ERROR_SUCCESS; continue; } BAIL_ON_LW_ERROR(dwError); // Found a good PAC ! goto end_search; } //returns LBER_ERROR when there are no more elements left. tag = ber_next_element(ber, &len, cookie); } } ppCur++; } end_search: *ppchLogonInfo = pchLogonInfo; *psLogonInfo = sLogonInfo; cleanup: if (contents.bv_val != NULL) { ber_memfree(contents.bv_val); } if (ber != NULL) { ber_free(ber, 0); } return dwError; error: LW_SAFE_FREE_MEMORY(pchLogonInfo); *ppchLogonInfo = NULL; goto cleanup; } DWORD LwSetupUserLoginSession( uid_t uid, gid_t gid, PCSTR pszUsername, PCSTR pszPassword, BOOLEAN bUpdateUserCache, PCSTR pszServicePrincipal, PCSTR pszServiceRealm, PCSTR pszServicePassword, char** ppchLogonInfo, size_t* psLogonInfo, PDWORD pdwGoodUntilTime ) { DWORD dwError = 0; krb5_error_code ret = 0; krb5_context ctx = NULL; krb5_ccache cc = NULL; // Free with krb5_free_cred_contents krb5_creds credsRequest = {0}; krb5_creds *pTgsCreds = NULL; krb5_ticket *pTgsTicket = NULL; krb5_ticket *pDecryptedTgs = NULL; krb5_auth_context authContext = NULL; krb5_data apReqPacket = {0}; krb5_keyblock serviceKey = {0}; krb5_data salt = {0}; // Do not free krb5_data machinePassword = {0}; krb5_flags flags = 0; krb5_int32 authcon_flags = 0; BOOLEAN bInLock = FALSE; PCSTR pszTempCacheName = NULL; PSTR pszTempCachePath = NULL; char* pchLogonInfo = NULL; size_t sLogonInfo = 0; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); /* Generates a new filed based credentials cache in /tmp. The file will * be owned by root and only accessible by root. */ ret = krb5_cc_new_unique( ctx, "FILE", "hint", &cc); BAIL_ON_KRB_ERROR(ctx, ret); dwError = LwKrb5GetTgt( pszUsername, pszPassword, krb5_cc_get_name(ctx, cc), pdwGoodUntilTime ); BAIL_ON_LW_ERROR(dwError); ret = krb5_parse_name(ctx, pszServicePrincipal, &credsRequest.server); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_get_principal(ctx, cc, &credsRequest.client); BAIL_ON_KRB_ERROR(ctx, ret); /* Get a TGS for our service using the tgt in the cache */ ret = krb5_get_credentials( ctx, 0, /*no options (not user to user encryption, and not only cached) */ cc, &credsRequest, &pTgsCreds); // Don't trust pTgsCreds on an unsuccessful return // This may be non-zero due to the krb5 libs following referrals // but has been freed in the krb5 libs themselves and any useful // tickets have already been cached. if (ret != 0) { pTgsCreds = NULL; } BAIL_ON_KRB_ERROR(ctx, ret); //No need to store the tgs in the cc. Kerberos does that automatically /* Generate an ap_req message, but don't send it anywhere. Just decode it * immediately. This is the only way to get kerberos to decrypt the tgs * using public APIs */ ret = krb5_mk_req_extended( ctx, &authContext, 0, /* no options necessary */ NULL, /* since this isn't a real ap_req, we don't have any supplemental data to send with it. */ pTgsCreds, &apReqPacket); BAIL_ON_KRB_ERROR(ctx, ret); /* Decode (but not decrypt) the tgs ticket so that we can figure out * which encryption type was used in it. */ ret = krb5_decode_ticket(&pTgsCreds->ticket, &pTgsTicket); /* The TGS ticket is encrypted with the machine password and salted with * the service principal. pszServicePrincipal could probably be used * directly, but it's safer to unparse pTgsCreds->server, because the KDC * sent that to us. */ salt.magic = KV5M_DATA; ret = krb5_unparse_name( ctx, pTgsCreds->server, &salt.data); BAIL_ON_KRB_ERROR(ctx, ret); salt.length = strlen(salt.data); machinePassword.magic = KV5M_DATA; machinePassword.data = (PSTR)pszServicePassword, machinePassword.length = strlen(pszServicePassword), /* Generate a key to decrypt the TGS */ ret = krb5_c_string_to_key( ctx, pTgsTicket->enc_part.enctype, &machinePassword, &salt, &serviceKey); BAIL_ON_KRB_ERROR(ctx, ret); /* Typically krb5_rd_req would decode the AP_REQ using the keytab, but * we don't want to depend on the keytab. As a side effect of kerberos' * user to user authentication support, if a key is explictly set on the * auth context, that key will be used to decrypt the TGS instead of the * keytab. * * By manually generating the key and setting it, we don't require * a keytab. */ if (authContext != NULL) { ret = krb5_auth_con_free(ctx, authContext); BAIL_ON_KRB_ERROR(ctx, ret); } ret = krb5_auth_con_init(ctx, &authContext); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_auth_con_setuseruserkey( ctx, authContext, &serviceKey); BAIL_ON_KRB_ERROR(ctx, ret); /* Disable replay detection which is unnecessary and * can fail when authenticating large numbers of users. */ krb5_auth_con_getflags(ctx, authContext, &authcon_flags); krb5_auth_con_setflags(ctx, authContext, authcon_flags & ~KRB5_AUTH_CONTEXT_DO_TIME); if (pszServiceRealm) { ret = krb5_set_default_realm(ctx, pszServiceRealm); BAIL_ON_KRB_ERROR(ctx, ret); } /* This decrypts the TGS. As a side effect it ensures that the KDC that * the user's TGT came from is in the same realm that the machine was * joined to (this prevents users from spoofing the KDC). */ ret = krb5_rd_req( ctx, &authContext, &apReqPacket, pTgsCreds->server, NULL, /* we're not using the keytab */ &flags, &pDecryptedTgs); BAIL_ON_KRB_ERROR(ctx, ret); dwError = LwKrb5FindPac( ctx, pDecryptedTgs, &serviceKey, &pchLogonInfo, &sLogonInfo); BAIL_ON_LW_ERROR(dwError); if (bUpdateUserCache) { /* 1. Copy old credentials from the existing user creds cache to * the temporary cache. * 2. Delete the existing creds cache. * 3. Move the temporary cache file into the final path. */ dwError = pthread_mutex_lock(&gLwKrb5State.UserCacheMutex); BAIL_ON_LW_ERROR(dwError); bInLock = TRUE; dwError = LwKrb5CopyFromUserCache( ctx, cc, uid ); BAIL_ON_LW_ERROR(dwError); pszTempCacheName = krb5_cc_get_name(ctx, cc); if (!strncasecmp(pszTempCacheName, "FILE:", sizeof("FILE:")-1)) { pszTempCacheName += sizeof("FILE:") - 1; } dwError = LwAllocateString(pszTempCacheName, &pszTempCachePath); BAIL_ON_LW_ERROR(dwError); krb5_cc_close(ctx, cc); // Just to make sure no one accesses this now invalid pointer cc = NULL; dwError = LwKrb5MoveCCacheToUserPath( ctx, pszTempCachePath, uid, gid); if (dwError != LW_ERROR_SUCCESS) { /* Let the user login, even if we couldn't create the ccache for * them. Possible causes are: * 1. /tmp is readonly * 2. Another user maliciously setup a weird file (such as a * directory) where the ccache would go. * 3. Someone created a ccache in the small window after we delete * the old one and before we move in the new one. */ LW_LOG_WARNING("Unable to set up credentials cache with tgt for uid %ld", (long)uid); dwError = LwRemoveFile(pszTempCachePath); BAIL_ON_LW_ERROR(dwError); } } *ppchLogonInfo = pchLogonInfo; *psLogonInfo = sLogonInfo; cleanup: if (ctx) { // This function skips fields which are NULL krb5_free_cred_contents(ctx, &credsRequest); if (pTgsCreds != NULL) { krb5_free_creds(ctx, pTgsCreds); } if (pTgsTicket != NULL) { krb5_free_ticket(ctx, pTgsTicket); } if (pDecryptedTgs != NULL) { krb5_free_ticket(ctx, pDecryptedTgs); } if (authContext != NULL) { krb5_auth_con_free(ctx, authContext); } krb5_free_data_contents(ctx, &apReqPacket); krb5_free_data_contents(ctx, &salt); krb5_free_keyblock_contents(ctx, &serviceKey); if (cc != NULL) { krb5_cc_destroy(ctx, cc); } krb5_free_context(ctx); } if (bInLock) { pthread_mutex_unlock(&gLwKrb5State.UserCacheMutex); } LW_SAFE_FREE_STRING(pszTempCachePath); return dwError; error: LW_SAFE_FREE_MEMORY(pchLogonInfo); *ppchLogonInfo = NULL; goto cleanup; } /* local variables: mode: c c-basic-offset: 4 indent-tabs-mode: nil tab-width: 4 end: */