Windows NT Security

 

Christopher Nefcy

September 1994

Abstract

Microsoft® Windows NT® security is discussed in detail in the Win32® API Programmer's Reference. The Security Model is very powerful and has many features. Unfortunately, there are very few examples which show the usage of security on an object. Most of the examples show how to add security to an object to allow all users to access that object. However, most programmers want to be able to limit access to an object. This technical article will try to explain how to use the some of the security features and will also show some examples of programming the security features.

Security Model

A brief overview of the Security Model will provide familiarity with relevant concepts and terminology. A more in-depth examination should be done using the Microsoft® Win32® Security API Overviews.

Every Microsoft Windows NT® object (files, named pipes, registry keys, user objects, kernel objects, private objects, and so on) has its security attributes described by a security descriptor (SD). The security-descriptor contains information about the owner of the object and has an access-control list (ACL), which is a list specifying user and groups and their access permissions on that object.

There are two types of ACLs: discretionary and system. The discretionary ACL (DACL) is controlled by the owner of an object and specifies the access particular users or groups can have to that object. The system ACL (SACL) is controlled by the system administrator, and allows system-level security to be associated with the object. The usage of DACL and SACL APIs is very similar. However, the SACL APIs can only be used by a process with System Administrator privileges. Because most security programming does not involve setting the system-level security, the discussion here will focus on the DACL APIs.

The DACL contains an entry for each user, global group, or local group that is given access permission (whether allowing or denying access) to the object. Each of these entries is in the form of an access-control entry (ACE). An ACE contains an ACE_HEADER structure, along with the access permission for that ACE type, and the security identifier (SID). The ACE_HEADER defines the type of ACE (ACCESS_ALLOWED_ACE_TYPE or ACCESS_DENIED_ACE_TYPE), the size of the ACE, and control flags for the ACE. The access permission will determine the type of permission (that is, Read, Write, and so on) that the user or group has. The user or group is specified using its SID.

When a DACL does not have any ACEs, then no access rights have been explicitly granted. Therefore, access to the object is implicitly denied. However, when an object does not have a DACL, then no protection is assigned to the object and any access request is granted. An SD for an object is initially set to have a DACL with no ACEs, meaning that there is no access for any user. To give access to all users or groups, the DACL for the SD must be explicitly set to NULL.

There are two types of SDs used in the system: absolute or self-relative. The distinction between these two types is very important when programming. An absolute SD contains pointers to the DACL and ACE information, while the self-relative format contains information in a contiguous block of memory, so the DACLs and ACEs are positioned at offsets. The security APIs use both absolute and self-relative formats at different times. When asking for SD information, it is always returned by the system in self-relative format. The program can then walk through the offsets to obtains the proper information. However, when adding to or changing the SD, it must be in absolute format. When programming a change to the SD, for instance, this means that the program must read in SD in one format, convert it and write it back in the other format.

A user of a process is assigned an access token, which contains identifiers that represent the user and any group to which the user belongs. This access token is checked against the SD of an object to determine the permission of the user has with respect to that object. When a Client connects to a Server, the Server process can impersonate the access token of the Client process. This gives the Server the ability to check the access permission of the Client user.

Programming

To manipulate the security on an object, the SD of that object must be retrieved from or set back to the object. There are a number of APIs to perform these functions, depending on the type of object. For example, retrieving object SD APIs include GetFileSecurity, RegGetKeySecurity, GetKernelObjectSecurity, GetUserObjectSecurity, and GetPrivateObjectSecurity. Setting object SD APIs include SetFileSecurity, RegSetKeySecurity, SetKernelObjectSecurity, SetUserObjectSecurity, and SetPrivateObjectSecurity. When getting or setting the SD of an object, the program can limit its access to only certain parts of the security information: OWNER_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, and SACL_SECURITY_INFORMATION. It should be noted that only a process with Administrative access privileges can get the SACL_SECURITY_INFORMATION. If the process does not have those privileges, the API will fail when trying to retrieve the object SD.

Another way to get or set the security on an object is to use the SECURITY_ATTRIBUTES structure of that object. This structure contains a pointer to the SD associated with that object, and is returned when the object is created (for example, CreateFile, CreateNamedPipe, and so on). This structure can be manipulated directly by viewing or changing the SD pointer element of the structure. There are reasons for using both methods of manipulating object security.

The programming steps outlined below address some of the more common security tasks: giving everyone access to an object; adding a user or group to a new object; adding another user or group to an object with an existing SD; and checking the access permission of a Client program to an object on a Server. The outline shows the basic steps needed to perform these tasks, although there may also be other considerations. Most of these considerations deal with the size of the elements of the SD, which can be of variable length, and the type (absolute or self-relative) of the SD. These are explained under the subheading "Programming Considerations," below. Examples of coding these functions are shown in the "Sample Code" subheading below.

To give everyone access to an object that was just created (for example, a named pipe), the following actions are necessary:

  • Initialize the SD.
  • Set the DACL to the SD as a NULL DACL.
  • Set the SD to the object.

To give a user or a group specific access to an new object, the following actions are necessary:

  • Initialize the SD.
  • Initialize the DACL.
  • Obtain the user or group SID.
  • Add an ACE to the DACL, using the SID and the required access permission.
  • Set the DACL to the SD.
  • Set the SD to the object.

To add a user or group to an object with an existing SD, the following actions are necessary:

  • Get the SID of the new user or group.
  • Get the SD of the object.
  • Get the DACL of the SD.
  • Initialize a new SD to the size required to handle the new SID.
  • Initialize a new DACL to the size required to handle the new SID.
  • Copy each ACE from the original DACL to the new DACL.
  • Add an ACE to the new DACL using the new SID and the required access permission.
  • Set the new DACL to the new SD.
  • Set the new SD to the object.

To check the access permission of a Client program to an object on the Server (assuming the Client program has already connected to the Server), the following actions are necessary on the Server:

  • Get the SD of the object.
  • Impersonate the Client to change the access token of the current thread.
  • Get the access token of the current thread.
  • Check the access token against the SD of the object.
  • Revert the thread back to its original access token.

Programming Considerations

One of the considerations of security programming is determining the size of the security structures. The SDs can be of variable length, as can the DACLs, ACEs and the SIDs. Therefore, when obtaining the SD of an object, or creating a new SD, there must be enough memory in the buffer to handle the full SD. The same holds true of the other security structures. The minimum length of an SD is SECURITY_DESCRIPTOR_MIN_LENGTH, which means there are not any ACLs or SIDs associated with that SD. For every ACL that is to be added to the SD, the length of that ACL must be incorporated into the length of the SD. As the DACL requires more buffer space, so too will the SD.

The length of a DACL (or SACL) must be large enough to contain the ACL header and all of the ACEs that are to be stored in the DACL. Each ACE (whether an ACCESS_ALLOWED_ACE_TYPE or an ACCESS_DENIED_ACE_TYPE) has the following structure:

typedef struct_ACCESS_ALLOWED_ACE
{
      ACE_HEADER           Header;
      ACCESS_MASK        Mask;
      DWORD              SidStart;
   } ACCESS_ALLOWED_ACE;

This means that each ACE added to the DACL requires room for the ACE plus room for its SID, minus the size of the SidStart member (a DWORD). For example, the size of a DACL buffer large enough to contain three ACCESS_ALLOWED_ACE structures is:

cbDacl = sizeof (ACL) + 3 * ( sizeof ( ACCESS_ALLOWED_ACE) - sizeof ( DWORD) ) +
GetLengthSid ( pSid1) + GetLengthSid ( pSid2)+ GetLengthSid ( pSid3);

There are a number of APIs that return the length of a security structure, given that a pointer to the security structure has already been retrieved. These include GetSecurityDescriptorLength for the SD, GetAclInformation for the DACL, and GetLengthSid for the SID. Sometimes, there is a need to know the length of the security structure before retrieving it (for example, when the memory for the structures is being dynamically allocated throughout the program). Some of the objects have Query APIs, such as RegQueryInfoKey, that will return information about the object, including the length of the SD. Otherwise, APIs that retrieve the SD of an object have to be used (these APIs are listed above). The SD length parameter should be set at 0, the API called, and the return checked for ERROR_INSUFFICIENT_BUFFER. In this last case, the SD length parameter should be set to the length of the SD, so memory can be allocated for the SD, and the API called again to retrieve the SD.

Another programming consideration is whether the SD is in self-relative or absolute format. When retrieving an SD from the system using an API, the SD is always returned in self-relative format. When setting an SD using an API, or adding DACLs to an SD, it must be done in absolute format. When initializing a new SD, it is in absolute format.

