October 1999


Encapsulate NT File Security

Learn how to set NT File Security with pure VB code and a little help from the API.

by L.J. Johnson

Reprinted with permission from Visual Basic Programmer's Journal, October 1999, Volume 9, Issue 10, Copyright 1999, Fawcette Technical Publications, Palo Alto, CA, USA. To subscribe, call 1-800-848-5523, 650-833-7100, visit www.vbpj.com, or visit The Development Exchange.

NT provides rich and flexible security, but these features come at a price: complexity. Microsoft's supplied utilities can confuse even experienced NT administrators, and NT's security APIs are arguably the most complex and difficult-to-use APIs Microsoft has ever exposed to programmers.

Consider NT's file-security dialogs, which you bring up by right-clicking on a file in Explorer, selecting Properties, and clicking on the Security tab. This tab lets you access dialogs to add, delete, or modify a user's or group's permissions to a file. Sometimes you need to modify these settings programmatically. For example, you might have several files on a server directory that a user has permission to view, but not modify. If a user installs your program, he or she might need the ability to modify these files. So the server-side component could modify the security for the directory (or individual files within that directory) to allow the user modification rights after he or she has successfully passed your client-side security checks.

The difficulties the NT security APIs present aren't for the faint of heart. For some, the problems begin with something as basic as the host of acronyms and terms you must learn before using NT File Security (see Table 1). But there's more to it than memorizing some terms. Even experienced programmers can be tripped up when attempting to follow Micro-soft's often obscure and sometimes downright circular documentation on security. Plus, the large number of API calls and the sometimes less-than-obvious ways in which those calls are related (or even codependent) can drive you to distraction. What's more, VB programmers face the additional task of translating the Unicode-only API calls into something that VB can deal with.

NT doesn't give you direct programmatic access to the security dialogs, but you can replicate their functionality using nothing more than VB (version 5 or later) and a little API magic. This sample project works only with files, but extending its code to work with directories should be relatively straightforward. You could also expend a little more effort to extend the program so it works with other secured objects such as named pipes and Registry entries. Note that file security in NT applies only to Microsoft Windows NT File System (NTFS) formatted volumes; File Allocation Tables (FAT) volumes cannot use NT file-level security because they don't have the required extended attributes within the file system.

The file-security dialogs app consists of a single EXE (a file-picker form) and two DLLs. The first DLL, FilePermissions, presents the various dialog boxes you display, and you call it directly from the EXE. The second DLL, NT_Security, consists of objects that encapsulate the Security API; you call it through FilePermissions. You can download the full source code for all three projects here.

Note that the complexity of the subject and size of the code permit me to cover only part of the overall app in this article, but the code itself is heavily commented. If you encounter any difficulties extending this approach or have questions about NT in general, you might try posting a question on VBPJ's newsgroups, which are moderated by many regular VBPJ contributors, or on the Ask The NT Pro site.

Identify User/Group Uniquely
You must be able to identify a user or group uniquely before you can add or deny access to a file for that particular user or group. NT uses Security Identifiers (SIDs) for this purpose. A SID is a large binary number NT generates when it creates an account for a user or group. NT partially bases this number on which security authority authorized the account, whether a domain controller (DC or server) or a nondomain controller (non-DC or workstation). An NT computer creates several predefined groups when you first install the OS—the exact number of accounts it creates depends upon whether it's configured as a server or workstation. Both servers and workstations include a set of fixed, standard SIDs such as Administrators, Guests, and Users. A text representation of the Administrator SID for workstations and servers looks like this:

S-1-5-32-544

An Administrator SID for a domain controller looks similar:

S-1-5-32-548

 
Figure 1 Give Your Files Permissions. Click here.

A group of SIDs that identify generic groups and users (well-known SIDs) includes Null, World, Local, and Creator Owner ID. A Relative Identifier (RID) value uniquely identifies the user/group relative to the authority that issues the SID. The well-known SIDs populate the possible selections on the right ListView control of the File Permissions form (see Figure 1).

You also need to return the local machine name and the local domain (if applicable), return the local or domain groups (depending on the selection in the List Names From combo box), and show the users for the local machine or the domain if the user chooses the Show Users button. You can learn more information on the API calls required to do this by reading Patrick Long's article, "Your Network's Who's Who."

