/*++ Copyright (c) 1990 Microsoft Corporation Module Name: group.c Abstract: This file contains services related to the SAM "group" object. Author: Jim Kelly (JimK) 4-July-1991 Environment: User Mode - Win32 Revision History: --*/ /////////////////////////////////////////////////////////////////////////////// // // // Includes // // // /////////////////////////////////////////////////////////////////////////////// #include #include /////////////////////////////////////////////////////////////////////////////// // // // private service prototypes // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampDeleteGroupKeys( IN PSAMP_OBJECT Context ); NTSTATUS SampChangeGroupAccountName( IN PSAMP_OBJECT Context, IN PUNICODE_STRING NewAccountName, OUT PUNICODE_STRING OldAccountName ); NTSTATUS SampReplaceGroupMembers( IN PSAMP_OBJECT GroupContext, IN ULONG MemberCount, IN PULONG Members ); NTSTATUS SampAddAccountToGroupMembers( IN PSAMP_OBJECT GroupContext, IN ULONG UserRid ); NTSTATUS SampRemoveAccountFromGroupMembers( IN PSAMP_OBJECT GroupContext, IN ULONG AccountRid ); /////////////////////////////////////////////////////////////////////////////// // // // Exposed RPC'able Services // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SamrOpenGroup( IN SAMPR_HANDLE DomainHandle, IN ACCESS_MASK DesiredAccess, IN ULONG GroupId, OUT SAMPR_HANDLE *GroupHandle ) /*++ Routine Description: This API opens an existing group in the account database. The group is specified by a ID value that is relative to the SID of the domain. The operations that will be performed on the group must be declared at this time. This call returns a handle to the newly opened group that may be used for successive operations on the group. This handle may be closed with the SamCloseHandle API. Parameters: DomainHandle - A domain handle returned from a previous call to SamOpenDomain. DesiredAccess - Is an access mask indicating which access types are desired to the group. These access types are reconciled with the Discretionary Access Control list of the group to determine whether the accesses will be granted or denied. GroupId - Specifies the relative ID value of the group to be opened. GroupHandle - Receives a handle referencing the newly opened group. This handle will be required in successive calls to operate on the group. Return Values: STATUS_SUCCESS - The group was successfully opened. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_NO_SUCH_GROUP - The specified group does not exist. STATUS_INVALID_HANDLE - The domain handle passed is invalid. --*/ { NTSTATUS NtStatus; NtStatus = SampOpenAccount( SampGroupObjectType, DomainHandle, DesiredAccess, GroupId, FALSE, GroupHandle ); return(NtStatus); } NTSTATUS SamrQueryInformationGroup( IN SAMPR_HANDLE GroupHandle, IN GROUP_INFORMATION_CLASS GroupInformationClass, OUT PSAMPR_GROUP_INFO_BUFFER *Buffer ) /*++ Routine Description: This API retrieves information on the group specified. Parameters: GroupHandle - The handle of an opened group to operate on. GroupInformationClass - Class of information to retrieve. The accesses required for each class is shown below: Info Level Required Access Type ----------------------- ---------------------- GroupGeneralInformation GROUP_READ_INFORMATION GroupNameInformation GROUP_READ_INFORMATION GroupAttributeInformation GROUP_READ_INFORMATION GroupAdminInformation GROUP_READ_INFORMATION Buffer - Receives a pointer to a buffer containing the requested information. When this information is no longer needed, this buffer must be freed using SamFreeMemory(). Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_INVALID_INFO_CLASS - The class provided was invalid. --*/ { NTSTATUS NtStatus; NTSTATUS IgnoreStatus; PSAMP_OBJECT AccountContext; SAMP_OBJECT_TYPE FoundType; ACCESS_MASK DesiredAccess; ULONG i; SAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed; // // Used for tracking allocated blocks of memory - so we can deallocate // them in case of error. Don't exceed this number of allocated buffers. // || // vv PVOID AllocatedBuffer[10]; ULONG AllocatedBufferCount = 0; #define RegisterBuffer(Buffer) \ { \ if ((Buffer) != NULL) { \ \ ASSERT(AllocatedBufferCount < \ sizeof(AllocatedBuffer) / sizeof(*AllocatedBuffer)); \ \ AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \ } \ } #define AllocateBuffer(NewBuffer, Size) \ { \ (NewBuffer) = MIDL_user_allocate(Size); \ RegisterBuffer(NewBuffer); \ } \ // // Make sure we understand what RPC is doing for (to) us. // ASSERT (Buffer != NULL); ASSERT ((*Buffer) == NULL); // // Set the desired access based upon the Info class // switch (GroupInformationClass) { case GroupGeneralInformation: case GroupNameInformation: case GroupAttributeInformation: case GroupAdminCommentInformation: DesiredAccess = GROUP_READ_INFORMATION; break; default: (*Buffer) = NULL; return(STATUS_INVALID_INFO_CLASS); } // end_switch // // Allocate the info structure // (*Buffer) = MIDL_user_allocate( sizeof(SAMPR_GROUP_INFO_BUFFER) ); if ((*Buffer) == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } RegisterBuffer(*Buffer); SampAcquireReadLock(); // // Validate type of, and access to object. // AccountContext = (PSAMP_OBJECT)GroupHandle; NtStatus = SampLookupContext( AccountContext, DesiredAccess, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { // // If the information level requires, retrieve the V1_FIXED record // from the registry. // switch (GroupInformationClass) { case GroupGeneralInformation: case GroupAttributeInformation: NtStatus = SampRetrieveGroupV1Fixed( AccountContext, &V1Fixed ); break; //out of switch default: NtStatus = STATUS_SUCCESS; } // end_switch if (NT_SUCCESS(NtStatus)) { // // case on the type information requested // switch (GroupInformationClass) { case GroupGeneralInformation: (*Buffer)->General.Attributes = V1Fixed.Attributes; // // Get the member count // NtStatus = SampRetrieveGroupMembers( AccountContext, &(*Buffer)->General.MemberCount, NULL // Only need members ); if (NT_SUCCESS(NtStatus)) { // // Get copies of the strings we must retrieve from // the registry. // NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_NAME, TRUE, // Make copy (PUNICODE_STRING)&((*Buffer)->General.Name) ); if (NT_SUCCESS(NtStatus)) { RegisterBuffer((*Buffer)->General.Name.Buffer); NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_ADMIN_COMMENT, TRUE, // Make copy (PUNICODE_STRING)&((*Buffer)->General.AdminComment) ); if (NT_SUCCESS(NtStatus)) { RegisterBuffer((*Buffer)->General.AdminComment.Buffer); } } } break; case GroupNameInformation: // // Get copies of the strings we must retrieve from // the registry. // NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_NAME, TRUE, // Make copy (PUNICODE_STRING)&((*Buffer)->Name.Name) ); if (NT_SUCCESS(NtStatus)) { RegisterBuffer((*Buffer)->Name.Name.Buffer); } break; case GroupAdminCommentInformation: // // Get copies of the strings we must retrieve from // the registry. // NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_ADMIN_COMMENT, TRUE, // Make copy (PUNICODE_STRING)&((*Buffer)->AdminComment.AdminComment) ); if (NT_SUCCESS(NtStatus)) { RegisterBuffer((*Buffer)->AdminComment.AdminComment.Buffer); } break; case GroupAttributeInformation: (*Buffer)->Attribute.Attributes = V1Fixed.Attributes; break; } // end_switch } // end_if // // De-reference the object, discarding changes // IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(IgnoreStatus)); } // // Free the read lock // SampReleaseReadLock(); // // If we didn't succeed, free any allocated memory // if (!NT_SUCCESS(NtStatus)) { for ( i=0; iTypeBody.Group.Rid; NtStatus = SampLookupContext( AccountContext, DesiredAccess, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { // // Get a pointer to the domain this object is in. // This is used for auditing. // DomainIndex = AccountContext->DomainIndex; Domain = &SampDefinedDomains[ DomainIndex ]; // // If the information level requires, retrieve the V1_FIXED record // from the registry. This includes anything that will cause // us to update the display cache. // switch (GroupInformationClass) { case GroupAdminCommentInformation: case GroupNameInformation: case GroupAttributeInformation: NtStatus = SampRetrieveGroupV1Fixed( AccountContext, &V1Fixed ); MustUpdateAccountDisplay = TRUE; OldGroupAttributes = V1Fixed.Attributes; break; //out of switch default: NtStatus = STATUS_SUCCESS; } // end_switch if (NT_SUCCESS(NtStatus)) { // // case on the type information requested // switch (GroupInformationClass) { case GroupNameInformation: NtStatus = SampChangeGroupAccountName( AccountContext, (PUNICODE_STRING)&(Buffer->Name.Name), &OldAccountName ); if (!NT_SUCCESS(NtStatus)) { OldAccountName.Buffer = NULL; } // // Don't free OldAccountName yet; we'll need it at the // very end. // break; case GroupAdminCommentInformation: // // build the key name // NtStatus = SampSetUnicodeStringAttribute( AccountContext, SAMP_GROUP_ADMIN_COMMENT, (PUNICODE_STRING)&(Buffer->AdminComment.AdminComment) ); break; case GroupAttributeInformation: MustUpdateAccountDisplay = TRUE; V1Fixed.Attributes = Buffer->Attribute.Attributes; NtStatus = SampReplaceGroupV1Fixed( AccountContext, // ParentKey &V1Fixed ); break; } // end_switch } // end_if // // Go fetch any data we'll need to update the display cache // Do this before we dereference the context // if (NT_SUCCESS(NtStatus)) { if ( MustUpdateAccountDisplay ) { NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_NAME, TRUE, // Make copy &NewAccountName ); if (NT_SUCCESS(NtStatus)) { NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_ADMIN_COMMENT, TRUE, // Make copy &NewAdminComment ); // // If the account name has changed, then OldAccountName // is already filled in. If the account name hasn't changed // then the OldAccountName is the same as the new! // if (NT_SUCCESS(NtStatus) && (OldAccountName.Buffer == NULL)) { NtStatus = SampDuplicateUnicodeString( &OldAccountName, &NewAccountName); } } } } // // Generate an audit if necessary // if (NT_SUCCESS(NtStatus) && SampDoAccountAuditing(DomainIndex)) { UNICODE_STRING AccountName; IgnoreStatus = SampGetUnicodeStringAttribute( AccountContext, // Context SAMP_GROUP_NAME, // AttributeIndex FALSE, // MakeCopy &AccountName // UnicodeAttribute ); if (NT_SUCCESS(IgnoreStatus)) { LsaIAuditSamEvent( STATUS_SUCCESS, SE_AUDITID_GLOBAL_GROUP_CHANGE, // AuditId Domain->Sid, // Domain SID NULL, // Member Rid (not used) NULL, // Member Sid (not used) &AccountName, // Account Name &Domain->ExternalName, // Domain &AccountContext->TypeBody.Group.Rid, // Account Rid NULL // Privileges used ); } } // // Dereference the account context // if (NT_SUCCESS(NtStatus)) { // // De-reference the object, write out any change to current xaction. // NtStatus = SampDeReferenceContext( AccountContext, TRUE ); } else { // // De-reference the object, ignore changes // TmpStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); } } //end_if // // Commit the transaction, update the display cache, // and notify netlogon of the changes // if ( NT_SUCCESS(NtStatus) ) { NtStatus = SampCommitAndRetainWriteLock(); if ( NT_SUCCESS(NtStatus) ) { // // Update the display information if the cache may be affected // if ( MustUpdateAccountDisplay ) { SAMP_ACCOUNT_DISPLAY_INFO OldAccountInfo; SAMP_ACCOUNT_DISPLAY_INFO NewAccountInfo; OldAccountInfo.Name = OldAccountName; OldAccountInfo.Rid = ObjectRid; OldAccountInfo.AccountControl = OldGroupAttributes; RtlInitUnicodeString(&OldAccountInfo.Comment, NULL); RtlInitUnicodeString(&OldAccountInfo.FullName, NULL); // Not used for groups NewAccountInfo.Name = NewAccountName; NewAccountInfo.Rid = ObjectRid; NewAccountInfo.AccountControl = V1Fixed.Attributes; NewAccountInfo.Comment = NewAdminComment; NewAccountInfo.FullName = NewFullName; IgnoreStatus = SampUpdateDisplayInformation(&OldAccountInfo, &NewAccountInfo, SampGroupObjectType); ASSERT(NT_SUCCESS(IgnoreStatus)); } if ( GroupInformationClass == GroupNameInformation ) { SampNotifyNetlogonOfDelta( SecurityDbRename, SecurityDbObjectSamGroup, ObjectRid, &OldAccountName, (DWORD) FALSE, // Replicate immediately NULL // Delta data ); } else { SampNotifyNetlogonOfDelta( SecurityDbChange, SecurityDbObjectSamGroup, ObjectRid, (PUNICODE_STRING) NULL, (DWORD) FALSE, // Replicate immediately NULL // Delta data ); } } } // // Release the write lock // TmpStatus = SampReleaseWriteLock( FALSE ); if (NT_SUCCESS(NtStatus)) { NtStatus = TmpStatus; } // // Clean up strings // SampFreeUnicodeString( &OldAccountName ); SampFreeUnicodeString( &NewAccountName ); SampFreeUnicodeString( &NewFullName ); SampFreeUnicodeString( &NewAdminComment ); return(NtStatus); } NTSTATUS SamrAddMemberToGroup( IN SAMPR_HANDLE GroupHandle, IN ULONG MemberId, IN ULONG Attributes ) /*++ Routine Description: This API adds a member to a group. Note that this API requires the GROUP_ADD_MEMBER access type for the group. Parameters: GroupHandle - The handle of an opened group to operate on. MemberId - Relative ID of the member to add. Attributes - The attributes of the group assigned to the user. The attributes assigned here may have any value. However, at logon time these attributes are minimized by the attributes of the group as a whole. Mandatory - If the Mandatory attribute is assigned to the group as a whole, then it will be assigned to the group for each member of the group. EnabledByDefault - This attribute may be set to any value for each member of the group. It does not matter what the attribute value for the group as a whole is. Enabled - This attribute may be set to any value for each member of the group. It does not matter what the attribute value for the group as a whole is. Owner - If the Owner attribute of the group as a whole is not set, then the value assigned to members is ignored. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_NO_SUCH_MEMBER - The member specified is unknown. STATUS_MEMBER_IN_GROUP - The member already belongs to the group. STATUS_INVALID_GROUP_ATTRIBUTES - Indicates the group attribute values being assigned to the member are not compatible with the attribute values of the group as a whole. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed; NTSTATUS NtStatus, TmpStatus; PSAMP_OBJECT AccountContext; SAMP_OBJECT_TYPE FoundType; ULONG ObjectRid; BOOLEAN UserAccountActive; UNICODE_STRING GroupName; // // Initialize buffers we will cleanup at the end // RtlInitUnicodeString(&GroupName, NULL); // // Grab the lock // NtStatus = SampAcquireWriteLock(); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } // // Validate type of, and access to object. // AccountContext = (PSAMP_OBJECT)(GroupHandle); ObjectRid = AccountContext->TypeBody.Group.Rid; NtStatus = SampLookupContext( AccountContext, GROUP_ADD_MEMBER, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { NtStatus = SampRetrieveGroupV1Fixed( AccountContext, &GroupV1Fixed ); if (NT_SUCCESS(NtStatus)) { // // Perform the user object side of things // NtStatus = SampAddGroupToUserMembership( ObjectRid, Attributes, MemberId, (GroupV1Fixed.AdminCount == 0) ? NoChange : AddToAdmin, (GroupV1Fixed.OperatorCount == 0) ? NoChange : AddToAdmin, &UserAccountActive ); // // Now perform the group side of things // if (NT_SUCCESS(NtStatus)) { // // Add the user to the group (should not fail) // NtStatus = SampAddAccountToGroupMembers( AccountContext, MemberId ); } } if (NT_SUCCESS(NtStatus)) { // // Get and save the account name for // I_NetNotifyLogonOfDelta. // NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_NAME, TRUE, // Make copy &GroupName ); if (!NT_SUCCESS(NtStatus)) { RtlInitUnicodeString(&GroupName, NULL); } } // // Dereference the account context // if (NT_SUCCESS(NtStatus)) { // // De-reference the object, write out any change to current xaction. // NtStatus = SampDeReferenceContext( AccountContext, TRUE ); } else { // // De-reference the object, ignore changes // TmpStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); } } // // Commit the transaction and notify net logon of the changes // if (NT_SUCCESS(NtStatus)) { NtStatus = SampCommitAndRetainWriteLock(); if ( NT_SUCCESS( NtStatus ) ) { SAM_DELTA_DATA DeltaData; // // Fill in id of member being added // DeltaData.GroupMemberId.MemberRid = MemberId; SampNotifyNetlogonOfDelta( SecurityDbChangeMemberAdd, SecurityDbObjectSamGroup, ObjectRid, &GroupName, (DWORD) FALSE, // Replicate immediately &DeltaData ); } } // // Free up the group name // SampFreeUnicodeString(&GroupName); TmpStatus = SampReleaseWriteLock( FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); return(NtStatus); } NTSTATUS SamrDeleteGroup( IN SAMPR_HANDLE *GroupHandle ) /*++ Routine Description: This API removes a group from the account database. There may be no members in the group or the deletion request will be rejected. Note that this API requires DELETE access to the specific group being deleted. Parameters: GroupHandle - The handle of an opened group to operate on. Return Values: STATUS_SUCCESS - The Service completed successfully. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. This may be because someone has deleted the group while it was open. STATUS_SPECIAL_ACCOUNT - The group specified is a special group and cannot be operated on in the requested fashion. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { UNICODE_STRING GroupName; NTSTATUS NtStatus, TmpStatus; PSAMP_OBJECT AccountContext; PSAMP_DEFINED_DOMAINS Domain; PSID AccountSid; SAMP_OBJECT_TYPE FoundType; ULONG MemberCount, ObjectRid, DomainIndex; // // Grab the lock // NtStatus = SampAcquireWriteLock(); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } // // Validate type of, and access to object. // AccountContext = (PSAMP_OBJECT)(*GroupHandle); NtStatus = SampLookupContext( AccountContext, DELETE, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { ObjectRid = AccountContext->TypeBody.Group.Rid; // // Get a pointer to the domain this object is in. // This is used for auditing. // DomainIndex = AccountContext->DomainIndex; Domain = &SampDefinedDomains[ DomainIndex ]; // // Make sure the account is one that can be deleted. // Can't be a built-in account, unless the caller is trusted. // if ( !AccountContext->TrustedClient ) { NtStatus = SampIsAccountBuiltIn( ObjectRid ); } if (NT_SUCCESS( NtStatus) ) { // // and it can't have any members // NtStatus = SampRetrieveGroupMembers( AccountContext, &MemberCount, NULL // Only need member count (not list) ); if (MemberCount != 0) { NtStatus = STATUS_MEMBER_IN_GROUP; } } if (NT_SUCCESS(NtStatus)) { // // Remove this account from all aliases // NtStatus = SampCreateAccountSid(AccountContext, &AccountSid); if (NT_SUCCESS(NtStatus)) { NtStatus = SampRemoveAccountFromAllAliases( AccountSid, FALSE, NULL, NULL, NULL ); } } // // Looks promising. if (NT_SUCCESS(NtStatus)) { // // First get and save the account name for // I_NetNotifyLogonOfDelta. // NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_NAME, TRUE, // Make copy &GroupName ); if (NT_SUCCESS(NtStatus)) { // // This must be done before we invalidate contexts, because our // own handle to the group gets closed as well. // NtStatus = SampDeleteGroupKeys( AccountContext ); if (NT_SUCCESS(NtStatus)) { // // We must invalidate any open contexts to this group. // This will close all handles to the group's keys. // THIS IS AN IRREVERSIBLE PROCESS. // SampInvalidateGroupContexts( ObjectRid ); // // Commit the whole mess // NtStatus = SampCommitAndRetainWriteLock(); if ( NT_SUCCESS( NtStatus ) ) { SAMP_ACCOUNT_DISPLAY_INFO AccountInfo; // // Update the Cached Alias Information // NtStatus = SampAlRemoveAccountFromAllAliases( AccountSid, FALSE, NULL, NULL, NULL ); MIDL_user_free(AccountSid); // // Update the display information // AccountInfo.Name = GroupName; AccountInfo.Rid = ObjectRid; AccountInfo.AccountControl = 0; // Don't care about this value for delete RtlInitUnicodeString(&AccountInfo.Comment, NULL); RtlInitUnicodeString(&AccountInfo.FullName, NULL); TmpStatus = SampUpdateDisplayInformation(&AccountInfo, NULL, SampGroupObjectType); ASSERT(NT_SUCCESS(TmpStatus)); // // Audit the deletion before we free the write lock // so that we have access to the context block. // if (SampDoAccountAuditing(DomainIndex) && NT_SUCCESS(NtStatus) ) { LsaIAuditSamEvent( STATUS_SUCCESS, SE_AUDITID_GLOBAL_GROUP_DELETED, // AuditId Domain->Sid, // Domain SID NULL, // Member Rid (not used) NULL, // Member Sid (not used) &GroupName, // Account Name &Domain->ExternalName, // Domain &ObjectRid, // Account Rid NULL // Privileges used ); } // // Do delete auditing // if (NT_SUCCESS(NtStatus)) { (VOID) NtDeleteObjectAuditAlarm( &SampSamSubsystem, *GroupHandle, AccountContext->AuditOnClose ); } // // Notify netlogon of the change // SampNotifyNetlogonOfDelta( SecurityDbDelete, SecurityDbObjectSamGroup, ObjectRid, &GroupName, (DWORD) FALSE, // Replicate immediately NULL // Delta data ); } } SampFreeUnicodeString( &GroupName ); } } // // De-reference the object, discarding changes // TmpStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); if ( NT_SUCCESS( NtStatus ) ) { // // If we actually deleted the group, delete the context and // let RPC know that the handle is invalid. // SampDeleteContext( AccountContext ); (*GroupHandle) = NULL; } } //end_if // // Free the lock - // // Everything has already been committed above, so we must indicate // no additional changes have taken place. // // // TmpStatus = SampReleaseWriteLock( FALSE ); if (NtStatus == STATUS_SUCCESS) { NtStatus = TmpStatus; } return(NtStatus); } NTSTATUS SamrRemoveMemberFromGroup( IN SAMPR_HANDLE GroupHandle, IN ULONG MemberId ) /*++ Routine Description: This service Arguments: ???? Return Value: ???? --*/ { SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed; NTSTATUS NtStatus, TmpStatus; PSAMP_OBJECT AccountContext; SAMP_OBJECT_TYPE FoundType; ULONG ObjectRid; BOOLEAN UserAccountActive; UNICODE_STRING GroupName; // // Initialize buffers to be cleaned up at the end // RtlInitUnicodeString(&GroupName, NULL); // // Grab the lock // NtStatus = SampAcquireWriteLock(); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } // // Validate type of, and access to object. // AccountContext = (PSAMP_OBJECT)(GroupHandle); ObjectRid = AccountContext->TypeBody.Group.Rid; NtStatus = SampLookupContext( AccountContext, GROUP_REMOVE_MEMBER, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { NtStatus = SampRetrieveGroupV1Fixed( AccountContext, &GroupV1Fixed ); if (NT_SUCCESS(NtStatus)) { // // Perform the user object side of things // NtStatus = SampRemoveMembershipUser( ObjectRid, MemberId, (GroupV1Fixed.AdminCount == 0) ? NoChange : RemoveFromAdmin, (GroupV1Fixed.OperatorCount == 0) ? NoChange : RemoveFromAdmin, &UserAccountActive ); // // Now perform the group side of things // if (NT_SUCCESS(NtStatus)) { // // Remove the user from the group (should not fail) // NtStatus = SampRemoveAccountFromGroupMembers( AccountContext, MemberId ); } } if (NT_SUCCESS(NtStatus)) { // // Get and save the account name for // I_NetNotifyLogonOfDelta. // NtStatus = SampGetUnicodeStringAttribute( AccountContext, SAMP_GROUP_NAME, TRUE, // Make copy &GroupName ); if (!NT_SUCCESS(NtStatus)) { RtlInitUnicodeString(&GroupName, NULL); } } // // Dereference the account context // if (NT_SUCCESS(NtStatus)) { // // De-reference the object, write out any change to current xaction. // NtStatus = SampDeReferenceContext( AccountContext, TRUE ); } else { // // De-reference the object, ignore changes // TmpStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); } } if (NT_SUCCESS(NtStatus)) { NtStatus = SampCommitAndRetainWriteLock(); if ( NT_SUCCESS( NtStatus ) ) { SAM_DELTA_DATA DeltaData; // // Fill in id of member being deleted // DeltaData.GroupMemberId.MemberRid = MemberId; SampNotifyNetlogonOfDelta( SecurityDbChangeMemberDel, SecurityDbObjectSamGroup, ObjectRid, &GroupName, (DWORD) FALSE, // Replicate immediately &DeltaData ); } } // // Free up the group name // SampFreeUnicodeString(&GroupName); TmpStatus = SampReleaseWriteLock( FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); return(NtStatus); } NTSTATUS SamrGetMembersInGroup( IN SAMPR_HANDLE GroupHandle, OUT PSAMPR_GET_MEMBERS_BUFFER *GetMembersBuffer ) /*++ Routine Description: This API lists all the members in a group. This API may be called repeatedly, passing a returned context handle, to retrieve large amounts of data. This API requires GROUP_LIST_MEMBERS access to the group. Parameters: GroupHandle - The handle of an opened group to operate on. GROUP_LIST_MEMBERS access is needed to the group. GetMembersBuffer - Receives a pointer to a set of returned structures with the following format: +-------------+ --------->| MemberCount | |-------------+ +-------+ | Members --|------------------->| Rid-0 | |-------------| +------------+ | ... | | Attributes-|-->| Attribute0 | | | +-------------+ | ... | | Rid-N | | AttributeN | +-------+ +------------+ Each block individually allocated with MIDL_user_allocate. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no addition entries. STATUS_ACCESS_DENIED - Caller does not have privilege required to request that data. STATUS_INVALID_HANDLE - The handle passed is invalid. This service --*/ { NTSTATUS NtStatus; NTSTATUS IgnoreStatus; ULONG i; ULONG ObjectRid; PSAMP_OBJECT AccountContext; SAMP_OBJECT_TYPE FoundType; // // Make sure we understand what RPC is doing for (to) us. // ASSERT (GetMembersBuffer != NULL); if ((*GetMembersBuffer) != NULL) { return(STATUS_INVALID_PARAMETER); } // // Allocate the first of the return buffers // (*GetMembersBuffer) = MIDL_user_allocate( sizeof(SAMPR_GET_MEMBERS_BUFFER) ); if ( (*GetMembersBuffer) == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } // // Grab the lock // SampAcquireReadLock(); // // Validate type of, and access to object. // AccountContext = (PSAMP_OBJECT)GroupHandle; ObjectRid = AccountContext->TypeBody.Group.Rid; NtStatus = SampLookupContext( AccountContext, GROUP_LIST_MEMBERS, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { NtStatus = SampRetrieveGroupMembers( AccountContext, &(*GetMembersBuffer)->MemberCount, &(*GetMembersBuffer)->Members ); if (NT_SUCCESS(NtStatus)) { // // Allocate a buffer for the attributes - which we get from // the individual user records // (*GetMembersBuffer)->Attributes = MIDL_user_allocate((*GetMembersBuffer)->MemberCount * sizeof(ULONG) ); if ((*GetMembersBuffer)->Attributes == NULL) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } for ( i=0; (i<((*GetMembersBuffer)->MemberCount) && NT_SUCCESS(NtStatus)); i++) { (*GetMembersBuffer)->Attributes[i] = SAMP_DEFAULT_GROUP_ATTRIBUTES; // // Don't call the user code to get the attribute - this is too // expensive. Since group attributes are not really used we // hardwire the result to be the default. // // // NtStatus = SampRetrieveUserGroupAttribute( // (*GetMembersBuffer)->Members[i], // ObjectRid, // &(*GetMembersBuffer)->Attributes[i] // ); } if (!NT_SUCCESS(NtStatus)) { MIDL_user_free( (*GetMembersBuffer)->Members ); if ((*GetMembersBuffer)->Attributes != NULL) { MIDL_user_free( (*GetMembersBuffer)->Attributes ); } } } // // De-reference the object, discarding changes // IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(IgnoreStatus)); } // // Free the read lock // SampReleaseReadLock(); if (!NT_SUCCESS(NtStatus) || ((*GetMembersBuffer)->MemberCount == 0)){ (*GetMembersBuffer)->MemberCount = 0; (*GetMembersBuffer)->Members = NULL; (*GetMembersBuffer)->Attributes = NULL; } return( NtStatus ); } NTSTATUS SamrSetMemberAttributesOfGroup( IN SAMPR_HANDLE GroupHandle, IN ULONG MemberId, IN ULONG Attributes ) /*++ Routine Description: This routine modifies the group attributes of a member of the group. Parameters: GroupHandle - The handle of an opened group to operate on. MemberId - Contains the relative ID of member whose attributes are to be modified. Attributes - The group attributes to set for the member. These attributes must not conflict with the attributes of the group as a whole. See SamAddMemberToGroup() for more information on compatible attribute settings. Return Values: STATUS_SUCCESS - The Service completed successfully, and there are no addition entries. STATUS_INVALID_INFO_CLASS - The class provided was invalid. STATUS_ACCESS_DENIED - Caller does not have the appropriate access to complete the operation. STATUS_INVALID_HANDLE - The handle passed is invalid. STATUS_NO_SUCH_USER - The user specified does not exist. STATUS_MEMBER_NOT_IN_GROUP - Indicates the specified relative ID is not a member of the group. STATUS_INVALID_DOMAIN_STATE - The domain server is not in the correct state (disabled or enabled) to perform the requested operation. The domain server must be enabled for this operation STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the incorrect role (primary or backup) to perform the requested operation. --*/ { NTSTATUS NtStatus, TmpStatus; PSAMP_OBJECT AccountContext; SAMP_OBJECT_TYPE FoundType; ULONG ObjectRid; // // Grab the lock // NtStatus = SampAcquireWriteLock(); if (!NT_SUCCESS(NtStatus)) { return(NtStatus); } // // Validate type of, and access to object. // AccountContext = (PSAMP_OBJECT)(GroupHandle); ObjectRid = AccountContext->TypeBody.Group.Rid; NtStatus = SampLookupContext( AccountContext, GROUP_ADD_MEMBER, SampGroupObjectType, // ExpectedType &FoundType ); if (NT_SUCCESS(NtStatus)) { // // Update user object // NtStatus = SampSetGroupAttributesOfUser( ObjectRid, Attributes, MemberId ); // // Dereference the account context // if (NT_SUCCESS(NtStatus)) { // // De-reference the object, write out any change to current xaction. // NtStatus = SampDeReferenceContext( AccountContext, TRUE ); } else { // // De-reference the object, ignore changes // TmpStatus = SampDeReferenceContext( AccountContext, FALSE ); ASSERT(NT_SUCCESS(TmpStatus)); } } if (NT_SUCCESS(NtStatus)) { NtStatus = SampCommitAndRetainWriteLock(); if ( NT_SUCCESS( NtStatus ) ) { SampNotifyNetlogonOfDelta( SecurityDbChange, SecurityDbObjectSamGroup, ObjectRid, (PUNICODE_STRING) NULL, (DWORD) FALSE, // Replicate immediately NULL // Delta data ); } } TmpStatus = SampReleaseWriteLock( FALSE ); return(NtStatus); } /////////////////////////////////////////////////////////////////////////////// // // // Internal Services Available For Use in Other SAM Modules // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampAddUserToGroup( IN ULONG GroupRid, IN ULONG UserRid ) /*++ Routine Description: This service is expected to be used when a user is being created. It is used to add that user as a member to a specified group. This is done by simply adding the user's ID to the list of IDs in the MEMBERS sub-key of the the specified group. The caller of this service is expected to be in the middle of a RXACT transaction. This service simply adds some actions to that RXACT transaction. If the group is the DOMAIN_ADMIN group, the caller is responsible for updating the ActiveAdminCount (if appropriate). Arguments: GroupRid - The RID of the group the user is to be made a member of. UserRid - The RID of the user being added as a new member. Return Value: STATUS_SUCCESS - The user has been added. --*/ { NTSTATUS NtStatus; PSAMP_OBJECT GroupContext; NtStatus = SampCreateAccountContext( SampGroupObjectType, GroupRid, TRUE, // Trusted client TRUE, // Account exists &GroupContext ); if (NT_SUCCESS(NtStatus)) { // // Add the user to the group member list. // NtStatus = SampAddAccountToGroupMembers( GroupContext, UserRid ); // // Write out any changes to the group account // Don't use the open key handle since we'll be deleting the context. // if (NT_SUCCESS(NtStatus)) { NtStatus = SampStoreObjectAttributes(GroupContext, FALSE); } // // Clean up the group context // SampDeleteContext(GroupContext); } return(NtStatus); } NTSTATUS SampRemoveUserFromGroup( IN ULONG GroupRid, IN ULONG UserRid ) /*++ Routine Description: This routine is used to Remove a user from a specified group. This is done by simply Removing the user's ID From the list of IDs in the MEMBERS sub-key of the the specified group. It is the caller's responsibility to know that the user is, in fact, currently a member of the group. The caller of this service is expected to be in the middle of a RXACT transaction. This service simply adds some actions to that RXACT transaction. If the group is the DOMAIN_ADMIN group, the caller is responsible for updating the ActiveAdminCount (if appropriate). Arguments: GroupRid - The RID of the group the user is to be removed from. UserRid - The RID of the user being Removed. Return Value: STATUS_SUCCESS - The user has been Removed. --*/ { NTSTATUS NtStatus; PSAMP_OBJECT GroupContext; NtStatus = SampCreateAccountContext( SampGroupObjectType, GroupRid, TRUE, // Trusted client TRUE, // Account exists &GroupContext ); if (NT_SUCCESS(NtStatus)) { // // Remove the user from the group member list. // NtStatus = SampRemoveAccountFromGroupMembers( GroupContext, UserRid ); // // Write out any changes to the group account // Don't use the open key handle since we'll be deleting the context. // if (NT_SUCCESS(NtStatus)) { NtStatus = SampStoreObjectAttributes(GroupContext, FALSE); } // // Clean up the group context // SampDeleteContext(GroupContext); } return(NtStatus); } /////////////////////////////////////////////////////////////////////////////// // // // Services Private to this file // // // /////////////////////////////////////////////////////////////////////////////// NTSTATUS SampRetrieveGroupV1Fixed( IN PSAMP_OBJECT GroupContext, IN PSAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed ) /*++ Routine Description: This service retrieves the V1 fixed length information related to a specified group. Arguments: GroupRootKey - Root key for the group whose V1_FIXED information is to be retrieved. V1Fixed - Is a buffer into which the information is to be returned. Return Value: STATUS_SUCCESS - The information has been retrieved. Other status values that may be returned are those returned by: SampGetFixedAttributes() --*/ { NTSTATUS NtStatus; PVOID FixedData; NtStatus = SampGetFixedAttributes( GroupContext, FALSE, // Don't make copy &FixedData ); if (NT_SUCCESS(NtStatus)) { // // Copy data into return buffer // *V1Fixed = *((PSAMP_V1_0A_FIXED_LENGTH_GROUP)FixedData); // RtlMoveMemory( V1Fixed, FixedData, sizeof(SAMP_V1_0A_FIXED_LENGTH_GROUP) ); } return( NtStatus ); } NTSTATUS SampReplaceGroupV1Fixed( IN PSAMP_OBJECT Context, IN PSAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed ) /*++ Routine Description: This service replaces the current V1 fixed length information related to a specified group. The change is made to the in-memory object data only. Arguments: Context - Points to the account context whose V1_FIXED information is to be replaced. V1Fixed - Is a buffer containing the new V1_FIXED information. Return Value: STATUS_SUCCESS - The information has been replaced. Other status values that may be returned are those returned by: SampSetFixedAttributes() --*/ { NTSTATUS NtStatus; NtStatus = SampSetFixedAttributes( Context, (PVOID)V1Fixed ); return( NtStatus ); } NTSTATUS SampRetrieveGroupMembers( IN PSAMP_OBJECT GroupContext, IN PULONG MemberCount, IN PULONG *Members OPTIONAL ) /*++ Routine Description: This service retrieves the number of members in a group. If desired, it will also retrieve an array of RIDs of the members of the group. Arguments: GroupContext - Group context block MemberCount - Receives the number of members currently in the group. Members - (Optional) Receives a pointer to a buffer containing an array of member Relative IDs. If this value is NULL, then this information is not returned. The returned buffer is allocated using MIDL_user_allocate() and must be freed using MIDL_user_free() when no longer needed. The Members array returned always includes space for one new entry. Return Value: STATUS_SUCCESS - The information has been retrieved. STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the string to be returned in. Other status values that may be returned are those returned by: SampGetUlongArrayAttribute() --*/ { NTSTATUS NtStatus; PULONG Array; ULONG LengthCount; NtStatus = SampGetUlongArrayAttribute( GroupContext, SAMP_GROUP_MEMBERS, FALSE, // Reference data directly &Array, MemberCount, &LengthCount ); if (NT_SUCCESS(NtStatus)) { // // Fill in return info // if (Members != NULL) { // // Allocate a buffer large enough to hold the existing membership // data plus one. // ULONG BytesNow = (*MemberCount) * sizeof(ULONG); ULONG BytesRequired = BytesNow + sizeof(ULONG); *Members = MIDL_user_allocate(BytesRequired); if (*Members == NULL) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { RtlCopyMemory(*Members, Array, BytesNow); } } } return( NtStatus ); } NTSTATUS SampReplaceGroupMembers( IN PSAMP_OBJECT GroupContext, IN ULONG MemberCount, IN PULONG Members ) /*++ Routine Description: This service sets the members of a group. The information is updated in the in-memory copy of the group's data only. The data is not written out by this routine. Arguments: GroupContext - The group whose member list is to be replaced MemberCount - The number of new members Membership - A pointer to a buffer containing an array of account rids. Return Value: STATUS_SUCCESS - The information has been set. Other status values that may be returned are those returned by: SampSetUlongArrayAttribute() --*/ { NTSTATUS NtStatus = STATUS_SUCCESS; PULONG LocalMembers; ULONG LengthCount; ULONG SmallListGrowIncrement = 25; ULONG BigListGrowIncrement = 250; ULONG BigListSize = 800; // // These group user lists can get pretty big, and grow many // times by a very small amount as each user is added. The // registry doesn't like that kind of behaviour (it tends to // eat up free space something fierce) so we'll try to pad // out the list size. // if ( MemberCount < BigListSize ) { // // If less than 800 users, make the list size the smallest // possible multiple of 25 users. // LengthCount = ( ( MemberCount + SmallListGrowIncrement - 1 ) / SmallListGrowIncrement ) * SmallListGrowIncrement; } else { // // If 800 users or more, make the list size the smallest // possible multiple of 250 users. // LengthCount = ( ( MemberCount + BigListGrowIncrement - 1 ) / BigListGrowIncrement ) * BigListGrowIncrement; } ASSERT( LengthCount >= MemberCount ); if ( LengthCount == MemberCount ) { // // Just the right size. Use the buffer that was passed in. // LocalMembers = Members; } else { // // We need to allocate a larger buffer before we set the attribute. // LocalMembers = MIDL_user_allocate( LengthCount * sizeof(ULONG)); if ( LocalMembers == NULL ) { NtStatus = STATUS_INSUFFICIENT_RESOURCES; } else { // // Copy the old buffer to the larger buffer, and zero out the // empty stuff at the end. // RtlCopyMemory( LocalMembers, Members, MemberCount * sizeof(ULONG)); RtlZeroMemory( (LocalMembers + MemberCount), (LengthCount - MemberCount) * sizeof(ULONG) ); } } if ( NT_SUCCESS( NtStatus ) ) { NtStatus = SampSetUlongArrayAttribute( GroupContext, SAMP_GROUP_MEMBERS, LocalMembers, MemberCount, LengthCount ); } if ( LocalMembers != Members ) { // // We must have allocated a larger local buffer, so free it. // MIDL_user_free( LocalMembers ); } return( NtStatus ); } NTSTATUS SampDeleteGroupKeys( IN PSAMP_OBJECT Context ) /*++ Routine Description: This service deletes all registry keys related to a group object. Arguments: Context - Points to the group context whose registry keys are being deleted. Return Value: STATUS_SUCCESS - The information has been retrieved. Other status values that may be returned by: RtlAddActionToRXact() --*/ { NTSTATUS NtStatus; ULONG Rid; UNICODE_STRING AccountName, KeyName; Rid = Context->TypeBody.Group.Rid; // // Groups are arranged as follows: // // +-- Groups [Count] // ---+-- // +-- Names // | --+-- // | +-- (GroupName) [GroupRid,] // | // +-- (GroupRid) [Revision,SecurityDescriptor] // ---+----- // +-- V1_Fixed [,SAM_V1_0A_FIXED_LENGTH_GROUP] // +-- Name [,Name] // +-- AdminComment [,unicode string] // +-- Members [Count,(Member0Rid, (...), MemberX-1Rid)] // // This all needs to be deleted from the bottom up. // // // Decrement the group count // NtStatus = SampAdjustAccountCount(SampGroupObjectType, FALSE ); // // Delete the registry key that has the group's name to RID mapping. // if (NT_SUCCESS(NtStatus)) { // // Get the name // NtStatus = SampGetUnicodeStringAttribute( Context, SAMP_GROUP_NAME, TRUE, // Make copy &AccountName ); if (NT_SUCCESS(NtStatus)) { NtStatus = SampBuildAccountKeyName( SampGroupObjectType, &KeyName, &AccountName ); SampFreeUnicodeString( &AccountName ); if (NT_SUCCESS(NtStatus)) { NtStatus = RtlAddActionToRXact( SampRXactContext, RtlRXactOperationDelete, &KeyName, 0, NULL, 0 ); SampFreeUnicodeString( &KeyName ); } } } // // Delete the attribute keys // if (NT_SUCCESS(NtStatus)) { NtStatus = SampDeleteAttributeKeys( Context ); } // // Delete the RID key // if (NT_SUCCESS(NtStatus)) { NtStatus = SampBuildAccountSubKeyName( SampGroupObjectType, &KeyName, Rid, NULL ); if (NT_SUCCESS(NtStatus)) { NtStatus = RtlAddActionToRXact( SampRXactContext, RtlRXactOperationDelete, &KeyName, 0, NULL, 0 ); SampFreeUnicodeString( &KeyName ); } } return( NtStatus ); } NTSTATUS SampChangeGroupAccountName( IN PSAMP_OBJECT Context, IN PUNICODE_STRING NewAccountName, OUT PUNICODE_STRING OldAccountName ) /*++ Routine Description: This routine changes the account name of a group account. THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET. Arguments: Context - Points to the group context whose name is to be changed. NewAccountName - New name to give this account OldAccountName - old name is returned here. The buffer should be freed by calling MIDL_user_free. Return Value: STATUS_SUCCESS - The information has been retrieved. Other status values that may be returned by: SampGetUnicodeStringAttribute() SampSetUnicodeStringAttribute() SampValidateAccountNameChange() RtlAddActionToRXact() --*/ { NTSTATUS NtStatus; UNICODE_STRING KeyName; ///////////////////////////////////////////////////////////// // There are two copies of the name of each account. // // one is under the DOMAIN\(domainName)\GROUP\NAMES key, // // one is the value of the // // DOMAIN\(DomainName)\GROUP\(rid)\NAME key // ///////////////////////////////////////////////////////////// // // Get the current name so we can delete the old Name->Rid // mapping key. // NtStatus = SampGetUnicodeStringAttribute( Context, SAMP_GROUP_NAME, TRUE, // Make copy OldAccountName ); // // Make sure the name is valid and not already in use // if (NT_SUCCESS(NtStatus)) { NtStatus = SampValidateAccountNameChange( NewAccountName, OldAccountName ); // // Delete the old name key // if (NT_SUCCESS(NtStatus)) { NtStatus = SampBuildAccountKeyName( SampGroupObjectType, &KeyName, OldAccountName ); if (NT_SUCCESS(NtStatus)) { NtStatus = RtlAddActionToRXact( SampRXactContext, RtlRXactOperationDelete, &KeyName, 0, NULL, 0 ); SampFreeUnicodeString( &KeyName ); } } // // // Create the new Name->Rid mapping key // if (NT_SUCCESS(NtStatus)) { NtStatus = SampBuildAccountKeyName( SampGroupObjectType, &KeyName, NewAccountName ); if (NT_SUCCESS(NtStatus)) { ULONG GroupRid = Context->TypeBody.Group.Rid; NtStatus = RtlAddActionToRXact( SampRXactContext, RtlRXactOperationSetValue, &KeyName, GroupRid, (PVOID)NULL, 0 ); SampFreeUnicodeString( &KeyName ); } } // // replace the account's name // if (NT_SUCCESS(NtStatus)) { NtStatus = SampSetUnicodeStringAttribute( Context, SAMP_GROUP_NAME, NewAccountName ); } // // Free up the old account name if we failed // if (!NT_SUCCESS(NtStatus)) { SampFreeUnicodeString(OldAccountName); } } return(NtStatus); } NTSTATUS SampAddAccountToGroupMembers( IN PSAMP_OBJECT GroupContext, IN ULONG AccountRid ) /*++ Routine Description: This service adds the specified account rid to the member list for the specified group. This is a low-level function that simply edits the member attribute of the group context passed. Arguments: GroupContext - The group whose member list will be modified AccountRid - The RID of the account being added as a new member. Return Value: STATUS_SUCCESS - The account has been added. STATUS_MEMBER_IN_GROUP - The account is already a member --*/ { NTSTATUS NtStatus; ULONG MemberCount, i; PULONG MemberArray; // // Get the existing member list // Note that the member array always includes space // for one new member // NtStatus = SampRetrieveGroupMembers( GroupContext, &MemberCount, &MemberArray ); if (NT_SUCCESS(NtStatus)) { // // Fail if the account is already a member // for (i = 0; iDomainIndex)) { PSAMP_DEFINED_DOMAINS Domain; UNICODE_STRING NameString; SAMP_OBJECT_TYPE ObjectType; NTSTATUS Status; Domain = &SampDefinedDomains[ GroupContext->DomainIndex ]; Status = SampLookupAccountName( GroupContext->TypeBody.Alias.Rid, &NameString, &ObjectType ); if ( !NT_SUCCESS( Status )) { RtlInitUnicodeString( &NameString, L"-" ); } LsaIAuditSamEvent( STATUS_SUCCESS, SE_AUDITID_GLOBAL_GROUP_ADD, // AuditId Domain->Sid, // Domain SID &AccountRid, // Member Rid NULL, // Member Sid (not used) &NameString, // Account Name &Domain->ExternalName, // Domain &GroupContext->TypeBody.Group.Rid, // Account Rid NULL // Privileges used ); if ( NT_SUCCESS( Status )) { MIDL_user_free( NameString.Buffer ); } } } // // Free up the member list // MIDL_user_free( MemberArray ); } return(NtStatus); } NTSTATUS SampRemoveAccountFromGroupMembers( IN PSAMP_OBJECT GroupContext, IN ULONG AccountRid ) /*++ Routine Description: This service removes the specified account rid from the member list for the specified group. This is a low-level function that simply edits the member attribute of the group context passed. Arguments: GroupContext - The group whose member list will be modified AccountRid - The RID of the account being added as a new member. Return Value: STATUS_SUCCESS - The account has been added. STATUS_MEMBER_NOT_IN_GROUP - The account is not a member of the group. --*/ { NTSTATUS NtStatus; ULONG MemberCount, i; PULONG MemberArray; // // Get the existing member list // NtStatus = SampRetrieveGroupMembers( GroupContext, &MemberCount, &MemberArray ); if (NT_SUCCESS(NtStatus)) { // // Remove the account // NtStatus = STATUS_MEMBER_NOT_IN_GROUP; for (i = 0; iDomainIndex)) { PSAMP_DEFINED_DOMAINS Domain; UNICODE_STRING NameString; SAMP_OBJECT_TYPE ObjectType; NTSTATUS Status; Status = SampLookupAccountName( GroupContext->TypeBody.Alias.Rid, &NameString, &ObjectType ); if ( !NT_SUCCESS( Status )) { RtlInitUnicodeString( &NameString, L"-" ); } Domain = &SampDefinedDomains[ GroupContext->DomainIndex ]; LsaIAuditSamEvent( STATUS_SUCCESS, SE_AUDITID_GLOBAL_GROUP_REM, // AuditId Domain->Sid, // Domain SID &AccountRid, // Member Rid NULL, // Member Sid (not used) &NameString, // Account Name &Domain->ExternalName, // Domain &GroupContext->TypeBody.Group.Rid, // Account Rid NULL // Privileges used ); if ( NT_SUCCESS( Status )) { MIDL_user_free( NameString.Buffer ); } } } // // Free up the member list // MIDL_user_free( MemberArray ); } return(NtStatus); }