This conversion is most needed when adding or deleting a user or group to the existing SD of an object. There are two APIs which convert between self-relative and absolute format (or vice-versa): MakeAbsoluteSD and MakeSelfRelativeSD. However, if another user or group is to be added to the SD, the SD may not have enough buffer space, and this must be taken into account when creating the absolute SD. One method to do this is to:

  1. Retrieve the SD of an object.
  2. Add the length of the new ACE (corresponds to the length of the SID of the user or group to be added) to the length of the SD and the length of the DACL.
  3. Create a new SD and a new DACL with this new length.
  4. Copy all of the ACEs from the DACL to the new DACL.
  5. Add the new ACE to the new DACL.
  6. Set the DACL to the new SD.
  7. Set the new SD to the object.

This is shown in the "Sample Code" section below.

Sample Code

Below are examples of sample code that show some methods to manipulate the security on an object. Also included is a Windows NT service installation program. This program does things such as changing the SD of a registry key, checking if the user who scheduled the process has Administrative privileges, creating local groups on the domain, and creating and setting registry keys and values. It provides some insight into some of the capabilities of NT programming. This code is for example only and is not supported.

The following code would give everyone access to an object that was just created (for example, a named pipe):

/*------------------------------------------------------------------
| Name: SetPipeSecurity
| Desc: sets up security to use on pipe to allow access to everyone;
|   upon return, can use saPipeSecurity to set security on pipe
|   when using CreateNamedPipe
------------------------------------------------------------------*/
VOID SetPipeSecurity ( VOID)
{
 HANDLE    hPipe;
 SECURITY_ATTRIBUTES saPipeSecurity;
 PSECURITY_DESCRIPTOR pPipeSD = NULL;

 // security inits
 memset ( ( VOID *) &saPipeSecurity, 0, sizeof ( SECURITY_ATTRIBUTES) );

 // alloc & init SD
 if ( ! ( pPipeSD = ( PSECURITY_DESCRIPTOR) 
     ( malloc ( SECURITY_DESCRIPTOR_MIN_LENGTH)) ) )
  return;

 if ( ! InitializeSecurityDescriptor ( pPipeSD, 
         SECURITY_DESCRIPTOR_REVISION) )
  return;

 // set NULL DACL on the SD
 if ( ! SetSecurityDescriptorDacl ( pPipeSD, TRUE, ( PACL) NULL, FALSE) )
  return;

 // now set up the security attributes
 saPipeSecurity.nLength    = sizeof ( SECURITY_ATTRIBUTES);
 saPipeSecurity.bInheritHandle  = TRUE; 
 saPipeSecurity.lpSecurityDescriptor = pPipeSD;

 // now create named pipe with security
 hPipe = CreateNamedPipe ( 
     PIPENAME,                        // name of pipe
     PIPE_ACCESS_DUPLEX |             // Open mode
     FILE_FLAG_OVERLAPPED,            // use overlapped structure
     PIPE_TYPE_MESSAGE  |             // message mode
     PIPE_READMODE_MESSAGE | 
     PIPE_WAIT,                       // blocking
     dwMaxNumberOfClients,            // Max. number of instances
     PIPEPKTSIZE,                     // Size of output buffer 
     PIPEPKTSIZE,                     // Size of input buffer 
     0L,                              // Time-out value (use default)
     &saPipeSecurity );               // security flag

}
/* eof - SetPipeSecurity */

The following code would give a user or a group specific access to a new object:

