Administering Trustee Accounts

[Previous] [Next]

In the typical enterprise environment, a system administrator or an account operator creates user and group accounts on the system. Server software running in such environments can operate with active awareness of security and user identification without ever directly creating or managing trustee accounts. The software simply assumes that these accounts have already been created by another entity. As such, it would seem that many service developers would never need to programmatically create trustee accounts.

Although this may be true, you will find that Windows 2000 security is extremely flexible. You can make creative use of trustees that aren't directly associated with a specific user nor would ever be used by a user to log on to a system. For example, Windows 2000 allows you to limit the access of a user account to its original rights combined with the rights of a trustee determined by your software, a technique known as creating a restricted token. (Restricted tokens are discussed in detail in Chapter 11.) As such, your software can create accounts that your system administrator would not have the ability (or the desire) to create manually.

Furthermore, if you are writing server software that directly supports creation of an account such as an Internet gaming community or an online banking front end, your service will have to be able to create and manage trustee accounts programmatically. Windows 2000 provides two sets of functions that allow you to do this:

  • Net API The "Net" functions allow you to create and manage user accounts on any Windows 2000 system, including workstation and server systems, whether or not the system is a domain controller.
  • Active Directory Services Interface (ADSI) ADSI is a set of COM objects that allows you to administer Active Directory on Windows 2000. Active Directory is the repository for all user and group accounts (among many other objects) on a Windows 2000 domain and is a very important addition to Windows. Your software can use the ADSI objects to create, delete, and otherwise administer user and group objects on a domain controller's Active Directory.

Which set of APIs you use depends on your needs. Active Directory is an enterprise management tool, and as such, the ADSI interface to Active Directory should be used if your software is creating user accounts in an enterprise environment. However, if your service software is creating trustee accounts that are only local to a single server that may not be a domain controller, you might find it much simpler and more direct to use the Net functions.

NOTE
ADSI objects will allow your software to administer users and groups on systems that are not domain controllers, but because such systems do not maintain an Active Directory, the ADSI objects simply pass through to the Net functions. Similarly, if the Net functions are used to add, remove, or otherwise administer trustees on a domain controller, they will automatically use the ADSI objects to modify Active Directory on that machine.

This chapter focuses on the Net functions, since they are the most commonly used. However, corresponding ADSI components that can be used to perform similar tasks are listed as notes so that you can look up the particular topic in the Platform SDK documentation.

Understanding the Net Functions

The Net API is feature rich and contains many functions and structures that are defined to facilitate the management of trustee accounts. The Net functions allow you to manage user accounts, local group accounts (which only have relevance on a single machine), and domain or global group accounts, which exist on domain controllers and function for the entire domain. These functions are logically and consistently designed, and we can learn a lot by knowing a few rules.

NOTE
If you are writing a source module that will be calling a Net function, you need to take a couple of preliminary steps with your source module and projects. Unlike most API functions in Win32, the Net functions are not declared in a header file that is included in Windows.h. Therefore, you must include the Lm.h header file at the beginning of your module. Similarly, you must add the NetApi32.lib file to the list of library files linked with your project.

One positive feature of the Net functions is that they are laid out in a consistent manner. For this reason, I'm able to provide some general rules for understanding these functions without covering every single function in the API set:

  • Net functions are named consistently, using the pattern Net+[Trustee Type]+[Action to Perform]. Examples: NetUserAdd, NetLocalGroupGetInfo, and NetUserSetGroups.
  • Net functions support only Unicode strings. If your project is built for ANSI, you will have to make a Unicode copy of each string buffer before passing it to a Net function. (This is just another reason to always use Unicode when writing server software for Windows 2000.)
  • Net functions identify trustee accounts by system name and account name. You can use these functions to manage trustees on a remote system, assuming you have sufficient rights on the remote machine.
  • Net functions implement sets of structures that are used to report trustee information to your software or that can be used by your software to set trustee information. Because of the many structures involved, this is a potentially confusing feature of the Net functions, though very logical and useful once understood.

Here's an example: If you were using the NetLocalGroupAdd function (discussed in detail shortly) to create a local group, you have the option of filling in and passing an instance of the LOCALGROUP_INFO_0 structure, which contains only a group name, or the LOCALGROUP_INFO_1 structure, which allows you to specify a group name and a comment string. The choice is yours, and each Net function allows you to select the structure to use by passing a parameter called level. In our example, LOCALGROUP_INFO_0 is a level 0 structure and LOCALGROUP_INFO_1 is a level 1 structure.

