One of the most frustrating aspects of administrating Win32 machines is the way that permissions are managed. The only way to modify permissions is by using the GUI tools that come with the operating system, such as the Explorer.exe program. There are other tools such as Cacl.exe but they are not necessarily easy to find nor are they easy to use. Additionally such tools typically only work on permissions for one particular type of object (such as files and directories or printer shares).
This is what the Win32::Perms extension was designed to combat. It allows a script to query and modify the permissions on files, directories, registry keys, network shares and printer shares. In addition to permissions the extension also allows the management of how an object is audited as well as editing the objects owner and group.
Back to the top
The latest version of Win32::Perms is:
There are two ways to obtain Win32::Perms. You can either download and install it manually from our FTP site:
ftp://ftp.roth.net/pub/ntperl/perms/
Or, if you have ActivePerl (aka Perl from ActiveState version 5.005 or higher), you can auto download and install the extension. You need to run the Perl Package Manager script which comes in the perl\bin directory:
perl ppm.pl install http://www.roth.net/perl/packages/win32-perms.ppd
This will automatically download and install the latest version.
In chapter 11 of our popular book, Win32 Perl Programming: The Standard Extensions, 2nd Edition, we describe using the Win32::Perms extension to query and set permissions on files, directories, Registry keys, named pipes, network shares and network printers.
Additionally we offer a different approach to describing Win32 Security.
Permissions are placed on Win32 objects such as files, directories and registry keys. They are the "rules" which dictate who can access these objects and how they are to be accessed. For example one user may need to read and write files while another user may need to delete files. This page will provide a brief overview of just what Win32 permissions are and how they work. For the sake of clarity let's assume that you want to place permissions so user cartman can only read a file but the administrator has full access (the ability to read, write, delete, change the ownership and modify permissions). To understand how we go about doing this you need to first understand a few things about Win32 security...
The first thing you need to understand is that usernames and group names are just strings of text that mean nothing to Win32. The OS does not care what an "Administrator" string is. It does care, however, about a funky long thing that looks like:
S-1-5-21-143982112-578333669-1869494990-500
You see, an odd looking number like this is what Win32 sees when it thinks of the Administrator account. When the Administrator logs in Win32 looks up this fancy sting and applies it whenever it needs to identify the user. For humans it is simply easier to refer to 'Administrator'. The string is really a text representation of a binary memory structure which is called a Security Identifier (SID). Every username, groupname, domain name and machine name have a SID tucked away somewhere in the Registry.
So if you want to grant the user cartman some permissions you must first request Win32 to lookup the SID for the cartman and administrator accounts.
Once you have a SID you need to create a thing called an Access Control Entry (ACE). An ACE is a combination of SID, permissions and some flags. The permissions and flags are discussed later. You simply need to make an ACE for cartman and an ACE for administrator. Sounds simple so far...
Now that you have this collection of ACEs you need to glue them altogether. You do this by creating an Access Control List (ACL). An ACL is simply a list of ACEs. Once you have created an ACL you can apply it to the object (file, dir, registry key, etc).
But wait, not so fast. Having created your nice, shiny ACL makes you happy and all, but we are not out of this yet. There are two types of ACLs. The type of ACL that most users are familiar with is the one which allows you to determine who has access to what. This is what you created the cartman and administrator ACEs for in the first place. This ACL is known as a Discretionary Access Control List (DACL). Makes sense since it is up to your discretion who has access, eh?
The other type of ACL is one that most administrators are familiar with. This is the one which determines if and how the object is audited. This way you can set it such that when the object is successfully opened an entry is placed in the event log. There are successful audits and there are failure audits. This type of audit ACL is known as a System Access Control List (SACL).
By the way, if you have an empty DACL (that is a DACL that contains no ACEs) then that means that nobody will be granted access to the object (except for the owner, but that comes later). An empty SACL simply means that no auditing takes place on the object.
Okay, one last wrinkle to toss in. It is possible to create a NULL DACL. A NULL DACL is NOT the same as an empty DACL. An empty DACL means that you have a chunk of memory allocated that represents a DACL but no ACEs are inside of it. A NULL DACL means that no memory has been allocated for the DACL. (The NULL comes from the fact that since there is no memory allocated for the DACL you supply a NULL pointer whenever a function requires a DACL). A NULL DACL will be interpreted by Win32 as neither granting access to anyone nor denying access to anyone. This confusion is resolved by Win32 implicitly granting FULL access to EVERYONE! (this is why when you create a new net share in Explorer and do not specify any permissions you see, by default, that full access is granted to everyone -- the share has a NULL DACL)
A NULL SACL, by the way, will do the same as an empty SACL (no auditing is performed).
If this makes sense thus far then you are going to find the rest of this as pretty easy.
Two more very simple concepts and then on to the meaning of life: owner and group. Most Win32 objects can have an owner and group. An owner is just a SID of who "owns" the object.
The owner is never denied access to the object, ever. Even if the DACL explicitly denies access to the user, the fact that he is the owner allows him to get to it. An owner can either be a user SID or a group SID. Yes, the Administrators group can be the owner of a file (which means that anyone who is a member of the Administrators group would share in the ownership).
The last simple thing is just like the owner except that it is a group (aka primary group). This does not really mean much to Win32 but it exists for compatibility with POSIX. An objects primary group can be any Global Group. And that is about it. It does not grant anything neat-o or cool. It simply is. Oh yeah, the owner and group are both SIDs. They are not ACEs since there are no permissions and/or flags that are associated with the them. Just simply SIDs.
If you take your DACL and SACL and toss in an owner SID and a primary group SID then you have what is known as a Security Descriptor (SD). Ahh, this is it! This is what a Win32::Perms object really is; a security descriptor!
Most Win32 API functions that allow you to create or manipulate objects allow you to supply a security descriptor. Which means all of this madness applies to any securable Win32 object.
If you create a Win32::Perms object passing in a path or Win32::Registry object then an empty SD is created which will immediately import the DACL, SACL, owner and group.
When you create a Win32::Perms object you are creating an empty Security Descriptor. This means that it has no owner SID, no primary group SID and it has both a NULL DACL and a NULL SACL.
The Win32::Perms makes use of user, machine and domain accounts. This can be any valid account. For example:
Joel Administrator Guest SERVER_A$
In addition to accounts, groups are also valid (both local and global groups):
Managers Administrators Guests Friends
To specify a particular account or group from a particular domain you can prepend the account with a domain name followed by a single backslash:
ACCOUNTING\Joel TECH_GROUP\Administrator TECH_GROUP\Domain Admins NEW_YORK\Guests
It is often necessary to specify a user account from a particular machine. The machine can be a Primary Domain Controller, Backup Domain Controller, NT Server, or NT Workstation. Since local groups are machine specific (not domain wide as global groups are) it is necessary to specify which machine a local group belongs to.
To specify a particular machine instead of a domain you specify the proper name of the machine (with double backslashes prepending the name) followed by a backslash and the account or group name:
\\Server_A\Administrators \\JOHNS_PC\IUSR_MACHINE \\PRIMARY_DC\Guest
One of the most important elements of Win32 permissions are the flags. Every ACE can specify one of several flags. These flags indicate how the permissions are applied to the object. This is the topic where people become most confused but they are of the utmost importance.
Let's say that you have a directory. On that directory you place permissions that grant administrators full control and users read and write control. This means that the user who tries to access that directory will be subject to the permissions. But what happens if you create a subdirectory within this directory? What permissions does it have?
Typically when you place permission on a directory using the Explorer.exe program any new subdirectory will inherit the permissions from it's parent (the directory it is created within). This happens only because Explorer.exe, by default, sets a flag indicating that new subdirectories are to inherit the permissions.
This leads us to define what a directory is. Any object which can hold another object (such as a directory, a Registry key, a network share, etc) is called a container. They are capable of containing objects. Typically a container can contain other containers. This is evident in the way you can have directories within other directories and Registry keys within other Registry key. A container that is inside of another container is said to be a child container. Likewise the child container is held within it's paren container.
Non-containers are just that; objects that are not containers. Examples of non-containers are files and Registry values. They are always held within a container of some sort.
Now, flags are used to reflect how containers will handle permissions. There are basically four flags to be aware of:
You can combine these flags by logically ORing them together.
For every directory you typically have to add two sets of permissions for each user. In other words, to grant Administrator full access on a directory you would create two ACES; one for permissions on the directory and one for permission on the files within the directory:
Typically when you set permissions on a directory using the Explorer.exe program it will create two ACEs. One will be for the directory which will have the flag:
CONTAINER_INHERIT_ACE
The other ACE will be for files in the directory and this ACE will have the flags:
OBJECT_INHERIT_ACE | INHERIT_ONLY
You can use any combination of flags that your needs mandate. Be advised, however, that if you use anything but the typical flag combinations described above the Explorer.exe and File Manager programs will complain with a dialog (as in figure 1). This is because even though the flag combinations may be legitimate and valid these programs do not know how to describe them.
In this example we will be placing read only permissions for cartman and full permissions for administrator on the file c:\test.txt and removing all references for guest from and then explicitly denying joel access to the c:\temp directory.
Apply permissions on a file.
Modify permissions on a directory
use Win32::Perms; # Create a new Security Descriptor and auto import permissions # from the directory $Dir = new Win32::Perms( 'c:/temp' ) || die; # One of three ways to remove an ACE $Dir->Remove('guest'); # Deny access for all attributes (deny read, deny write, etc) $Dir->Deny( 'joel', FULL ); # Set the directory permissions (no need to specify the # path since the object was created with it) $Dir->Set(); # If you are curious about the contents of the SD # dump the contents to STDOUT $Dir->Dump;
This method will create an ACE with the specified account, permission mask, ACE type and ACE flags and add the ACE to either the SACL or DACL depending on what Type is specified. Alternatively references to hashes can be passed in. Each hash must consist of the following keys:
The hashes contained in the resulting array from a call to Dump() will contain the appropriate keys. This allows for:
$Perm->Dump( \@Array );
Mask can be any combination of constants from Table 1 OR'ed together. Type can be any one single constant from Table 2. Flag can be any combination of constants from Table 3 OR'ed together.
Returns the number of entries successfully added.
This method will create a new audit ACE with the specified account, mask and flag. The ACE will cause the system to log (audit) the specified $Account when it accesses the object using one of the events specified by $Mask.
Mask can be any combination of constants from Table 1 OR'ed together. Flag can be any combination of constants from Table 3 OR'ed together.
Returns TRUE (1) if successful and FALSE (0) if it fails.
This method will create a new access allowed ACE with the specified account, mask and flag. The ACE will allow the specified $Account to access the object with the specified $Mask.
This method is called to destroy a Win32::Perms object. Close() is automatically called when the object is destroyed due to an undef(), delete() or it falls out of scope.
Returns no value.
This function will decipher a permission mask into human readable names. The first parameter can either be a permission mask value or it can be a hash reference obtained by a call to Dump(\@List). If an optional array reference is passed in as a second parameter then the array is cleared and populated with the permission names.
If an optional third array reference is passed in then an attempt to map the permissions masks into "friendly" permission names.
Example:
$Perm->Dump( \@List );
Returns the number of permissions names that $Mask represents.
This function will decipher a type value into human readable names. The first parameter can either be a type value or it can be a hash reference obtained by a call to Dump(\@List).
If an optional array reference is passed in as a second parameter then the array is cleared and populated with the permission names.
Returns the number of permissions names that $Type represents.
This function will decipher a flag value into human readable names. The first parameter can either be a flag value or it can be a hash reference obtained by a call to Dump(\@List).
If an optional array reference is passed in as a second parameter then the array is cleared and populated with the flag names.
Returns the number of permissions names that $Flag represents.
This method will create a new access denied ACE with the specified account, mask and flag. The ACE will prevent the specified $Account from accessing the object with the specified $Mask.
Prints all ACEs and their values to STDOUT. This is used primarily for debugging.
If an optional array reference is passed in then nothing is printed to STDOUT instead the array is populated with hashes that describe each ACE.
Returns the total number of ACE's in the Win32::Perms object. If a reference to an array is passed then the array is populated with hashes describing each ACE.
Returns a number representing the total number of ACE's in the object.
Retrieves the memory address of the Win32::Perms object's Discretionary ACL (DACL). The return value is the memory address of the DACL (a pointer to the DACL).
It is not wise to use this function since it points to a memory structure that can change.
Returns a pointer to the Win32::Perms object's Discretionary ACL (DACL).
This method will retrieve a the object's Security Descriptor (SD). There are two types of SDs:
If no type is passed in then it defaults to SD_ABSOLUTE.
Returns either the memory location of the SD (SD_ABSOLUTE), a binary data structure (SD_RELATIVE) or undef (upon failure).
Returns the extension version number in a string format. The version number is in universal date format (eg. "19990119").
Returns the extension's version string.
Retrieves the group of the object. If an optional account is passed in then the group will be set to that account.
NOTE: Not all objects have groups (eg. Network shares).
Returns the current group of the object.
This method imports the ACEs from $Path and adds them to the current ACE list.
Refer to Appendix A for information on how to format the $Path variable.
Returns TRUE if successful and FALSE if failure.
This function will create a new Win32::Perms object. The first parameter is either a path or a valid recognized Perl object. Refer to Appendix A for details on formats a path can be.
An $Object can be used instead of a $Path. Currently the only valid object is a Win32::Registry object.
If a second parameter is specified it forces the path to be interpreted as the specified type:
If nothing is passed into the function then a new, empty Win32::Perms object will be created. It can be applied to any object later.
Returns a valid Win32::Perms object otherwise returns undef.
Retrieves the owner of the object. If an optional account is passed in then the owner will be set to that account.
Returns the current (or new ) owner of the object.
Sets the permissions onto the object specified by $Path. Refer to Import() for details of the $Path. If the optional first parameter is not specified or is an empty string then the default path for the object is used. (If you created this object by specifying a path this is the default path).
Same as Set() except that it will recurse into all subcontainers (dirs, keys, etc). Setting permissions on all matching objects found in subcontainers.
If the path is to a file then wildcards can be used such as:
c:\temp\*.tmp
This will set permissions on all files ending in .tmp in the c:\temp directory. If recursion is specified then all .tmp files in c:\temp and all subdirectories will be set.
Paths that specify a registry key can not contain wildcards but can use recursion.
Recursion does not apply to network shares.
This is functionally the same as calling Set() passing in TRUE as the $Recurse parameter.
These four methods are equivalent to the Set() method except that the Set() will set all information on the object (DACL, SACL, Owner and Group). The methods will only set the specified information:
Returns the account name of the specified SID in the form of "domain\name". The passed in SID can either be a text or binary representation of a SID.
If the passed in SID is not valid then nothing is returned.
Returns a text based SID which represents the specified account. An example of a text SID is:
S-1-5-21-143984352-578909669-1869494990-1009
If a scalar variable is passed in as a second parameter then it is set to the binary representation of the same SID.
If the specified account is not valid then nothing is returned and if the optional second parameter is specified then it is cleared.
Either an account or index can be specified. The index number is the index number that is a result of calling Dump() or the Get() methods.
If -1 is passed as a parameter then all ACEs are removed.
Returns number of removed ACEs.
Removes ACEs from the SACL (Audit List). Either an account or index can be specified. The index number is the index number that is a result of calling Dump() or the Get() methods.
When specifying a path for the following methods:
new()
You can use one of two formats:
path
The first format (path) is simply the path of the object such as:
c:\temp\file.txt
The second format (object_type:path) is the path prepended by a object type and colon:
file:c:\temp\file.txt
There may be times when it will be necessary to specify the object_type:path format to resolve an ambiguous path. For example, if you make a call to:
$Dir = new Win32::Perms( "\\\\server\\share" );
The extension does not know if the script intends the path \\server\share to represent the permissions for the root directory at the UNC \\server\share or the permissions for the network share \\server\share.
By design the network share has higher precedent over a directory. Therefore the above code will reflect a network share, not a directory. If a script needs to force the extension to recognize the path as a directory specify the object type as in:
$Dir = new Win32::Perms( "dir:\\\\server\\share" );
Note that file: and dir: are synonyms; either can be used.