/*------------------------------------------------------------------
| Name: CreateSDForRegKey
| Desc: creates SD with access for up to MAX_LIST users or groups
|   passed into function:
|    SID from the group or user
|    permission access requested
------------------------------------------------------------------*/
DWORD CreateSDForRegKey ( LPTSTR pszGroupName[], DWORD dwAccessMask)
{
#define MAX_LIST 10

 PSID        pGroupSID [MAX_LIST];
 DWORD       dwGroupSIDCount = 0;
 DWORD       cbSID;

 SID_NAME_USE snuGroup;

 DWORD       dwDomainSize = 80;
 TCHAR       szDomainName[80];

 PSECURITY_DESCRIPTOR pAbsSD = NULL;

 PACL        pDACL;

 DWORD       dwSDRevision;
 DWORD       dwDACLLength = sizeof ( ACL);

 SECURITY_DESCRIPTOR_CONTROL sdcSDControl;

 PACL        pNewDACL  = NULL;

 BOOL        fAceFound = 0;

 BOOL        fHasDACL  = FALSE;
 BOOL        fDACLDefaulted = FALSE; 

 ACCESS_ALLOWED_ACE  *pDACLAce;

 DWORD       dwError = 0;

 DWORD       i;

 // handle for security registry key
 HKEY  hSecurityRegKey = ( HKEY) 0;


 // inits
 for ( i = 0; i < MAX_LIST; i++)
  pGroupSID[i] = NULL;

 // count number of groups or users to be added; list ends in NULL
 for ( i = 0; pszGroupName[i] != NULL; i++);

 if ( i > MAX_LIST)
  return ( ERROR_TOO_MANY_NAMES);
 else
  dwGroupSIDCount = i;

 // get SIDs for each group or user passed in
 for ( i = 0; i < dwGroupSIDCount; i++)
 {
  cbSID = GetSidLengthRequired (2);

  pGroupSID[i] = ( PSID) malloc ( cbSID);

  // loop if not enough room for SID; otherwise set to NULL
  while ( ! LookupAccountName ( NULL, pszGroupName[i], pGroupSID[i],
     &cbSID, szDomain, &dwDomainSize, &snuGroup) )
  {
   dwError = GetLastError();

   if ( dwError == ERROR_INSUFFICIENT_BUFFER)
    pGroupSID[i] = ( PSID) realloc ( pGroupSID[i], cbSID);
   else
   {
    pGroupSID[i] = NULL;

    break;
   }
  }
  // check if found group or user
  if ( pGroupSID[i])
  {
   // add to DACL length
   dwDACLLength += ( sizeof ( ACCESS_ALLOWED_ACE) - 
        sizeof ( DWORD) + GetLengthSid ( pGroupSID[i]);
  }
 }

 // get memory needed for new DACL
 if ( ! ( pNewDACL = ( PACL) malloc ( dwDACLLength) ) )
  return ( GetLastError());

 // get memory for new SD
 if ( ! ( pAbsSD = ( PSECURITY_DESCRIPTOR) 
      malloc ( SECURITY_DESCRIPTOR_MIN_LENGTH + dwDACLLength) ) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // init new SD
 if ( ! InitializeSecurityDescriptor ( pAbsSD, 
            SECURITY_DESCRIPTOR_REVISION) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // init new DACL
 if ( ! InitializeAcl ( pNewDACL, dwDACLLength, ACL_REVISION) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // now add new ACEs to new DACL
 for ( i = 0; i < dwGroupSIDCount; i++)
 {
  // if there is a valid SID, then attach to the ACE and add to the DACL
  if ( pGroupSID[i])
  {
   if ( ! AddAccessAllowedAce ( pNewDACL, ACL_REVISION, dwAccessMask,
               pGroupSID[i]) )
   {
    dwError = GetLastError();

    goto ErrorExit;
   }
  }
 }

 // check if everything went ok
 if ( ! IsValidAcl ( pNewDACL) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // now set DACL to the SD
 if ( ! SetSecurityDescriptorDacl ( pAbsSD, TRUE, pNewDACL, 
              fDACLDefaulted) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // check if everything went ok
 if ( ! IsValidSecurityDescriptor ( pAbsSD) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // now open reg key to set security 
 // note: pzsRegKeyName is a global
 if ( ( dwError = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, pszRegKeyName, 0,
          KEY_ALL_ACCESS, &hSecurityRegKey) ) )
  goto ErrorExit;


 // now set the reg key security (this will overwrite any existing security)
 dwError = RegSetKeySecurity ( 
      hSecurityRegKey, 
      (SECURITY_INFORMATION)( DACL_SECURITY_INFORMATION),
      pAbsSD);

 // close reg key
 RegCloseKey ( hSecurityRegKey);


ErrorExit:

 // free memory
 if ( pAbsSD)
  free ( ( VOID *) pAbsSD);
 if ( pNewDACL)
  free ( ( VOID *) pNewDACL);

 return ( dwError);
}
/* eof - CreateSDForRegKey */

The following code would add a user or group to an object with an existing SD:

/*------------------------------------------------------------------
| Name: AddToRegKeySD
| Desc: adds SID to SD on reg key
|   passed into function:
|    SD in self-relative mode
|    SID from the group or user
|    permission access requested
------------------------------------------------------------------*/
DWORD AddToRegKeySD ( PSECURITY_DESCRIPTOR pRelSD, PSID pGroupSID,
               DWORD dwAccessMask)
{
 PSECURITY_DESCRIPTOR pAbsSD = NULL;

 PACL  pDACL;

 DWORD  dwSDLength = 0;
 DWORD  dwSDRevision;
 DWORD  dwDACLLength = 0;

 SECURITY_DESCRIPTOR_CONTROL sdcSDControl;

 PACL  pNewDACL  = NULL;
 DWORD  dwAddDACLLength = 0;

 BOOL  fAceFound = 0;

 BOOL  fHasDACL  = FALSE;
 BOOL  fDACLDefaulted = FALSE; 

 ACCESS_ALLOWED_ACE  *pDACLAce;

 DWORD  dwError = 0;

 DWORD  i;

 // handle for security registry key
 HKEY  hSecurityRegKey = ( HKEY) 0;

 // get SD control bits
 if ( ! GetSecurityDescriptorControl ( pRelSD, 
        ( PSECURITY_DESCRIPTOR_CONTROL) &sdcSDControl,
             ( LPDWORD) &dwSDRevision) )
  return ( GetLastError() );

 // check if DACL is present
 if ( SE_DACL_PRESENT & sdcSDControl)
 {
  // get dacl 
  if ( ! GetSecurityDescriptorDacl ( pRelSD, ( LPBOOL) &fHasDACL,
           ( PACL *) &pDACL, 
           ( LPBOOL) &fDACLDefaulted) )
   return ( GetLastError());

  // get dacl length
  dwDACLLength = pDACL->AclSize;

  // now check if SID's ACE is there
  for ( i = 0; i < pDACL->AceCount; i++)
  {
   if ( ! GetAce ( pDACL, i, ( LPVOID *) &pDACLAce) )
    return ( GetLastError());

   // check if group sid is already there
   if ( EqualSid ( ( PSID) &(pDACLAce->SidStart), pGroupSID) )
    break;
  }
  // exit if found (means already has been set)
  if ( i < pDACL->AceCount)
  {
   dwError = ERROR_GROUP_EXISTS;

   return ( dwError);
  }

  // get length of new DACL
  dwAddDACLLength = sizeof ( ACCESS_ALLOWED_ACE) - 
        sizeof ( DWORD) + GetLengthSid ( pGroupSID);
 }
 else
  // get length of new DACL
  dwAddDACLLength = sizeof ( ACL) + sizeof ( ACCESS_ALLOWED_ACE) - 
        sizeof ( DWORD) + GetLengthSid ( pGroupSID);

 // get memory needed for new DACL
 if ( ! ( pNewDACL = ( PACL) malloc ( dwDACLLength + dwAddDACLLength) ) )
  return ( GetLastError());

 // get the sd length
 dwSDLength = GetSecurityDescriptorLength ( pRelSD);

 // get memory for new SD
 if ( ! ( pAbsSD = ( PSECURITY_DESCRIPTOR) 
       malloc ( dwSDLength + dwAddDACLLength) ) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // change self-relative SD to absolute by making new SD
 if ( ! InitializeSecurityDescriptor ( pAbsSD, 
            SECURITY_DESCRIPTOR_REVISION) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // init new DACL
 if ( ! InitializeAcl ( pNewDACL, dwDACLLength + dwAddDACLLength, 
               ACL_REVISION) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // now add in all of the ACEs into the new DACL (if org DACL is there)
 if ( SE_DACL_PRESENT & sdcSDControl)
 { 
  for ( i = 0; i < pDACL->AceCount; i++)
  {
   // get ace from original dacl
   if ( ! GetAce ( pDACL, i, ( LPVOID *) &pDACLAce) )
   {
    dwError = GetLastError();

    goto ErrorExit;
   }

   // now add ace to new dacl
   if ( ! AddAccessAllowedAce ( pNewDACL, 
           ACL_REVISION, 
           pDACLAce->Mask,
           ( PSID) &(pDACLAce->SidStart) ) )
   {
    dwError = GetLastError();

    goto ErrorExit;
   }
  }
 }

 // now add new ACE to new DACL
 if ( ! AddAccessAllowedAce ( pNewDACL, ACL_REVISION, dwAccessMask,
               pGroupSID) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // check if everything went ok
 if ( ! IsValidAcl ( pNewDACL) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // now set security descriptor DACL
 if ( ! SetSecurityDescriptorDacl ( pAbsSD, TRUE, pNewDACL, 
              fDACLDefaulted) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // check if everything went ok
 if ( ! IsValidSecurityDescriptor ( pAbsSD) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }
 // now open reg key to set security 
 // note: pzsRegKeyName is a global
 if ( ( dwError = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, pszRegKeyName, 0,
          KEY_ALL_ACCESS, &hSecurityRegKey) ) )
  goto ErrorExit;


 // now set the reg key security (this will overwrite any existing security)
 dwError = RegSetKeySecurity ( 
      hSecurityRegKey, 
      (SECURITY_INFORMATION)( DACL_SECURITY_INFORMATION),
      pAbsSD);

 // close reg key
 RegCloseKey ( hSecurityRegKey);


ErrorExit:

 // free memory
 if ( pAbsSD)
  free ( ( VOID *) pAbsSD);
 if ( pNewDACL)
  free ( ( VOID *) pNewDACL);

 return ( dwError);
}
/* eof - AddToRegKeySD */

The following code would obtain the SD from an existing object (i.e.- a registry key):

/*----------------------------------------------------------------
| Name: GetRegKeySecurity
| Desc: gets security registry sec. descriptor
|   pRegKeySD is global of type PSECURITY_DESCRIPTOR; you must free
|   the memory alloc'ed for this when done with the reg key
-----------------------------------------------------------------*/
DWORD GetRegKeySecurity ( szRegKey)
{
#define PERR(szApi,lError) printf ( "\n%s: Error %d from %s on line %d", \
 __FILE__, lError, szApi, __LINE__);


 HKEY  hRegKey;   // handle for register key
 LONG  lError = 0L;  // reg errors 
        // (GetLastError won't work with registry calls)


 CHAR  szClassName[MAX_PATH] = ""; // Buffer for class name.
 DWORD dwcClassLen = MAX_PATH;  // Length of class string.
 DWORD dwcSubKeys;     // Number of sub keys.
 DWORD dwcMaxSubKey;    // Longest sub key size.
 DWORD dwcMaxClass;    // Longest class string.
 DWORD dwcValues;     // Number of values for this key.
 DWORD dwcMaxValueName;   // Longest Value name.
 DWORD dwcMaxValueData;   // Longest Value data.
 DWORD dwcSDLength;    // Security descriptor length
 FILETIME ftLastWriteTime;   // Last write time.


 // open the security key
 if ( ( lError = RegOpenKey ( HKEY_LOCAL_MACHINE, szRegKey, &hRegKey) ) )
 {
  PERR ( "RegOpenKey", lError);

  return ( lError);
 }



 // get length of security descriptor
 if ( ( lError = RegQueryInfoKey ( hRegKey, szClassName, &dwcClassLen, 
      NULL, &dwcSubKeys, &dwcMaxSubKey, &dwcMaxClass, 
      &dwcValues, &dwcMaxValueName, &dwcMaxValueData, 
           &dwcSDLength, &ftLastWriteTime) ) )
 {
  PERR ( "RegQueryInfoKey", lError);

  goto CleanUp;
 }

/* could have used this to get length of SD:

 lError = RegGetKeySecurity ( hRegKey, 
      (SECURITY_INFORMATION)( OWNER_SECURITY_INFORMATION
            | GROUP_SECURITY_INFORMATION
            | DACL_SECURITY_INFORMATION),
             pRegKeySD, &dwcSDLength);

 // check if not enough memory returned
 if ( lError != ERROR_INSUFFICIENT_BUFFER)
 {
  PERR ( "RegGetKeySecurity", lError);

  goto CleanUp;
 }

*/

 
 // get SD memory
 pRegKeySD = ( PSECURITY_DESCRIPTOR) LocalAlloc ( LPTR, ( UINT)dwcSDLength);

 // now get SD
 if ( ( lError = RegGetKeySecurity ( hRegKey, 
      (SECURITY_INFORMATION)( OWNER_SECURITY_INFORMATION
            | GROUP_SECURITY_INFORMATION
            | DACL_SECURITY_INFORMATION),
             pRegKeySD, &dwcSDLength) ) )
 {
  PERR ( "RegGetKeySecurity", lError);

  goto CleanUp;
 }

 // check if SD is good
 if ( ! IsValidSecurityDescriptor ( pRegKeySD))
 {
  lError = GetLastError();

  PERR ( "IsValidSecurityDescriptor", lError);
 }

CleanUp:

 // must close reg key
 RegCloseKey ( hRegKey);

 return ( lError);
}
/* eof - GetRegKeySecurity */

The following code would check the access permission of a Client program to an object on the Server (assuming the Client program has already connected to the Server):

/*------------------------------------------------------------------
| Name: GetUserNameAccess
| Desc: gets the client user name and checks their access rights
------------------------------------------------------------------*/
DWORD GetUserNameAccess ( HPIPE hPipe)
{
#define READ_ACCESS  KEY_QUERY_VALUE
#define WRITE_ACCESS  KEY_SET_VALUE
#define READWRITE_ACCESS  KEY_QUERY_VALUE|KEY_SET_VALUE


 CHAR  szClientName[100];

 DWORD  dwCurInstances;

 HANDLE  hThread  = NULL;
 HANDLE  hThreadToken = NULL;

 DWORD  dwDesiredAccess;
 DWORD  dwGrantedAccess;

 SHORT  sErrorFlag = 1;

 GENERIC_MAPPING gmMapping;
 PRIVILEGE_SET psPrivilege;
 DWORD   dwPrivilegeLength;
 BOOL   fStatus;
 BOOL   fReturn;

 // inits 
 memset ( szClientName, 0, sizeof ( szClientName) );

 dwClientAccessType = 0;

 // get client name from handle info 
 if ( ! ( GetNamedPipeHandleState ( hPipe, NULL, &dwCurInstances, NULL, 
     NULL, ( LPTSTR) szClientName, sizeof ( szClientName) ) ) )
  goto CleanUp;

 // change thread's security with impersonation
 if ( ! ImpersonateNamedPipeClient ( hPipe) )
  goto CleanUp;

 // now get token for thread to use with access check
 hThread = GetCurrentThread ();

 if ( ! OpenThreadToken ( hThread, TOKEN_ALL_ACCESS, FALSE, &hThreadToken))
  goto CleanUp;

 // if made it here, then no api errors, just access denied return
 sErrorFlag = 0;

 // check for read/write access
 dwDesiredAccess = READWRITE_ACCESS;
 dwPrivilegeLength = sizeof ( psPrivilege);

 fReturn = AccessCheck ( pRegKeySD, hThreadToken, dwDesiredAccess, 
       &gmMapping, &psPrivilege, &dwPrivilegeLength,
            &dwGrantedAccess, &fStatus);

 // if access allowed, set access
 if ( fReturn && fStatus)
 {
  dwClientAccessType = READWRITE_ACCESS;

  goto CleanUp;
 }

 // check for read only access
 dwDesiredAccess = READ_ACCESS;
 dwPrivilegeLength = sizeof ( psPrivilege);

 fReturn = AccessCheck ( pRegKeySD, hThreadToken, dwDesiredAccess, 
       &gmMapping, &psPrivilege, &dwPrivilegeLength,
            &dwGrantedAccess, &fStatus);

 // if access allowed, set access
 if ( fReturn && fStatus)
 {
  dwClientAccessType = READ_ACCESS;

  goto CleanUp;
 }

 // check for write only access
 dwDesiredAccess = WRITE_ACCESS;
 dwPrivilegeLength = sizeof ( psPrivilege);

 fReturn = AccessCheck ( pRegKeySD, hThreadToken, dwDesiredAccess, 
       &gmMapping, &psPrivilege, &dwPrivilegeLength,
            &dwGrantedAccess, &fStatus);

 // if access allowed, set access
 if ( fReturn && fStatus)
 {
  dwClientAccessType = WRITE_ACCESS;

  goto CleanUp;
 }

CleanUp:

 // put security back to normal
 RevertToSelf ();

 // close up handles
 if ( hThreadToken)
  CloseHandle ( hThreadToken);

 if ( hThread)
  CloseHandle ( hThread);

 // check for errors
 if ( sErrorFlag)
  return ( GetLastError() );

 // check if access flag was set
 if ( dwClientAccessType)
  return ( 0);
 else
  return ( ERROR_ACCESS_DENIED);
}
/* eof - GetUserNameAccess */

The following is sample code that installs a service. This code provides many examples detailing different aspects of Win32 programming. Besides installing the service, it also creates registry keys, sets registry parameter values, changes the SD on registry keys, creates local groups on the domain, and checks the user for Administrative privileges.

/* ---------------------------------------------------------------------
| file:  siaminst.c
| author:  Chris Nefcy
| date:  7/20/93
| desc:  installs SIAM service for windows NT
| args:  
| return:  
| compiler: dos32x
| links:  see siaminst.mak
| rev:  
----------------------------------------------------------------------- */
#include <windows.h>
#include <lm.h>

#include <stdio.h>

// defaults (change when necessary)
// VERSION will change; client s/w needs to match this
#define VERSION  "1.00.00"
#define TIMEOUT  180

#define DOMAINNAME  "MS-INTERNET"

#define SERVICENAME "SiamServer"
#define SERVICEPROGRAM "siamsrvc.exe"
#define INSTALLCONFIG "siaminst.cfg"

#define MAXCLIENTS  100

// registry keys
#define PARAMETERSKEY "SYSTEM\\CurrentControlSet\\Services\\SiamServer\\Parameters"
#define SECURITYKEY "SYSTEM\\CurrentControlSet\\Services\\SiamServer\\Security"
#define TCPIPKEY  "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"

// registry values
#define VERSIONVALUE "Version"
#define TIMEOUTVALUE "Timeout"
#define MAXCLIENTSVALUE "MaxNumberOfClients"
#define TCPMAXCONNVALUE "TcpMaxConnectAttempts"

// used to limit the connect time to 20-25 seconds
#define TCPIP_MAXCONN 3

// client access types
#define READ_ACCESS  KEY_QUERY_VALUE
#define WRITE_ACCESS  KEY_SET_VALUE
#define READWRITE_ACCESS KEY_QUERY_VALUE|KEY_SET_VALUE

// handles
SC_HANDLE  schService;
SC_HANDLE  schSCManager;

// groups
struct GROUPDESCRIPT
{
 CHAR *pszGroupName;
 LPWSTR lpwszLocalGroupComment;
 DWORD dwAccessMask;
} GroupDescript[] = 

 { 
  { "SIAMFULL", L"SIAM Users with Full Access to Internet", 
   READWRITE_ACCESS},
  { "SIAMREAD", L"SIAM Users with Read Only Access to Internet", 
   READ_ACCESS},
  { "SIAMWRITE", L"SIAM Users with Write Only Access to Internet", 
   WRITE_ACCESS},

  { NULL, NULL, 0}
 };


// security descriptors for security registry key
PSECURITY_DESCRIPTOR pSecurityRegKeySD = NULL;

// handle for security registry key
HKEY  hSecurityRegKey = ( HKEY) 0;

// memory allocs/frees
LONG lAllocCnt = 0L;
LONG lFreeCnt = 0L;


// routines
extern DWORD AddGroupsToDomain  ( VOID);
extern DWORD AddToSecurityDACL  ( PSECURITY_DESCRIPTOR, PSID, DWORD);
extern DWORD CheckDomainName  ( CHAR *, DWORD);
extern BOOL CheckForTcpip   ( VOID);
extern VOID CloseRegKeySecurity ( VOID);
extern VOID DoExitFunction   ( DWORD);
extern VOID ErrorMessage   ( DWORD, CHAR *, ...);
extern VOID FreeMemory    ( VOID * );
extern VOID *_GetMemory   ( UINT, CHAR *, UINT);
extern DWORD GetRegKeySecurity  ( CHAR *);
extern BOOL RunningAsAdministrator ( VOID);
extern DWORD SetParameters   ( VOID);

#define GetMemory(size) _GetMemory(size, __FILE__, __LINE__)


// error messages
#define MSGERRDACLACCESS "ERR: INS1000-Could not add ACE to DACL"
#define MSGERRDACLACE  "ERR: INS1001-Could not get DACL ACE"
#define MSGERRDACLINFOGET "ERR: INS1002-Could not get DACL information"
#define MSGERRDACLINFOSET "ERR: INS1003-Could not set DACL information"
#define MSGERRDACLINIT  "ERR: INS1004-Could not initialize new DACL"
#define MSGERRDACLSECUREGET "ERR: INS1005-Could not get Security Descriptor
                             for DACL"
#define MSGERRDACLSECURESET "ERR: INS1006-Could not set Security Descriptor
                             with DACL"
#define MSGERRDACLVALID  "ERR: INS1007-DACL is not valid"

#define MSGERRGROUPADD  "ERR: INS2000-Group not added to Domain: %s"
#define MSGERRGROUPCONVERT "ERR: INS2001-Could not change Group Name 
                            to UNICODE: %s"
#define MSGERRGROUPDOMAIN "ERR: INS2002-Some of the Groups have not been
                           added to the Domain correctly"
#define MSGERRGROUPNAME  "ERR: INS2003-Group Name not found: %s"
#define MSGERRGROUPSECURE "ERR: INS2004-Group not given permission
                           to use SiamServer: %s"

#define MSGERRREGCREATE  "ERR: INS3000-Could not create Reg key: %s"
#define MSGERRREGINFO  "ERR: INS3001-Could not get Reg key info: %s"
#define MSGERRREGINVALID "ERR: INS3002-Invalid Reg key security desc.: %s"
#define MSGERRREGOPEN  "ERR: INS3003-Could not open Reg key: %s"
#define MSGERRREGSECUREGET "ERR: INS3004-Could not get Reg key security: %s"
#define MSGERRREGSECURESET "ERR: INS3005-Could not set Reg key security"
#define MSGERRREGVALUEGET "ERR: INS3006-Could not get Reg value: %s"
#define MSGERRREGVALUESET "ERR: INS3007-Could not set Reg value: %s"

#define MSGERRSDCONTROLGET "ERR: INS4000-Could not get Security Descriptor
                            Control"
#define MSGERRSDINIT  "ERR: INS4001-Could not initialize Security Descriptor"
#define MSGERRSDVALID  "ERR: INS4002-Security Descriptor is not valid"

#define MSGERRUSEADMIN  "ERR: INS5000-Must have Administrative privileges
                         to run this program"
#define MSGERRUSEALLOC  "ERR: INS5001-Malloc failed (%u; %s; %u)"
#define MSGERRUSEDOMAIN  "ERR: INS5002-This can only be installed on
                          Domain: %s (not: %s)"

#define MSGERRSRVCCREATE "ERR: INS6000-Could not create Service: %s
                         (with program: %s)"
#define MSGERRSRVCSCMGR  "ERR: INS6001-Could not open the Service Control
                          Manager"
#define MSGERRSRVCSTART  "ERR: INS6002-Could not start Service: %s"
#define MSGERRSRVCTCPIP  "ERR: INS6003-TCP/IP needs to be installed 
                          on this system"

#define MSGERRDOMAINCHK  "ERR: INS7000-Could not access Domain"
#define MSGERRDOMAINCTRL "ERR: INS7001-Could not access Domain Controller"

#define MSGERRFILEOPEN  "ERR: INS8000-Could not open file: %s"
#define MSGERRFILEREAD  "ERR: INS8001-Could not read file: %s"

/*------------------------------------------------------------------
| Name: main
| Desc: main program
------------------------------------------------------------------*/
VOID main ( INT argc, CHAR **argv)
{
 // change here if want manual start to SERVICE_DEMAND_START
 DWORD  dwStartType = SERVICE_AUTO_START;

 CHAR  szExePath[MAX_PATH];

 DWORD  dwError = 0;

 // see if user is an administrator
 if ( ! RunningAsAdministrator () )
 {
  ErrorMessage ( ERROR_ACCESS_DENIED, MSGERRUSEADMIN);

  ExitProcess ( ERROR_ACCESS_DENIED);
 }

 // see if tcpip is installed on system already
 if ( ! CheckForTcpip ())
 {
  ErrorMessage ( ERROR_BAD_ENVIRONMENT, MSGERRSRVCTCPIP);

  ExitProcess ( ERROR_BAD_ENVIRONMENT);
 }

 // get full service program name
 GetCurrentDirectory ( sizeof ( szExePath), szExePath);

 strcat ( szExePath, "\\");
 strcat ( szExePath, SERVICEPROGRAM);

 // print notifications
 printf ( "\n");
 printf ( "Installation of the service:\n");
 printf ( "----------------------------\n");

 printf ( "Service name:     %s\n", SERVICENAME);
 printf ( "Service executable path:  %s\n", szExePath);
 printf ( "Service startup type:   %s\n",
    dwStartType == SERVICE_AUTO_START ? "AutoStart" : "ManualStart");

 // open service control manager
 if ( !( schSCManager = OpenSCManager ( NULL, NULL, SC_MANAGER_ALL_ACCESS)))
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRSRVCSCMGR);

  ExitProcess ( dwError);
 }

 // create the service
 if ( !( schService = CreateService ( schSCManager, SERVICENAME, 
          SERVICENAME, SERVICE_ALL_ACCESS, 
          SERVICE_WIN32_OWN_PROCESS, dwStartType,
          SERVICE_ERROR_NORMAL, szExePath, 
          NULL, NULL, NULL, NULL, NULL) ) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRSRVCCREATE, SERVICENAME, szExePath);

  ExitProcess ( dwError);
 }

 SetParameters ();

 printf ( "Starting service:    %s\n\n", SERVICENAME);

 if ( ! StartService ( schService, 0, NULL) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRSRVCSTART, SERVICENAME);

  DoExitFunction ( dwError);
 }

 printf ( "SERVICE STARTED SUCCESSFULLY\n\n");

 // now add groups and set security
 if ( ( dwError = AddGroupsToDomain() ) )
  ErrorMessage ( dwError, MSGERRGROUPDOMAIN);

 DoExitFunction ( dwError);
}
/* eof - main */

/*----------------------------------------------------------------
| Name: AddGroupsToDomain
| Desc: adds all of the groups to domain and sets reg key security
----------------------------------------------------------------*/
DWORD AddGroupsToDomain ( VOID)
{
 LOCALGROUP_INFO_1  LocalGroupInfo1;

 LPWSTR    lpwszDomainController;

 WCHAR     wszGroupName[MAX_PATH];

 DWORD     dwGroupSIDLength = 0;
 DWORD     dwGroupSIDMaxLength = 0;

 PSID     pGroupSID = NULL;

 SID_NAME_USE   snuGroupSID;

 CHAR     szDomainName[80];
 DWORD     dwDomainNameSize = sizeof ( szDomainName);

 DWORD     dwError = 0;

 DWORD     i;

 printf ( "\n");
 printf ( "Adding Groups to the Domain:\n");
 printf ( "----------------------------\n");

 if ( ( dwError = CheckDomainName ( szDomainName, dwDomainNameSize) ) )
 {
  ErrorMessage ( dwError, MSGERRDOMAINCHK);

  return ( dwError);
 }

 printf ( "Domain Name:  %s\n", szDomainName);

 // get domain controller server name
 if ( ( dwError = NetGetDCName ( NULL, NULL, 
         ( LPBYTE *) &lpwszDomainController) ) )
 {
  ErrorMessage ( dwError, MSGERRDOMAINCTRL);

  return ( dwError);
 }

 printf ( "Domain Controller: %ws\n\n", lpwszDomainController);


 // now add groups to domain
 for ( i = 0; GroupDescript[i].pszGroupName; i++)
 {
  // change args to long string
  if ( ! MultiByteToWideChar ( CP_ACP, MB_PRECOMPOSED, 
          GroupDescript[i].pszGroupName, -1, 
          wszGroupName, sizeof ( wszGroupName) ) )
  {
   dwError = GetLastError();

   ErrorMessage ( dwError, MSGERRGROUPCONVERT, 
           GroupDescript[i].pszGroupName);

   return ( dwError);
  }

  // set name & comment
  LocalGroupInfo1.lgrpi1_name = wszGroupName;
  LocalGroupInfo1.lgrpi1_comment = GroupDescript[i].lpwszLocalGroupComment;

  dwError = NetLocalGroupAdd ( lpwszDomainController, 1, 
           ( LPBYTE) &LocalGroupInfo1, NULL);

  if ( dwError)
  {
   // check if group already there
   if ( dwError == ERROR_ALIAS_EXISTS)
   {
    printf ( "Group already in %s Domain: %s\n", szDomainName,
          GroupDescript[i].pszGroupName);

    dwError = 0;
   }
   else
   {
    ErrorMessage ( dwError, MSGERRGROUPADD, szDomainName,
          GroupDescript[i].pszGroupName);

    return ( dwError);
   }
  }
  else
  {
   printf ( "Group added to %s Domain: %s\n", szDomainName,
          GroupDescript[i].pszGroupName);
  }
 }

 NetApiBufferFree ( lpwszDomainController);

 // now need to wait until domain recognizes the group names
 printf ( "\n\nWaiting for the Groups to be recognized by the %s Domain\n",
                szDomainName);
 printf ( "This may take a few minutes ");

 // for each group, wait until recognized by domain
 for ( i = 0; GroupDescript[i].pszGroupName; i++)
 {
  dwGroupSIDLength = 0;
  dwDomainNameSize = sizeof ( szDomainName);

  // loop until domain recognizes name
  for ( ; ; )
  {
   // should always have an error (sid not big enough)
   if ( ! LookupAccountName ( NULL, GroupDescript[i].pszGroupName, 
        pGroupSID, &dwGroupSIDLength, szDomainName, 
        &dwDomainNameSize, &snuGroupSID) )
   {
    dwError = GetLastError();

    printf ( ".");

    if ( dwError == ERROR_MORE_DATA || 
      dwError == ERROR_INSUFFICIENT_BUFFER)
     break;
   }
  } 

  // check if biggest sid length
  if ( dwGroupSIDLength > dwGroupSIDMaxLength)
   dwGroupSIDMaxLength = dwGroupSIDLength;
 }

 printf ( "\nGroups are now recognized by the %s Domain\n", szDomainName);

 // now add groups to the reg key security
 printf ( "\n");
 printf ( "Setting Groups' permissions on the Registry Security Key:\n");
 printf ( "---------------------------------------------------------\n");
 printf ( "Registry key: HKEY_LOCAL_MACHINE\\%s\n\n", SECURITYKEY);
 // get sid memory
 if ( ! ( pGroupSID = ( PSID) GetMemory ( dwGroupSIDMaxLength) ) )
 {
  dwError = GetLastError();

  return ( dwError);
 }

 // for each group, wait until recognized by domain
 for ( i = 0; GroupDescript[i].pszGroupName; i++)
 {
  // get security descriptor from reg key
  if ( ( dwError = GetRegKeySecurity ( SECURITYKEY) ) )
   return ( dwError);

  dwGroupSIDLength = dwGroupSIDMaxLength;
  dwDomainNameSize = sizeof ( szDomainName);

  // get account info
  if ( ! LookupAccountName ( NULL, GroupDescript[i].pszGroupName, 
       pGroupSID, &dwGroupSIDLength, szDomainName, 
       &dwDomainNameSize, &snuGroupSID) )
  {
   dwError = GetLastError();

   ErrorMessage ( dwError, MSGERRGROUPNAME, 
           GroupDescript[i].pszGroupName);

   goto ErrorExit;
  }
 

  // now add this group to dacl
  if ( ( dwError = AddToSecurityDACL ( pSecurityRegKeySD, pGroupSID,
           GroupDescript[i].dwAccessMask) ) )
  {
   if ( dwError == ERROR_GROUP_EXISTS)
    printf ( "Group already has permission to use SiamServer: %s\n",
           GroupDescript[i].pszGroupName);
   else
   {
    ErrorMessage ( dwError, MSGERRGROUPSECURE, 
           GroupDescript[i].pszGroupName);

    goto ErrorExit;
   }
  }

  CloseRegKeySecurity();

  printf ( "Group given permission to use SiamServer:  %s\n", 
           GroupDescript[i].pszGroupName);
 }

 printf ( "\n");

ErrorExit:

 FreeMemory ( ( VOID *) pGroupSID);

 CloseRegKeySecurity();

 return ( dwError);
}
/* eof - AddGroupsToDomain */
/*-----------------------------------------------------------------
| Name: AddToSecurityDACL
| Desc: adds SID to SD
-----------------------------------------------------------------*/
DWORD AddToSecurityDACL ( PSECURITY_DESCRIPTOR pRelSD, PSID pGroupSID,
               DWORD dwAccessMask)
{
 PSECURITY_DESCRIPTOR pAbsSD = NULL;

 PACL  pDACL;

 DWORD  dwSDLength = 0;
 DWORD  dwSDRevision;
 DWORD  dwDACLLength = 0;

 SECURITY_DESCRIPTOR_CONTROL sdcSDControl;

 PACL  pNewDACL  = NULL;
 DWORD  dwAddDACLLength = 0;

 BOOL  fAceFound = 0;

 BOOL  fHasDACL  = FALSE;
 BOOL  fDACLDefaulted = FALSE; 

 ACCESS_ALLOWED_ACE  *pDACLAce;

 DWORD  dwError = 0;

 DWORD  i;


 // get sd control bits
 if ( ! GetSecurityDescriptorControl ( pRelSD, 
        ( PSECURITY_DESCRIPTOR_CONTROL) &sdcSDControl,
             ( LPDWORD) &dwSDRevision) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRSDCONTROLGET);

  return ( dwError);
 }

 // check if dacl is present
 if ( SE_DACL_PRESENT & sdcSDControl)
 {
  // get dacl 
  if ( ! GetSecurityDescriptorDacl ( pRelSD, ( LPBOOL) &fHasDACL,
           ( PACL *) &pDACL, 
           ( LPBOOL) &fDACLDefaulted) )
  {
   dwError = GetLastError();

   ErrorMessage ( dwError, MSGERRDACLSECUREGET);

   return ( dwError);
  }

  // get dacl length
  dwDACLLength = pDACL->AclSize;

  // now check if SID ace is there
  for ( i = 0; i < pDACL->AceCount; i++)
  {
   if ( ! GetAce ( pDACL, i, ( LPVOID *) &pDACLAce) )
   {
    dwError = GetLastError();

    ErrorMessage ( dwError, MSGERRDACLACE);

    return ( dwError);
   }

   // check if group sid is already there
   if ( EqualSid ( ( PSID) &(pDACLAce->SidStart), pGroupSID) )
    break;
  }

  // exit if found (means already has been set)
  if ( i < pDACL->AceCount)
  {
   dwError = ERROR_GROUP_EXISTS;

   return ( dwError);
  }

  // get length of new DACL
  dwAddDACLLength = sizeof ( ACCESS_ALLOWED_ACE) - 
        sizeof ( DWORD) + GetLengthSid ( pGroupSID);
 }
 else
  // get length of new DACL
  dwAddDACLLength = sizeof ( ACL) + sizeof ( ACCESS_ALLOWED_ACE) - 
        sizeof ( DWORD) + GetLengthSid ( pGroupSID);

 // get memory needed for new DACL
 if ( ! ( pNewDACL = ( PACL) GetMemory ( dwDACLLength + dwAddDACLLength) ) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // get the sd length
 dwSDLength = GetSecurityDescriptorLength ( pRelSD);

 // get memory for new sd
 if ( ! ( pAbsSD = ( PSECURITY_DESCRIPTOR) 
       GetMemory ( dwSDLength + dwAddDACLLength) ) )
 {
  dwError = GetLastError();

  goto ErrorExit;
 }

 // change self-relative SD to absolute
 if ( ! InitializeSecurityDescriptor ( pAbsSD, 
            SECURITY_DESCRIPTOR_REVISION) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRSDINIT);

  goto ErrorExit;
 }

 // init new dacl
 if ( ! InitializeAcl ( pNewDACL, dwDACLLength + dwAddDACLLength, 
               ACL_REVISION) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRDACLINIT);

  goto ErrorExit;
 }
 // now add in all of the aces into the new dacl (if org dacl is there)
 if ( SE_DACL_PRESENT & sdcSDControl)
 { 
  for ( i = 0; i < pDACL->AceCount; i++)
  {
   // get ace from original dacl
   if ( ! GetAce ( pDACL, i, ( LPVOID *) &pDACLAce) )
   {
    dwError = GetLastError();

    ErrorMessage ( dwError, MSGERRDACLACE);

    goto ErrorExit;
   }

   // now add ace to new dacl
   if ( ! AddAccessAllowedAce ( pNewDACL, 
           ACL_REVISION, 
           pDACLAce->Mask,
           ( PSID) &(pDACLAce->SidStart) ) )
   {
    dwError = GetLastError();

    ErrorMessage ( dwError, MSGERRDACLACCESS);

    goto ErrorExit;
   }
  }
 }

 // now add new ace to new dacl
 if ( ! AddAccessAllowedAce ( pNewDACL, ACL_REVISION, dwAccessMask,
               pGroupSID) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRDACLACCESS);

  goto ErrorExit;
 }

 // check if everything went ok
 if ( ! IsValidAcl ( pNewDACL) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRDACLVALID);

  goto ErrorExit;
 }

 // now set security descriptor dacl
 if ( ! SetSecurityDescriptorDacl ( pAbsSD, TRUE, pNewDACL, 
              fDACLDefaulted) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRDACLSECURESET);

  goto ErrorExit;
 }

 // check if everything went ok
 if ( ! IsValidSecurityDescriptor ( pAbsSD) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRSDVALID);

  goto ErrorExit;
 }

 // now set the reg key security
 if ( ( dwError = RegSetKeySecurity ( hSecurityRegKey, 
      (SECURITY_INFORMATION)( DACL_SECURITY_INFORMATION),
                pAbsSD) ) )
  ErrorMessage ( dwError, MSGERRREGSECURESET);

ErrorExit:

 // free memory
 FreeMemory ( ( VOID *) pAbsSD);
 FreeMemory ( ( VOID *) pNewDACL);

 return ( dwError);
}
/* eof - AddToSecurityDACL */
/*-----------------------------------------------------------------
| Name: CheckDomainName
| Desc: gets the domain name & checks it is the right one
-----------------------------------------------------------------*/
DWORD CheckDomainName ( CHAR *pszDomainName, DWORD dwDomainNameSize)
{
// just guessing at using this well known name; 
// might be a better way to find the domain name

#define CHECKUSERNAME "Domain Users"

 DWORD     dwGroupSIDLength = 0;

 PSID     psidGroupSID = NULL;

 SID_NAME_USE   snuGroupSID;

 HANDLE    hConfig;

 CHAR     szReadBuffer[MAX_PATH];
 DWORD     dwBytesRead;

 DWORD     dwError = 0;

 DWORD     i;

 // inits
 memset ( pszDomainName, 0, dwDomainNameSize);

 // get the domain name
 if ( ! LookupAccountName ( NULL, CHECKUSERNAME, psidGroupSID, 
      &dwGroupSIDLength, pszDomainName, 
      &dwDomainNameSize, &snuGroupSID))
 {
  dwError = GetLastError();

  if ( dwError == ERROR_MORE_DATA || 
    dwError == ERROR_INSUFFICIENT_BUFFER)
  {
   dwError = 0;

   // get sid memory
   if ( ! ( psidGroupSID = ( PSID) GetMemory ( dwGroupSIDLength) ) )
   {
    dwError = GetLastError();

    return ( dwError);
   }

   // now get name
   if ( ! LookupAccountName ( NULL, CHECKUSERNAME, psidGroupSID, 
          &dwGroupSIDLength, pszDomainName, 
          &dwDomainNameSize, &snuGroupSID) )
    dwError = GetLastError();

   FreeMemory ( ( VOID *) psidGroupSID);
  }

  if ( dwError)
  {
   ErrorMessage ( dwError, MSGERRGROUPNAME, CHECKUSERNAME);

   return ( dwError);
  }
 }

 // now get the domain from the config file
 if ( ( hConfig = CreateFile ( INSTALLCONFIG, GENERIC_READ, FILE_SHARE_READ,
         NULL, OPEN_EXISTING, 0, 0) )
              == INVALID_HANDLE_VALUE)
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRFILEOPEN, INSTALLCONFIG);

  return ( dwError);
 }

 if ( ! ReadFile ( hConfig, ( LPVOID) szReadBuffer, sizeof ( szReadBuffer),
        &dwBytesRead, NULL) )
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRFILEREAD, INSTALLCONFIG);
 }

 CloseHandle ( hConfig);

 if ( dwError)
  return ( dwError); 
 
 // take out the \n or \r or other non-ascii char
 for ( i = 0; i < strlen ( szReadBuffer); i++)
 {
  if ( szReadBuffer[i] == '\r' || szReadBuffer[i] == '\n')
  {
   szReadBuffer[i] = '\0';

   break;
  }
 }

 // now check if they are equal
 if ( stricmp ( szReadBuffer, pszDomainName) )
 {
  dwError = ERROR_INVALID_DOMAIN_ROLE;

  ErrorMessage ( dwError, MSGERRUSEDOMAIN, szReadBuffer, pszDomainName);
 }

 return ( dwError);
}
/* eof - CheckDomainName */