NOTE
Because a single function can take or return more than one structure type defined by the system, the Net functions define the type as PBYTE and rely on you to pass the proper information, using the proper casting. If you are passing a structure to a function, you should pass a pointer to the specific structure and cast it to a PBYTE. If a Net function is returning a structure to you, the function will expect a pointer to a PBYTE, which it fills in, and you can later cast to the appropriate structure type.

In this chapter, I will be referring to sets of structures where I replace the level indicator with an asterisk character (*) so that I can generalize information without detailing each of the many structures available for use. For example, the two structures mentioned in the last paragraph are both in the set of LOCALGROUP_INFO_* structures. I will be defining some of the more common structures that are used, as well as some of the larger ones that include members of the shorter structures. Detailed explanations of all the structures can be found in the Platform SDK documentation.

Table 9-1 lists the structure sets defined for trustee management and the functions with which they can be used.

Table 9-1. Structure sets for managing trustees

Structure Set Use and Functions
GROUP_INFO_* These structures are used to retrieve information for global or domain groups, as well as to set information for global groups. They are used when creating groups as well as when working with existing groups.
Functions:
NetGroupAdd, NetGroupEnum, NetGroupGetInfo, NetGroupSetInfo
GROUP_USERS_INFO_* These structures are used when managing user membership in global or domain groups. They can be used for setting or getting user information for a group.
Functions:
NetGroupGetUsers, NetGroupSetUsers, NetUserGetGroups, NetUserSetGroups
LOCALGROUP_INFO_* These structures are used to retrieve and set information for local groups. They are used with existing groups as well as when creating new local groups.
Functions:
NetLocalGroupAdd, NetLocalGroupEnum, NetLocalGroupGetInfo, NetLocalGroupSetInfo
LOCALGROUP_MEMBERS_INFO_* These structures are used to set and retrieve member lists for local groups.
Functions:
NetLocalGroupAddMembers, NetLocalGroupDelMembers, NetLocalGroupGetMembers, NetLocalGroupSetMembers
LOCALGROUP_USERS_INFO_* This set of structures contains only a single structure called LOCALGROUP_USERS_INFO_0, which is used in calls to NetUserGetLocalGroups to get a list of all the local groups of which a user account is a member.
Functions:
NetUserGetLocalGroups
USER_INFO_* This set of structures contains, by far, the largest number of structures, due to the wealth of information you can associate with a user account in Windows 2000. These structures are used when creating users as well as when getting and setting information for existing user accounts.
Functions:
NetUserAdd, NetUserEnum, NetUserSetInfo, NetUserGetInfo

When a Net function is returning information about a trustee to your software, it will allocate a buffer for you. As I indicated before, the Net functions require that you pass the address of a pointer to the appropriate structure type, cast to a PBYTE*. When you are finished with the buffer returned by the system, you should always pass the buffer to NetApiBufferFree, which will free the buffer. This function is defined as follows:

 NET_API_STATUS NetApiBufferFree(PVOID pvBuffer); 

When setting information for a trustee, a Net function might determine that your software did not properly set one of the members of the structure. In this case, the "Set" function (e.g., NetUserSetInfo) will return ERROR_INVALID_PARAMETER. To determine the cause of the error, you can use the function's error parameter that will be filled in with a predefined value indicating the first structure member that caused the error. If you are not interested in this information, you can pass NULL to the "Set" function.

With this general knowledge of the Net functions, you'll find that learning the details is a breeze. So let's start with creating trustee accounts.

Creating Trustee Accounts

User and group accounts are similar in terms of their roles in controlling access to securable objects in the system and privileged functions of the system. The system allows you to assign and deny rights to user and group accounts interchangeably. However, a human user can utilize a user account to log on to a system, whereas a group account cannot be used in this way. This association with a flesh and bone user makes it necessary for the system to maintain a great deal of information for a user account that is not stored for group accounts.

NOTE
Domain or global group accounts have much in common with local group accounts, in concept and in software. Although I'll be discussing the management details of local groups from this point forward, many of the concepts also apply when programmatically administering global groups. For a complete discussion of the global group functionality provided by the Net API, see the Platform SDK documentation for the set of functions with the "NetGroup" prefix.

To create a local group account, you can use the NetLocalGroupAdd function, prototyped as follows:

 NET_API_STATUS NetLocalGroupAdd(    PCWSTR pstrServername,     DWORD  dwLevel,     PBYTE  pbBuf,    PDWORD pdwParmErr); 

