1100 lines
31 KiB
C++
1100 lines
31 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1993.
|
|
//
|
|
// File: kpasswd.cxx
|
|
//
|
|
// Contents: Functions for the kpasswd protocol
|
|
//
|
|
// History: 30-Sep-97 MikeSw Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
#include "kdcsvr.hxx"
|
|
#include "kpasswd.h"
|
|
#include "kdctrace.h"
|
|
extern "C"
|
|
{
|
|
#include <nlrepl.h>
|
|
}
|
|
|
|
#define FILENO FILENO_KPASSWD
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcbMarshallApReply
|
|
//
|
|
// Synopsis: Takes a reply and reply body and encrypts and marshalls them
|
|
// into a return message
|
|
//
|
|
// Effects: Allocates output buffer
|
|
//
|
|
// Arguments: Reply - The outer reply to marshall
|
|
// ReplyBody - The reply body to marshall
|
|
// EncryptionType - Encryption algorithm to use
|
|
// SessionKey - Session key to encrypt reply
|
|
// PackedReply - Recives marshalled reply buffer
|
|
// PackedReplySize - Receives size in bytes of marshalled reply
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcMarshallApReply(
|
|
IN PKERB_AP_REPLY Reply,
|
|
IN PKERB_ENCRYPTED_AP_REPLY ReplyBody,
|
|
IN ULONG EncryptionType,
|
|
IN PKERB_ENCRYPTION_KEY SessionKey,
|
|
OUT PUCHAR * PackedReply,
|
|
OUT PULONG PackedReplySize
|
|
)
|
|
{
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
ULONG PackedApReplySize;
|
|
PUCHAR PackedApReply = NULL;
|
|
ULONG EncryptionOverhead;
|
|
ULONG BlockSize;
|
|
ULONG ReplySize;
|
|
|
|
KerbErr = KerbPackApReplyBody(
|
|
ReplyBody,
|
|
&PackedApReplySize,
|
|
&PackedApReply
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now encrypt the response
|
|
//
|
|
|
|
KerbErr = KerbAllocateEncryptionBufferWrapper(
|
|
EncryptionType,
|
|
PackedApReplySize,
|
|
&Reply->encrypted_part.cipher_text.length,
|
|
&Reply->encrypted_part.cipher_text.value
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
KerbErr = KerbEncryptDataEx(
|
|
&Reply->encrypted_part,
|
|
PackedApReplySize,
|
|
PackedApReply,
|
|
EncryptionType,
|
|
KERB_AP_REP_SALT,
|
|
SessionKey
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to encrypt AP Reply.%d\n", KerbErr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now pack the reply into the output buffer
|
|
//
|
|
|
|
KerbErr = KerbPackApReply(
|
|
Reply,
|
|
PackedReplySize,
|
|
PackedReply
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
if (Reply->encrypted_part.cipher_text.value != NULL)
|
|
{
|
|
MIDL_user_free(Reply->encrypted_part.cipher_text.value);
|
|
Reply->encrypted_part.cipher_text.value = NULL;
|
|
}
|
|
if (PackedApReply != NULL)
|
|
{
|
|
MIDL_user_free(PackedApReply);
|
|
}
|
|
return(KerbErr);
|
|
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcBuildKpasswdResponse
|
|
//
|
|
// Synopsis: builds the response to a kpasswd request
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
KERBERR
|
|
KdcBuildKpasswdResponse(
|
|
IN OPTIONAL PKERB_ENCRYPTED_TICKET EncryptedTicket,
|
|
IN OPTIONAL PKERB_AUTHENTICATOR Authenticator,
|
|
IN OPTIONAL PKERB_ENCRYPTION_KEY SessionKey,
|
|
IN OPTIONAL PSOCKADDR ServerAddress,
|
|
IN NTSTATUS ChangeResult,
|
|
IN KERBERR ProtocolResult,
|
|
IN PKERB_EXT_ERROR pExtendedError,
|
|
OUT PKERB_MESSAGE_BUFFER Response
|
|
)
|
|
{
|
|
KERB_PRIV_MESSAGE PrivMessage = {0};
|
|
KERB_ENCRYPTED_PRIV PrivBody = {0};
|
|
USHORT ReplySize = 0;
|
|
PBYTE PackedApReply = NULL;
|
|
ULONG PackedApReplySize = 0;
|
|
BYTE ResultData[2] = {0};
|
|
PBYTE PackedPrivBody = NULL;
|
|
ULONG PackedPrivBodySize = 0;
|
|
ULONG EncryptionOverhead;
|
|
ULONG BlockSize;
|
|
KERBERR KerbErr = KRB_ERR_GENERIC;
|
|
PBYTE ReplyData = NULL;
|
|
ULONG ReplyDataSize = 0;
|
|
PSOCKET_ADDRESS IpAddresses = NULL;
|
|
ULONG AddressCount = 0;
|
|
BOOLEAN ReturningError = TRUE;
|
|
|
|
if (!NT_SUCCESS(ChangeResult))
|
|
{
|
|
switch(ChangeResult)
|
|
{
|
|
case STATUS_PASSWORD_RESTRICTION:
|
|
SET_SHORT(ResultData,KERB_KPASSWD_POLICY);
|
|
ProtocolResult = KDC_ERR_POLICY;
|
|
break;
|
|
case STATUS_ACCOUNT_RESTRICTION:
|
|
SET_SHORT(ResultData, KERB_KPASSWD_AUTHENTICATION);
|
|
ProtocolResult = KDC_ERR_CLIENT_REVOKED;
|
|
break;
|
|
case STATUS_INVALID_PARAMETER:
|
|
SET_SHORT(ResultData, KERB_KPASSWD_MALFORMED);
|
|
ProtocolResult = KRB_ERR_GENERIC;
|
|
break;
|
|
case STATUS_ACCESS_DENIED:
|
|
SET_SHORT(ResultData, KERB_KPASSWD_AUTHORIZATION);
|
|
ProtocolResult = KRB_ERR_GENERIC;
|
|
break;
|
|
default:
|
|
SET_SHORT(ResultData, KERB_KPASSWD_ERROR);
|
|
ProtocolResult = KRB_ERR_GENERIC;
|
|
break;
|
|
}
|
|
}
|
|
else if (!KERB_SUCCESS(ProtocolResult))
|
|
{
|
|
switch(ProtocolResult)
|
|
{
|
|
case KRB_ERR_GENERIC:
|
|
//
|
|
// BUG 453652: how does this distinguish between random hard errors
|
|
// and malformed data?
|
|
//
|
|
SET_SHORT(ResultData, KERB_KPASSWD_MALFORMED);
|
|
break;
|
|
default:
|
|
//
|
|
// The other errors come from the call to verify the
|
|
// AP request
|
|
//
|
|
|
|
SET_SHORT(ResultData, KERB_KPASSWD_AUTHENTICATION);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now build the AP reply, if possible.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(EncryptedTicket))
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
KERB_AP_REPLY Reply = {0};
|
|
KERB_ENCRYPTED_AP_REPLY ReplyBody = {0};
|
|
|
|
|
|
Reply.version = KERBEROS_VERSION;
|
|
Reply.message_type = KRB_AP_REP;
|
|
|
|
|
|
|
|
ReplyBody.client_time = Authenticator->client_time;
|
|
ReplyBody.client_usec = Authenticator->client_usec;
|
|
|
|
|
|
KerbErr = KdcMarshallApReply(
|
|
&Reply,
|
|
&ReplyBody,
|
|
EncryptedTicket->key.keytype,
|
|
&EncryptedTicket->key,
|
|
&PackedApReply,
|
|
&PackedApReplySize
|
|
);
|
|
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto BuildError;
|
|
}
|
|
|
|
PrivBody.sender_address.addr_type = KERB_ADDRTYPE_INET;
|
|
|
|
if (ARGUMENT_PRESENT(ServerAddress))
|
|
{
|
|
PrivBody.sender_address.address.length = 4;
|
|
PrivBody.sender_address.address.value =
|
|
(PUCHAR) &((PSOCKADDR_IN)ServerAddress)->sin_addr.S_un.S_addr;
|
|
|
|
}
|
|
else
|
|
{
|
|
PrivBody.sender_address.address.length = 0;
|
|
PrivBody.sender_address.address.value = NULL;
|
|
}
|
|
|
|
PrivBody.user_data.length = sizeof(ResultData);
|
|
PrivBody.user_data.value = ResultData;
|
|
|
|
KerbErr = KerbPackData(
|
|
&PrivBody,
|
|
KERB_ENCRYPTED_PRIV_PDU,
|
|
&PackedPrivBodySize,
|
|
&PackedPrivBody
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto BuildError;
|
|
}
|
|
|
|
//
|
|
// Now encrypt it with the session key
|
|
//
|
|
|
|
|
|
KerbErr = KerbAllocateEncryptionBufferWrapper(
|
|
SessionKey->keytype,
|
|
PackedPrivBodySize,
|
|
&PrivMessage.encrypted_part.cipher_text.length,
|
|
&PrivMessage.encrypted_part.cipher_text.value
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto BuildError;
|
|
}
|
|
|
|
|
|
KerbErr = KerbEncryptDataEx(
|
|
&PrivMessage.encrypted_part,
|
|
PackedPrivBodySize,
|
|
PackedPrivBody,
|
|
SessionKey->keytype,
|
|
KERB_PRIV_SALT,
|
|
SessionKey
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto BuildError;
|
|
}
|
|
|
|
PrivMessage.version = KERBEROS_VERSION;
|
|
PrivMessage.message_type = KRB_PRIV;
|
|
|
|
//
|
|
// Now pack the KERB_PRIV
|
|
//
|
|
|
|
KerbErr = KerbPackData(
|
|
&PrivMessage,
|
|
KERB_PRIV_MESSAGE_PDU,
|
|
&ReplyDataSize,
|
|
&ReplyData
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto BuildError;
|
|
}
|
|
ReturningError = FALSE;
|
|
}
|
|
|
|
BuildError:
|
|
|
|
//
|
|
// If we have an error of one of the three error codes, build the
|
|
// appropriate result code & error code for the KERB_ERROR
|
|
//
|
|
|
|
if (!KERB_SUCCESS(KerbErr) || (ReplyData == NULL))
|
|
{
|
|
UNICODE_STRING TempString;
|
|
RtlInitUnicodeString(
|
|
&TempString,
|
|
KERB_KPASSWD_NAME
|
|
);
|
|
|
|
KerbErr = KerbBuildErrorMessageEx(
|
|
ProtocolResult,
|
|
pExtendedError, // note: probably won't get used
|
|
SecData.KdcDnsRealmName(),
|
|
SecData.KpasswdInternalName(),
|
|
NULL, // no client realm,
|
|
ResultData,
|
|
sizeof(ResultData),
|
|
&ReplyDataSize,
|
|
&ReplyData
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, if we have an AP reply, build a kpasswd response. Otherwise
|
|
// return the kerb_error message
|
|
//
|
|
|
|
if (ReturningError)
|
|
{
|
|
Response->Buffer = ReplyData;
|
|
ReplyData = NULL;
|
|
Response->BufferSize = ReplyDataSize;
|
|
goto Cleanup;
|
|
}
|
|
else
|
|
{
|
|
USHORT TempShort;
|
|
PKERB_KPASSWD_REP Reply = NULL;
|
|
if ((FIELD_OFFSET(KERB_KPASSWD_REP,Data) +
|
|
PackedApReplySize +
|
|
ReplyDataSize) > SHRT_MAX)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Kpasswd reply too long!\n"));
|
|
KerbErr = KRB_ERR_FIELD_TOOLONG;
|
|
goto Cleanup;
|
|
}
|
|
ReplySize = (USHORT) (FIELD_OFFSET(KERB_KPASSWD_REP,Data) +
|
|
PackedApReplySize +
|
|
ReplyDataSize);
|
|
|
|
Reply = (PKERB_KPASSWD_REP) MIDL_user_allocate(ReplySize);
|
|
|
|
if (Reply == NULL)
|
|
{
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
SET_SHORT(Reply->MessageLength,ReplySize);
|
|
SET_SHORT(Reply->Version, KERB_KPASSWD_VERSION);
|
|
SET_SHORT(Reply->ApRepLength, (USHORT) PackedApReplySize);
|
|
RtlCopyMemory(
|
|
Reply->Data,
|
|
PackedApReply,
|
|
PackedApReplySize
|
|
);
|
|
RtlCopyMemory(
|
|
Reply->Data + PackedApReplySize,
|
|
ReplyData,
|
|
ReplyDataSize
|
|
);
|
|
Response->Buffer = (PBYTE) Reply;
|
|
Response->BufferSize = ReplySize;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if (IpAddresses != NULL)
|
|
{
|
|
I_NetLogonFree(IpAddresses);
|
|
}
|
|
|
|
if (PackedApReply != NULL)
|
|
{
|
|
MIDL_user_free(PackedApReply);
|
|
}
|
|
if (PackedPrivBody !=NULL)
|
|
{
|
|
MIDL_user_free(PackedPrivBody);
|
|
}
|
|
if (ReplyData != NULL)
|
|
{
|
|
MIDL_user_free(ReplyData);
|
|
}
|
|
return(KerbErr);
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: KdcChangePassword
|
|
//
|
|
// Synopsis: receives a kerb-change-password buffer and attempts the
|
|
// password change
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: Context - ATQ context, used for requesting more data if the
|
|
// buffer isn't complete
|
|
// ClientAddress - Address of client, from the socket
|
|
// ServerAddress - address client used to contact this server.
|
|
// InputMessage - Receives data sent by client
|
|
// OutputMessage - Contains data to be sent to client & freed
|
|
// using KdcFreeEncodedData
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
extern "C"
|
|
KERBERR
|
|
KdcChangePassword(
|
|
IN OPTIONAL PVOID Context,
|
|
IN OPTIONAL PSOCKADDR ClientAddress,
|
|
IN OPTIONAL PSOCKADDR ServerAddress,
|
|
IN PKERB_MESSAGE_BUFFER InputMessage,
|
|
OUT PKERB_MESSAGE_BUFFER OutputMessage
|
|
)
|
|
{
|
|
PKERB_KPASSWD_REQ Request = NULL;
|
|
PKERB_KPASSWD_REP Reply = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
KERBERR KerbErr = KDC_ERR_NONE;
|
|
KERB_EXT_ERROR ExtendedError = {0,0};
|
|
PKERB_EXT_ERROR pExtendedError = &ExtendedError;
|
|
USHORT ProtocolVersion;
|
|
USHORT MessageLength;
|
|
USHORT ApReqLength;
|
|
ULONG PrivLength;
|
|
PKERB_AUTHENTICATOR Authenticator = NULL;
|
|
PKERB_ENCRYPTED_TICKET EncryptedTicket = NULL;
|
|
KERB_ENCRYPTION_KEY SessionKey = {0};
|
|
KERB_ENCRYPTION_KEY ServerKey = {0};
|
|
KDC_TICKET_INFO ServerTicketInfo = {0};
|
|
KDC_TICKET_INFO ClientTicketInfo = {0};
|
|
UNICODE_STRING Password = {0};
|
|
ANSI_STRING AnsiPassword = {0};
|
|
PKERB_PRIV_MESSAGE PrivMessage = NULL;
|
|
PKERB_ENCRYPTED_PRIV PrivBody = NULL;
|
|
BOOLEAN UseSubKey = FALSE;
|
|
ULONG TicketFlags;
|
|
BOOLEAN DoPasswordSet = FALSE;
|
|
PKERB_CHANGE_PASSWORD_DATA ChangeData = NULL;
|
|
|
|
PKERB_INTERNAL_NAME ClientName = NULL;
|
|
UNICODE_STRING ClientRealm = {0};
|
|
UNICODE_STRING ReferralRealm = {0};
|
|
BOOLEAN ClientReferral = FALSE;
|
|
HANDLE TokenHandle = NULL;
|
|
PSID UserSid = NULL;
|
|
ULONG UserRid = 0;
|
|
|
|
SAM_CLIENT_INFO SamClientInfoBuffer;
|
|
PSAM_CLIENT_INFO SamClientInfo = NULL;
|
|
|
|
KDC_CHANGEPASS_INFO ChangePassTraceInfo;
|
|
if( KdcEventTraceFlag ) // Event Trace: KerbChangePasswordStart {No Data}
|
|
{
|
|
ChangePassTraceInfo.EventTrace.Guid = KdcChangePassGuid;
|
|
ChangePassTraceInfo.EventTrace.Class.Type = EVENT_TRACE_TYPE_START;
|
|
ChangePassTraceInfo.EventTrace.Flags = WNODE_FLAG_TRACED_GUID;
|
|
ChangePassTraceInfo.EventTrace.Size = sizeof(EVENT_TRACE_HEADER);
|
|
|
|
TraceEvent( KdcTraceLoggerHandle, (PEVENT_TRACE_HEADER)&ChangePassTraceInfo );
|
|
}
|
|
|
|
Status = EnterApiCall();
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return(Status);
|
|
}
|
|
|
|
if (InputMessage->BufferSize < sizeof(KERB_KPASSWD_REQ))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Bad message size to KdcChangePassword: %d\n",InputMessage->BufferSize));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
KerbErr = KDC_ERR_NO_RESPONSE;
|
|
goto NoMsgResponse;
|
|
}
|
|
|
|
Request = (PKERB_KPASSWD_REQ) InputMessage->Buffer;
|
|
//
|
|
// Verify the length & protocol version
|
|
//
|
|
|
|
GET_SHORT(MessageLength, Request->MessageLength);
|
|
GET_SHORT(ProtocolVersion, Request->Version);
|
|
|
|
if (ProtocolVersion == KERB_KPASSWD_SET_VERSION)
|
|
{
|
|
//
|
|
// This is the advanced protocol
|
|
//
|
|
|
|
DoPasswordSet = TRUE;
|
|
}
|
|
else if (ProtocolVersion != KERB_KPASSWD_VERSION)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Bad version passed to KdcChangePassword: %d\n",
|
|
ProtocolVersion ));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
KerbErr = KDC_ERR_NO_RESPONSE;
|
|
goto NoMsgResponse;
|
|
}
|
|
|
|
if ((ULONG)MessageLength != InputMessage->BufferSize)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Bad length passed to KdcChangePassword: %d instead of %d\n",
|
|
MessageLength, InputMessage->BufferSize ));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
KerbErr = KDC_ERR_NO_RESPONSE;
|
|
goto NoMsgResponse;
|
|
}
|
|
|
|
//
|
|
// Unpack the AP request
|
|
//
|
|
|
|
GET_SHORT(ApReqLength, Request->ApReqLength);
|
|
|
|
if (FIELD_OFFSET(KERB_KPASSWD_REQ, ApReqLength) + ApReqLength > MessageLength)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"ApReqLength in kpasswd request bad: %d\n",ApReqLength));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
KerbErr = KDC_ERR_NO_RESPONSE;
|
|
goto NoMsgResponse;
|
|
}
|
|
|
|
//
|
|
// Verify the AP request
|
|
//
|
|
|
|
KerbErr = KdcVerifyKdcRequest(
|
|
Request->Data,
|
|
ApReqLength,
|
|
ClientAddress,
|
|
FALSE, // this is not a kdc request
|
|
NULL,
|
|
&Authenticator,
|
|
&EncryptedTicket,
|
|
&SessionKey,
|
|
&ServerKey,
|
|
&ServerTicketInfo,
|
|
&UseSubKey,
|
|
pExtendedError
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
if (KerbErr == KDC_ERR_NO_RESPONSE)
|
|
{
|
|
goto NoMsgResponse;
|
|
}
|
|
|
|
DebugLog((DEB_WARN,"Failed to unpack AP req in kpasswd request: 0x%x\n",
|
|
KerbErr ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// The spec says the client has to ask for a sub key.
|
|
//
|
|
|
|
if (!UseSubKey)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"The client of kpasswd did not ask for a sub key.\n"));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// The name of the principal must be correct.
|
|
//
|
|
// WAS BUG: check by RID
|
|
//
|
|
|
|
if (ServerTicketInfo.UserId != DOMAIN_USER_RID_KRBTGT)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Wrong principal for kpasswd: %wZ\n",
|
|
&ServerTicketInfo.AccountName ));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
KerbErr = KRB_AP_ERR_NOT_US;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now try to unpack the KERB_PRIV
|
|
//
|
|
|
|
PrivLength = MessageLength - (ApReqLength + FIELD_OFFSET(KERB_KPASSWD_REQ, Data));
|
|
KerbErr = KerbUnpackData(
|
|
Request->Data + ApReqLength,
|
|
PrivLength,
|
|
KERB_PRIV_MESSAGE_PDU,
|
|
(PVOID *) &PrivMessage
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to decode priv message in kpasswd req: 0x%x\n",KerbErr));
|
|
FILL_EXT_ERROR(pExtendedError, STATUS_KDC_INVALID_REQUEST, FILENO, __LINE__);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now decrypt the KERB_PRIV message
|
|
//
|
|
|
|
if (PrivMessage->version != KERBEROS_VERSION)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Bad version in kpasswd priv message: %d\n",
|
|
PrivMessage->version ));
|
|
KerbErr = KRB_AP_ERR_BADVERSION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (PrivMessage->message_type != KRB_PRIV)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Bad message type in kpasswd priv message: %d\n",
|
|
PrivMessage->message_type ));
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
KerbErr = KerbDecryptDataEx(
|
|
&PrivMessage->encrypted_part,
|
|
&SessionKey,
|
|
KERB_PRIV_SALT,
|
|
(PULONG) &PrivMessage->encrypted_part.cipher_text.length,
|
|
PrivMessage->encrypted_part.cipher_text.value
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to decrypt priv message from kpasswd: 0x%x\n",
|
|
KerbErr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now decode the kerb priv body
|
|
//
|
|
|
|
KerbErr = KerbUnpackData(
|
|
PrivMessage->encrypted_part.cipher_text.value,
|
|
(ULONG) PrivMessage->encrypted_part.cipher_text.length,
|
|
KERB_ENCRYPTED_PRIV_PDU,
|
|
(PVOID *) &PrivBody
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to unpack priv body from kpasswd: 0x%x\n",
|
|
KerbErr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify the client's address
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(ClientAddress))
|
|
{
|
|
KERB_HOST_ADDRESSES Addresses;
|
|
//
|
|
// Build a host_addresses structure because the caller sent a single
|
|
// address.
|
|
//
|
|
|
|
Addresses.next = NULL;
|
|
Addresses.value.address_type = PrivBody->sender_address.addr_type;
|
|
Addresses.value.address.value = PrivBody->sender_address.address.value;
|
|
Addresses.value.address.length = PrivBody->sender_address.address.length;
|
|
|
|
KerbErr = KdcVerifyClientAddress(
|
|
ClientAddress,
|
|
&Addresses
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Client sent kpasswd request with wrong address\n"));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, we should have a password
|
|
//
|
|
|
|
if (DoPasswordSet)
|
|
{
|
|
//
|
|
// Unpack the chaneg password data in the priv body.
|
|
//
|
|
|
|
KerbErr = KerbUnpackData(
|
|
PrivBody->user_data.value,
|
|
PrivBody->user_data.length,
|
|
KERB_CHANGE_PASSWORD_DATA_PDU,
|
|
(PVOID *) &ChangeData
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to unpack password change data from kpasswd: 0x%x\n",
|
|
KerbErr));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (ChangeData->new_password.length > SHRT_MAX / 2)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Password length too long: %d\n",
|
|
ChangeData->new_password.length ));
|
|
KerbErr = KRB_ERR_FIELD_TOOLONG;
|
|
goto Cleanup;
|
|
}
|
|
AnsiPassword.Length = (USHORT)ChangeData->new_password.length;
|
|
AnsiPassword.MaximumLength = AnsiPassword.Length;
|
|
AnsiPassword.Buffer = (PCHAR) ChangeData->new_password.value;
|
|
|
|
KerbErr = KerbStringToUnicodeString(
|
|
&Password,
|
|
&AnsiPassword
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// if the target name and realm aren't present, this is a
|
|
// change password
|
|
//
|
|
|
|
if ((ChangeData->bit_mask & (target_name_present | target_realm_present)) !=
|
|
(target_name_present | target_realm_present))
|
|
{
|
|
DoPasswordSet = FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Get the names from the change data
|
|
//
|
|
|
|
KerbErr = KerbConvertPrincipalNameToKdcName(
|
|
&ClientName,
|
|
&ChangeData->target_name
|
|
);
|
|
if (!NT_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbConvertRealmToUnicodeString(
|
|
&ClientRealm,
|
|
&ChangeData->target_realm
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!DoPasswordSet)
|
|
{
|
|
//
|
|
// The spec says the ticket must be an initial ticket for a
|
|
// change.
|
|
//
|
|
|
|
TicketFlags = KerbConvertFlagsToUlong(&EncryptedTicket->flags);
|
|
if ((TicketFlags & KERB_TICKET_FLAGS_initial) == 0)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Ticket to kpasswd was not initial\n"));
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// NOTE: verify other kerb-priv fields here
|
|
// Current ID doesn't require other kerb priv fields, so
|
|
// this is a no-op. If things change, however, we'll need
|
|
// to add code here.
|
|
//
|
|
//
|
|
// If we didn't get a password from the change password data,
|
|
// get it directly from the data
|
|
//
|
|
|
|
if (Password.Buffer == NULL)
|
|
{
|
|
|
|
//
|
|
// The password has to fit in a unicode string, so it can't be longer
|
|
// than half a ushort.
|
|
//
|
|
|
|
if (PrivBody->user_data.length > SHRT_MAX / 2)
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Password length too long: %d\n",
|
|
PrivBody->user_data.length ));
|
|
KerbErr = KRB_ERR_FIELD_TOOLONG;
|
|
goto Cleanup;
|
|
}
|
|
AnsiPassword.Length = (USHORT)PrivBody->user_data.length;
|
|
AnsiPassword.MaximumLength = AnsiPassword.Length;
|
|
AnsiPassword.Buffer = (PCHAR) PrivBody->user_data.value;
|
|
|
|
KerbErr = KerbStringToUnicodeString(
|
|
&Password,
|
|
&AnsiPassword
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
KerbErr = KerbConvertPrincipalNameToKdcName(
|
|
&ClientName,
|
|
&EncryptedTicket->client_name
|
|
);
|
|
if (!NT_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
KerbErr = KerbConvertRealmToUnicodeString(
|
|
&ClientRealm,
|
|
&EncryptedTicket->client_realm
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the client ticket info so we can do a set pass
|
|
//
|
|
|
|
KerbErr = KdcNormalize(
|
|
ClientName,
|
|
NULL,
|
|
&ClientRealm,
|
|
KDC_NAME_CLIENT,
|
|
&ClientReferral,
|
|
&ReferralRealm,
|
|
&ClientTicketInfo,
|
|
pExtendedError,
|
|
NULL, // no UserHandle
|
|
0L, // no fields to fetch
|
|
0L, // no extended fields
|
|
NULL, // no fields to fetch
|
|
NULL // no GroupMembership
|
|
);
|
|
if (!KERB_SUCCESS(KerbErr))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to normalize name "));
|
|
KerbPrintKdcName(DEB_ERROR,ClientName);
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
{
|
|
|
|
LUID LogonId = {0};
|
|
UNICODE_STRING UClientName = {0};
|
|
UNICODE_STRING UClientDomain = {0};
|
|
|
|
Status = KerbCreateTokenFromTicket(
|
|
EncryptedTicket,
|
|
Authenticator,
|
|
0, // no flags
|
|
&ServerKey,
|
|
SecData.KdcDnsRealmName(),
|
|
&SessionKey,
|
|
&LogonId,
|
|
&UserSid,
|
|
&TokenHandle,
|
|
&UClientName,
|
|
&UClientDomain
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to create token from ticket: 0x%x\n",Status));
|
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
|
KerbErr = KRB_ERR_GENERIC;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Setup the client info
|
|
//
|
|
if ( (ClientAddress == NULL)
|
|
|| (ClientAddress->sa_family == AF_INET) ) {
|
|
// Set to local address (known to be 4 bytes) or IP address
|
|
RtlZeroMemory(&SamClientInfoBuffer, sizeof(SamClientInfoBuffer));
|
|
SamClientInfoBuffer.Type = SamClientIpAddr;
|
|
SamClientInfoBuffer.Data.IpAddr = *((ULONG*)GET_CLIENT_ADDRESS(ClientAddress));
|
|
SamClientInfo = &SamClientInfoBuffer;
|
|
}
|
|
|
|
//
|
|
// Free all the memory returned
|
|
//
|
|
KerbFree(UClientName.Buffer);
|
|
KerbFree(UClientDomain.Buffer);
|
|
KerbFree(UserSid);
|
|
//
|
|
// Store the password on the user's account
|
|
//
|
|
|
|
//
|
|
// We shouldn't enforce password policy restrictions if we do a password SET
|
|
//
|
|
|
|
if (!DoPasswordSet)
|
|
{
|
|
Status = SamIChangePasswordForeignUser2(
|
|
SamClientInfo,
|
|
&ClientTicketInfo.AccountName,
|
|
&Password,
|
|
TokenHandle,
|
|
USER_CHANGE_PASSWORD
|
|
);
|
|
|
|
} else {
|
|
|
|
Status = SamISetPasswordForeignUser2(
|
|
SamClientInfo,
|
|
&ClientTicketInfo.AccountName,
|
|
&Password,
|
|
TokenHandle
|
|
);
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to change password for user %wZ: 0x%x\n",
|
|
&ClientTicketInfo.AccountName, Status ));
|
|
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
|
|
KerbErr = KDC_ERR_POLICY;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
KerbErr = KdcBuildKpasswdResponse(
|
|
EncryptedTicket,
|
|
Authenticator,
|
|
&SessionKey,
|
|
ServerAddress,
|
|
Status,
|
|
KerbErr,
|
|
pExtendedError,
|
|
OutputMessage
|
|
);
|
|
|
|
NoMsgResponse:
|
|
|
|
|
|
|
|
if( KdcEventTraceFlag ) // Event Trace: KdcChangePasswordEnd {KerbErr, ExtErr, Klininfo, (ClientRealm), (AccountName)}
|
|
{
|
|
INSERT_ULONG_INTO_MOF( KerbErr, ChangePassTraceInfo.MofData, 0 );
|
|
INSERT_ULONG_INTO_MOF( ExtendedError.status, ChangePassTraceInfo.MofData, 1 );
|
|
INSERT_ULONG_INTO_MOF( ExtendedError.klininfo, ChangePassTraceInfo.MofData, 2 );
|
|
|
|
// Protect against uninitialized UNICODE_STRINGs
|
|
|
|
WCHAR UnicodeNullChar = 0;
|
|
UNICODE_STRING UnicodeEmptyString = {sizeof(WCHAR),sizeof(WCHAR),&UnicodeNullChar};
|
|
PUNICODE_STRING pClientRealmTraceString = &ClientRealm;
|
|
PUNICODE_STRING pAccountNameTraceString = &ClientTicketInfo.AccountName;
|
|
|
|
if( ClientRealm.Buffer == NULL || ClientRealm.Length <= 0 )
|
|
pClientRealmTraceString = &UnicodeEmptyString;
|
|
|
|
if( ClientTicketInfo.AccountName.Buffer == NULL || ClientTicketInfo.AccountName.Length <= 0 )
|
|
pAccountNameTraceString = &UnicodeEmptyString;
|
|
|
|
//
|
|
|
|
INSERT_UNICODE_STRING_INTO_MOF( *pClientRealmTraceString, ChangePassTraceInfo.MofData, 3 );
|
|
INSERT_UNICODE_STRING_INTO_MOF( *pAccountNameTraceString, ChangePassTraceInfo.MofData, 5 );
|
|
|
|
ChangePassTraceInfo.EventTrace.Size = sizeof(EVENT_TRACE_HEADER) + 7*sizeof(MOF_FIELD);
|
|
|
|
ChangePassTraceInfo.EventTrace.Guid = KdcChangePassGuid;
|
|
ChangePassTraceInfo.EventTrace.Class.Type = EVENT_TRACE_TYPE_END;
|
|
ChangePassTraceInfo.EventTrace.Flags = WNODE_FLAG_TRACED_GUID | WNODE_FLAG_USE_MOF_PTR;
|
|
|
|
TraceEvent( KdcTraceLoggerHandle, (PEVENT_TRACE_HEADER)&ChangePassTraceInfo );
|
|
}
|
|
|
|
KerbFreeKey( &SessionKey );
|
|
KerbFreeKey( &ServerKey );
|
|
KerbFreeTicket(EncryptedTicket);
|
|
FreeTicketInfo(&ServerTicketInfo);
|
|
FreeTicketInfo(&ClientTicketInfo);
|
|
KerbFreeData(KERB_PRIV_MESSAGE_PDU, PrivMessage);
|
|
KerbFreeData(KERB_ENCRYPTED_PRIV_PDU, PrivBody);
|
|
KerbFreeData(KERB_CHANGE_PASSWORD_DATA_PDU, ChangeData);
|
|
KerbFreeString(&Password);
|
|
KerbFreeAuthenticator(Authenticator);
|
|
|
|
KerbFreeKdcName(&ClientName);
|
|
KerbFreeString(&ClientRealm);
|
|
KerbFreeString(&ReferralRealm);
|
|
|
|
if (TokenHandle != NULL)
|
|
{
|
|
NtClose(TokenHandle);
|
|
}
|
|
|
|
LeaveApiCall();
|
|
|
|
return(KerbErr);
|
|
}
|