How To Program a Secure Server on Microsoft Windows NT

Article translations Article translations
Article ID: 171273 - View products that this article applies to.
This article was previously published under Q171273
This article has been archived. It is offered "as is" and will no longer be updated.
Expand all | Collapse all

On This Page

SUMMARY

In the Client/Server model, it is often desirable to arbitrate access to the server's functions or data based on a client's privileges.

Given an application-derived Security Descriptor (SD) that specifies a certain set of rights for a particular group of users in its Discretionary Access Control List (DACL), and a client token representing a user who wants access to the protected server, you can call the AccessCheck function to determine whether the client should be granted that access.

This process is explained in detail in the following section. Sample code is provided at the end of this article.

NOTE: It is assumed that you already have an understanding of Windows NT Security in general, and Access Control Security in particular.

MORE INFORMATION

Resource manager applications designed for Windows XP or later should use the AuthzAPI to implement private object access control. The AuthzAPI provide similar functionality to that of the Private Object API with more flexibility and better performance. For more information see the Authz API documentation in the Platform SDK.

To build a simple secure server, follow these steps:
  1. Build a SD and a DACL for it that defines what users/groups have rights to the secured portion of the server. The DACL should contain, at the minimum, an Access Allowed ACE for every user or group that should be allowed access to the protected portion of the server. Each ACE will specify an Access Privilege level that the server defines.

    The SD is normally reusable by the server and should be saved so that it can be reused every time the server runs. The InitializeSecurityDescriptor function returns a SD in absolute format, which is unsuitable for storing in a file or in the registry. Use the MakeSelfRelativeSD function to convert the absolute SD to a self-relative format.

    NOTE: The Private Object Security functions might be useful if you have a large number of objects to secure that are hierarchical in relationship (for example, a directory tree). The CreatePrivateObjectSecurity function allows you to pass creator and parent SDs that give the object security based on inheritable ACEs that are contained in these SDs.
  2. Get the client's token. For example, if client applications connect to the server by way of named pipes, then you can use the the ImpersonateNamedPipeClient function to impersonate the client. Once you are impersonating, you can get the clients token by calling OpenThreadToken(GetCurrentThread(), ...).
  3. When a client requests access to a protected part of the server, call the AccessCheck function by using the client token and the SD obtained in steps 1 and 2. Access can then be granted or denied based on the results.
The following sample code shows how to construct an SD, build a simple DACL, and how to call the AccessCheck function.

Sample Code

The following sample code demonstrates how to use the AccessCheck function to determine if a client token has sufficient access to perform some operation against an object that is protected by a Security Descriptor or to determine what the client's maximum access is on that same object.
/* Secure Server Sample

   David Mowers (davemo)   15-May-97 */ 

#include <windows.h>
#include <stdio.h>

// This code must be linked with the Advapi32.lib file.

// Make up some private access rights.
#define ACCESS_READ  1
#define ACCESS_WRITE 2