The first parameter is the name of the system on which you wish to create the group account. Passing NULL for pstrServername indicates that you wish to create a group on the local system. The dwLevel parameter indicates the type of structure you will be passing as a reference for the pbBuf parameter. NetLocalGroupAdd uses the LOCALGROUP_INFO_* set of structures discussed in Table 9-1. You can pass either a 0 or a 1 as the level value for this function. The definitions of both of these structures are as follows:

 typedef struct _LOCALGROUP_INFO_0 {    PWSTR   lgrpi0_name; } LOCALGROUP_INFO_0; typedef struct _LOCALGROUP_INFO_1 {    PWSTR   lgrpi0_name;    PWSTR   lgrpi0_comment; } LOCALGROUP_INFO_1; 

Both structures contain a pointer to a string that is used as the group's name, and the LOCALGROUP_INFO_1 structure contains an additional pointer to a comment string. You can use either structure type when creating groups.

The final parameter to pass to NetLocalGroupAdd is pdwParmErr, which should be the pointer to a DWORD. In the case of a bad parameter, the system returns a value indicating which parameter was bad in the variable referenced in the pdwParmErr parameter. The value returned in the pdwParmErr is valid only if the return value of NetLocalGroupAdd is equal to ERROR_INVALID_PARAMETER. Table 9-2 lists the possible values. If you do not wish to receive this information, you can pass NULL for pdwParmErr.

Table 9-2. Possible values that can be returned in the pdwParamErr parameter

Value Description
LOCALGROUP_NAME_PARMNUM This value indicates that you have specified an invalid group name for your new group.
LOCALGROUP_COMMENT_PARMNUM This value indicates that the comments value was invalid in your call to NetLocalGroupAdd.

You should always check the return value of NetLocalGroupAdd to insure that the system has returned NERR_Success. Otherwise, the system has failed to create a new local group.

NOTE
The ADSI set of components can also be used to create group accounts. Active Directory is organized as a hierarchy of objects, and a group object can be created in any container object. You can create a group object by using the Create method of the IADsContainer interface. Once you have created the object, you can use the QueryInterface method to obtain a pointer to the IADsGroup interface, which can be used to further manage your new group object. See the Platform SDK documentation for more details.

Creating a user account is similar to creating a group account; however, you can provide the system with significantly more information for user accounts. You can use the NetUserAdd function to create new users on a Windows 2000 system.

 NET_API_STATUS NetUserAdd(    PCWSTR pstrServername,    DWORD  dwLevel,    PBYTE  pbBuf,    PDWORD pdwParmErr); 

Notice that the parameter list for NetUserAdd is exactly the same as that for NetLocalGroupAdd. The differences lie in the structure types passed as the pbBuf parameter. You can pass a pointer to a level 1, 2, or 3 USER_INFO_* structure to create your new user. Here are the definitions of USER_INFO_1 (the simplest structure that you can use with NetUserAdd) and USER_INFO_3 (the most comprehensive structure you can use).

 typedef struct _USER_INFO_1 {    PWSTR   usri1_name;    PWSTR   usri1_password;    DWORD   usri1_password_age;    DWORD   usri1_priv;    PWSTR   usri1_home_dir;    PWSTR   usri1_comment;    DWORD   usri1_flags;    PWSTR   usri1_script_path; } USER_INFO_1 ; typedef struct _USER_INFO_3 {    PWSTR   usri3_name;    PWSTR   usri3_password;    DWORD   usri3_password_age;    DWORD   usri3_priv;    PWSTR   usri3_home_dir;    PWSTR   usri3_comment;    DWORD   usri3_flags;    PWSTR   usri3_script_path;    DWORD   usri3_auth_flags;    PWSTR   usri3_full_name;    PWSTR   usri3_usr_comment;    PWSTR   usri3_parms;    PWSTR   usri3_workstations;    DWORD   usri3_last_logon;    DWORD   usri3_last_logoff;    DWORD   usri3_acct_expires;    DWORD   usri3_max_storage;    DWORD   usri3_units_per_week;    PBYTE   usri3_logon_hours;    DWORD   usri3_bad_pw_count;    DWORD   usri3_num_logons;    PWSTR   usri3_logon_server;    DWORD   usri3_country_code;    DWORD   usri3_code_page;    DWORD   usri3_user_id;    DWORD   usri3_primary_group_id;    PWSTR   usri3_profile;    PWSTR   usri3_home_dir_drive;    DWORD   usri3_password_expired; } USER_INFO_3 ; 

As you can see, you could choose to pass a great deal of information to the system when creating a user. For a detailed description of each member of the USER_INFO_3 structure, as well as the USER_INFO_2 structure, see the Platform SDK documentation. For the purposes of this discussion, it is only necessary to describe the members found in the USER_INFO_1 structure. They are listed in Table 9-3.