/*------------------------------------------------------------------
| Name: CheckForTcpip
| Desc: checks if tcpip has been installed on system
------------------------------------------------------------------*/
BOOL CheckForTcpip ( VOID)
{
 HKEY hTcpipRegKey;

 // open the tcpip parameters key
 if ( RegOpenKeyEx ( HKEY_LOCAL_MACHINE, TCPIPKEY, 0, 
           KEY_ALL_ACCESS, &hTcpipRegKey) )
  return ( FALSE);

 // close key
 RegCloseKey ( hTcpipRegKey);

 return ( TRUE);
}
/* eof - CheckForTcpip */


/*-----------------------------------------------------------------
| Name: CloseRegKeySecurity
| Desc: release memory and close reg handle
-----------------------------------------------------------------*/
VOID CloseRegKeySecurity ( VOID)
{

 // free up memory
 FreeMemory ( ( VOID *) pSecurityRegKeySD);

 // close down reg key
 if ( hSecurityRegKey)
  RegCloseKey ( hSecurityRegKey);

 hSecurityRegKey = ( HKEY) 0;
}
/* eof - CloseRegKeySecurity */



/*------------------------------------------------------------------
| Name: DoExitFunction
| Desc: exits program
------------------------------------------------------------------*/
VOID DoExitFunction ( DWORD dwError)
{
 // close service control mgr
 if ( schSCManager)
  CloseServiceHandle ( schSCManager);

 // close service handle
 if ( schService)
  CloseServiceHandle ( schService);

 // check if error
 if ( dwError)
  printf ( "\nSERVICE NOT SUCCESSFULLY INSTALLED\n\n");
 else
  printf ( "\nSERVICE SUCCESSFULLY INSTALLED\n\n");

 fflush ( stdout);

 ExitProcess ( dwError);
}
/* eof - DoExitFunction */

