/* Copyright (c) 1992 Microsoft Corporation Module Name: client.c Abstract: This module contains the client impersonation code. Author: Jameel Hyder (microsoft!jameelh) Revision History: 16 Jun 1992 Initial Version Notes: Tab stop: 4 --*/ #define FILENUM FILE_CLIENT #include #include #include #include #ifdef ALLOC_PRAGMA #pragma alloc_text( PAGE, AfpImpersonateClient) #pragma alloc_text( PAGE, AfpRevertBack) #pragma alloc_text( PAGE, AfpGetChallenge) #pragma alloc_text( PAGE, AfpLogonUser) #endif /*** AfpImpersonateClient * * Impersonates the remote client. The token representing the remote client * is available in the SDA. If the SDA is NULL (i.e. server context) then * impersonate the token that we have created for ourselves. */ VOID AfpImpersonateClient( IN PSDA pSda OPTIONAL ) { NTSTATUS Status = STATUS_SUCCESS; HANDLE Token; PAGED_CODE( ); if (pSda != NULL) { Token = pSda->sda_UserToken; } else Token = AfpFspToken; ASSERT(Token != NULL); Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&Token, sizeof(Token)); ASSERT(NT_SUCCESS(Status)); } /*** AfpRevertBack * * Revert back to the default thread context. */ VOID AfpRevertBack( VOID ) { NTSTATUS Status = STATUS_SUCCESS; HANDLE Handle = NULL; PAGED_CODE( ); Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&Handle, sizeof(Handle)); ASSERT(NT_SUCCESS(Status)); } /*** AfpGetChallenge * * Obtain a challenge token from the MSV1_0 package. This token is used by * AfpLogin call. * * The following function modified so that we generate the challenge ourselves * instead of making a call. This routine borrowed almost verbatim from * the LM server code. */ PBYTE AfpGetChallenge( IN VOID ) { PMSV1_0_LM20_CHALLENGE_REQUEST ChallengeRequest; PMSV1_0_LM20_CHALLENGE_RESPONSE ChallengeResponse; ULONG Length; PBYTE pRetBuf; NTSTATUS Status, StatusX; union { LARGE_INTEGER time; UCHAR bytes[8]; } u; ULONG seed; ULONG challenge[2]; ULONG result3; PAGED_CODE( ); ChallengeRequest = NULL; // // Create a pseudo-random 8-byte number by munging the system time // for use as a random number seed. // // Start by getting the system time. // ASSERT( MSV1_0_CHALLENGE_LENGTH == 2 * sizeof(ULONG) ); KeQuerySystemTime( &u.time ); // // To ensure that we don't use the same system time twice, add in the // count of the number of times this routine has been called. Then // increment the counter. // // *** Since we don't use the low byte of the system time (it doesn't // take on enough different values, because of the timer // resolution), we increment the counter by 0x100. // // *** We don't interlock the counter because we don't really care // if it's not 100% accurate. // u.time.LowPart += EncryptionKeyCount; EncryptionKeyCount += 0x100; // // Now use parts of the system time as a seed for the random // number generator. // // *** Because the middle two bytes of the low part of the system // time change most rapidly, we use those in forming the seed. // seed = ((u.bytes[1] + 1) << 0) | ((u.bytes[2] + 0) << 8) | ((u.bytes[2] - 1) << 16) | ((u.bytes[1] + 0) << 24); // // Now get two random numbers. RtlRandom does not return negative // numbers, so we pseudo-randomly negate them. // challenge[0] = RtlRandom( &seed ); challenge[1] = RtlRandom( &seed ); result3 = RtlRandom( &seed ); if ( (result3 & 0x1) != 0 ) { challenge[0] |= 0x80000000; } if ( (result3 & 0x2) != 0 ) { challenge[1] |= 0x80000000; } // Allocate a buffer to hold the challenge and copy it in if ((pRetBuf = AfpAllocNonPagedMemory(MSV1_0_CHALLENGE_LENGTH)) != NULL) { RtlCopyMemory(pRetBuf, challenge, MSV1_0_CHALLENGE_LENGTH); } return (pRetBuf); } /*** AfpLogonUser * * Attempt to login the user. The password is either encrypted or cleartext * based on the UAM used. The UserName and domain is extracted out of the Sda. * * LOCKS: AfpStatisticsLock (SPIN) */ AFPSTATUS AfpLogonUser( IN PSDA pSda, IN PANSI_STRING UserPasswd ) { NTSTATUS Status, SubStatus; PUNICODE_STRING WSName; ULONG ulUnused; ULONG NtlmInTokenSize; PNTLM_AUTHENTICATE_MESSAGE NtlmInToken = NULL; PAUTHENTICATE_MESSAGE InToken = NULL; ULONG InTokenSize; PNTLM_ACCEPT_RESPONSE OutToken = NULL; ULONG OutTokenSize; SIZE_T AllocateSize; SecBufferDesc InputToken; SecBuffer InputBuffers[2]; SecBufferDesc OutputToken; SecBuffer OutputBuffer; CtxtHandle hNewContext; TimeStamp Expiry; ULONG BufferOffset; PCHAR pTmp; PRAS_SUBAUTH_INFO pRasSubAuthInfo; PARAP_SUBAUTH_REQ pSfmSubAuthInfo; PARAP_SUBAUTH_RESP pSfmResp; DWORD ResponseHigh; DWORD ResponseLow; DWORD dwTmpLen; PAGED_CODE( ); ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); #ifdef OPTIMIZE_GUEST_LOGONS // 11/28/94 SueA: Now that there is a License Service to track the number // of sessions via LsaLogonUser, we can no longer fake the guest tokens. // Optimization for subsequent guest logons // After the first guest logon, we save the token and do not free it till the // server stops. All subsequent guest logons 'share' that token. if (pSda->sda_ClientType == SDA_CLIENT_GUEST) { AfpSwmrAcquireExclusive(&AfpEtcMapLock); if (AfpGuestToken != NULL) { pSda->sda_UserToken = AfpGuestToken; pSda->sda_UserSid = &AfpSidWorld; pSda->sda_GroupSid = &AfpSidWorld; // Primary group of Guest is also 'World' #ifdef INHERIT_DIRECTORY_PERMS pSda->sda_UID = AfpIdWorld; pSda->sda_GID = AfpIdWorld; #else ASSERT (AfpGuestSecDesc != NULL); pSda->sda_pSecDesc = AfpGuestSecDesc; #endif AfpSwmrRelease(&AfpEtcMapLock); return AFP_ERR_NONE; } else { AfpSwmrRelease(&AfpEtcMapLock); } } #endif // OPTIMIZE_GUEST_LOGONS WSName = &AfpDefaultWksta; if (pSda->sda_WSName.Length != 0) WSName = &pSda->sda_WSName; // // Figure out how big a buffer we need. We put all the messages // in one buffer for efficiency's sake. // NtlmInTokenSize = sizeof(NTLM_AUTHENTICATE_MESSAGE); // alignment needs to be correct based on 32/64 bit addressing!!! NtlmInTokenSize = (NtlmInTokenSize + 7) & 0xfffffff8; InTokenSize = sizeof(AUTHENTICATE_MESSAGE) + pSda->sda_UserName.Length + WSName->Length + (sizeof(RAS_SUBAUTH_INFO) + sizeof(ARAP_SUBAUTH_REQ)) + pSda->sda_DomainName.Length + UserPasswd->Length + 24; // extra for byte aligning InTokenSize = (InTokenSize + 7) & 0xfffffff8; OutTokenSize = sizeof(NTLM_ACCEPT_RESPONSE); OutTokenSize = (OutTokenSize + 7) & 0xfffffff8; // // Round this up to 8 byte boundary becaus the out token needs to be // quad word aligned for the LARGE_INTEGER. // AllocateSize = ((NtlmInTokenSize + InTokenSize + 7) & 0xfffffff8) + OutTokenSize; Status = NtAllocateVirtualMemory(NtCurrentProcess(), &InToken, 0L, &AllocateSize, MEM_COMMIT, PAGE_READWRITE); if (!NT_SUCCESS(Status)) { AFPLOG_ERROR(AFPSRVMSG_PAGED_POOL, Status, &AllocateSize,sizeof(AllocateSize), NULL); #if DBG DbgBreakPoint(); #endif return(AFP_ERR_MISC); } NtlmInToken = (PNTLM_AUTHENTICATE_MESSAGE) ((PUCHAR) InToken + InTokenSize); OutToken = (PNTLM_ACCEPT_RESPONSE) ((PUCHAR)NtlmInToken + ((NtlmInTokenSize + 7) & 0xfffffff8)); RtlZeroMemory(InToken, InTokenSize + NtlmInTokenSize); // // set up the NtlmInToken first // if (pSda->sda_Challenge) { RtlCopyMemory(NtlmInToken->ChallengeToClient, pSda->sda_Challenge, MSV1_0_CHALLENGE_LENGTH ); } if ((pSda->sda_ClientType == SDA_CLIENT_RANDNUM) || (pSda->sda_ClientType == SDA_CLIENT_TWOWAY)) { NtlmInToken->ParameterControl = (MSV1_0_SUBAUTHENTICATION_DLL_RAS << 24); } else { NtlmInToken->ParameterControl = 0; } // // Okay, now for the tought part - marshalling the AUTHENTICATE_MESSAGE // RtlCopyMemory(InToken->Signature, NTLMSSP_SIGNATURE, sizeof(NTLMSSP_SIGNATURE) ); InToken->MessageType = NtLmAuthenticate; BufferOffset = sizeof(AUTHENTICATE_MESSAGE); // // LM password - case insensitive // pTmp = (PBYTE)InToken + BufferOffset; *(LPWSTR)pTmp = L'\0'; InToken->LmChallengeResponse.Buffer = BufferOffset; InToken->LmChallengeResponse.Length = 1; InToken->LmChallengeResponse.MaximumLength = sizeof(WCHAR); InToken->NtChallengeResponse.Buffer = BufferOffset; InToken->NtChallengeResponse.Length = 0; InToken->NtChallengeResponse.MaximumLength = sizeof(WCHAR); InToken->DomainName.Buffer = BufferOffset; InToken->DomainName.Length = 0; InToken->DomainName.MaximumLength = sizeof(WCHAR); InToken->Workstation.Buffer = BufferOffset; InToken->Workstation.Length = 0; InToken->Workstation.MaximumLength = sizeof(WCHAR); InToken->UserName.Buffer = BufferOffset; InToken->UserName.Length = 0; InToken->UserName.MaximumLength = sizeof(WCHAR); if (pSda->sda_UserName.Length != 0) { if (pSda->sda_DomainName.Length != 0) { InToken->DomainName.Length = pSda->sda_DomainName.Length; InToken->DomainName.MaximumLength = pSda->sda_DomainName.MaximumLength; InToken->DomainName.Buffer = BufferOffset; RtlCopyMemory((PBYTE)InToken + BufferOffset, (PBYTE)pSda->sda_DomainName.Buffer, pSda->sda_DomainName.Length); BufferOffset += pSda->sda_DomainName.Length; BufferOffset = (BufferOffset + 3) & 0xfffffffc; // dword align it } InToken->LmChallengeResponse.Buffer = BufferOffset; // // is he using native Apple UAM? setup buffers differently! // if ((pSda->sda_ClientType == SDA_CLIENT_RANDNUM) || (pSda->sda_ClientType == SDA_CLIENT_TWOWAY)) { pRasSubAuthInfo = (PRAS_SUBAUTH_INFO)((PBYTE)InToken + BufferOffset); pRasSubAuthInfo->ProtocolType = RAS_SUBAUTH_PROTO_ARAP; pRasSubAuthInfo->DataSize = sizeof(ARAP_SUBAUTH_REQ); pSfmSubAuthInfo = (PARAP_SUBAUTH_REQ)&pRasSubAuthInfo->Data[0]; if (pSda->sda_ClientType == SDA_CLIENT_RANDNUM) { pSfmSubAuthInfo->PacketType = SFM_SUBAUTH_LOGON_PKT; } else { pSfmSubAuthInfo->PacketType = SFM_2WAY_SUBAUTH_LOGON_PKT; } pSfmSubAuthInfo->Logon.fGuestLogon = FALSE; ASSERT(pSda->sda_Challenge != NULL); // put the 2 dwords of challenge that we gave the Mac pTmp = pSda->sda_Challenge; GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.NTChallenge1,pTmp); pTmp += sizeof(DWORD); GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.NTChallenge2,pTmp); // put the 2 dwords of response that the Mac gave us pTmp = UserPasswd->Buffer; GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacResponse1,pTmp); pTmp += sizeof(DWORD); GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacResponse2,pTmp); // 2-way guy sends his own challenge: doesn't trust us! if (pSda->sda_ClientType == SDA_CLIENT_TWOWAY) { pTmp += sizeof(DWORD); GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacChallenge1,pTmp); pTmp += sizeof(DWORD); GETDWORD2DWORD_NOCONV((PBYTE)&pSfmSubAuthInfo->Logon.MacChallenge2,pTmp); } dwTmpLen = (sizeof(RAS_SUBAUTH_INFO) + sizeof(ARAP_SUBAUTH_REQ)); InToken->LmChallengeResponse.Length = (USHORT)dwTmpLen; InToken->LmChallengeResponse.MaximumLength = (USHORT)dwTmpLen; BufferOffset += dwTmpLen; } // // this client is using MS-UAM or Apple's cleartext // else { InToken->LmChallengeResponse.Length = UserPasswd->Length; InToken->LmChallengeResponse.MaximumLength = UserPasswd->MaximumLength; RtlCopyMemory( (PBYTE)InToken + BufferOffset, UserPasswd->Buffer, UserPasswd->Length ); BufferOffset += UserPasswd->Length; } BufferOffset = (BufferOffset + 3) & 0xfffffffc; // dword align it // // Workstation Name // InToken->Workstation.Buffer = BufferOffset; InToken->Workstation.Length = WSName->Length; InToken->Workstation.MaximumLength = WSName->MaximumLength; RtlCopyMemory((PBYTE)InToken + BufferOffset, WSName->Buffer, WSName->Length); BufferOffset += WSName->Length; BufferOffset = (BufferOffset + 3) & 0xfffffffc; // dword align it // // User Name // InToken->UserName.Buffer = BufferOffset; InToken->UserName.Length = pSda->sda_UserName.Length; InToken->UserName.MaximumLength = pSda->sda_UserName.MaximumLength; RtlCopyMemory((PBYTE)InToken + BufferOffset, pSda->sda_UserName.Buffer, pSda->sda_UserName.Length); BufferOffset += pSda->sda_UserName.Length; } InputToken.pBuffers = InputBuffers; InputToken.cBuffers = 2; InputToken.ulVersion = 0; InputBuffers[0].pvBuffer = InToken; InputBuffers[0].cbBuffer = InTokenSize; InputBuffers[0].BufferType = SECBUFFER_TOKEN; InputBuffers[1].pvBuffer = NtlmInToken; InputBuffers[1].cbBuffer = NtlmInTokenSize; InputBuffers[1].BufferType = SECBUFFER_TOKEN; OutputToken.pBuffers = &OutputBuffer; OutputToken.cBuffers = 1; OutputToken.ulVersion = 0; OutputBuffer.pvBuffer = OutToken; OutputBuffer.cbBuffer = OutTokenSize; OutputBuffer.BufferType = SECBUFFER_TOKEN; Status = AcceptSecurityContext(&AfpSecHandle, NULL, &InputToken, ASC_REQ_LICENSING, SECURITY_NATIVE_DREP, &hNewContext, &OutputToken, &ulUnused, &Expiry ); if (NT_SUCCESS(Status)) { AFPTIME CurrentTime; NTSTATUS SecStatus; if (pSda->sda_ClientType != SDA_CLIENT_GUEST) { SecPkgContext_PasswordExpiry PasswordExpires; // Get the kickoff time from the profile buffer. Round this to // even # of SESSION_CHECK_TIME units SecStatus = QueryContextAttributes( &hNewContext, SECPKG_ATTR_PASSWORD_EXPIRY, &PasswordExpires ); if( SecStatus == NO_ERROR ) { AfpGetCurrentTimeInMacFormat(&CurrentTime); pSda->sda_tTillKickOff = (DWORD) ( AfpConvertTimeToMacFormat(&PasswordExpires.tsPasswordExpires) - CurrentTime ); pSda->sda_tTillKickOff -= pSda->sda_tTillKickOff % SESSION_CHECK_TIME; } else { DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR, ("AfpLogonUser: QueryContextAttributes failed %lx\n",SecStatus)); } } // return stuff from subauth pSfmResp = (PARAP_SUBAUTH_RESP)&OutToken->UserSessionKey[0]; ResponseHigh = pSfmResp->Response.high; ResponseLow = pSfmResp->Response.low; SubStatus = NtFreeVirtualMemory(NtCurrentProcess( ), (PVOID *)&InToken, &AllocateSize, MEM_RELEASE); ASSERT(NT_SUCCESS(SubStatus)); // // 2-Way authentication? client expects us to send a response to // the challenge that it sent // if (pSda->sda_ClientType == SDA_CLIENT_TWOWAY) { pSda->sda_ReplySize = RANDNUM_RESP_LEN; if (AfpAllocReplyBuf(pSda) != AFP_ERR_NONE) { return(AFP_ERR_USER_NOT_AUTH); } pTmp = pSda->sda_ReplyBuf; PUTBYTE42BYTE4(pTmp, (PBYTE)&ResponseHigh); pTmp += sizeof(DWORD); PUTBYTE42BYTE4(pTmp, (PBYTE)&ResponseLow); } else if ((pSda->sda_ClientType == SDA_CLIENT_MSUAM_V2) || (pSda->sda_ClientType == SDA_CLIENT_MSUAM_V3)) { pSda->sda_ReplySize = sizeof(DWORD); if (AfpAllocReplyBuf(pSda) != AFP_ERR_NONE) { return(AFP_ERR_USER_NOT_AUTH); } pTmp = pSda->sda_ReplyBuf; PUTBYTE42BYTE4(pTmp, (PBYTE)&pSda->sda_tTillKickOff); } } else // if (NT_SUCCESS(Status) != NO_ERROR) { NTSTATUS ExtErrCode = Status; DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR, ("AfpLogonUser: AcceptSecurityContext() failed with %X\n", Status)); SubStatus = NtFreeVirtualMemory(NtCurrentProcess(), (PVOID *)&InToken, &AllocateSize, MEM_RELEASE ); ASSERT(NT_SUCCESS(SubStatus)); // Set extended error codes here if using custom UAM or AFP 2.1 Status = AFP_ERR_USER_NOT_AUTH; // default // The mac will map this to a session error dialog for each UAM. // The dialog may be a little different for different versions of // the mac OS and each UAM, but will always have something to do // with getting the message across about no more sessions available. if (ExtErrCode == STATUS_LICENSE_QUOTA_EXCEEDED) { DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR, ("AfpLogonUser: License Quota Exceeded: returning ASP_SERVER_BUSY\n")); return (ASP_SERVER_BUSY); } if ((pSda->sda_ClientVersion >= AFP_VER_21) && (pSda->sda_ClientType != SDA_CLIENT_MSUAM_V2) && (pSda->sda_ClientType != SDA_CLIENT_MSUAM_V3)) { if ((ExtErrCode == STATUS_PASSWORD_EXPIRED) || (ExtErrCode == STATUS_PASSWORD_MUST_CHANGE)) Status = AFP_ERR_PWD_EXPIRED; } else if ((pSda->sda_ClientType == SDA_CLIENT_MSUAM_V1) || (pSda->sda_ClientType == SDA_CLIENT_MSUAM_V2) || (pSda->sda_ClientType == SDA_CLIENT_MSUAM_V3)) { if ((ExtErrCode == STATUS_PASSWORD_EXPIRED) || (ExtErrCode == STATUS_PASSWORD_MUST_CHANGE)) Status = AFP_ERR_PASSWORD_EXPIRED; else if ((ExtErrCode == STATUS_ACCOUNT_DISABLED) || (ExtErrCode == STATUS_ACCOUNT_LOCKED_OUT)) Status = AFP_ERR_ACCOUNT_DISABLED; else if (ExtErrCode == STATUS_INVALID_LOGON_HOURS) Status = AFP_ERR_INVALID_LOGON_HOURS; else if (ExtErrCode == STATUS_INVALID_WORKSTATION) Status = AFP_ERR_INVALID_WORKSTATION; } return( Status ); } // // get the token out using the context // Status = QuerySecurityContextToken( &hNewContext, &pSda->sda_UserToken ); if (!NT_SUCCESS(Status)) { DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR, ("AfpLogonUser: QuerySecurityContextToken() failed with %X\n", Status)); pSda->sda_UserToken = NULL; // just paranoia return(Status); } Status = DeleteSecurityContext( &hNewContext ); if (!NT_SUCCESS(Status)) { DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR, ("AfpLogonUser: DeleteSecurityContext() failed with %X\n", Status)); } Status = AfpGetUserAndPrimaryGroupSids(pSda); if (!NT_SUCCESS(Status)) { DBGPRINT(DBG_COMP_SECURITY, DBG_LEVEL_ERR, ("AfpLogonUser: AfpGetUserAndPrimaryGroupSids() failed with %X\n", Status)); AFPLOG_ERROR(AFPSRVMSG_LOGON, Status, NULL, 0, NULL); return( Status ); } #ifdef INHERIT_DIRECTORY_PERMS // Convert the user and group sids to IDs AfpSidToMacId(pSda->sda_UserSid, &pSda->sda_UID); AfpSidToMacId(pSda->sda_GroupSid, &pSda->sda_GID); #else // Make a security descriptor for user Status = AfpMakeSecurityDescriptorForUser(pSda->sda_UserSid, pSda->sda_GroupSid, &pSda->sda_pSecDesc); #endif #ifdef OPTIMIZE_GUEST_LOGONS if (pSda->sda_ClientType == SDA_CLIENT_GUEST) { // Save the guest login token and security descriptor AfpSwmrAcquireExclusive(&AfpEtcMapLock); AfpGuestToken = pSda->sda_UserToken; #ifdef INHERIT_DIRECTORY_PERMS AfpSidToMacId(&AfpSidWorld, &AfpIdWorld); #else AfpGuestSecDesc = pSda->sda_pSecDesc; #endif AfpSwmrRelease(&AfpEtcMapLock); } #endif // OPTIMIZE_GUEST_LOGONS return Status; }