/************************************************************************* * * nw.c * * Netware security support * * Copyright Microsoft Corporation, 1998 * * *************************************************************************/ /* * Includes */ #include "precomp.h" #pragma hdrstop #include #include #if DBG ULONG DbgPrint( PCH Format, ... ); #define DBGPRINT(x) DbgPrint x #if DBGTRACE #define TRACE0(x) DbgPrint x #define TRACE1(x) DbgPrint x #else #define TRACE0(x) #define TRACE1(x) #endif #else #define DBGPRINT(x) #define TRACE0(x) #define TRACE1(x) #endif /* * This is the prefix for the secret object name. */ #define CITRIX_NW_SECRET_NAME L"CTX_NW_INFO_" /*============================================================================= == Public functions =============================================================================*/ /*============================================================================= == Functions Used =============================================================================*/ NTSTATUS CreateSecretInLsa( PWCHAR pSecretName, PWCHAR pSecretData ); NTSTATUS QuerySecretInLsa( PWCHAR pSecretName, PWCHAR pSecretData, DWORD ByteCount ); BOOL IsCallerSystem( VOID ); BOOL IsCallerAdmin( VOID ); BOOL TestUserForAdmin( VOID ); NTSTATUS IsZeroterminateStringA( PBYTE pString, DWORD dwLength ); NTSTATUS IsZeroterminateStringW( PWCHAR pwString, DWORD dwLength ) ; /*============================================================================= == Global data =============================================================================*/ /******************************************************************************* * * RpcServerNWLogonSetAdmin (UNICODE) * * Creates or updates the specified server's NWLogon Domain Administrator * UserID and Password in the SAM secret objects of the specified server. * * The caller must be ADMIN. * * ENTRY: * pServerName (input) * Server to store info for. This server is typically a domain controller. * * pNWLogon (input) * Pointer to a NWLOGONADMIN structure containing specified server's * domain admin and password. * * EXIT: * ERROR_SUCCESS - no error * ERROR_INSUFFICIENT_BUFFER - pUserConfig buffer too small * otherwise: the error code * ******************************************************************************/ BOOLEAN RpcServerNWLogonSetAdmin( HANDLE hServer, DWORD *pResult, PWCHAR pServerName, DWORD ServerNameSize, PNWLOGONADMIN pNWLogon, DWORD ByteCount ) { DWORD Size; DWORD Result; PWCHAR pDomain; UINT LocalFlag; PWCHAR pSecretName; RPC_STATUS RpcStatus; WCHAR UserPass[ USERNAME_LENGTH + PASSWORD_LENGTH + DOMAIN_LENGTH + 3 ]; if(!hServer) { *pResult = STATUS_UNSUCCESSFUL; return( FALSE ); } // Do minimal buffer validation if (pNWLogon == NULL ) { *pResult = STATUS_INVALID_USER_BUFFER; return FALSE; } if (ByteCount < sizeof(NWLOGONADMIN)) { *pResult = STATUS_INVALID_USER_BUFFER; return FALSE; } if( pServerName == NULL ) { DBGPRINT(("NWLogonSetAdmin: No ServerName\n")); *pResult = (ULONG)STATUS_INVALID_PARAMETER; return( FALSE ); } *pResult = IsZeroterminateStringW(pServerName, ServerNameSize ); if (*pResult != STATUS_SUCCESS) { return FALSE; } pNWLogon->Username[USERNAME_LENGTH] = (WCHAR) 0; pNWLogon->Password[PASSWORD_LENGTH] = (WCHAR) 0; pNWLogon->Domain[DOMAIN_LENGTH] = (WCHAR) 0; // // Only a SYSTEM mode caller (IE: Winlogon) is allowed // to query this value. // RpcStatus = RpcImpersonateClient( NULL ); if( RpcStatus != RPC_S_OK ) { DBGPRINT(("RpcServerNWLogonSetAdmin: Not impersonating! RpcStatus 0x%x\n",RpcStatus)); *pResult = (ULONG)STATUS_CANNOT_IMPERSONATE; return( FALSE ); } // // Inquire if local RPC call // RpcStatus = I_RpcBindingIsClientLocal( 0, // Active RPC call we are servicing &LocalFlag ); if( RpcStatus != RPC_S_OK ) { DBGPRINT(("NWLogonSetAdmin Could not query local client RpcStatus 0x%x\n",RpcStatus)); RpcRevertToSelf(); *pResult = (ULONG)STATUS_ACCESS_DENIED; return( FALSE ); } if( !LocalFlag ) { DBGPRINT(("NWLogonSetAdmin Not a local client call\n")); RpcRevertToSelf(); *pResult = (ULONG)STATUS_ACCESS_DENIED; return( FALSE ); } if( !IsCallerAdmin() ) { RpcRevertToSelf(); DBGPRINT(("RpcServerNWLogonSetAdmin: Caller Not SYSTEM\n")); *pResult = (ULONG)STATUS_ACCESS_DENIED; return( FALSE ); } RpcRevertToSelf(); if( ByteCount < sizeof(NWLOGONADMIN) ) { DBGPRINT(("NWLogonSetAdmin: Bad size %d\n",ByteCount)); *pResult = (ULONG)STATUS_INFO_LENGTH_MISMATCH; return( FALSE ); } // check for username, and if there is one then encrypt username and pw TRACE0(("NWLogonSetAdmin: UserName %ws\n",pNWLogon->Username)); // concatenate the username, password, and domain together wcscpy(UserPass, pNWLogon->Username); wcscat(UserPass, L"/"); wcscat(UserPass, pNWLogon->Password); wcscat(UserPass, L"/"); // Skip over any \\ backslashes (if a machine name was passed in) pDomain = pNWLogon->Domain; while (*pDomain == L'\\') { pDomain++; } wcscat(UserPass, pDomain); // // Build the secret name from the server name. // // This is because each domain will have a different entry. // // Skip over any \\ backslashes (if a machine name was passed in) while (*pServerName == L'\\') { pServerName++; } Size = wcslen(pServerName) + 1; Size *= sizeof(WCHAR); Size += sizeof(CITRIX_NW_SECRET_NAME); pSecretName = MemAlloc( Size ); if( pSecretName == NULL ) { DBGPRINT(("NWLogonSetAdmin: No memory\n")); *pResult = (ULONG)STATUS_NO_MEMORY; return( FALSE ); } wcscpy(pSecretName, CITRIX_NW_SECRET_NAME ); wcscat(pSecretName, pServerName ); // check for username, and if there is one then encrypt username and pw if ( wcslen( pNWLogon->Username ) ) { // store encrypted username Result = CreateSecretInLsa( pSecretName, UserPass ); } else { // If there wasn't a username, clear this secret object. Result = CreateSecretInLsa( pSecretName, L""); DBGPRINT(("TERMSRV: RpcServerNWLogonSetAdmin: UserName not supplied\n")); } MemFree( pSecretName ); *pResult = Result; return( Result == STATUS_SUCCESS ); } /******************************************************************************* * * RpcServerQueryNWLogonAdmin * * Query NWLOGONADMIN structure from the SAM Secret object on the given * WinFrame server. * * The caller must be SYSTEM context, IE: WinLogon. * * ENTRY: * hServer (input) * Rpc handle * * pServerName (input) * Server to store info for. This server is typically a domain controller. * * pNWLogon (output) * pointer to NWLOGONADMIN structure * * EXIT: * nothing * ******************************************************************************/ BOOLEAN RpcServerNWLogonQueryAdmin( HANDLE hServer, DWORD *pResult, PWCHAR pServerName, DWORD ServerNameSize, PNWLOGONADMIN pNWLogon, DWORD ByteCount ) { PWCHAR pwch; DWORD Size; ULONG ulcsep; UINT LocalFlag; NTSTATUS Status; PWCHAR pSecretName; RPC_STATUS RpcStatus; WCHAR encString[ USERNAME_LENGTH + PASSWORD_LENGTH + DOMAIN_LENGTH + 3 ]; BOOLEAN SystemCaller = FALSE; if(!hServer) { *pResult = STATUS_UNSUCCESSFUL; return( FALSE ); } // Do minimal buffer validation if (pNWLogon == NULL) { *pResult = STATUS_INVALID_USER_BUFFER; return FALSE; } if( ByteCount < sizeof(NWLOGONADMIN) ) { DBGPRINT(("NWLogonQueryAdmin: Bad size %d\n",ByteCount)); *pResult = (ULONG)STATUS_INFO_LENGTH_MISMATCH; return( FALSE ); } if( pServerName == NULL ) { DBGPRINT(("NWLogonQueryAdmin: No ServerName\n")); *pResult = (ULONG)STATUS_INVALID_PARAMETER; return( FALSE ); } *pResult = IsZeroterminateStringW(pServerName, ServerNameSize ); if (*pResult != STATUS_SUCCESS) { return FALSE; } pNWLogon->Username[USERNAME_LENGTH] = (WCHAR) 0; pNWLogon->Password[PASSWORD_LENGTH] = (WCHAR) 0; pNWLogon->Domain[DOMAIN_LENGTH] = (WCHAR) 0; // // // Only a SYSTEM mode caller (IE: Winlogon) is allowed // to query this value. // RpcStatus = RpcImpersonateClient( NULL ); if( RpcStatus != RPC_S_OK ) { DBGPRINT(("RpcServerNWLogonQueryAdmin: Not impersonating! RpcStatus 0x%x\n",RpcStatus)); *pResult = (ULONG)STATUS_CANNOT_IMPERSONATE; return( FALSE ); } // // Inquire if local RPC call // RpcStatus = I_RpcBindingIsClientLocal( 0, // Active RPC call we are servicing &LocalFlag ); if( RpcStatus != RPC_S_OK ) { DBGPRINT(("NWLogonQueryAdmin Could not query local client RpcStatus 0x%x\n",RpcStatus)); RpcRevertToSelf(); *pResult = (ULONG)STATUS_ACCESS_DENIED; return( FALSE ); } if( !LocalFlag ) { DBGPRINT(("NWLogonQueryAdmin Not a local client call\n")); RpcRevertToSelf(); *pResult = (ULONG)STATUS_ACCESS_DENIED; return( FALSE ); } /* find out who is calling us system has complete access, admin can't get password, user is kicked out */ if( IsCallerSystem() ) { SystemCaller = TRUE; } if( !TestUserForAdmin() && (SystemCaller != TRUE) ) { RpcRevertToSelf(); DBGPRINT(("RpcServerNWLogonQueryAdmin: Caller Not SYSTEM or Admin\n")); *pResult = (ULONG)STATUS_ACCESS_DENIED; return( FALSE ); } RpcRevertToSelf(); // // Build the secret name from the server name. // // This is because each domain will have a different entry. // // Skip over any \\ backslashes (if a machine name was passed in) while (*pServerName == L'\\') { pServerName++; } Size = wcslen(pServerName) + 1; Size *= sizeof(WCHAR); Size += sizeof(CITRIX_NW_SECRET_NAME); pSecretName = MemAlloc( Size ); if( pSecretName == NULL ) { DBGPRINT(("NWLogonSetAdmin: No memory\n")); *pResult = (ULONG)STATUS_NO_MEMORY; return( FALSE ); } wcscpy(pSecretName, CITRIX_NW_SECRET_NAME ); wcscat(pSecretName, pServerName ); Status = QuerySecretInLsa( pSecretName, encString, sizeof(encString) ); MemFree( pSecretName ); if( !NT_SUCCESS(Status) ) { *pResult = Status; DBGPRINT(("NWLogonQueryAdmin: Error 0x%x querying secret object\n",Status)); return( FALSE ); } // check for username/password if there is one then decrypt it if ( wcslen( encString ) ) { // Change the '/' seperator to null pwch = &encString[0]; ulcsep = 0; while (pwch && *pwch) { pwch = wcschr(pwch, L'/'); if (pwch) { *pwch = L'\0'; pwch++; ulcsep++; } } // get clear text username wcscpy( pNWLogon->Username, &encString[0] ); if (ulcsep >= 1) { // Skip to the password pwch = &encString[0] + wcslen(&encString[0]) + 1; if( SystemCaller == TRUE ){ // get clear text password wcscpy( pNWLogon->Password, pwch); } else { *pNWLogon->Password = L'\0'; } } else { *pNWLogon->Password = L'\0'; } if (ulcsep >= 2) { // Skip to the domain string pwch = pwch + wcslen(pwch) + 1; // get clear text domain wcscpy( pNWLogon->Domain, pwch); } else { *pNWLogon->Domain = L'\0'; } *pResult = STATUS_SUCCESS; return( TRUE ); } else { DBGPRINT(("RpcServerNWLogonQueryAdmin: zero length data\n")); // set to username and password to NULL strings pNWLogon->Password[0] = L'\0'; pNWLogon->Username[0] = L'\0'; pNWLogon->Domain[0] = L'\0'; *pResult = STATUS_SUCCESS; return( TRUE ); } } /******************************************************************************* * * CreateSecretInLsa * * Create the secret object in the LSA to keep it from prying eyes. * * NOTE: There is no need to encode the data since it is RSA encrypted * by the LSA secret routines. * * ENTRY: * pSecretName (input) * Secret name to create. * * pSecretData (input) * Data to store in secret * * EXIT: * NTSTATUS * ******************************************************************************/ NTSTATUS CreateSecretInLsa( PWCHAR pSecretName, PWCHAR pSecretData ) { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; LSA_HANDLE PolicyHandle; UNICODE_STRING SecretName; UNICODE_STRING SecretValue; LSA_HANDLE SecretHandle; ACCESS_MASK DesiredAccess; if( pSecretName == NULL ) { DBGPRINT(("CreateSecretInLsa: NULL SecretName\n")); return( STATUS_INVALID_PARAMETER ); } SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL ); ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; Status = LsaOpenPolicy( NULL, // SystemName (Local) &ObjectAttributes, GENERIC_ALL, &PolicyHandle ); if( !NT_SUCCESS(Status) ) { DBGPRINT(("Error 0x%x Opening Policy\n",Status)); return( Status ); } RtlInitUnicodeString( &SecretName, pSecretName ); DesiredAccess = GENERIC_ALL; Status = LsaCreateSecret( PolicyHandle, &SecretName, DesiredAccess, &SecretHandle ); // Its OK if the name already exits, we will set a new value or delete if( Status == STATUS_OBJECT_NAME_COLLISION ) { TRACE0(("CreateSecretInLsa: Existing Entry, Opening\n")); Status = LsaOpenSecret( PolicyHandle, &SecretName, DesiredAccess, &SecretHandle ); } if( !NT_SUCCESS(Status) ) { DBGPRINT(("Error 0x%x Creating Secret\n",Status)); /* makarp; Close Policy Handle in case of LsaCreateSecrete, LsaopenSecret failures. #182787 */ LsaClose( PolicyHandle ); return( Status ); } TRACE0(("CreateSecretInLsa: Status 0x%x\n",Status)); if ( wcslen(pSecretData) != 0 ){ RtlInitUnicodeString( &SecretValue, pSecretData ); Status = LsaSetSecret( SecretHandle, &SecretValue, NULL ); TRACE0(("CreateSecretInLsa: LsaSetSecret Status 0x%x\n",Status)); LsaClose(SecretHandle); } else{ Status = LsaDelete(SecretHandle); } LsaClose( PolicyHandle ); return( Status ); } /******************************************************************************* * * QuerySecretInLsa * * Query the secret object in the LSA. * * ENTRY: * pSecretName (input) * Secret name to create. * * pSecretData (output) * Buffer to store secret data. * * ByteCount (input) * Maximum size of buffer to store result. * * EXIT: * NTSTATUS * ******************************************************************************/ NTSTATUS QuerySecretInLsa( PWCHAR pSecretName, PWCHAR pSecretData, DWORD ByteCount ) { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; LSA_HANDLE PolicyHandle; UNICODE_STRING SecretName; LSA_HANDLE SecretHandle; ACCESS_MASK DesiredAccess; LARGE_INTEGER CurrentTime; PUNICODE_STRING pCurrentValue = NULL; SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, NULL ); ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; Status = LsaOpenPolicy( NULL, // SystemName (Local) &ObjectAttributes, GENERIC_ALL, &PolicyHandle ); if( !NT_SUCCESS(Status) ) { DBGPRINT(("Error 0x%x Opening Policy\n",Status)); return( Status ); } RtlInitUnicodeString( &SecretName, pSecretName ); DesiredAccess = GENERIC_ALL; Status = LsaOpenSecret( PolicyHandle, &SecretName, DesiredAccess, &SecretHandle ); if( !NT_SUCCESS(Status) ) { /* makarp; Close Policy Handle in case of LsaopenSecret failures. #182787 */ LsaClose( PolicyHandle ); return( Status ); } Status = LsaQuerySecret( SecretHandle, &pCurrentValue, &CurrentTime, NULL, NULL ); TRACE0(("QuerySecretInLsa: Status 0x%x\n",Status)); if( NT_SUCCESS(Status) ) { if (pCurrentValue != NULL) { if( (pCurrentValue->Length+sizeof(WCHAR)) > ByteCount ) { Status = STATUS_INFO_LENGTH_MISMATCH; } else { RtlMoveMemory( pSecretData, pCurrentValue->Buffer, pCurrentValue->Length ); pSecretData[pCurrentValue->Length/sizeof(WCHAR)] = 0; } LsaFreeMemory( pCurrentValue ); } else { pSecretData[0] = (WCHAR) 0; } } LsaClose(SecretHandle); LsaClose( PolicyHandle ); TRACE0(("QuerySecretInLsa: Final Status 0x%x\n",Status)); return( Status ); }