/*------------------------------------------------------------------
| Name: ErrorMessage
| Desc: prints error messages and exits
------------------------------------------------------------------*/
VOID ErrorMessage ( DWORD dwError, CHAR *szFormat, ...)
{
 CHAR szBuffer[MAX_PATH];

 va_list vaPtr;

 // get formatted string
 va_start ( vaPtr, szFormat);
 vsprintf ( szBuffer, szFormat, vaPtr);
 va_end ( vaPtr);

 // put error code into string
 if ( dwError)
  sprintf ( &szBuffer[ strlen ( szBuffer)], " (error = %d)", dwError);

 strcat ( szBuffer, "\r\n");
 printf ( szBuffer);

// DoExitFunction ( dwError);
}
/* eof - ErrorMessage */


/*------------------------------------------------------------------
| Name: FreeMemory
| Desc: frees memory
------------------------------------------------------------------*/
VOID FreeMemory( VOID *vPtr )
{

 // free memory if it is set
 if ( vPtr) 
 {
  if ( ! LocalFree ( ( HLOCAL) vPtr) )
   lFreeCnt++;
  
  vPtr = NULL;
 }
}
/* eof - FreeMemory */

/*-------------------------------------------------------------------
| Name: _GetMemory ()
| Desc: This safe version of malloc provides a quick check that a malloc
|   succeeds; it terminates with an error if malloc does not succeed.
|   Production code, especially for a Windows or Presentation Manager
|   environment, should use a more robust version of SafeMallocFunc().
|
|   This function is not meant to be called directly. Instead, programs
|   should call the SafeMalloc macro defined in SAMPLES.H as:
|   #define GetMemory(size) _GetMemory(size, __FILE__, __LINE__)
-------------------------------------------------------------------*/
VOID *_GetMemory ( UINT cbSize, CHAR *pszFilename, UINT usLine)
{
 VOID *vPtr;
 
 if ( ( vPtr = ( VOID *) LocalAlloc ( LPTR, cbSize)) == NULL)
  ErrorMessage ( GetLastError(), MSGERRUSEALLOC, 
            cbSize, pszFilename, usLine);
 else
  lAllocCnt++; 
 
 return ( vPtr);
}
/* eof - GetMemory */