Table 9-3. USER_INFO_1 members

Member Description Value Returned in pdwParmErr if ERROR_INVALID_PARAMETER
usri1_name Points to a buffer containing the Unicode user name for the user account to be created. This member must contain a valid pointer or NetUserAdd will return ERROR_INVALID_PARAMETER. USER_NAME_PARMNUM
usri1_password Points to a buffer containing the Unicode password for the user account to be created. Passwords are limited to 14 characters and must also conform to the rules set forth by the system to which you are adding a user account. USER_PASSWORD_PARMNUM
usri1_password_age NetUserAdd ignores this member. USER_PASSWORD_AGE_PARMNUM
usri1_priv When you create a user, this member must be set to the value USER_PRIV_USER. Any other value will cause NetUserAdd to return ERROR_INVALID_PARAMETER. USER_PRIV_PARMNUM
usri1_home_dir A user can have a home directory defined for his account. If you wish to associate a home directory with a new user, this member should point to a buffer containing a Unicode string indicating the path to the directory. You can assign NULL to this member. USER_HOME_DIR_PARMNUM
usri1_comment This member points to a buffer containing the Unicode comment for the new user. If you do not wish to associate a comment with your new user account, you can assign NULL to this member. USER_COMMENT_PARMNUM
usri1_flags This member can take a combination of flags indicating the functionality of the new user account. (See Table 9-4.) USER_FLAGS_PARMNUM
usri1_script_path The system will execute the .exe, .cmd, or .bat file associated with your user when she logs on to a system. You can use this member to set the path of this log-on script. USER_SCRIPT_PATH_PARMNUM

Any combination of the flags listed in Table 9-4 can be assigned to the usri1_flags member of USER_INFO_1 when calling NetUserAdd. Other flags are available to specify additional features. See the Platform SDK documentation for information on these other flags.

Table 9-4. USER_INFO_1 flags of interest

Flag Description
UF_ACCOUNTDISABLE Creates an account that is disabled.
UF_PASSWD_NOTREQD Creates an account that does not require a password. Note that there might be a policy in place for the system or domain that requires all accounts to have passwords.
UF_PASSWD_CANT_CHANGE Creates an account for which the user is unable to change his password. An administrator of the system can still change the password.
UF_DONT_EXPIRE_PASSWD The password for this user will never expire.
UF_NOT_DELEGATED This account cannot be delegated. (See Chapters 11 and 12 for discussions on delegation.)
UF_SMARTCARD_REQUIRED Requires the user to use a smart card to log on with this new account.
UF_TRUSTED_FOR_DELEGATION This account can be delegated. (See Chapters 11 and 12 for discussions on delegation.)

Despite what it may seem, the minimum coding requirement to create a new user account using NetUserAdd is actually quite reasonable. The following code demonstrates the simplest possible case:

 BOOL CreateUser(PWSTR pszSystem, PWSTR pszName, PWSTR pszPassword) {    USER_INFO_1 userInfo = { 0 };    userInfo.usri1_name = pszName;    userInfo.usri1_password = pszPassword;    userInfo.usri1_priv = USER_PRIV_USER;    NET_API_STATUS netStatus =        NetUserAdd(pszSystem, 1, (PBYTE) &userInfo, NULL);    return(netStatus == NERR_Success); } 

As you can see, this is fairly trivial code to create a user on a given system with a given name and password. Calling this sample function as follows would create a user named "MrMan" with the password of "HowDoYouDo" on the local system:

 CreateUser(NULL, L"MrMan", L"HowDoYouDo"); 

NOTE
The ADSI set of components can also be used to create user accounts. Like group objects, user objects can be created in any container object. You can create a user object using the Create member of the IADsContainer interface. Once you have created the object, you can use the QueryInterface method to obtain a pointer to the IADsUser interface, which can be used to further manage your new user object. See the Platform SDK documentation for more details on this topic.

Setting User and Group Information

The system also provides Net functions for getting and setting information for a user or group account once it has been created. You will see that the calling style for these functions is similar to that of NetGroupAdd and NetUserAdd.

To get and set information for a user account, you can use the NetUserGetInfo and NetUserSetInfo functions prototyped as follows:

 NET_API_STATUS NetUserGetInfo(    PCWSTR pszServerName,    PCWSTR pzUsername,    DWORD  dwLevel,    PBYTE  *ppbBuf); NET_API_STATUS NetUserSetInfo(    PCWSTR pszServerName,    PCWSTR pszUsername,    DWORD  dwLevel,    PBYTE  pbBuf,    PDWORD pdwParmErr); 

