Microsoft KB Archive/171273

From BetaArchive Wiki
< Microsoft KB Archive
Revision as of 20:50, 16 July 2020 by X010 (talk | contribs) (1 revision imported: importing part 2)

HOWTO: Program a Secure Server on Microsoft Windows NT

Q171273



The information in this article applies to:


  • Microsoft Win32 Application Programming Interface (API), used with:
    • Microsoft Windows NT Server version 4.0
    • Microsoft Windows NT Workstation version 4.0
    • the operating system: Microsoft Windows 2000





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

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.

  1. 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(), ...).
  2. 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);
   }

} 

Additional query words:

Keywords : _IK kbprogramming kbAPI kbKernBase kbDSupport kbGrpDSKernBase
Issue type : kbhowto
Technology : kbAudDeveloper kbWin32sSearch kbWin32API


Last Reviewed: October 23, 2000
© 2001 Microsoft Corporation. All rights reserved. Terms of Use.