/*------------------------------------------------------------------
| Name: GetRegKeySecurity
| Desc: gets security registry sec. descriptor
------------------------------------------------------------------*/
DWORD GetRegKeySecurity ( CHAR *pszRegKeyName)
{
 LONG  dwError = 0;    // reg errors (GetLastError won't work)

 CHAR  szClassName[MAX_PATH] = ""; // Buffer for class name.
 DWORD dwcClassLen = MAX_PATH;  // Length of class string.
 DWORD dwcSubKeys;     // Number of sub keys.
 DWORD dwcMaxSubKey;    // Longest sub key size.
 DWORD dwcMaxClass;    // Longest class string.
 DWORD dwcValues;     // Number of values for this key.
 DWORD dwcMaxValueName;   // Longest Value name.
 DWORD dwcMaxValueData;   // Longest Value data.
 DWORD dwcSDLength;    // Security descriptor length
 FILETIME ftLastWriteTime;   // Last write time.


 // open the security key
 if ( ( dwError = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, pszRegKeyName, 0,
          KEY_ALL_ACCESS, &hSecurityRegKey) ) )
 {
  ErrorMessage ( dwError, MSGERRREGOPEN, pszRegKeyName);

  goto ErrorExit;
 }

 // get length of security descriptor
 if ( ( dwError = RegQueryInfoKey ( hSecurityRegKey, szClassName, 
      &dwcClassLen, NULL, &dwcSubKeys, &dwcMaxSubKey, 
      &dwcMaxClass, &dwcValues, &dwcMaxValueName, 
      &dwcMaxValueData, &dwcSDLength, &ftLastWriteTime) ) )
 {
  ErrorMessage ( dwError, MSGERRREGINFO, pszRegKeyName);

  goto ErrorExit;
 }

 
 // get SD memory
 if ( ! ( pSecurityRegKeySD = ( PSECURITY_DESCRIPTOR) 
          GetMemory ( ( UINT) dwcSDLength) ) )
 {
  dwError = GetLastError();

  return ( dwError);
 }

 // now get SD
 if ( ( dwError = RegGetKeySecurity ( hSecurityRegKey, 
      (SECURITY_INFORMATION)( DACL_SECURITY_INFORMATION),
           pSecurityRegKeySD, &dwcSDLength) ) )
 {
  ErrorMessage ( dwError, MSGERRREGSECUREGET, pszRegKeyName);

  goto ErrorExit;
 }

 // check if SD is good
 if ( ! IsValidSecurityDescriptor ( pSecurityRegKeySD))
 {
  dwError = GetLastError();

  ErrorMessage ( dwError, MSGERRREGINVALID, pszRegKeyName);
 }

