Book Home Perl for System AdministrationSearch this book

3.2. Windows NT/2000 User Identity

Now that we've explored the pieces of information that Unix systems cobble together to form a user's identity, let's take a look at the same topic for NT/2000 users. Much of this info is conceptually similar, so we'll dwell mostly on the differences between the two operating systems.

3.2.1. NT/2000 User Identity Storage and Access

NT/2000 stores the persistent identity information for a user in a database called the SAM (Security Accounts Manager), or directory, database. The SAM database is part of the NT/2000 registry living in %SYSTEMROOT%/system32/config. The files that make up the registry are all stored in a binary format, meaning normal Perl text manipulation idioms cannot be used to read or write changes to this database. It is theoretically possible to use Perl's binary data operators (i.e., pack( ) and unpack( )) with the SAM, providing you do so when NT/2000 is not running, but this way lies madness and misery.

Luckily, there are better ways to access and manipulate this information via Perl.

One approach is to call an external binary to interact with the OS for you. Every NT/2000 machine has a feature-bloated command called net that can add, delete, and view users. net is quirky and limited, and probably the method of last resort.

For example, here's the net command in action on a machine with two accounts:

C:\>net users

User accounts for \\HOTDIGGITYDOG
----------------------------------
Administrator            Guest
The command completed successfully.

It would be easy to parse the output of this program from Perl if we needed to. Besides net, there are other commercial packages that offer a command-line executable to perform similar tasks.

Another approach is to use the Perl module Win32::NetAdmin (bundled with the ActiveState Perl distribution) or one of the modules created to fill in the gaps in the functionality of Win32::NetAdmins. These modules include David Roth's Win32::AdminMisc (from http://www.roth.net), or Win32::UserAdmin (described in Ashley Meggitt and Timothy Ritchey's O'Reilly book, Windows NT User Administration, module found at ftp://ftp.oreilly.com/pub/examples/windows/winuser/ ).

I prefer Roth's Win32::AdminMisc for most user operations because it offers the largest grab-bag of system administration tools and Roth actively supports it in a number of online forums. Though the online documentation for this module is good, the best documentation for this module is the author's book, Win32 Perl Programming: The Standard Extensions (Macmillan Technical Publishing). It's a good book to have on hand in any case if you plan to write Win32-specific Perl programs.

Here's some example code that shows the users on the local machine and some details about them. It prints out lines that look similar to /etc/passwd under Unix:

use Win32::AdminMisc

# retrieve all of the local users
Win32::AdminMisc::GetUsers('','',\@users) or 
   die "Unable to get users: $!\n";

# get their attributes and print them
foreach $user (@users){
  Win32::AdminMisc::UserGetMiscAttributes('',$user,\%attribs) or 
    warn "Unable to get attrib: $!\n";
  print join(":",$user,
                 '*',
                 $attribs{USER_USER_ID},
                 $attribs{USER_PRIMARY_GROUP_ID},
                 '',
                 $attribs{USER_COMMENT},
                 $attribs{USER_FULL_NAME},
                 $attribs{USER_HOME_DIR_DRIVE}."\\".
                 $attribs{USER_HOME_DIR},
                 ''),"\n";
}

Finally, you can use the Win32::OLE module to access the Active Directory Service Interfaces (ADSI) functionality built in to Windows 2000 and installable on Windows NT 4.0. We'll go into this topic in great detail in Chapter 6, "Directory Services", so we won't look at an example here.

We'll see more Perl code to access and manipulate NT/2000 users later on, but for the time being let's return to our exploration of the differences between Unix and NT/2000 users.

3.2.2. NT/2000 User ID Numbers

User IDs in NT/2000 are not created by mortals, and they cannot be reused. Unlike Unix, where we simply picked a UID number out of the air, the OS uniquely generates the equivalent identifier in NT/2000 every time a user is created. A unique user identifier (which NT/2000 calls a Relative ID, or RID) is combined with machine and domain IDs to create a large ID number called a SID, or Security Identifier, which acts as a users UID. An example RID is 500, part of a longer SID which looks like this:

S-1-5-21-2046255566-1111630368-2110791508-500

The RID is a number we get back as part of the UserGetMiscAttributes( ) call we saw in our last code snippet. Here's the code necessary to print the RID for a particular user:

use Win32::AdminMisc;

Win32::AdminMisc::UserGetMiscAttributes('',$user,\%attribs);
print $attribs{USER_USER_ID},"\n";

You can't (by normal means) recreate a user after she/he/it is deleted. Even if you create a new user with the same name as the deleted user, the SID will not be the same. The new user will not have access to its predecessor's files and resources.

This is why some NT books recommend renaming accounts that are due to be inherited by another person. If a new employee is supposed to receive all of the files and privileges of a departing employee, they suggest renaming the existing account to preserve the SID rather than creating a new account, transferring files, and then deleting the old account. I personally find this method for account handoffs to be a little uncouth because it means the new employee will inherit all of the corrupted and useless registry settings of her or his predecessor. But it is the most expedient method, and sometimes that is important.

