NT4/private/newsam2/server/security.c

933 lines
22 KiB
C
Raw Permalink Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 1990 Microsoft Corporation
Module Name:
security.c
Abstract:
This file contains services which perform access validation on
attempts to access SAM objects. It also performs auditing on
both open and close operations.
Author:
Jim Kelly (JimK) 6-July-1991
Environment:
User Mode - Win32
Revision History:
--*/
///////////////////////////////////////////////////////////////////////////////
// //
// Includes //
// //
///////////////////////////////////////////////////////////////////////////////
#include <samsrvp.h>
#include <ntseapi.h>
#include <seopaque.h>
///////////////////////////////////////////////////////////////////////////////
// //
// private service prototypes //
// //
///////////////////////////////////////////////////////////////////////////////
VOID
SampRemoveAnonymousChangePasswordAccess(
IN OUT PSECURITY_DESCRIPTOR Sd
);
///////////////////////////////////////////////////////////////////////////////
// //
// Routines //
// //
///////////////////////////////////////////////////////////////////////////////
NTSTATUS
SampImpersonateNullSession(
)
/*++
Routine Description:
Impersonates the null session token
Arguments:
None
Return Value:
STATUS_CANNOT_IMPERSONATE - there is no null session token to imperonate
--*/
{
SAMTRACE("SampImpersonateNullSession");
if (SampNullSessionToken == NULL) {
return(STATUS_CANNOT_IMPERSONATE);
}
return( NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) &SampNullSessionToken,
sizeof(HANDLE)
) );
}
NTSTATUS
SampRevertNullSession(
)
/*++
Routine Description:
Reverts a thread from impersonating the null session token.
Arguments:
None
Return Value:
STATUS_CANNOT_IMPERSONATE - there was no null session token to be
imperonating.
--*/
{
HANDLE NullHandle = NULL;
SAMTRACE("SampRevertNullSession");
if (SampNullSessionToken == NULL) {
return(STATUS_CANNOT_IMPERSONATE);
}
return( NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
(PVOID) &NullHandle,
sizeof(HANDLE)
) );
}
NTSTATUS
SampValidateObjectAccess(
IN PSAMP_OBJECT Context,
IN ACCESS_MASK DesiredAccess,
IN BOOLEAN ObjectCreation
)
/*++
Routine Description:
This service performs access validation on the specified object.
The security descriptor of the object is expected to be in a sub-key
of the ObjectRootKey named "SecurityDescriptor".
This service:
1) Retrieves the target object's SecurityDescriptor from the
the ObjectRootKey,
2) Impersonates the client. If this fails, and we have a
null session token to use, imperonate that.
3) Uses NtAccessCheckAndAuditAlarm() to validate access to the
object,
4) Stops impersonating the client.
Upon successful completion, the passed context's GrantedAccess mask
and AuditOnClose fields will be properly set to represent the results
of the access validation. If the AuditOnClose field is set to TRUE,
then the caller is responsible for calling SampAuditOnClose() when
the object is closed.
Arguments:
Context - The handle value that will be assigned if the access validation
is successful.
DesiredAccess - Specifies the accesses being requested to the target
object.
ObjectCreation - A boolean flag indicated whether the access will
result in a new object being created if granted. A value of TRUE
indicates an object will be created, FALSE indicates an existing
object will be opened.
Return Value:
STATUS_SUCCESS - Indicates access has been granted.
Other values that may be returned are those returned by:
NtAccessCheckAndAuditAlarm()
--*/
{
NTSTATUS NtStatus, IgnoreStatus, AccessStatus;
ULONG SecurityDescriptorLength;
PSECURITY_DESCRIPTOR SecurityDescriptor;
ACCESS_MASK MappedDesiredAccess;
BOOLEAN TrustedClient;
SAMP_OBJECT_TYPE ObjectType;
PUNICODE_STRING ObjectName;
ULONG DomainIndex;
BOOLEAN ImpersonatingNullSession = FALSE;
SAMTRACE("SampValidateObjectAccess");
//
// Extract various fields from the account context
//
TrustedClient = Context->TrustedClient;
ObjectType = Context->ObjectType;
DomainIndex = Context->DomainIndex;
//
// Map the desired access
//
MappedDesiredAccess = DesiredAccess;
RtlMapGenericMask(
&MappedDesiredAccess,
&SampObjectInformation[ ObjectType ].GenericMapping
);
// This doesn't take ACCESS_SYSTEM_SECURITY into account.
//
//if ((SampObjectInformation[ObjectType].InvalidMappedAccess &
// MappedDesiredAccess) != 0) {
// return(STATUS_ACCESS_DENIED);
//}
if (TrustedClient) {
Context->GrantedAccess = MappedDesiredAccess;
Context->AuditOnClose = FALSE;
return(STATUS_SUCCESS);
}
//
// Calculate the string to use as an object name for auditing
//
NtStatus = STATUS_SUCCESS;
switch (ObjectType) {
case SampServerObjectType:
ObjectName = &SampServerObjectName;
break;
case SampDomainObjectType:
ObjectName = &SampDefinedDomains[DomainIndex].ExternalName;
break;
case SampUserObjectType:
case SampGroupObjectType:
case SampAliasObjectType:
ObjectName = &Context->RootName;
break;
default:
ASSERT(FALSE);
break;
}
if ( NT_SUCCESS(NtStatus)) {
//
// Fetch the object security descriptor so we can validate
// the access against it
//
NtStatus = SampGetObjectSD( Context, &SecurityDescriptorLength, &SecurityDescriptor);
if ( NT_SUCCESS(NtStatus)) {
//
// If this is a USER object, then we may have to mask the
// ability for Anonymous logons to change passwords.
//
if ( (ObjectType == SampUserObjectType) &&
(SampDefinedDomains[DomainIndex].UnmodifiedFixed.PasswordProperties
& DOMAIN_PASSWORD_NO_ANON_CHANGE) ) {
//
// Change our (local) copy of the object's DACL
// so that it doesn't grant CHANGE_PASSWORD to
// either WORLD or ANONYMOUS
//
SampRemoveAnonymousChangePasswordAccess(SecurityDescriptor);
}
//
// Impersonate the client. If RPC impersonation fails because
// it is not supported (came in unauthenticated), then impersonate
// the null session.
//
NtStatus = I_RpcMapWin32Status(RpcImpersonateClient( NULL ));
if (NtStatus == RPC_NT_CANNOT_SUPPORT) {
NtStatus = SampImpersonateNullSession();
ImpersonatingNullSession = TRUE;
}
if (NT_SUCCESS(NtStatus)) {
//
// Access validate the client
//
NtStatus = NtAccessCheckAndAuditAlarm(
&SampSamSubsystem,
(PVOID)Context,
&SampObjectInformation[ ObjectType ].ObjectTypeName,
ObjectName,
SecurityDescriptor,
MappedDesiredAccess,
&SampObjectInformation[ ObjectType ].GenericMapping,
ObjectCreation,
&Context->GrantedAccess,
&AccessStatus,
&Context->AuditOnClose
);
//
// Stop impersonating the client
//
if (ImpersonatingNullSession) {
IgnoreStatus = SampRevertNullSession();
}
IgnoreStatus = I_RpcMapWin32Status(RpcRevertToSelf());
ASSERT( NT_SUCCESS(IgnoreStatus) );
}
//
// Free up the security descriptor
//
MIDL_user_free( SecurityDescriptor );
}
}
//
// If we got an error back from the access check, return that as
// status. Otherwise, return the access check status.
//
if (!NT_SUCCESS(NtStatus)) {
return(NtStatus);
}
return(AccessStatus);
}
VOID
SampAuditOnClose(
IN PSAMP_OBJECT Context
)
/*++
Routine Description:
This service performs auditing necessary during a handle close operation.
This service may ONLY be called if the corresponding call to
SampValidateObjectAccess() during openned returned TRUE.
Arguments:
Context - This must be the same value that was passed to the corresponding
SampValidateObjectAccess() call. This value is used for auditing
purposes only.
Return Value:
None.
--*/
{
SAMTRACE("SampAuditOnClose");
//FIX, FIX - Call NtAuditClose() (or whatever it is).
return;
DBG_UNREFERENCED_PARAMETER( Context );
}
VOID
SampRemoveAnonymousChangePasswordAccess(
IN OUT PSECURITY_DESCRIPTOR Sd
)
/*++
Routine Description:
This routine removes USER_CHANGE_PASSWORD access from
any GRANT aces in the discretionary acl that have either
the WORLD or ANONYMOUS SIDs in the ACE.
Parameters:
Sd - Is a pointer to a security descriptor of a SAM USER
object.
Returns:
None.
--*/
{
PACL
Dacl;
ULONG
i,
AceCount;
PACE_HEADER
Ace;
BOOLEAN
DaclPresent,
DaclDefaulted;
SAMTRACE("SampRemoveAnonymousChangePasswordAccess");
RtlGetDaclSecurityDescriptor( Sd,
&DaclPresent,
&Dacl,
&DaclDefaulted
);
if ( !DaclPresent || (Dacl == NULL)) {
return;
}
if ((AceCount = Dacl->AceCount) == 0) {
return;
}
for ( i = 0, Ace = FirstAce( Dacl ) ;
i < AceCount ;
i++, Ace = NextAce( Ace )
) {
if ( !(((PACE_HEADER)Ace)->AceFlags & INHERIT_ONLY_ACE)) {
if ( (((PACE_HEADER)Ace)->AceType == ACCESS_ALLOWED_ACE_TYPE) ) {
if ( (RtlEqualSid( SampWorldSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart )) ||
(RtlEqualSid( SampAnonymousSid, &((PACCESS_ALLOWED_ACE)Ace)->SidStart ))) {
//
// Turn off CHANGE_PASSWORD access
//
((PACCESS_ALLOWED_ACE)Ace)->Mask &= ~USER_CHANGE_PASSWORD;
}
}
}
}
return;
}
NTSTATUS
SampCreateNullToken(
)
/*++
Routine Description:
This function creates a token representing a null logon.
Arguments:
Return Value:
The status value of the NtCreateToken() call.
--*/
{
NTSTATUS Status;
TOKEN_USER UserId;
TOKEN_PRIMARY_GROUP PrimaryGroup;
TOKEN_GROUPS GroupIds;
TOKEN_PRIVILEGES Privileges;
TOKEN_SOURCE SourceContext;
OBJECT_ATTRIBUTES ObjectAttributes;
SECURITY_QUALITY_OF_SERVICE ImpersonationQos;
LARGE_INTEGER ExpirationTime;
LUID LogonId = SYSTEM_LUID;
SAMTRACE("SampCreateNullToken");
UserId.User.Sid = SampWorldSid;
UserId.User.Attributes = 0;
GroupIds.GroupCount = 0;
Privileges.PrivilegeCount = 0;
PrimaryGroup.PrimaryGroup = SampWorldSid;
ExpirationTime.LowPart = 0xfffffff;
ExpirationTime.LowPart = 0x7ffffff;
//
// Build a token source for SAM.
//
Status = NtAllocateLocallyUniqueId( &SourceContext.SourceIdentifier );
if (!NT_SUCCESS(Status)) {
return(Status);
}
strncpy(SourceContext.SourceName,"SamSS ",sizeof(SourceContext.SourceName));
//
// Set the object attributes to specify an Impersonation impersonation
// level.
//
InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
ImpersonationQos.ImpersonationLevel = SecurityImpersonation;
ImpersonationQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
ImpersonationQos.EffectiveOnly = TRUE;
ImpersonationQos.Length = (ULONG)sizeof(SECURITY_QUALITY_OF_SERVICE);
ObjectAttributes.SecurityQualityOfService = &ImpersonationQos;
Status = NtCreateToken(
&SampNullSessionToken, // Handle
(TOKEN_ALL_ACCESS), // DesiredAccess
&ObjectAttributes, // ObjectAttributes
TokenImpersonation, // TokenType
&LogonId, // Authentication LUID
&ExpirationTime, // Expiration Time
&UserId, // User ID
&GroupIds, // Group IDs
&Privileges, // Privileges
NULL, // Owner
&PrimaryGroup, // Primary Group
NULL, // Default Dacl
&SourceContext // TokenSource
);
return Status;
}
ULONG
SampSecureRpcInit(
PVOID Ignored
)
/*++
Routine Description:
This routine waits for the NTLMSSP service to start and then registers
security information with RPC to allow authenticated RPC to be used to
SAM. It also registers an SPX endpoint if FPNW is installed.
Arguments:
Ignored - required parameter for starting a thread.
Return Value:
None.
--*/
{
#define MAX_RPC_RETRIES 30
ULONG RpcStatus;
ULONG RpcRetry;
ULONG RpcSleepTime = 10 * 1000; // retry every ten seconds
RPC_BINDING_VECTOR * BindingVector = NULL;
SAMTRACE("SampSecureRpcInit");
RpcStatus = RpcServerRegisterAuthInfoW(
NULL, // server principal name
RPC_C_AUTHN_WINNT,
NULL, // no get key function
NULL // no get key argument
);
if (RpcStatus != 0) {
KdPrint(("SAMSS: Could not register auth. info: %d\n",
RpcStatus ));
goto ErrorReturn;
}
//
// If the Netware server is installed, register the SPX protocol.
// Since the transport may not be loaded yet, retry a couple of times
// if we get a CANT_CREATE_ENDPOINT error (meaning the transport isn't
// there).
//
if (SampNetwareServerInstalled) {
RpcRetry = MAX_RPC_RETRIES;
while (RpcRetry != 0) {
RpcStatus = RpcServerUseProtseqW(
L"ncacn_spx",
10,
NULL // no security descriptor
);
//
// If it succeded break out of the loop.
//
if (RpcStatus == ERROR_SUCCESS) {
break;
}
Sleep(RpcSleepTime);
RpcRetry--;
continue;
}
if (RpcStatus != 0) {
KdPrint(("SAMSS: Could not register SPX endpoint: %d\n", RpcStatus ));
goto ErrorReturn;
}
}
//
// do the same thing all over again with TcpIp
//
if (SampIpServerInstalled) {
RpcRetry = MAX_RPC_RETRIES;
while (RpcRetry != 0) {
RpcStatus = RpcServerUseProtseqW(
L"ncacn_ip_tcp",
10,
NULL // no security descriptor
);
//
// If it succeeded, break out of the loop.
//
if (RpcStatus == ERROR_SUCCESS) {
break;
}
Sleep(RpcSleepTime);
RpcRetry--;
continue;
}
if (RpcStatus != 0) {
KdPrint(("SAMSS: Could not register TCP endpoint: %d\n", RpcStatus ));
goto ErrorReturn;
return(RpcStatus);
}
}
//
// If we started Tcp/Ip or Spx, go on to register the endpoints
//
if (SampNetwareServerInstalled || SampIpServerInstalled) {
RpcStatus = RpcServerInqBindings(&BindingVector);
if (RpcStatus != 0) {
KdPrint(("SAMSS: Could not inq bindings: %d\n",RpcStatus));
goto ErrorReturn;
}
RpcStatus = RpcEpRegister(
samr_ServerIfHandle,
BindingVector,
NULL, // no uuid vector
L"" // no annotation
);
RpcBindingVectorFree(&BindingVector);
if (RpcStatus != 0) {
KdPrint(("SAMSS: Could not register endpoints: %d\n",RpcStatus));
goto ErrorReturn;
}
}
return(ERROR_SUCCESS);
ErrorReturn:
SampWriteEventLog(
EVENTLOG_ERROR_TYPE,
0, // Category
SAMMSG_RPC_INIT_FAILED,
NULL, // User Sid
0, // Num strings
sizeof(NTSTATUS), // Data size
NULL, // String array
(PVOID)&RpcStatus // Data
);
return(RpcStatus);
}
BOOLEAN
SampStartNonNamedPipeTransports(
)
/*++
Routine Description:
This routine checks to see if we should listen on a non-named pipe
transport. We check the registry for flags indicating that we should
listen on Tcp/Ip and SPX. There is a flag
in the registry under system\currentcontrolset\Control\Lsa\
NetwareClientSupport and TcpipClientSupport indicating whether or not
to setup the endpoint.
Arguments:
Return Value:
TRUE - Netware (FPNW or SmallWorld) is installed and the SPX endpoint
should be started.
FALSE - Either Netware is not installed, or an error occurred while
checking for it.
--*/
{
NTSTATUS NtStatus;
UNICODE_STRING KeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE KeyHandle;
UCHAR Buffer[100];
PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION) Buffer;
ULONG KeyValueLength = 100;
ULONG ResultLength;
PULONG SpxFlag;
SAMTRACE("SampStartNonNamedPipeTransport");
SampNetwareServerInstalled = FALSE;
SampIpServerInstalled = FALSE;
//
// Open the Lsa key in the registry
//
RtlInitUnicodeString(
&KeyName,
L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Lsa"
);
InitializeObjectAttributes(
&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE,
0,
NULL
);
SampDumpNtOpenKey((KEY_READ), &ObjectAttributes, 0);
NtStatus = NtOpenKey(
&KeyHandle,
KEY_READ,
&ObjectAttributes
);
if (!NT_SUCCESS(NtStatus)) {
return(FALSE);
}
//
// Query the NetwareClientSupport value
//
RtlInitUnicodeString(
&KeyName,
L"NetWareClientSupport"
);
NtStatus = NtQueryValueKey(
KeyHandle,
&KeyName,
KeyValuePartialInformation,
KeyValueInformation,
KeyValueLength,
&ResultLength
);
SampDumpNtQueryValueKey(&KeyName,
KeyValuePartialInformation,
KeyValueInformation,
KeyValueLength,
&ResultLength);
if (NT_SUCCESS(NtStatus)) {
//
// Check that the data is the correct size and type - a ULONG.
//
if ((KeyValueInformation->DataLength >= sizeof(ULONG)) &&
(KeyValueInformation->Type == REG_DWORD)) {
SpxFlag = (PULONG) KeyValueInformation->Data;
if (*SpxFlag == 1) {
SampNetwareServerInstalled = TRUE;
}
}
}
//
// Query the Tcp/IpClientSupport value
//
RtlInitUnicodeString(
&KeyName,
L"TcpipClientSupport"
);
NtStatus = NtQueryValueKey(
KeyHandle,
&KeyName,
KeyValuePartialInformation,
KeyValueInformation,
KeyValueLength,
&ResultLength
);
SampDumpNtQueryValueKey(&KeyName,
KeyValuePartialInformation,
KeyValueInformation,
KeyValueLength,
&ResultLength);
if (NT_SUCCESS(NtStatus)) {
//
// Check that the data is the correct size and type - a ULONG.
//
if ((KeyValueInformation->DataLength >= sizeof(ULONG)) &&
(KeyValueInformation->Type == REG_DWORD)) {
SpxFlag = (PULONG) KeyValueInformation->Data;
if (*SpxFlag == 1) {
SampIpServerInstalled = TRUE;
}
}
}
NtClose(KeyHandle);
if (SampNetwareServerInstalled || SampIpServerInstalled)
{
return(TRUE);
}
else
{
return(FALSE);
};
}