![]() |
Web Development »
Applications & Tools »
Tools with source code
Intermediate
License: The Code Project Open License (CPOL)
NT Remote and Local Group and User Account SID Collector ToolBy Lim Bio LiongTool to collect SIDs of Group and User Accounts from a Local or Remote NT Machine and output in an INI file and an XML file. |
VC6, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Some time at the beginning of 2000, a friend of mine approached me to write a tool that is able to collect all the SIDs of all Group and User Accounts of an NT machine (local or remote).
One of his junior staff had a need to periodically collect these Group and User Account SIDs of several machines. He would later need to collate all these information into a report, using his own report generator programming tool. I offered to write a DLL with high level functions that encapsulate the LAN Manager NET functions. That'll be great, according to my friend but it'd be better if I could simply write a small application that collects the necessary information and store them into a file that the report generator program could later read and process. Sure, I replied. A simple console program that can be launched via a batch file will do. Now, what format do you need this file to be in? They were at a loss. I suggested the Windows INI file format for the following reasons:
GetPrivateProfileString()). We all agreed and so I wrote the CollectSID console application. It uses the LAN Manager NET APIs that probes local or remote NT machines for Group and User Account SIDs and outputs them all into an INI file (I'll cover the format of this INI file in the section "INI File Format", later in this article). One year on, and with the widespread popularity of XML, I decided to revisit this tool that I wrote and extend it to include XML file output.
The CollectSID program source is composed of 6 files:
main(). main() will perform the usual command line processing and then calls on the CollectGroups() function to start the ball rolling.
CollectGroups() will later call CollectMembers() and LookupSID() to do other necessary work.
CollectGroups() completed successfully, we call on ConvertINIToXML() to convert the output INI file into an XML formatted file. Three functions are defined here:
CollectGroups()
CollectMembers()
LookupSID() Let's take a look at CollectGroups() in more detail.
// The NetLocalGroupEnum() API retrieves //information about each local group account // on a target machine. NetStatus = NetLocalGroupEnum ( wszMachineName, 0, &Data, 8192, &Index, &Total, &ResumeHandle ); if (NetStatus != NERR_Success || Data == NULL) { dwLastError = GetLastError(); bRet = FALSE; goto CollectGroups_0; } // Write down in the output file's // header how many groups there are. sprintf (szOutString, "%d", Total); WritePrivateProfileString ("HEADER", "GROUP_COUNT", szOutString, lpszOutputFileName); GroupInfo = (LOCALGROUP_INFO_0 *)Data;
CollectGroups() uses NetLocalGroupEnum() to retrieve information about each local group account. After successfully calling this function, information on the Group Accounts of the target machine are stored in an array of LOCALGROUP_INFO_0 structs which is returned in the Data byte pointer. The total number of Group Accounts is returned in Total.
We then store the total number of Group Accounts in the INI file under the section HEADER and under the key name GROUP_COUNT. Full information on the format of the INI file will be discussed in the section "INI File Format", later.
We then iterate through the entire array of LOCALGROUP_INFO_0 structs and store the name of each Group Account into the target INI file.
GroupInfo = (LOCALGROUP_INFO_0 *)Data;
for (i=0; i < Total; i++)
{
// Convert group name from UNICODE to ansi.
iRetOp = WideCharToMultiByte
(
(UINT)CP_ACP, // code page
(DWORD)0, // performance and mapping flags
(LPCWSTR)(GroupInfo->lgrpi0_name),
// address of wide-character string
(int)-1, // number of characters in string
(LPSTR)szAnsiName, // address of buffer for new string
(int)(sizeof(szAnsiName)), // size of buffer
(LPCSTR)NULL, // address of default for unmappable characters
(LPBOOL)NULL // address of flag set when default char used.
);
// Write down the name of each group in the
// HEADER section indexed by the key name GROUP_n
sprintf (szOutString, "GROUP_%d", i + 1);
WritePrivateProfileString ("HEADER", szOutString,
(LPCTSTR)szAnsiName, lpszOutputFileName);
// Find out the SID of the Group.
strcpy (szOutString, ""); // Initialise szOutString
LookupSID ((LPCTSTR)lpszMachineName,
(LPCTSTR)szAnsiName, (LPTSTR)szOutString);
// Write down details of each group in its own
// section named by the group name itself.
WritePrivateProfileString (szAnsiName,
"SID", szOutString, lpszOutputFileName);
// Now lookup all members of this group
// and record down their names and SIDs into
// the output file.
CollectMembers((LPCTSTR)lpszMachineName,
(LPCTSTR)szAnsiName, (LPCTSTR)lpszOutputFileName);
GroupInfo++;
}
Using the name of the Group Account, we further call on the function LookupSID() to get the SID of this Group Account.
Now, once we get the SID of a Group Account, we immediately CREATE a section in the INI file for that Group and also store a SID key in that section containing the SID value.
For example, once we have determined that the local machine has got 8 Group Accounts, and that one of these groups has the name "Administrators", the INI file will contain the following information:
[HEADER]
GROUP_COUNT=8
GROUP_1=Administrators
...
...
...
[Administrators]
SID=S-1-5-32-544
...
...
...
Notice that there will be an entry in the HEADER section for "Administrators" and "Administrators" will have its own section with a SID key that contains the SID value for "Administrators".
Let's take a look at CollectMembers() in more detail.
NetStatus = NetLocalGroupGetMembers
(
wszMachineName,
wszGroupName,
1,
&Data,
8192,
&Index,
&Total,
&ResumeHandle
);
if (NetStatus != NERR_Success || Data == NULL)
{
dwLastError = GetLastError();
bRet = FALSE;
goto CollectMembers_0;
}
// Write down in the output file's section
// for this group the totla nu,ber of
// members it has.
sprintf (szSID, "%d", Total);
WritePrivateProfileString (lpszGroupName,
"MEMBER_COUNT", szSID, lpszOutputFileName);
MemberInfo = (LOCALGROUP_MEMBERS_INFO_1 *)Data;
CollectMembers() calls on the NetLocalGroupGetMembers() API to retrieve a list of the members of a particular Group. This API works similarly to NetLocalGroupEnum() by storing all retrieved information in an array. This array is an array of LOCALGROUP_MEMBERS_INFO_1 structs which is returned in the LPBYTE Data.
The total number of members in this Group is returned in Total. This value is stored in the MEMBER_COUNT key of the Group's section which has already been created in the CollectGroups() function.
We then iterate through this array of LOCALGROUP_MEMBERS_INFO_1 structs and store each member name as a key value in the Group's section. The key itself is of the form MEMBER_n where n is a unique number within that Group.
We then call LookupSID() to get the member's SID string value and store this string value as a key value in the Group's section. The key itself is the member name.
For example, if we determined that the Group "Administrators" has got 4 members and that "Domain Admins" is one such member, then the INI file will contain the following information:
[Administrators]
SID=<SID string for Administrators>
MEMBER_COUNT=4
...
...
...
MEMBER_2=Domain Admins
Domain Admins=<SID string for Domain Admnins>
...
...
...
Let's take a look at LookupSID() in more detail.
A full discussion on the subject of NT Security Identifiers is beyond the scope of this article. I will assume that the reader has some knowledge on SIDs in general and its components. Many good books on Windows NT Security would cover this in detail, e.g. "Programming Windows Security" by Keith Brown (Addison Wesley). There is also a good article by Mark Russinovich at the Systems Internals Web Site, entitled Windows NT Security, which covers SIDs.
I'll give a brief overview of an NT SID. A SID is a variable-length numeric value that consists of the following:
The Win32 API provides the SID struct as well as a PSID pointer but does not encourage developers to extract values from such a structure directly. Instead, APIs are provided to help us retrieve individual values from this struct. The only field value from this struct that I directly extract is the REVISION value. I have not found any API that can help me ascertain this value from a SID.
The identifier authority value identifies the agent that issued the SID. This agent is usually an NT local system or a domain. Subauthority values identify trustees relative to the issuing authority. RIDs provide a way for NT to create unique SIDs from a base SID.
pSid = (PSID)bySidBuffer; dwSidSize = sizeof(bySidBuffer); dwDomainNameSize = sizeof(szDomainName); bRetOp = LookupAccountName ( (LPCTSTR)lpszMachineName, // address of string for system name (LPCTSTR)lpszAccountName, // address of string for account name (PSID)pSid, // address of security identifier (LPDWORD)&dwSidSize, // address of size of security identifier (LPTSTR)szDomainName, // address of string for referenced domain (LPDWORD)&dwDomainNameSize, // address of size of domain string (PSID_NAME_USE)&sidType // address of SID-type indicator ); if (bRetOp == FALSE) { dwLastError = GetLastError(); lRet = -1; // Unable to obtain Account SID. goto LookupSID_0; } bRetOp = IsValidSid((PSID)pSid); if (bRetOp == FALSE) { dwLastError = GetLastError(); lRet = -2; // SID returned is invalid. goto LookupSID_0; }
LookupSID() calls on LookupAccountName() to obtain the security identifier (SID) for an account and the name of the domain on which the account was found. This API requires the name of the account and the name of the machine on which the account exists.
Before calling on LookupAccountName(), we first declare a pointer to an SID (pSid (type PSID)) and a BYTE buffer (bySidBuffer). This BYTE buffer bySidBuffer will hold full SID data after LookupAccountName() returns.
Although the name of the domain and the SID type are returned, the project that I worked on did not have use for these values and so I ignored them. It will be good if the reader modifies the source of this program to output these values too. After successfully calling LookupAccountName(), we call IsValidSid() to further validate the returned SID.
// We initialise our SID string with the current standard for SID strings. // The "S" is the standard prefix. // We extract the revision number // directly from the SID struct itself. // This revision number is the only field // value from SID that we extract directly. // The other field values must be enquired via APIs. sprintf (szSID, "S-%d", (((SID*)pSid) -> Revision));
I then begin forming a string version of the SID. A string SID starts with a standard prefix of "S" and hyphens separate its various components. After the "S", the revision number is stored. We take this revision number directly from the SID struct returned to us.
// Obtain via APIs the identifier authority value. psid_identifier_authority = GetSidIdentifierAuthority ((PSID)pSid); // Make a copy of it. memcpy (&sid_identifier_authority, psid_identifier_authority, sizeof(SID_IDENTIFIER_AUTHORITY)); // The value in IDENTIFIER AUTHORITY is an array of 6 bytes. // However, we are only interested in the last byte of the array. sprintf (szIdentAuthValue, "-%d", (sid_identifier_authority.Value)[5]); strcat (szSID, szIdentAuthValue);
The identifier authority value is to be inserted next and we use the GetSidIdentifierAuthority() to obtain a pointer to the SID_IDENTIFIER_AUTHORITY structure contained inside our SID. This structure contains the 48-bit identifier authority value. This 48-bit number is divided into 6 bytes. Currently, only the last byte is of any importance because the first 5 bytes are always 0.
We next obtain the count of subauthority values associated with this SID by calling on API GetSidSubAuthorityCount(). We then perform a loop that obtains the subauthority values from the SID via the GetSidSubAuthority() API. Each retrieved subauthority value is appended into our SID string.
// Determine how many sub-authority values there are in the current SID. puchar_SubAuthCount = (PUCHAR)GetSidSubAuthorityCount((PSID)pSid); // Assign it to a more convenient variable. j = (unsigned char)(*puchar_SubAuthCount); // Now obtain all the sub-authority values from the current SID. for (i = 0; i < (int)j; i++) { DWORD dwSubAuth = 0; PDWORD pdwSubAuth = NULL; char szSubAuthValue[80]; // Obtain the current sub-authority // DWORD (referenced by a pointer) pdwSubAuth = (PDWORD)GetSidSubAuthority ( (PSID)pSid, // address of security identifier to query (DWORD)i // index of subauthority to retrieve ); dwSubAuth = *pdwSubAuth; sprintf (szSubAuthValue, "-%d", dwSubAuth); strcat (szSID, szSubAuthValue); }
This source file contains definitions for 3 basic classes CDOM, CMSXML_DOMDocument and CMSXML_Element. These classes encapsulate basic functionalities to write an XML file. No XML file read processing functionalities are provided. We simply want to output an XML file.
The XML file processing is done via MSXML due to its widespread availability. For simplicity, I have used the IDispatch interface (OLE Automation) for talking to MSXML and avoided importing MSXML's type library.
The main function that is of interest in msxmlwrp.cpp is ConvertINIToXML() which takes the outputted INI file, processes it and creates an XML file equivalent for it. This function should also serve as an example on how to process the INI file.
The INI file that is output from this program will always have a fixed HEADER section. This section will always contain a fixed GROUP_COUNT key:
[HEADER]
GROUP_COUNT=n
GROUP_1=...
GROUP_2=...
GROUP_3=...
GROUP_n=...
The key value for GROUP_COUNT is an integer that indicates how many groups there are in this INI file. There will be a GROUP_n key name for each group defined, where n is a number from 1 through GROUP_COUNT. The corresponding values for these keys will be the name of each group.
The INI file will then contain an individual section for every group.
[{GROUP NAME}]
SID=...
MEMBER_COUNT=n
MEMBER_1={Member 1 name}
{Member 1 name}=...
{Member 2 name}=...
...
...
...
{Member n name}=...
This section will always contain a fixed SID key which will contain the SID value for the group and a fixed MEMBER_COUNT key that will contain the total number of members contained in this group.
There will be a MEMBER_n key name for each member in this group where n is a number from 1 through MEMBER_COUNT. The corresponding values for these keys will be the name of the member.
Each member will also have a key in this section (the key name is the member name itself) and the value is the SID of the member.
For example, let's say there are 3 groups defined in a local machine. The groups are "Administrators", "Backup Operators" and "Guests". The following is a sample output in the INI file:
[HEADER]
GROUP_COUNT=3
GROUP_1=Administrators
GROUP_2=Backup Operators
GROUP_3=Guests
[Administrators]
SID=...
MEMBER_COUNT=1
MEMBER_1=Administrator
Administrator=...
SID=...
MEMBER_COUNT=0
[Guests]
SID=S-1-5-32-546
MEMBER_COUNT=1
MEMBER_1=Guest
Guest=...
CollectSID uses the standard Windows API WritePrivateProfielString(), if you do not specify a full path to your output file, the generated INI file will be written to the Windows directory. However, the generated XML file will be output to the active directory. Beware of this fact which may raise confusion. To be safe, always specify a full path.
WritePrivateProfileString() is used, if an existing INI file with the same name as your target file already exists, this existing file will not first be deleted. Its contents will be appended. Be aware of this fact which may raise confusion as well due to irrelevant information being included in the INI file. | You must Sign In to use this message board. | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 10 Dec 2001 Editor: Smitha Vijayan |
Copyright 2001 by Lim Bio Liong Everything else Copyright © CodeProject, 1999-2010 Web18 | Advertise on the Code Project |