void main(int argc, char *argv[]) {

   PSECURITY_DESCRIPTOR psdSD;

   // User/SID variables.
   HANDLE      hToken   = NULL;
   PTOKEN_USER ptuUser  = NULL;
   DWORD       cbBuffer = 0;
   PSID        pUserSid = NULL;

   // ACE variables.
   DWORD dwAccessMask = ACCESS_READ | ACCESS_WRITE;
   PACL  pACL         = NULL;
   DWORD dwACLSize;

   // AccessCheck() variables
   DWORD           dwAccessDesired;
   PRIVILEGE_SET   PrivilegeSet;
   DWORD           dwPrivSetSize;
   DWORD           dwAccessGranted;
   BOOL            fAccessGranted = FALSE;
   GENERIC_MAPPING GenericMapping;

   __try {

      // Get a SID for later use. A real server would use a function
      // such as LookupAccountName() to get SIDs that you will use to
      // build your access control list.
      if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) {

         printf("Error %d:OpenProcessToken", GetLastError());
         __leave;
      }

      // Determine required size of buffer for token information.
      if (GetTokenInformation(hToken, TokenUser, NULL, 0, &cbBuffer)) {

         // Call should have failed due to zero-length buffer.
         __leave;
   
      } else {

         // Call should have failed due to zero-length buffer.
         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
            __leave;
      }

      // Allocate buffer for token information.
      ptuUser = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
            cbBuffer);
      if (!ptuUser)
         __leave;

      if (!GetTokenInformation(hToken, TokenUser, ptuUser, cbBuffer,
            &cbBuffer))
         __leave;

      pUserSid = ptuUser->User.Sid;

      // Build a Security Descriptor.
      psdSD = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
            SECURITY_DESCRIPTOR_MIN_LENGTH);

      if(!InitializeSecurityDescriptor(psdSD, 
            SECURITY_DESCRIPTOR_REVISION)) {

         printf("Error %d:InitializeSecurityDescriptor\n", 
               GetLastError());
         __leave;
      }

      // Compute size needed for the ACL.
      dwACLSize = sizeof(ACCESS_ALLOWED_ACE) + 8 +
            GetLengthSid(pUserSid) - sizeof(DWORD);

      // Allocate memory for ACL.
      pACL = (PACL) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
            dwACLSize);

      // Initialize the new ACL.
      if(!InitializeAcl(pACL, dwACLSize, ACL_REVISION2)) {
         printf("Error %d:InitializeAcl\n", GetLastError());
         __leave;
      }

      // Add the access-allowed ACE to the DACL.
      if(!AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, 
            pUserSid)) {

         printf("Error %d:AddAccessAllowedAce",GetLastError());
         __leave;
      }

      // Set our DACL to the SD.
      if (!SetSecurityDescriptorDacl(psdSD, TRUE, pACL, FALSE)) {
         printf("Error %d:SetSecurityDescriptorDacl", GetLastError());
         __leave;
      }

      // AccessCheck() is picky about what is in the SD. Set the group 
      // and owner using our convenient SID.
      SetSecurityDescriptorGroup(psdSD, pUserSid, FALSE);
      SetSecurityDescriptorOwner(psdSD, pUserSid, FALSE);

      // AccessCheck() requires an impersonation token.
      // For demonstration purposes, we are going to impersonate
      // ourselves. A real server would impersonate the client by using
      // ImpersonateNamedPipeClient(), RPCImpersonateClient(),
      // ImpersonateLoggedOnUser() with a token obtained through
      // LogonUser() or the SSPI API ImpersonateSecurityContext().
      ImpersonateSelf(SecurityImpersonation);

      OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, 
            &hToken);

      // Using AccessCheck(), there are two different things we could do:
      // 
      // 1. See if we have Read/Write access to the object.
      // 
      dwAccessDesired = ACCESS_READ;

      // Initialize generic mapping structure to map all.
      memset(&GenericMapping, 0xff, sizeof(GENERIC_MAPPING));
      GenericMapping.GenericRead = ACCESS_READ;
      GenericMapping.GenericWrite = ACCESS_WRITE;
      GenericMapping.GenericExecute = 0;
      GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE;

      // This only does something if we want to use generic access
      // rights, like GENERIC_ALL, in our call to AccessCheck().
      MapGenericMask(&dwAccessDesired, &GenericMapping);

      dwPrivSetSize = sizeof(PRIVILEGE_SET);

      // Make the AccessCheck() call.
      if(!AccessCheck(psdSD, hToken, dwAccessDesired, &GenericMapping,
            &PrivilegeSet, &dwPrivSetSize, &dwAccessGranted, 
            &fAccessGranted)) {

         printf("Error in AccessCheck : %lu\n", GetLastError());
         __leave;
   
      } else {

         if (fAccessGranted)
            printf("Access was granted using mask %lx.\n",
                  dwAccessGranted);
         else
            printf("Access was NOT granted!\n");
      }

      // 
      // 2. Determine the maximum access I am allowed.
      // 
      dwAccessDesired = MAXIMUM_ALLOWED;

      if(!AccessCheck(psdSD, hToken, dwAccessDesired, &GenericMapping,
            &PrivilegeSet, &dwPrivSetSize, &dwAccessGranted, 
            &fAccessGranted)) {

         printf("Error in AccessCheck : %lu\n", GetLastError());
         __leave;
   
      } else {

         if (fAccessGranted)
            printf("Maximum Access Allowed = %lx\n", dwAccessGranted);
      }

      RevertToSelf();
    
   } __finally {

      // Close handles and free heap allocations.

      if (hToken)
         CloseHandle(hToken);

      if (ptuUser)
         HeapFree(GetProcessHeap(), 0, ptuUser);

      if (pACL)
         HeapFree(GetProcessHeap(), 0, pACL);

      if (psdSD)
         HeapFree(GetProcessHeap(), 0, psdSD);
   }

}
				

Properties

Article ID: 171273 - Last Review: February 28, 2014 - Revision: 4.2
APPLIES TO
  • Microsoft Win32 Application Programming Interface, when used with:
    • Microsoft Windows NT 4.0
    • Microsoft Windows 2000 Standard Edition
    • the operating system: Microsoft Windows XP
Keywords: 
kbnosurvey kbarchive kbhowto kbapi kbprogramming kbkernbase kbcode KB171273

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com