How To Program a Secure Server on Microsoft Windows NT

This article was previously published under Q171273
This article has been archived. It is offered "as is" and will no longer be updated.
In the Client/Server model, it is often desirable to arbitrate access tothe server's functions or data based on a client's privileges.

Given an application-derived Security Descriptor (SD) that specifies acertain set of rights for a particular group of users in its DiscretionaryAccess Control List (DACL), and a client token representing a user whowants 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.
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 haverights to the secured portion of the server. The DACL should contain,at the minimum, an Access Allowed ACE for every user or group thatshould be allowed access to the protected portion of the server. EachACE will specify an Access Privilege level that the serverdefines.

    The SD is normally reusable by the server and should be savedso 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 inrelationship (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 connectto the server by way of named pipes, then you can use thethe ImpersonateNamedPipeClient function to impersonate the client. Once you are impersonating, you can get the clients token by callingOpenThreadToken(GetCurrentThread(), ...).
  3. When a client requests access to a protected part of the server, callthe 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 2void 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);   }}				

Article ID: 171273 - Last Review: 02/28/2014 08:06:32 - Revision: 4.2

  • Microsoft Win32 Application Programming Interface
  • kbnosurvey kbarchive kbhowto kbapi kbprogramming kbkernbase kbcode KB171273