Part of this recommendation comes from the pain associated with transferring ownership of files. In Unix, a privileged user can say, "Change the ownership of all of these files so that they are now owned by the new user." In NT, however, there's no giving of ownership, only taking. Luckily there are two ways to get around this restriction and pretend we're using Unix's semantics. From Perl we can:

use Win32::Perms;
    
$acl  = new Win32::Perms(  );
$acl->Owner($NewAccountName);
$result = $acl->SetRecurse($dir);
$acl->Close(  );

3.2.3. NT/2000 Passwords

The algorithms used to obscure the passwords that protect access to a user's identity in NT/2000 and Unix are cryptologically incompatible. Once encrypted, you cannot transfer the encrypted password information from one OS to the other and expect to use it for password changes or account creations. As a result, two separate sets of passwords have to be used and/or kept in sync. This difference is the bane of every system administrator who has to administer a mixed Unix-NT/2000 environment. Some administrators get around this by using custom authentication modules, commercial or otherwise.

As a Perl programmer, the only thing you can do if you are not using custom authentication mechanisms is to create a system whereby the user provides her or his password in plain text. This plain text password then gets used to perform two separate password-related operations (changes, etc.), one for each OS.

3.2.4. NT Groups

So far in our discussion of user identity for both operating systems, I've been able to gloss over any distinction between storage of a user's identity on a local machine and storage in some network service like NIS. For the information we've encountered, it hasn't really mattered if that information was used on a single system or all of the systems in a network or workgroup. In order to talk cogently about NT/2000 user groups and their intersection with Perl, we unfortunately have to break from this convention. We'll concentrate primarily on Windows NT 4.0 groups. For Windows 2000, another layer of complication was added, so information about Windows 2000 groups has been banished to the sidebar "Windows 2000 Group Changes" later in this chapter.

On NT systems, a user's identity can be stored in one of two places: the SAM of a specific machine or the SAM of a domain controller. This is the distinction between a local user, who can only log into a single machine, and a domain user, who can log into any of the permitted machines that participate in that domain.

NT groups also come in two flavors, global and local. The difference between these two is not precisely what the name would lead you to expect. One is not just composed of domain users while the other of local users. Nor, as people with Unix backgrounds might expect, is one type effective on only one machine while the other is in effect globally on a network. Both of these descriptions are partially correct, but let's look at the full story.

If we start with the goals behind this mechanism's naming scheme and implementation it may make a little more sense. Here's what we're trying to accomplish:

Global and local groups allow us to do all of the above. The two sentence explanation is: global groups hold domain users only. Local groups hold local users and hold/import (the users of ) global groups.

We'll use a simple example to help show how this works. Say you have an NT domain for a university department that already has domain users created for all of the students, faculty, and staff. When a new research project called Omphaloskepsis is started up, the central system administration staff creates a new global group called Global-Omph People. This global group contains all of the domain users working on this project. As staff members and students join and leave the project, they are added or removed from the group.