As you can see, the NetUserGetInfo and NetUserSetInfo functions have nearly the exact same parameter list as NetUserAdd. In fact, the only additional parameter is pstrUsername, which indicates the name of the user for whom to get or set information.

Note also that for NetUserGetInfo, the familiar pbBuf parameter has been modified to become the address of a pointer to a buffer. As I pointed out earlier, this is because the system actually allocates the requested structure for you and returns a pointer to the new buffer in the variable pointed to by the ppbBuf parameter.

The dwLevel parameter indicates the level of the USER_INFO_* structure that you will use in your call to NetUserGetInfo or NetUserSetInfo. These functions accept USER_INFO_1 or USER_INFO_3, as defined above, as well as several other USER_INFO_* structures that NetUserAdd does not support. For a full description of these structures, refer to the Platform SDK documentation.

NOTE
When setting user information, you should use a structure level that includes only the information you want to set or accepts NULL as a member value. For example, to set just the password for a user account, use the USER_INFO_1003 structure type because it includes a member only for the user's new password.

The following two example functions demonstrate how to use NetUserSetInfo and NetUserGetInfo to set and get information for a user account. The first function shows how to set the password for a user account. The second function shows how to retrieve the comment field for a user. The principles used in these functions can be extrapolated to get or set any valid information for a user account.

 BOOL SetUserPassword(PWSTR pszSystem, PWSTR pszName, PWSTR pszPassword) {    USER_INFO_1003 userInfo = { 0 };    userInfo.usri1003_password = pszPassword;    NET_API_STATUS netStatus =        NetUserSetInfo(pszSystem, pszName, 1003, (PBYTE) &userInfo, NULL);    return (netStatus == NERR_Success); } BOOL GetUserComment(PWSTR pszSystem, PWSTR pszName, PWSTR pszComment,     int nBufLen) {    USER_INFO_10 *puserInfo;    BOOL fSuccess = FALSE;    NET_API_STATUS netStatus =        NetUserGetInfo(pszSystem, pszName, 10, (PBYTE*) &puserInfo);    if (netStatus == NERR_Success) {       if (nBufLen > lstrlen(puserInfo->usri10_comment)) {          lstrcpy(pszComment, puserInfo->usri10_comment);          fSuccess = TRUE;       }       NetApiBufferFree(puserInfo);    }    return(fSuccess); } 

NOTE
All successful calls to NetUserGetInfo must have a matching call to NetApiBufferFree to free the buffer returned by NetUserGetInfo. The NetApiBufferFree function is defined to take a single parameter, which is the pointer to the buffer to be freed.

As you may already have guessed, the system also provides similar functions for getting and setting information for group trustee accounts. To get information for a trustee, you can call NetLocalGroupGetInfo, and to set information for a group, call NetLocalGroupSetInfo. These functions are defined as follows:

 NET_API_STATUS NetLocalGroupGetInfo(    PCWSTR pservername,    PCWSTR groupname,    DWORD  dwLevel,    PBYTE  *ppbBuf); NET_API_STATUS NetLocalGroupSetInfo(    PCWSTR servername,    PCWSTR groupname,    DWORD  dwLevel,    PBYTE  pbBuf,    PDWORD pdwParmErr); 

As you can see, calling these functions will be almost identical to calling the functions to get and set information for user accounts. The difference is that you'll be dealing with group information and the LOCALGROUP_INFO_* structures discussed previously.

Although a name and a comment might seem like a minimal amount of information to associate with group accounts, remember that group member information is also stored with group accounts. However, I will defer this topic to a later part of this chapter because it closely relates to the topic of SIDs, which I will be discussing later in the section "Understanding SIDs."

Enumerating Users and Groups

It is often desirable to obtain a list of existing users or groups on a given system. Once again, the Net API provides two similar functions to perform these tasks. You can use NetLocalGroupEnum to obtain a list of the groups on a system. Similarly you can use NetUserEnum to get a list of user accounts on a system. These functions are defined as follows:

 NET_API_STATUS NetLocalGroupEnum(    PCWSTR     pszServerName,    DWORD      dwLevel,    PBYTE*     ppbBuf,    DWORD      dwPrefMaxLen,    PDWORD     pdwEntriesRead,    PDWORD     pdwTotalEntries,    PDWORD_PTR pdwResumeHandle); NET_API_STATUS NetUserEnum(    PCWSTR     pszServerName,    DWORD      dwLevel,    DWORD      dwFilter,    PBYTE*     ppbBuf,    DWORD      dwPrefMaxLen,    PDWORD     pdwEntriesRead,    PDWORD     pdwTotalEntries,    PDWORD_PTR pdwResumeHandle); 

