{*******************************************************} { MiTeC Common Routines } { Windows NT Account Enumeration } { } { Copyright (c) 1997-2021 Michal Mutl } { } {*******************************************************} {$INCLUDE Compilers.inc} unit MiTeC_AccountsNT; interface uses {$IFDEF RAD9PLUS} WinAPI.Windows, System.SysUtils, System.Classes; {$ELSE} Windows, SysUtils, Classes; {$ENDIF} type PUserInfo = ^TUserInfo; TUserInfo = record Name: string; Comment: string; Sid: string; NumberOfSubAuths: Cardinal; SidLength: Cardinal; Domain: string; SidType: SID_NAME_USE; FullName: string; LastLogon: TDateTime; LastLogoff: TDateTime; LogonCount: Cardinal; Expiration: TDateTime; Disabled: Boolean; Flags: Cardinal; _Membership: string; end; PGroupInfo = ^TGroupInfo; TGroupInfo = record Name: string; Comment: string; Sid: string; NumberOfSubAuths: Cardinal; SidLength: Cardinal; Domain: string; SidType: SID_NAME_USE; end; TAccounts = class(TPersistent) private FUsers, FLocalGroups, FGlobalGroups: TStringList; FMachine: string; FWMachine: WideString; procedure FreeUserList(var AList: TStringList); procedure FreeGroupList(var AList: TStringList); function GetLocalGroup(Index: Integer): PGroupInfo; function GetUser(Index: Integer): PUserInfo; function FindUser(AName: string): PUserInfo; function FindUserSID(ASID: string): PUserInfo; function GetUserCount: Cardinal; function GetLocalGroupCount: Cardinal; function GetGlobalGroup(Index: Integer): PGroupInfo; function GetGlobalGroupCount: Cardinal; function RetrieveUsers(AMachine: string): Cardinal; procedure RetrieveLocalGroups(AMachine: string); procedure RetrieveGlobalGroups(AMachine: string); function GetSIDFromAccount(AMachine, AName: string; var Domain: string; var SidLen,SubAuthCount: Cardinal; var NameUse: SID_NAME_USE): string; procedure SetMachine(const Value: string); public constructor Create; destructor Destroy; override; procedure Clear; function RefreshUsers: Cardinal; procedure RefreshLocalGroups; procedure RefreshGlobalGroups; function DeleteAccount(AName: string): cardinal; function CreateAccount(AName,APwd: string): cardinal; function EnableAccount(AName: string): Cardinal; function DisableAccount(AName: string): Cardinal; function SetMembership(AAccount,AGroup: string; Adelete: boolean): Cardinal; procedure GetUserLocalGroups(AUsername: string; var AList: TStringList); procedure GetLocalGroupUsers(AGroupname: string; var AList: TStringList); procedure GetUserGlobalGroups(AUsername: string; var AList: TStringList); procedure GetGlobalGroupUsers(AGroupname: string; var AList: TStringList); function IsStandardUser(AUsername: string): Boolean; property Machine: string read FMachine write SetMachine; property UserCount: Cardinal read GetUserCount; property Users[Index: Integer]: PUserInfo read GetUser; property UserByName[AName: string]: PUserInfo read FindUser; property UserBySID[ASID: string]: PUserInfo read FindUserSID; property LocalGroupCount: Cardinal read GetLocalGroupCount; property LocalGroups[Index: Integer]: PGroupInfo read GetLocalGroup; property GlobalGroupCount: Cardinal read GetGlobalGroupCount; property GlobalGroups[Index: Integer]: PGroupInfo read GetGlobalGroup; end; function GetNameUseStr(Value: Cardinal): string; function ConvertSIDToStringSID(ASID: PSID): string; implementation uses MiTeC_Windows, MiTeC_NetAPI32, MiTeC_Routines, MiTeC_Datetime; function ConvertSIDToStringSID(ASID: PSID): string; var i: integer; SIDAuth: PSIDIdentifierAuthority; SIDSubAuth: Cardinal; SIDSubAuthCount: Byte; begin Result:='S-1-'; SIDAuth:=GetSidIdentifierAuthority(ASID); for i:=0 to 5 do if SIDAuth.Value[i]<>0 then Result:=Result+IntToStr(SIDAuth.Value[i]); SIDSubAuthCount:=GetSidSubAuthorityCount(ASID)^; for i:=0 to SIDSubAuthCount-1 do begin SIDSubAuth:=GetSidSubAuthority(ASID,i)^; Result:=Result+'-'+IntToStr(SIDSubAuth); end; end; function GetNameUseStr(Value: Cardinal): string; begin case Value of Cardinal(SidTypeUser): Result:='SidTypeUser'; Cardinal(SidTypeGroup): Result:='SidTypeGroup'; Cardinal(SidTypeDomain): Result:='SidTypeDomain'; Cardinal(SidTypeAlias): Result:='SidTypeAlias'; Cardinal(SidTypeWellKnownGroup): Result:='SidTypeWellKnownGroup'; Cardinal(SidTypeDeletedAccount): Result:='SidTypeDeletedAccount'; Cardinal(SidTypeInvalid): Result:='SidTypeInvalid'; Cardinal(SidTypeUnknown): Result:='SidTypeUnknown'; 9: Result:='SidTypeComputer'; 10: Result:='SidTypeLabel'; end; end; { TAccounts } procedure TAccounts.Clear; begin FreeUserList(FUsers); FreeGroupList(FLocalGroups); FreeGroupList(FGlobalGroups); end; constructor TAccounts.Create; begin FUsers:=TStringList.Create; FLocalGroups:=TStringList.Create; FGlobalGroups:=TStringList.Create; InitNETAPI; end; function TAccounts.CreateAccount; var WUser, WPwd: WideString; usri1: USER_INFO_1; parm_err: Cardinal; begin WUser:=Aname; WPwd:=APwd; usri1.usri1_name:=PWideChar(WUser); usri1.usri1_password:=PWideChar(WPwd); usri1.usri1_password_age:=0; usri1.usri1_priv:=USER_PRIV_USER; usri1.usri1_home_dir:=''; usri1.usri1_comment:=''; usri1.usri1_flags:=UF_SCRIPT or UF_DONT_EXPIRE_PASSWD or UF_NORMAL_ACCOUNT;//$10201; usri1.usri1_script_path:=''; Result:=NetUserAdd(nil,1,@usri1,parm_err); end; function TAccounts.DeleteAccount; var i: Integer; WName: WideString; begin i:=Pos('\',AName); if i>0 then AName:=Copy(AName,i+1,255); WName:=AName; Result:=NetUserDel(PWideChar(FWMachine),PWideChar(WName)); end; destructor TAccounts.Destroy; begin FreeUserList(FUsers); FUsers.Free; FreeGroupList(FLocalGroups); FLocalGroups.Free; FreeGroupList(FGlobalGroups); FGlobalGroups.Free; inherited; end; function TAccounts.EnableAccount(AName: string): Cardinal; var WUser: WideString; usri: USER_INFO_1008; begin Result:=0; WUser:=Aname; try usri.usri1008_flags:=FindUser(Aname).Flags; if usri.usri1008_flags and UF_ACCOUNTDISABLE=0 then Exit; usri.usri1008_flags:=usri.usri1008_flags - UF_ACCOUNTDISABLE; Result:=NetUserSetInfo(nil,PWideChar(WUser),1008,@usri,nil); finally end; end; function TAccounts.DisableAccount(AName: string): Cardinal; var WUser: WideString; usri: USER_INFO_1008; begin Result:=0; WUser:=Aname; try usri.usri1008_flags:=FindUser(Aname).Flags; if usri.usri1008_flags and UF_ACCOUNTDISABLE>0 then Exit; usri.usri1008_flags:=usri.usri1008_flags or UF_ACCOUNTDISABLE; Result:=NetUserSetInfo(nil,PWideChar(WUser),1008,@usri,nil); finally end; end; function TAccounts.FindUser(AName: string): PUserInfo; var i: Integer; begin Result:=nil; for i:=0 to FUsers.Count-1 do if SameText(PUserInfo(FUsers.Objects[i])^.Name,AName) then begin Result:=PUserInfo(FUsers.Objects[i]); Break; end; end; function TAccounts.FindUserSID(ASID: string): PUserInfo; var i: Integer; begin Result:=nil; for i:=0 to FUsers.Count-1 do if SameText(PUserInfo(FUsers.Objects[i])^.Sid,ASID) then begin Result:=PUserInfo(FUsers.Objects[i]); Break; end; end; procedure TAccounts.FreeGroupList(var AList: TStringList); begin while AList.Count>0 do begin dispose(PGroupInfo(AList.Objects[AList.Count-1])); AList.Delete(AList.Count-1); end; end; procedure TAccounts.FreeUserList(var AList: TStringList); begin while AList.Count>0 do begin dispose(PUserInfo(AList.Objects[AList.Count-1])); AList.Delete(AList.Count-1); end; end; function TAccounts.GetGlobalGroup(Index: Integer): PGroupInfo; begin if Index0) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin AList.Add(WideCharToString(pTmpBuf.grui0_name)); pTmpBuf:=PGROUP_USERS_INFO_0(PAnsiChar(pTmpBuf)+SizeOf(GROUP_USERS_INFO_0)); end; if Assigned(pBuf) then begin NetApiBufferFree(pBuf); pBuf:=nil; end; if nStatus=ERROR_SUCCESS then Loop:=False; dwResumeHandle:=dwEntriesRead+1; end else Loop:=False; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; function TAccounts.GetLocalGroup(Index: Integer): PGroupInfo; begin if Index0) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin AList.Add(WideCharToString(pTmpBuf.lgrmi3_domainandname)); pTmpBuf:=PLOCALGROUP_MEMBERS_INFO_3(PAnsiChar(pTmpBuf)+SizeOf(LOCALGROUP_MEMBERS_INFO_3)); end; if Assigned(pBuf) then begin NetApiBufferFree(pBuf); pBuf:=nil; end; if nStatus=ERROR_SUCCESS then Loop:=False; dwResumeHandle:=dwEntriesRead+1; end else Loop:=False; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; function TAccounts.GetSIDFromAccount(AMachine, AName: string; var Domain: string; var SidLen,SubAuthCount: Cardinal; var NameUse: SID_NAME_USE): string; var SID: PSID; szDomain: PChar; cbDomain, cbSID: Cardinal; begin SID:=nil; cbDomain:=0; cbSID:=0; szDomain:=nil; LookupAccountName(PChar(AMachine),PChar(AName),SID,cbSID,szDomain,cbDomain,NameUse); szDomain:=StrAlloc(cbDomain); SID:=AllocMem(cbSID); if LookupAccountName(PChar(AMachine),PChar(AName),SID,cbSID,szDomain,cbDomain,NameUse) then begin Result:=ConvertSIDToStringSID(SID); SubAuthCount:=GetSidSubAuthorityCount(PSID(SID))^; SidLen:=GetLengthSid(PSID(SID)); Domain:=szDomain; end else Result:=GetErrorMessage(GetLastError); StrDispose(szDomain); FreeMem(SID); end; function TAccounts.GetUser(Index: Integer): PUserInfo; begin if Index0) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin AList.Add(WideCharToString(pTmpBuf.grui0_name)); pTmpBuf:=PGROUP_USERS_INFO_0(PAnsiChar(pTmpBuf)+SizeOf(GROUP_USERS_INFO_0)); end; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; procedure TAccounts.GetUserLocalGroups; var i: Cardinal; nStatus: NET_API_STATUS; pTmpBuf,pBuf: PLOCALGROUP_USERS_INFO_0; dwLevel: Cardinal; dwPrefMaxLen: Cardinal; dwEntriesRead: Cardinal; dwTotalEntries: Cardinal; wu: WideString; begin wu:=AUsername; AList.Clear; pBuf:=nil; dwLevel:=0; dwPrefMaxLen:=$FFFFFFFF; dwEntriesRead:=0; dwTotalEntries:=0; nStatus:=NetUserGetLocalGroups(PWideChar(FWMachine),PWideChar(wu), dwLevel, LG_INCLUDE_INDIRECT, Pointer(pBuf), dwPrefMaxLen, dwEntriesRead, dwTotalEntries); if ((nStatus=ERROR_SUCCESS) or (nStatus=ERROR_MORE_DATA)) and (dwEntriesRead>0) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin AList.Add(WideCharToString(pTmpBuf.lgrui0_name)); pTmpBuf:=PLOCALGROUP_USERS_INFO_0(PAnsiChar(pTmpBuf)+SizeOf(LOCALGROUP_USERS_INFO_0)); end; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; function TAccounts.IsStandardUser(AUsername: string): Boolean; var i: Integer; sl: TStringList; begin Result:=False; sl:=TStringList.Create; try GetUserLocalGroups(AUsername,sl); for i:=0 to LocalGroupCount-1 do if SameText(LocalGroups[i].Domain,'BUILTIN') and not SameText(LocalGroups[i].Name,'Guests') then if sl.IndexOf(LocalGroups[i].Name)>-1 then begin Result:=True; Break; end; finally sl.Free; end; end; procedure TAccounts.RefreshGlobalGroups; begin RetrieveGlobalGroups(Machine); end; procedure TAccounts.RefreshLocalGroups; begin RetrieveLocalGroups(Machine); end; function TAccounts.RefreshUsers: Cardinal; begin Result:=RetrieveUsers(Machine); end; procedure TAccounts.RetrieveGlobalGroups(AMachine: string); var i: Cardinal; nStatus: NET_API_STATUS; pgi: PGroupInfo; pTmpBuf,pBuf: PGROUP_INFO_2; Loop: Boolean; dwLevel: Cardinal; dwPrefMaxLen: Cardinal; dwEntriesRead: Cardinal; dwTotalEntries: Cardinal; dwResumeHandle: Cardinal; begin FreeGroupList(FGlobalGroups); pBuf:=nil; Loop:=True; dwLevel:=2; dwPrefMaxLen:=$FFFFFFFF; dwEntriesRead:=0; dwTotalEntries:=0; dwResumeHandle:=0; while Loop do begin nStatus:=NetGroupEnum(PWideChar(FWMachine), dwLevel, Pointer(pBuf), dwPrefMaxLen, dwEntriesRead, dwTotalEntries, dwResumeHandle); if (nStatus=ERROR_SUCCESS) or (nStatus=ERROR_MORE_DATA) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin new(pgi); with pTmpBuf^ do begin pgi^.Name:=WideCharToString(grpi2_name); pgi^.Comment:=WideCharToString(grpi2_comment); pgi^.Sid:=GetSIDFromAccount(AMachine,pgi^.Name,pgi^.Domain,pgi^.SidLength,pgi^.NumberOfSubAuths,pgi^.SidType); FGlobalGroups.AddObject(pgi^.Name,TObject(pgi)); pTmpBuf:=PGROUP_INFO_2(PAnsiChar(pTmpBuf)+SizeOf(GROUP_INFO_2)); end; end; if Assigned(pBuf) then begin NetApiBufferFree(pBuf); pBuf:=nil; end; if nStatus=ERROR_SUCCESS then Loop:=False; dwResumeHandle:=dwEntriesRead+1; end else Loop:=False; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; procedure TAccounts.RetrieveLocalGroups; var i: Cardinal; nStatus: NET_API_STATUS; pgi: PGroupInfo; pTmpBuf,pBuf: PLOCALGROUP_INFO_1; Loop: Boolean; dwLevel: Cardinal; dwPrefMaxLen: Cardinal; dwEntriesRead: Cardinal; dwTotalEntries: Cardinal; dwResumeHandle: Cardinal; begin FreeGroupList(FLocalGroups); pBuf:=nil; Loop:=True; dwLevel:=1; dwPrefMaxLen:=$FFFFFFFF; dwEntriesRead:=0; dwTotalEntries:=0; dwResumeHandle:=0; while Loop do begin nStatus:=NetLocalGroupEnum(PWideChar(FWMachine), dwLevel, Pointer(pBuf), dwPrefMaxLen, dwEntriesRead, dwTotalEntries, dwResumeHandle); if (nStatus=ERROR_SUCCESS) or (nStatus=ERROR_MORE_DATA) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin new(pgi); with pTmpBuf^ do begin pgi^.Name:=WideCharToString(lgrpi1_name); pgi^.Comment:=WideCharToString(lgrpi1_comment); pgi^.Sid:=GetSIDFromAccount(AMachine,pgi^.Name,pgi^.Domain,pgi^.SidLength,pgi^.NumberOfSubAuths,pgi^.SidType); FLocalGroups.AddObject(pgi^.Name,TObject(pgi)); pTmpBuf:=PLOCALGROUP_INFO_1(PAnsiChar(pTmpBuf)+SizeOf(LOCALGROUP_INFO_1)); end; end; if Assigned(pBuf) then begin NetApiBufferFree(pBuf); pBuf:=nil; end; if nStatus=ERROR_SUCCESS then Loop:=False; dwResumeHandle:=dwEntriesRead+1; end else Loop:=False; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; function TAccounts.RetrieveUsers(AMachine: string): Cardinal; var i: Cardinal; nStatus: NET_API_STATUS; pui: PUserInfo; pTmpBuf,pBuf: PUSER_INFO_3; Loop: Boolean; dwLevel: Cardinal; dwPrefMaxLen: Cardinal; dwEntriesRead: Cardinal; dwTotalEntries: Cardinal; dwResumeHandle: Cardinal; begin Result:=0; FreeUserList(FUsers); pBuf:=nil; Loop:=True; dwLevel:=3; dwPrefMaxLen:=$FFFFFFFF; dwEntriesRead:=0; dwTotalEntries:=0; dwResumeHandle:=0; while Loop do begin nStatus:=NetUserEnum(PWideChar(FWMachine), dwLevel, FILTER_NORMAL_ACCOUNT, Pointer(pBuf), dwPrefMaxLen, dwEntriesRead, dwTotalEntries, dwResumeHandle); if (nStatus=ERROR_SUCCESS) or (nStatus=ERROR_MORE_DATA) then begin pTmpBuf:=pBuf; for i:=0 to dwEntriesRead-1 do begin new(pui); with pTmpBuf^ do begin pui^.Name:=WideCharToString(usri3_name); pui^.FullName:=WideCharToString(usri3_full_name); pui^.Comment:=WideCharToString(usri3_comment); pui^.LastLogon:=UTCToDateTime(pTmpBuf^.usri3_last_logon); pui^.LastLogoff:=UTCToDateTime(pTmpBuf^.usri3_last_logoff); if pTmpBuf^.usri3_acct_expires=TIMEQ_FOREVER then pui^.Expiration:=-1 else pui^.Expiration:=UTCToDateTime(pTmpBuf^.usri3_acct_expires); pui^.LogonCount:=pTmpBuf^.usri3_num_logons; pui^.Sid:=GetSIDFromAccount(AMachine,pui^.Name,pui^.Domain,pui^.SidLength,pui^.NumberOfSubAuths,pui^.SidType); pui^.Disabled:=pTmpBuf^.usri3_flags and UF_ACCOUNTDISABLE = UF_ACCOUNTDISABLE; pui^.Flags:=pTmpBuf^.usri3_flags; FUsers.AddObject(pui^.Name,TObject(pui)); pTmpBuf:=PUSER_INFO_3(PAnsiChar(pTmpBuf)+SizeOf(USER_INFO_3)); end; end; if Assigned(pBuf) then begin NetApiBufferFree(pBuf); pBuf:=nil; end; if nStatus=ERROR_SUCCESS then Loop:=False; dwResumeHandle:=dwEntriesRead+1; end else begin Result:=GetLastError; Loop:=False; end; end; if Assigned(pBuf) then NetApiBufferFree(pBuf); end; procedure TAccounts.SetMachine(const Value: string); begin FMachine := Value; if FMachine='' then FWMachine:='\\.' else FWMachine:='\\'+FMachine; end; function TAccounts.SetMembership; var lgmi3: LOCALGROUP_MEMBERS_INFO_3; wuser,wgroup: WideString; begin wuser:=AAccount; wgroup:=AGroup; lgmi3.lgrmi3_domainandname:=PWideChar(WUser); if Adelete then Result:=NetLocalGroupDelMembers(PWideChar(FWMachine),PWideChar(wgroup),3,lgmi3,1) else Result:=NetLocalGroupAddMembers(PWideChar(FWMachine),PWideChar(wgroup),3,lgmi3,1); end; end.