A computer lab is set aside for the exclusive use of this research project. Local guest accounts are created on the machines in this lab for a couple of faculty members who are not part of the department (and hence not domain users). The system administrator for this lab does the following (via Perl, of course) to prevent all but project members from using these machines:

  1. Creates a local group on each machine called Local-Authorized Omphies.

  2. Adds the local guest accounts to this local group.

  3. Adds the global group Global-Omph People to this local group.

  4. Adds the user right (we'll discuss user rights in the next section) Log on Locally to the local group Local-Authorized Omphies.

  5. Removes the user right Log on Locally from all other unauthorized groups.

The result is that only the authorized local users and the users in the authorized global group can log on to the machines in this exclusive computer lab. A new user placed in the Global-Omph People group will instantly be granted permission to log on to these machines and nothing has to be changed on any of the machines themselves. Once you get a handle on the local/global group concepts, it's a pretty handy scheme.[4]

[4]For any Unix folks still reading along, a similar scheme can be set up by using NIS netgroups and special /etc/passwd entries on each machine in an NIS domain. See your machine's netgroup manual page for more details.

This scheme would be even handier if it didn't complicate our Perl programming. All the Perl modules we mentioned before follow the Win32 API lead by providing completely separate functions for local and global groups. For instance, with Win32::NetAdmin, we have:

GroupCreate( )

LocalGroupCreate(  )

GroupDelete( )

LocalGroupDelete(  )

GroupGetAttributes( )

LocalGroupGetAttributes(  )

GroupSetAttributes( )

LocalGroupSetAttributes(  )

GroupAddUsers( )

LocalGroupAddUsers(  )

GroupDeleteUsers( )

LocalGroupDeleteUsers(  )

GroupIsMember( )

LocalGroupIsMember(  )

GroupGetMembers( )

LocalGroupGetMembers(  )

This duality means your code may have to call two functions for the same operation. For example, if you need to obtain all of the groups a user may be in, you may have to call two functions, one for local groups and the other for global groups. The group functions above are pretty self-explanatory. See the online documentation and Roth's book for more details.

TIP

A quick tip found in Roth's book: your program must run with administrative privileges to access the list of local groups, but global group names are available to all users.

3.2.5. NT/2000 User Rights

The last different between Unix and NT/2000 user identity that we're going to address is the concept of a "user right." In Unix, the actions a user can take are either constrained by file permissions or by the superuser/non-superuser distinction. Under NT/2000, the permission scheme is more like superheroes. Users (and groups) can be imbued with special powers that become part of their identity. For instance, one can attach the user right Change the System Time to an ordinary user and that user will be able to effect the setting of the system clock on that machine.

Some people find the user rights concept confusing because they have attempted to use NT 4.0's heinous User Rights dialog in the User Manager or User Manager for Domains application. This dialog presents the information in exactly the opposite manner most people expect to see it. It shows a list of the possible user rights and expects you to add groups or users to a list of entities that already have this right. Figure 3-1 shows a screenshot of this UI example in action.

figure

Figure 3.1. The User Rights Policy dialog box from the NT4 User Manager

A more user-centric UI would offer a way to add or remove user rights to and from users, instead of the other way around. This is in fact how we will operate on rights using Perl.

One approach is to call the program ntrights.exe from the Microsoft NT Resource Kit. If you haven't heard of the resource kit, be sure to read the upcoming sidebar about it.

Using ntrights.exe is straightforward; we call the program from Perl like any other (i.e., using backticks or the system( ) function). In this case, we call ntrights.exe with a command line of the form:

C:\>ntrights.exe +r <right name> +u <user or group name> [-m \\machinename]

to give a right to a user or group (on an optional machine named machinename). To take that right away:

C:\>ntrights.exe -r <right name> +u <user or group name> [-m \\machinename]

Unix users will be familiar with the use of the + and - characters (as in chmod ), in this case used with the -r switch, to give and take away privileges. The list of right names like SetSystemtimePrivilege (can set the system time) can be found in the Microsoft NT Resource Kit documentation for the ntrights command.

A second, pure-Perl approach entails using the Win32::Lanman module by Jens Helberg, found at either ftp://ftp.roth.net/pub/ntperl/Others/Lanman/ or at http://jenda.krynicky.cz. Let's start off by looking at the process of retrieving an account's user rights. This is a multiple-step process; let's go over it step by step.

First, we need to load the module:

use Win32::Lanman;

Then, we need to retrieve the actual SID for the account we wish to query or modify. In the following sample, we'll get the Guest account's SID:

unless(Win32::Lanman::LsaLookupNames($server, ['Guest'], \@info)
   die "Unable to lookup SID: ".Win32::Lanman::GetLastError(  )."\n";

@info now contains an array of references to anonymous hashes, one element for each account we query (in this case, it is just a single element for Guest). Each hash contains the following keys: domain, domainsid, relativeid, sid, and use. We only care about sid for our next step. Now we can query the rights:

unless (Win32::Lanman::LsaEnumerateAccountRights($server, ${$info[0]}{sid},
                                                 \@rights);
   die "Unable to query rights: ".Win32::Lanman::GetLastError(  )."\n";

@rights now contains a set of names describing the rights apportioned to Guest.

Knowing the API (Application Program Interface) name of a user right and what it represents is tricky. The easiest way to learn which names correspond to which rights and what each right offers is to look at the SDK (Software Developement Kit) documentation found on http://msdn.microsoft.com. This documentation is easy to find because Helberg has kept the standard SDK function names for his Perl function names. To find the names of the available rights, we search the MSDN (Microsoft's Developer News) site for "LsaEnumerateAccountRights" and we'll find pointers to them quickly.

This information also comes in handy for the modification of user rights. For instance, if we want to add a user right to allow our Guest account to shut down the system, we could use:

use Win32::Lanman;

unless (Win32::Lanman::LsaLookupNames($server, ['Guest'], \@info))
  die "Unable to lookup SID: ".Win32::Lanman::GetLastError(  )."\n";


unless (Win32::Lanman::LsaAddAccountRights($server, ${$info[0]}{sid}, 
				                [&SE_SHUTDOWN_NAME]))
    die "Unable to change rights: ".Win32::Lanman::GetLastError(  )."\n"

In this case we found the SE_SHUTDOWN_NAME right in the SDK doc and used &SE_SHUTDOWN_NAME (a subroutine defined by Win32::Lanman), which returns the value for this SDK constant.

Win32::Lanman::LsaRemoveAccountRights( ), a function that takes similar arguments to those we used to add rights, is used to remove user rights.

Before we move on to other topics, it is worth mentioning that Win32::Lanman also provides a function that works just like User Manager 's broken interface described earlier. Instead of matching users to rights, we can match rights to users. If we use Win32::Lanman::LsaEnumerateAccountsWithUserRight( ) we can retrieve a list of SIDs that has a specific user right. This could be useful in certain select situations.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.