These functions are similar, but with a few notable differences. The NetUserEnum function will be returning structures from the USER_INFO_* set of structures, while NetLocalGroupEnum will be returning structures from the LOCALGROUP_INFO_* set of structures. Additionally, NetUserEnum allows you to specify a filter to narrow the scope of the list of user accounts returned. Passing 0 for the filter parameter of NetUserEnum indicates no filtering, so any account types can be returned. You will typically want to pass FILTER_NORMAL_ACCOUNT for the filter parameter; however, you could also pass any of the following values listed in Table 9-5.

Table 9-5. Filter values that can be passed for NetUserEnum's filter parameter

Value Meaning
FILTER_NORMAL_ACCOUNT Returns global user account data on a system
FILTER_TEMP_DUPLICATE_ACCOUNT Returns local user account data on a domain controller
FILTER_INTERDOMAIN_TRUST_ACCOUNT Returns domain trust account data on a domain controller
FILTER_WORKSTATION_TRUST_ACCOUNT Returns member server or workstation account data on a domain controller
FILTER_SERVER_TRUST_ACCOUNT Returns domain controller account data on a domain controller

Apart from the filter parameter and the structures returned, the use of NetUserEnum and NetLocalGroupEnum is identical. Like the functions we've looked at earlier, the pszServerName parameter indicates the system for which we wish to enumerate trustee accounts. The dwLevel parameter indicates the version of the LOCALGROUP_INFO_* or USER_INFO_* structure that we will be associating with the ppbBuf parameter.

You should pass the address of a pointer variable for the type of structure requested as the ppbBuf parameter. Depending on whether NetUserEnum and NetLocalGroupEnum are used, the system will allocate a buffer to hold an array of USER_INFO_* or LOCALGROUP_INFO_* structures, where ppbBuf is a pointer to the address of the buffer. The buffer will be the accounts enumerated by the function. Remember that like NetUserGetInfo, since the system allocates a buffer for you, that buffer must be freed by calling NetApiBufferFree.

You should indicate the preferred maximum size of the buffer returned by the function you are calling by passing the size in bytes as the dwPrefMaxLen parameter. If you would like the system to allocate as large a buffer as possible, you can pass MAX_PREFERRED_LENGTH. In this case, the system will commonly complete the enumeration in a single call to the function.

You might ask why you would ever choose to limit the size of the buffer returned by a "Net*Enum" function and thereby necessitate multiple calls to the function. There are a few reasons: A small number of accounts is not guaranteed, and a large number of accounts could overflow the largest possible buffer allocated by the system. Additionally, you might want your application to regain control periodically if the enumeration is a lengthy process.

NOTE
Because you can't be sure how much memory the system will be able to allocate, your code should be able to handle a case where multiple calls to the enumeration function are required, whether or not you selected the MAX_PREFERRED_LENGTH value for the buffer length.

The system returns the number of entries read to the variable pointed to by the pdwEntriesRead parameter. The total number of remaining available entries (including those returned) is stored in the variable pointed to by the pdwTotalEntries parameter.

The final parameter is an opaque value returned by the system that can be passed back to the system in a subsequent call to an enumeration function to continue receiving account information. You should set the variable pointed to by the pdwResumeHandle parameter to 0 on your initial call to the enumeration function, and then you should not modify the returned value.

The enumeration functions will return ERROR_MORE_DATA if they have successfully returned data but there are more accounts to be enumerated. When the last accounts available have been returned, the enumeration functions will return NERR_Success.

The following code demonstrates how to use NetLocalGroupEnum to enumerate the groups on the local system and print them to a console window:

 void PrintLocalGroups() {    ULONG_PTR lResume = 0;    ULONG  lTotal = 0;    ULONG  lReturned = 0;    ULONG  lIndex = 0;    NET_API_STATUS netStatus;    LOCALGROUP_INFO_0* pinfoGroup;    do {       netStatus = NetLocalGroupEnum(NULL, 0, (PBYTE*) &pinfoGroup,          MAX_PREFERRED_LENGTH, &lReturned, &lTotal, &lResume);       if ((netStatus == ERROR_MORE_DATA) ||            (netStatus == NERR_Success)) {          for (lIndex = 0; lIndex < lReturned; lIndex++) {             wprintf(L"%s\n", pinfoGroup[lIndex].lgrpi0_name);          }          NetApiBufferFree(pinfoGroup);       }    } while (netStatus == ERROR_MORE_DATA); } 