ErrorExit:

 return ( dwError);
}
/* eof - GetRegKeySecurity */

/*------------------------------------------------------------------
| Name: RunningAsAdministrator
| Desc: checks if user has administrator privileges
| Notes: This function returns TRUE if the user identifier associated with 
|   this process is a member of the the Administrators group.
------------------------------------------------------------------*/
BOOL RunningAsAdministrator ( VOID)
{
 BOOL   fAdmin;
 HANDLE  hThread;
 TOKEN_GROUPS *ptg = NULL;
 DWORD  cbTokenGroups;
 DWORD  dwGroup;
 PSID   psidAdmin;

 SID_IDENTIFIER_AUTHORITY SystemSidAuthority= SECURITY_NT_AUTHORITY;

 // First we must open a handle to the access token for this thread.

 if ( !OpenThreadToken ( GetCurrentThread(), TOKEN_QUERY, FALSE, &hThread))
 {
  if ( GetLastError() == ERROR_NO_TOKEN)
  {
   // If the thread does not have an access token, we'll examine the
   // access token associated with the process.

   if (! OpenProcessToken ( GetCurrentProcess(), TOKEN_QUERY, 
                 &hThread))
   return ( FALSE);
  }
  else 
   return ( FALSE);
 }

 // Then we must query the size of the group information associated with
 // the token. Note that we expect a FALSE result from GetTokenInformation
 // because we've given it a NULL buffer. On exit cbTokenGroups will tell
 // the size of the group information.

 if ( GetTokenInformation ( hThread, TokenGroups, NULL, 0, &cbTokenGroups))
  return ( FALSE);

 // Here we verify that GetTokenInformation failed for lack of a large
 // enough buffer.

 if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  return ( FALSE);

 // Now we allocate a buffer for the group information.
 // Since _alloca allocates on the stack, we don't have
 // to explicitly deallocate it. That happens automatically
 // when we exit this function.

 if ( ! ( ptg= _alloca ( cbTokenGroups))) 
  return ( FALSE);

 // Now we ask for the group information again.
 // This may fail if an administrator has added this account
 // to an additional group between our first call to
 // GetTokenInformation and this one.

 if ( !GetTokenInformation ( hThread, TokenGroups, ptg, cbTokenGroups,
          &cbTokenGroups) )
  return ( FALSE);

 // Now we must create a System Identifier for the Admin group.

 if ( ! AllocateAndInitializeSid ( &SystemSidAuthority, 2, 
            SECURITY_BUILTIN_DOMAIN_RID, 
            DOMAIN_ALIAS_RID_ADMINS,
            0, 0, 0, 0, 0, 0, &psidAdmin) )
  return ( FALSE);

 // Finally we'll iterate through the list of groups for this access
 // token looking for a match against the SID we created above.

 fAdmin= FALSE;

 for ( dwGroup= 0; dwGroup < ptg->GroupCount; dwGroup++)
 {
  if ( EqualSid ( ptg->Groups[dwGroup].Sid, psidAdmin))
  {
   fAdmin = TRUE;

   break;
  }
 }

 // Before we exit we must explicity deallocate the SID we created.

 FreeSid ( psidAdmin);

 return ( fAdmin);
}
/* eof - RunningAsAdministrator */