The left-most listview of NT's File Permissions form displays the current permissions for the selected file. You return this information through a collection class, but you retrieve it initially through the Get_FilePermissions code (see Listing 1). The currently selected file has already been set into a class-level variable through a property call, and the filename is the main input variable used in the GetNamedSecurityInfo API call. You need to indicate that this is a file object (SE_FILE_OBJECT) and that you want information on the Discretionary Access Control List (DACL) rather than the System Access Control List (SACL) or the Owner information (see Table 1). The most important output variable is the pointer to the DACL (p_lngPtrDACL), which you use in the GetExplicitEntries-FromAcl API call. This call retrieves an array of EXPLICIT_ACCESS structures that describe all the Access Control Entries (ACEs) in the ACL—exactly the information you need to fill in the collection items.

Getting the information from the pointer to the array in a usable form is fairly standard with the NT Unicode functions. Use CopyMem to copy the information out of the pointer to the returned data structure (p_lngListExplicitEntries) into a pre- dimensioned array of type structures (p_atypExplicitAccess). Now you can use most of the information directly, but four pieces of information needed for the exposed collection require additional processing. You can find more information on these in the online source. Next, you add another item to the internal collection of clsFilePermissions objects for each item in the array, and, per the API documentation, use the Local Free call to free the memory used by the memory structures.

Add Users With Proper Rights
The code to add a new user/group with appropriate add-access/deny-access rights comprises the heart of the program. An important part of this is adding a user/group ACE to the DACL for the selected files (see Listing 2). Most of the API calls live in separate private subroutines that can't be shown because of space restrictions, but the GetFileSecurityInfoDACL call should serve as a good example.

The general algorithm for adding a new ACE to the file's Security Descriptor (SD) consists of several steps. First, make sure you're on an NTFS-formatted drive. Next, translate the user/group name into a SID, create a new SD for this file, retrieve the file's current SD, and get the DACL from the file's current SD. Finally, get the size of the current DACL.

What you do next depends on whether the new user/group is the same as one of the user/group ACEs already attached to the file. If the user/group is a duplicate, you must resize the new DACL buffer to the returned size, initialize a new DACL, then copy the current DACL to the new ACL, merging the rights of the duplicate user. If the user/group isn't a duplicate, follow the same steps, but resize the new DACL buffer to the size of the current DACL plus the size of the new (added) ACE.

The Get_FileSecurityDACL method illustrates how to use a technique common to many of the security API calls: You must call its GetFileSecurity function twice. First, you call it to get the proper size for the SD information—the DACL, in this case. This function returns the size needed in the p_lngSizeNeeded return value. Next, you must ReDim the byte array to the returned size and call the function again to retrieve the DACL.

You can find more information on how to add an ACE to a file in Microsoft Knowledge Base article Q194757. However, using this technique to add an ACE for a user or group that already includes an ACE in the file presents several problems (see the sidebar, "Do It Right"). The best approach is to check whether a user or group you want to add already includes an entry in the DACL before you add the new ACE. Use the IsDupeACE method to check for duplicates; you can find this code in the online source. It returns True if this user/group already exists as an ACE in the DACL.

You need to branch your code before adding the ACE, depending on the result of IsDupeAce. If it is a new ACE for add-access rights, add it to the end of the DACL. If it is a new ACE for deny-access rights, add it to the beginning of the DACL. Both choices are wrapped in the function AddACEtoACL (see Listing 3). If it is an ACE for a duplicate user, update it in its current location with the merged security rights of the old and new ACEs using the UpdateACEinACL method. You must loop through the current DACL and copy the ACEs one by one to the new DACL in either case, adding the new ACE or modifying one of the current ACEs as needed.

I didn't even attempt to cover setting the file owner or deleting a user or group ACE from a DACL, but you can find code that shows you how to do this in the downloadable source code. The source code's error trapping is extensive, but you might want to modify it to make the component more robust.

Windows 2000 will change some of the API security calls and add some new and useful API methods (see the sidebar, "Build on a New Interface"). However, NT security is a complicated and convoluted subject, and the security component in this article uses more than 40 API calls and many constants and type structures. Adding to the confusion are MSDN's sometimes circular or incomplete definitions of the major components. However, creating the security component described in this article gives you a piece of binary-compatible code you can reuse in multiple projects—all without sweating the more confusing details each time you begin a new project.


L.J. Johnson, owner of Dallas-based Slightly Tilted Software, has been programming in Visual Basic since 1.0. He's a section leader on The Development Exchange at http://news.devx.com, an MVP (Microsoft Most Valuable Professional), and the Ask The NT Pro at www.inquiry.com. Reach him by e-mail at LJJohnsn@Flash.Net, and visit his Web site at www.flash.net/~ljjohnsn.