This function would require only minor modifications to make it enumerate the users on a system using NetUserEnum.

Destroying Users and Groups

Before switching gears and discussing the system structure for identifying trustee accounts, SIDs, I'd like to wrap up the discussion of user and group management by talking about how to destroy user and group accounts.

The reason I bring up the SID at this point is because it does have some relevance to the destruction of a trustee account. Although the Net functions allow you to deal with trustee accounts in terms of their names, the rest of the system largely ignores the name associated with a trustee account. Instead, the system uses the SID binary value associated with an account to identify an account. What does this have to do with destroying trustee accounts?

If you create a trustee with the name "JClark" and then I log on using this account and create a file, the system maintains that I am the owner of this object. However, if you destroy my user account and then create a new user account named "JClark," the system will assign a different SID value and therefore not recognize the new account as the owner of the old file object.

That said, you can use the following function to delete a user account:

 NET_API_STATUS NetUserDel(    PCWSTR pszServerName,    PCWSTR pszUsername); 

As you can see, this simple function takes only a system name and a name for a user account as parameters. The pszServerName parameter can be NULL, indicating the local system. This function returns NERR_Success if the function succeeds.

The Net API implements a similar function for deleting groups, which is defined as follows:

 NET_API_STATUS NetLocalGroupDel(    PCWSTR pszServerName,    PCWSTR pszGroupname); 

NOTE
You can also use the ADSI objects to enumerate and delete user and group accounts, as well as to get and set user and group information. See the Platform SDK documentation for discussion of the IADsUser and IADsGroup interfaces, as well as the IDirectorySearch interface used for searching and enumerating.

Managing Group Membership

Group membership information can be modified in several ways, but first we need to learn a couple of ways to retrieve it. The first method uses the NetUserGetLocalGroups function to retrieve a list of groups of which a given user is a member. The second method uses the NetLocalGroupGetMembers function to retrieve the set of members who are associated with a single group.

Using the NetUserGetLocalGroups Function

The NetUserGetLocalGroups function has the following prototype:

 NET_API_STATUS NetUserGetLocalGroups(    PCWSTR pszServerName,    PCWSTR pszUsername,    DWORD  dwLevel,    DWORD  dwFlags,    PBYTE* ppbBuf,    DWORD  dwPrefMaxLen,    PDWORD pdwEntriesRead,    PDWORD pdwTotalEntries); 

As you can see, this function looks similar to the NetLocalGroupEnum and NetUserEnum functions, which we have already discussed. In fact, the primary difference is that this function uses the LOCALGROUP_USERS_INFO_* set of structures (of which LOCALGROUP_USERS_INFO_0 exists so far).

The only valid values for dwFlags are LG_INCLUDE_INDIRECT, which indicates that NetUserGetLocalGroups should also return groups for which the user (indicated by pszUsername) is indirectly a member, or 0, which indicates that the function should return only groups of which the user is directly a member. Indirect membership can happen when pszUsername is a member of a global or domain group that is in turn a member of a local group.

You can pass MAX_PREFERRED_LENGTH for the dwPrefMaxLen parameter, but either way you must pay attention to the values returned via the pdwEntriesRead parameter and the pdwTotalEntries parameter to be sure that all possible entries were returned.

Unlike with the previous enumeration functions, there is no way to call NetUserGetLocalGroups again to continue enumeration. It is unlikely that you will face a situation in which you will be enumerating more than a couple of dozen groups, whereas enumerating users could feasibly return thousands of account entries.

Using the NetLocalGroupGetMembers function

The second way to enumerate membership information is to retrieve the set of members associated with a single group. You should use the NetLocalGroupGetMembers function to do this:

 NET_API_STATUS NetLocalGroupGetMembers(    PCWSTR     pszServerName,    PCWSTR     pszLocalGroupName,    DWORD      dwLevel,    PBYTE*     ppbBuf,    DWORD      dwPrefMaxLen,    PDWORD     pdwEntriesRead,    PDWORD     pdwTotalEntries,    PDWORD_PTR pdwResumeHandle); 

This function takes a server name and the name of a local group as its first parameters. The familiar dwLevel parameter indicates which level of the LOCALGROUP_MEMBERS_INFO_* set of structures you want returned via the ppbBuf parameter.