/*-----------------------------------------------------------------
| Name: SetParameters
| Desc: sets registry parameters for service
-----------------------------------------------------------------*/
DWORD SetParameters ( VOID)
{
 HKEY hRegKey;
 CHAR *pszVersion  = VERSION;
 DWORD dwTimeout   = TIMEOUT;
 DWORD dwMaxNumOfClients = MAXCLIENTS;

 DWORD dwType;
 DWORD dwcbData;
 DWORD dwTcpMaxConn;
 DWORD dwDisposition;

 DWORD dwError = 0;

 // create or open the parameters key
 if ( ( dwError = RegCreateKeyEx ( HKEY_LOCAL_MACHINE, PARAMETERSKEY, 0, 
       "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 
       NULL, &hRegKey, &dwDisposition) ) )
 {
  ErrorMessage ( dwError, MSGERRREGCREATE, PARAMETERSKEY);

  goto ErrorExit1;
 }

 // add version value
 if ( ( dwError = RegSetValueEx ( hRegKey, VERSIONVALUE, 0, REG_SZ, 
       ( LPBYTE) pszVersion, strlen ( pszVersion) + 1) ) )
 {
  ErrorMessage ( dwError, MSGERRREGVALUESET, VERSIONVALUE);

  goto ErrorExit;
 }

 // add timeout value
 if ( ( dwError = RegSetValueEx ( hRegKey, TIMEOUTVALUE, 0, REG_DWORD, 
       ( LPBYTE) &dwTimeout, sizeof ( DWORD) ) ) )
 {
  ErrorMessage ( dwError, MSGERRREGVALUESET, TIMEOUTVALUE);

  goto ErrorExit;
 }


 // add max # of clients value
 if ( ( dwError = RegSetValueEx ( hRegKey, MAXCLIENTSVALUE, 0, REG_DWORD, 
       ( LPBYTE) &dwMaxNumOfClients, sizeof ( DWORD) ) ) )
 {
  ErrorMessage ( dwError, MSGERRREGVALUESET, MAXCLIENTSVALUE);

  goto ErrorExit;
 }

 // close key
 RegCloseKey ( hRegKey);

 printf ( "Service version:    %s\n", pszVersion);
 printf ( "Service timeout:    %d\n", dwTimeout);
 printf ( "Service max. number of clients: %d\n\n", dwMaxNumOfClients);

 // open the tcpip parameters key
 if ( ( dwError = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, TCPIPKEY, 0,
           KEY_ALL_ACCESS, &hRegKey) ) )
 {
  ErrorMessage ( dwError, MSGERRREGOPEN, TCPIPKEY);

  goto ErrorExit1;
 }

 dwcbData = ( DWORD) sizeof ( dwTcpMaxConn);

 // check what the parameter is set to
 if ( RegQueryValueEx ( hRegKey, TCPMAXCONNVALUE, NULL, 
       &dwType, ( LPBYTE) &dwTcpMaxConn, &dwcbData) )
  dwTcpMaxConn = 0;

 if ( dwTcpMaxConn != TCPIP_MAXCONN)
 {
  dwTcpMaxConn = TCPIP_MAXCONN;

  // add version value
  if ( ( dwError = RegSetValueEx ( hRegKey, TCPMAXCONNVALUE, 0, 
           REG_DWORD, ( LPBYTE) &dwTcpMaxConn, 
           sizeof ( DWORD) ) ) )
  {
   ErrorMessage ( dwError, MSGERRREGVALUESET, TCPMAXCONNVALUE);

   goto ErrorExit;
  }

  printf ( "TcpMaxConnectAttempts changed to: %d\n\n", dwTcpMaxConn);
 }

ErrorExit:

 // close key
 RegCloseKey ( hRegKey);

ErrorExit1:

 return ( dwError);
}
/* eof - SetParameters */