The LOCALGROUP_MEMBERS_INFO_* structures allow you to deal with your trustees in terms of their textual names and domain names, or in terms of their SIDs, which are covered in detail in the next section. Here are the definitions for the LOCALGROUP_MEMBERS_INFO_0 and LOCALGROUP_MEMBERS_INFO_3 structures:

 typedef struct _LOCALGROUP_MEMBERS_INFO_0 {    PSID   lgrmi0_sid; } LOCALGROUP_MEMBERS_INFO_0; typedef struct _LOCALGROUP_MEMBERS_INFO_3 {    PWSTR   lgrmi3_domainandname; } LOCALGROUP_MEMBERS_INFO_3; 

NOTE
It is important to know that many security functions require SID values when dealing with trustees. Also, the local group member manipulation functions allow you to retrieve trustee entries in terms of SIDs.

The PSID type used in LOCALGROUP_MEMBERS_INFO_0 indicates a pointer to a SID structure. Remember that either of these structures can be used with NetLocalGroupGetMembers and should depend on the needs of your software.

The dwPrefMaxLen, pdwEntriesRead, pdwTotalEntries, and pdwResumeHandle parameters are the values for preferred buffer size, entries read, remaining entries, and resume enumeration. These parameters work in exactly the same way as the parameters of the same name for the NetUserEnum and NetLocalGroupEnum functions already discussed. In fact, the sample PrintLocalGroups function from the section on NetLocalGroupEnum can easily be modified as follows for use with NetLocalGroupGetMembers:

 void PrintLocalGroupMembers(WCHAR *pszGroup) {    ULONG_PTR lResume = 0;    ULONG  lTotal = 0;    ULONG  lReturned = 0;    ULONG  lIndex = 0;    NET_API_STATUS netStatus;    LOCALGROUP_MEMBERS_INFO_3* pinfoMembers;    do {       netStatus = NetLocalGroupGetMembers(NULL, pszGroup, 3,          (PBYTE*) &pinfoMembers, MAX_PREFERRED_LENGTH,          &lReturned, &lTotal, &lResume);       if ((netStatus == ERROR_MORE_DATA) ||           (netStatus == NERR_Success)) {          for (lIndex = 0; lIndex < lReturned; lIndex++) {             wprintf(L"%s\n",                pinfoMembers[lIndex].lgrmi3_domainandname);          }          NetApiBufferFree(pinfoMembers);       }    } while (netStatus == ERROR_MORE_DATA); } 

To set the members of a local group, use the NetLocalGroupSetMembers function:

 NET_API_STATUS NetLocalGroupSetMembers(    PCWSTR pszServerName,    PCWSTR pszGroupName,    DWORD  dwLevel,    PBYTE  pbBuf,    DWORD  dwTotalEntries); 

The pszServerName and pszGroupName parameters indicate the system name and local group for which you are setting the members. The dwLevel parameter indicates which level of the LOCALGROUP_MEMBERS_INFO_* set of structures you wish to use. You can pass a 0 or a 3, indicating LOCALGROUP_MEMBERS_INFO_0 or LOCALGROUP_MEMBERS_INFO_3, respectively, both of which are defined above. This means that you can choose to set membership in terms of trustee account name strings or SIDs.

You should pass a pointer to an array of the selected structure type as the pbBuf parameter, and the number of entries in the array as the dwTotalEntries parameter. If the function succeeds, the list of trustees represented by the array passed as pbBuf will be the new member list for the group. Remember that this list of trustees will replace any current members of the group. If the function succeeds, it will return NERR_Success.

Other Useful Functions

Now you know how to retrieve and set all the member trustees of a local group. I'd like to point out two useful functions that allow you to add only a specific list of trustees to and delete only a specific list of trustees from the current membership of a group. These functions are NetLocalGroupAddMembers and NetLocalGroupDelMembers:

 NET_API_STATUS NetLocalGroupAddMembers(    PCWSTR pszServerName,    PCWSTR pszGroupName,    DWORD  dwLevel,    PBYTE  pbBuf,    DWORD  dwTotalEntries); NET_API_STATUS NetLocalGroupDelMembers(    PCWSTR pszServerName,    PCWSTR pszGroupName,    DWORD  dwLevel,    PBYTE  pbBuf,    DWORD  dwTotalEntries); 

Use these functions in exactly the same way as NetLocalGroupSetMembers. For examples, see the TrusteeMan sample application described later in this chapter.



Programming Server-Side Applications for Microsoft Windows 2000
Programming Server-Side Applications for Microsoft Windows 2000 (Microsoft Programming)
ISBN: 0735607532
EAN: 2147483647
Year: 2000
Pages: 126

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net