首页 服务器 编程 必备知识 搜索引擎 圩日手册
站内搜索
最近浏览
推荐文章
热文排行

Windows2000 服务器端应用程序开发设计指南-信任成员的管理


Microsoft的开发者已经完成Microsoft Windows 2000安全性特色的设计工作,这些安全性特色比大多数人所习惯的环境更复杂且更有弹性。事实上,若加上适当的管理和软件开发,Windows 2000是现今最先进且安全的操作环境之一。然而,由于如此复杂而产生的大量细节以及纷繁难懂之处,将使得对Windows安全性没经验的大多数开发者感到气馁。就本书的读者和我而言,这个安全性的主题虽然意味着一个挑战,但应付挑战之后报偿将是具有安全性的伺服端软件和实际的成就感。
在「安全性」这个主题下,Windows 2000有许多适当的特色,大部份将在本书的这一篇谈论。本书将Windows安全性相关的特色区分为一或更多个如下所述的标题:

 身份识别及使用者内容 许多Windows安全性以使用者为基础,并且将它的能力应用在使用者识别以及维护「使用者内容」上。这个能力可让系统决定使用者是否被允许执行某些动作。
 
 存取控制 存取控制与私有资料和文件及注册密钥等系统物件之安全性有关。Windows 2000的存取控制特色使用在身份识别和使用者内容上,其依赖的程度在之后的两个章节中开始讨论时会更清楚。
 
 安全性连线 通讯安全性是Windows 2000的一个重要部份,包括资料加密和认证,两者皆确保您正在与一个可信赖的实体通讯。
 
因为安全性非常依赖于使用者身份识别的建立和管理,所以我们要开始讨论Windows 2000之信任成员的使用。

何谓信任成员?
 

当您登入一台执行Windows的机器,通常会输入使用者名称及密码。使用者名称会向系统确认这个代表您的帐号。像这样的帐号通常被称为 使用者帐户 。

使用者帐户维持一组与使用者相关的资讯,例如,名字、密码(或身分证件)、注释及地址。有两个必要项目与使用者帐户一起储存,即是使用者在系统上的权限清单以及数位身份识别-被称为安全识别项或是SID(读为「sid」)-当在保护物件及私人资料的安全时,系统会使用它们来确认帐户。SID是用来确认信任成员帐户的可变长度二进制结构(权限和SID在稍后将有详细的讨论)。第叁个与使用者帐户同样重要的资讯是使用者成员的群组清单。

Windows开发人员实作了群组帐户,以管理复杂性的事物。 群组帐户 包含一组可以是使用者帐户或是通用群组帐户的成员。群组帐户维持着与使用者帐户相似的资讯,例如系统权限及SID。只要经由将使用者帐户指派到群组帐户,可在一个地方管理一组使用者安全性而不必为每个使用者作调整。

使用者帐户及群组帐户是一个系统的信任成员。 信任成员 是 在Windows中可以拒绝或允许存取的实体。有些信任成员隐含在系统中(例如Everyone之内建群组),另一些则由系统动态地建立(例如在第十及十一章会更详细讨论的登入SID),还有其他因为已建立帐户而存在的信任成员。使用者、群组及电脑都是与信任成员帐户有关的Windows信任成员。所有的信任成员分享遍及系统的公共函数,尤其是在处理存取控制时(在下一章讨论)。


--------------------------------------------------------------------------------

说明

使用者帐户及群组帐户是称为 信任成员 的广泛类别成员。因为这一章主要是讨论使用者及群组帐户,所以除非详细指明,否则 信任成员 及 信任成员帐户 这两个术语即代表使用者及群组帐户。


--------------------------------------------------------------------------------

系统经由信任成员帐户授予某个文件的存取或拥有权。当系统执行某个特许的权限函数时,因为信任成员帐户已授予这个权限,所以它是被允许的。当使用者被系统识别时,是被她(他)的信任成员帐户及群组的信任成员帐户识别,而且使用者是这些帐户的成员。如您所见,信任成员是Windows安全性的重要部分。

在我们进入计划性的管理使用者及群组帐户的讨论之前,让我们先看一下管理系统信任成员的使用者介面。

Windows 2000的安全性工具
 

为了有效地编写具有安全性的软件,了解Windows 2000中如何管理安全性及熟悉一些有帮助的工具是必要的。

首先,您要有Windows 2000系统的管理权限(或者,就网域系统而言是帐户操作者的权限)。如果必要的话,安装一个以您为管理者的Windows 2000系统。

假如还没准备好的话,现在也应该对Microsoft Management Console(MMC)较为熟悉了。这个应用程序会与系统一起安装,而且属于MMC.exe。MMC可让您执行管理的任务,从安装系统上的硬盘到监视事件日志,包括安全性管理皆是。实作MMC的每个函数如同被载入的嵌入式管理单元一般。有关使用MMC的更多细节,请参阅Windows 2000 Help中的「使用Microsoft Management Console」主题。

本机使用者及MMC的群组嵌入式管理单元不但可让您建立使用者及群组,也可以增加成员到群组中。嵌入式管理单元还可以让您改变使用者的密码及规定一些适用于信任成员的原则。理想上,本书的范例应由管理群组成员的使用者执行(尽管您将发现建立权限较小的使用者及群组,才能使您感受到权限较小的方式更容易学习到东西)。

群组原则嵌入式管理单元可让您调整委派给信任成员的权限。因为系统上有大量群组原则的功能,所以这个嵌入式管理单元比本机使用者及群组嵌入式管理单元更复杂。经由展开以下的路径可以直接到群组原则嵌入式管理单元的权限功能:本机电脑原则\电脑配置\Windows设定\安全性设定\本机原则\使用者权限委派。图9-1说明了应显示的画面。


 

 

 图9-1 显示在群组原则嵌入式管理单元中的权限画面


--------------------------------------------------------------------------------

说明

透过一个称为群组原则的逻辑层来了解Windows 2000中有关权限之MMC嵌入式管理单元介面是重要的。群组原则是个复杂的主题,可以独立写成好几章。这个委派权限的方法是从Windows NT直接委派权限给信任成员的使用者管理员工具中分离出来的措施。透过呼叫系统函数,直接的权限操作仍旧是可能的;然而,Windows 2000提供的使用者介面只有用到群组原则。 使用MMC嵌入式管理单元,您可以体验委派使用者权限的过程。此外,委派使用者权限可能会被网域群组原则完全地覆盖。您可以使用直接管理权限的工具来处理这些潜在的问题。


--------------------------------------------------------------------------------

最后,在进入计划性管理信任成员的讨论之前,我应当提到在本章稍后所叙述的TrusteeMan范例应用程序,该范例程序能被用来建立及删除使用者和群组帐户,并委派权限给信任成员帐户的工具。尽管这个范例是个教学用的工具,但它也对Windows 2000所包含的MMC嵌入式管理单元提供一个简单的选择。

管理信任成员帐户
 

在典型的企业环境里,系统管理者或帐户操作者可以在系统上建立使用者及群组帐户。在这种环境下执行的服务器软件可以和已启用的安全性感知以及使用者识别一起运作,不用直接建立或管理信任成员帐户。这个软件仅假定这些帐户已经由另一个实体建立,这样的话,许多的服务开发人员似乎都不必计划性地建立信任成员帐户。

尽管这可能是真的,但是您会发现Windows 2000的安全性具有很大的弹性。您可以自行创造信任成员,不需与特定的使用者直接联系在一起,也不用总是被已登入系统的使用者使用。举例来说,Windows 2000可让您把使用者帐户的存取限定为与它的原始权限以及由软件决定的信任成员权限相结合,它是一种 建立Restricted Token 的技巧(Restricted Token在第十一章有详细的讨论)。如此一来,您的软件就可以建立系统管理者无法(或要求)手动建立的帐户。

此外,假如您正在编写可以直接支援建立帐户的服务器软件,例如Internet gaming群体(Internet gaming community)或是前端(Front End)连线银行,您的服务必须能够计划性的建立及管理信任成员帐户。Windows 2000提供两组可让您如此做的函数:

Net API 「Net」函数可让您建立及管理任何Windows 2000系统上的使用者帐户,包括工作站以及服务器系统,不管系统是否为网域控制站。
 
Active Directory Services Interface(ADSI) ADSI是一组COM物件,可让您在Windows 2000上管理Active Directory。Active Directory是Windows 2000网域上所有使用者及群组帐户(在许多其他的物件中)的容器,对Windows来说是个非常重要的附加部分。您的软件可以使用ADSI物件来建立、删除及管理网域控制站之Active Directory上的使用者及群组物件。
 
根据您的需求来决定使用哪一组APIs。Active Directory是个企业管理工具,就其本身而言,假如您的软件正在企业环境中建立使用者帐户的话,应该使用Active Directory的ADSI介面。然而,如果您的服务软件正在可能不是网域控制站的单一服务器中建立本机的信任成员帐户时,您会发现Net函数的使用变得更简单且更直接。


--------------------------------------------------------------------------------

说明

ADSI物件可让您的软件管理非网域控制站系统上的使用者及群组,但是因为这种系统不会维护Active Directory,所以ADSI物件可以只透过Net函数传递。同样地,假如Net函数被用来增加、移除或管理网域控制站上的信任成员,他们将会自动地使用ADSI物件去修改机器上的Active Directory。


--------------------------------------------------------------------------------

由于Net函数最常被使用,因此这一章会集中于这个部分。然而,可被用来执行类似任务的ADSI元件也会以注释的方式列举出来,以便您可以在《Platform SDK》文件中寻找特定的主题。

认识Net函数
 

Net API有很多的特色且包含许多使管理信任成员帐户更为容易的函数及结构。Net函数可让您管理使用者帐户、本机群组帐户(只与单一机器相关联)及存在于网域控制站和整个网域函数的网域或通用群组帐户。这些函数以一贯且符合逻辑的方式设计,我们可以经由对一些规则的了解而学习到很多。


--------------------------------------------------------------------------------

说明

假如您正在编写一个可能呼叫Net函数的原始模组,您的原始模组及专案必须执行几个步骤。跟大部分Win 32中的API函数不同的是,Net函数不需在Windows.h的标头档中宣告。因此,您必须在模组的一开始就含入Lm.h标头档。同样地,您必须将NetApi32.lib文件加到与专案连结的程序库文件清单中。


--------------------------------------------------------------------------------

Net函数的其中一个特色是以一致的方式呈现。因为这个原因,才能够在不涵盖API命名集的每个单一函数的情形下,提供了解这些函数之通用规则的内容:

Net函数使用Net+[信任成员类型]+[执行的动作]格式为一贯的命名。例如:NetUserAdd、NetLocalGroupGetInfo以及NetUserSetGroups。
 
Net函数只支援Unicode字串。假如您的专案以ANSI建立的话,在传递字串到Net函数前必须为每个字串缓冲器制作Unicode副本。(当在编写Windows 2000的服务器软件时,这是另一个经常使用Unicode的原因。)
 
Net函数经由系统及帐户名称来识别信任成员帐户。假设您在远端机器上拥有足够的权限,便可以在远端系统上使用这些函数管理信任成员。
 
Net函数实作了一组可用来向软件回报信任成员资讯或是被软件用来设定信任成员资讯的结构。因为牵涉到许多结构的部份,所以这个Net函数的特性可能会令人感到困惑,不过了解后会非常合理且有用。
 
这里有个例子:假如您使用NetLocalGroupAdd函数(稍后将详细地讨论)来建立本机群组,您可以选择输入及传递一个LOCALGROUP_INFO_0结构的实例,只包含群组名称,或是可让您指定群组名称及注解字串的LOCALGROUP_INFO_1结构。这取决于您,而经由一个称为level参数的传递,Net函数可让您选择要使用的结构。在我们的例子中,LOCALGROUP_INFO_0为level 0结构,而LOCALGROUP_INFO_1则是level 1结构。


--------------------------------------------------------------------------------

说明

因为一个单一的函数可以取得或回传一个以上的系统定义结构类型,Net函数定义的类型为PBYTE,并根据您所传递的资讯而适当的转型。假如您传递一个结构给函数,则您应该传递此特定结构的指标,并将它转型为PBYTE。如果Net函数回传一个结构给您,此时函数将会预期为一个PBYTE的指标,您可以在稍后将它转型为适当的结构类型。


--------------------------------------------------------------------------------

我在这一章谈论到的结构集将以星号(*)代替level标记,以便归纳资讯而不用详细说明每个可用的结构。举例来说,在上一段所提到的两个结构都在LOCALGROUP_INFO_* 结构集中。我将会解释一些经常使用的结构以及包含较少结构成员的一些大结构。所有结构的详细说明皆可在《Platform SDK》文件中找到。

表9-1列出为信任成员管理所定义的结构集以及可一起使用的函数。

 表9-1 管理信任成员的结构集

结构集  用法及函数
GROUP_INFO_* 这些结构为通用或网域群组取得资讯以及设定通用群组资讯,在建立群组及对现存的群组操作时会使用到。
 函数:

NetGroupAdd、NetGroupEnum、NetGroupGetInfo、NetGroupSetInfo
 
GROUP_USERS_INFO_* 在管理通用或网域群组中的使用者成员时会使用到这些结构,可用来为群组设定或取得使用者资讯。
 函数:

NetGroupGetUsers、NetGroupSetUsers、NetUserGetGroups、NetUserSetGroups
 
LOCALGROUP_INFO_* 用来为本机群组取得以及设定资讯,不但会被现存的群组使用,在建立新的本机群组时也会用到。
 函数:

NetLocalGroupAdd、NetLocalGroupEnum、NetLocalGroupGetInfo、NetLocalGroupSetInfo
 
LOCALGROUP_MEMBERS_INFO_* 用来为本机群组设定及取得成员清单。
 函数:

NetLocalGroupAddMembers、NetLocalGroupDelMembers、NetLocalGroupGetMembers、NetLocalGroupSetMembers
 
LOCALGROUP_USERS_INFO_* 此组结构只包含一个称为LOCALGROUP_ USERS_INFO_0的结构,呼叫NetUserGet-Local Groups以取得本机群组成员之所有使用者帐户清单时使用。
 函数:

NetUserGetLocalGroups
 
USER_INFO_* 由于Windows 2000使用者帐户联系的大量资讯,此组结构显然包含了最大的结构数量。建立使用者以及为现存使用者帐户取得和设定资讯时会使用到这些结构。
函数:

NetUserAdd、NetUserEnum、NetUserSetInfo、NetUserGetInfo
 

当Net函数将有关信任成员的资讯回传到软件时,它将指派一个缓冲器给您。如前所述,Net函数需要您传递指标位址到转型成PBYTE* 的适当结构类型中。当您用完系统传回的缓冲器后,应该将它传递到NetApiBufferFree,以释放缓冲器。这个函数定义如下:

NET_API_STATUS NetApiBufferFree(PVOID pvBuffer);在设定信任成员资讯时,Net函数可能会判定软件并没有正确地设定结构成员。在这个情况下,「Set」函数(如NetUserSetInfo)会回传ERROR_INVALID_PARAMETER。为了确定错误的原因,您可以使用函数的错误参数,这个参数会被填入一个事先定义的值,以指出第一个导致错误的结构成员。假如您对这个资讯不感兴趣,可以传递NULL给「Set」函数。

有了Net函数的常识,您将发现学习这些细节是件轻而易举的事。所以,让我们开始建立信任成员帐户吧!

建立信任成员帐户
 

就使用者及群组帐户在系统中控制存取安全物件的角色,以及系统权限的功能来说是类似的。系统可让您交替地指派以及否决使用者和群组帐户的权限。然而,使用者可以利用使用者帐户登入系统,反之,群组帐户却无法以这种方式使用。这种与使用者结合的情形,使得系统必须为使用者帐户维护大量的资讯。


--------------------------------------------------------------------------------

说明

网域或通用群组帐户在很多观念及软件上皆与本机群组帐户一样。尽管我从这里开始将讨论本机群组的管理细节,但是在计划性地管理通用群组时,许多观念还是适用的。有关Net API通用群组功能的完整讨论,请参阅《Platform SDK》文件中字首为「NetGroup」的函数组。


--------------------------------------------------------------------------------

您可以使用NetLocalGroupAdd函数来建立本机群组帐户,其原型如下:

NET_API_STATUS NetLocalGroupAdd(
 PCWSTR pstrServername,
 DWORD dwLevel,
 PBYTE pbBuf,
 PDWORD pdwParmErr);第一个参数是您想建立之群组帐户的系统名称。传递NULL给pstrServername表示您希望在本机系统上建立一个群组。dwLevel参数指出您将传递给pbBuf参数作为参考的结构类型。NetLocalGroupAdd使用

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;以上两个结构都包含一个群组名称字串的指标,LOCALGROUP_INFO_1结构还包含一个注解字串的附加指标。当您在建立群组时,这两个结构都可以使用。

最后一个传递给NetLocalGroupAdd的参数是pdwParmErr,是个DWORD的指标。对于不适当的参数,系统传回一个指出在pdwParmErr参考的变数中,不适当的参数值。只有NetLocalGroupAdd的传回值等于ERROR_INVALID_PARAMETER时,传回pdwParmErr的值才有效。表9-2列出这些可能的值。假如您不想要收到这些资讯,可以传递NULL给pdwParmErr。

 表9-2 pdwParamErr参数中可能被传回的值

值  叙述
LOCALGROUP_NAME_PARMNUM 这个值表示您指定了一个无效的群组名称给新的群组。
LOCALGROUP_COMMENT_PARMNUM 这个值表示呼叫NetLocalGroupAdd中的注解值是无效的。

您应该经常检查NetLocalGroupAdd的传回值以确保系统已经传回NERR_Success。否则表示系统在建立的新本机群组时执行失败。


--------------------------------------------------------------------------------

说明

ADSI套件也可以用来建立群组帐户。Active Directory被组织成层级物件,可以在任何的容器物件中建立群组物件。您可以使用IADsContainer介面的Create方法建立一个群组物件。一旦建立物件后,您可以使用QueryInterface方法获得IADsGroup介面的指标,它可以用来更进一步地管理新的群组物件。要取得更多的细节,请参阅《Platform SDK》文件。


--------------------------------------------------------------------------------

建立使用者帐户与建立群组帐户的方法类似;然而,您可以提供更多使用者帐户的资讯给系统。您可以使用NetUserAdd函数在Windows 2000系统上建立新的使用者。

NET_API_STATUS NetUserAdd(
 PCWSTR pstrServername,
 DWORD dwLevel,
 PBYTE pbBuf,
 PDWORD pdwParmErr);请注意到NetUserAdd与NetLocalGroupAdd的参数清单完全相同,差别在于传递到pbBuf参数的结构类型。您可以传递指标到level 1、2或3的USER_INFO_* 结构,以建立新的使用者。这里是USER_INFO_1(可以与NetUserAdd一起使用的最简单结构)及USER_INFO_3(您可以使用的最广泛结构)的定义。

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 ;如您所见,当您在建立使用者时,可以选择传递大量的资讯给系统。有关USER_INFO_2及USER_INFO_3结构中每个成员的详细叙述,请参阅《Platform SDK》文件。此处的讨论只需叙述在USER_INFO_1结构中出现的成员,这些成员会在表9-3中列出。

 表9-3 USER_INFO_1的成员

成员  叙述  如果ERROR_INVALID_ PARAMETER,pdwParmErr 的传回值
usri1_name 指向一个包含建立使用者帐户的Unicode使用者名称缓冲器。此成员必须包含一个有效的指标,否则NetUserAdd将传回ERROR_INVALID_PARAMETER。 USER_NAME_PARMNUM
usri1_password 指向一个包含建立使用者帐户的Unicode密码缓冲器。密码限制为14个字元而且必须符合您正在新增使用者帐户之系统提出的规则。 USER_PASSWORD_PARMNUM
usri1_password_age NetUserAdd忽略此成员。 USER_PASSWORD_AGE_PARMNUM
usri1_priv 当您在建立使用者时,这个成员的值必须设定为USER_PRIV_USER。任何其他的值将会导致NetUserAdd传回ERROR_INVALID_PARAMETER。 USER_PRIV_PARMNUM
usri1_home_dir 使用者可以为其帐户定义主目录。假如您想要联合主目录与新的使用者,此成员应该要指向一个包含Unicode字串目录路径的缓冲器。您可以指派NULL给这个成员。 USER_HOME_DIR_PARMNUM
usri1_comment 这个成员指向一个包含新使用者的Unicode注解缓冲器。假如您不想要结合注解与新的使用者帐户,可以指派NULL给这个成员。 USER_COMMENT_PARMNUM
usri1_flags 这个成员可以结合指示新使用者帐户功能的标记(请见 表9-4 )。 USER_FLAGS_PARMNUM
usri1_script_path 当使用者登入系统时,系统将执行与您的使用者结合之 .exe、.cmd或.bat档。您可以使用此成员来设定登入指令档的路径。 USER_SCRIPT_PATH_PARMNUM

呼叫NetUserAdd时,表9-4中任何一种标记的结合都可以被指派给USER_INFO_1的usri1_flags成员,其他的标记则可用来指定额外的特征。有关这些其他标记的资讯,请参阅《Platform SDK》文件。

 表9-4 与USER_INFO_1有关的标记

标记  叙述
UF_ACCOUNTDISABLE 建立一个无效的帐户。
UF_PASSWD_NOTREQD 建立一个不需要密码的帐户。对于要求所有帐户都需使用密码的系统或网域来说,可能是个适当的原则。
UF_PASSWD_CANT_CHANGE 建立一个使用者无法改变自己密码的帐户。系统管理者仍旧可以改变密码。
UF_DONT_EXPIRE_PASSWD 这个使用者的密码永不过期。
UF_NOT_DELEGATED 这个帐户不能被委派(有关委派的讨论,请参阅 第十一章 及 十二章 )。
UF_SMARTCARD_REQUIRED 要求使用者使用智慧卡登入这个新帐户。
UF_TRUSTED_FOR_DELEGATION 这个帐户可被委派(有关委派的讨论,请参阅 第十一章 及 十二章 )。

不管它看起来像什么,用NetUserAdd来建立新使用者帐户之最低编写程序必要条件实际上是非常合理的。以下的程序代码说明了最简单的实例:

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);
}如您所见,这是个相当琐碎的程序代码,用来在给定的系统上与给定的名称及密码建立一位使用者。使用以下的方式呼叫此范例函数将会在本机系统上建立一个名字为「MrMan」,密码为「HowDoYouDo」的使用者:

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

说明

ADSI套件也可被用来建立使用者帐户。如同群组物件一般,可以在任何的容器物件中建立使用者物件。您可以使用IADsContainer介面的Create成员建立一个使用者物件。一旦建立了这个物件,便可以使用QueryInterface方法,以取得IADsUser介面的指标,它可被用来进一步地管理您的新使用者物件。有关这个主题的细节,请参阅《Platform SDK》文件。


--------------------------------------------------------------------------------

设定使用者及群组资讯
 

系统也为了取得及设定已被建立之使用者或群组帐户的资讯而提供Net函数。您可以看到这些函数的呼叫格式与NetGroupAdd及NetUserAdd非常类似。要取得及设定使用者帐户资讯,可以使用NetUserGetInfo及NetUserSetInfo函数,其原型如下:

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);如您所见,NetUserGetInfo及NetUserSetInfo函数拥有几乎跟NetUserAdd一样的参数清单。事实上,唯一额外的参数是pstrUsername,它指出被取得或设定资讯的使用者名称。

请注意NetUserGetInfo的部份,其pbBuf参数已经被改成缓冲器的指标位址。如同先前所指出的,这是因为系统实际上会指派被要求的结构给您,并经由ppbBuf参数在变数中传回一个指向新缓冲器的指标。

dwLevel参数指出在呼叫NetUserGetInfo或NetUserSetInfo中,将会使用到的USER_INFO_* 结构level。这些函数接受USER_INFO_1或USER_INFO_3,如同前面所定义的,以及NetUserAdd不支援的其他几个USER_INFO_* 结构。有关这些结构的完整叙述,请参考《Platform SDK》文件。


--------------------------------------------------------------------------------

说明

设定使用者资讯时,您应该使用一个只包含您想设定的资讯或接受NULL,如同一个成员值的结构level。举例来说,仅设定使用者帐户的密码可以使用USER_INFO_1003结构类型,因为它只为使用者的新密码包含一个成员。


--------------------------------------------------------------------------------

以下的两个范例函数,说明了如何使用NetUserSetInfo及NetUserGetInfo来设定和取得使用者帐户资讯的方法。第一个函数显示如何为使用者帐户设定密码;第二个函数则说明如何撷取使用者的注解栏位。这些函数使用的原则可以为使用者帐户取得或设定任何有效的资讯。

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);
}
--------------------------------------------------------------------------------

说明

所有成功呼叫的NetUserGetInfo必须呼叫NetApiBufferFree,以释放NetUserGetInfo传回的缓冲器。NetApiBufferFree函数被定义为取得一个参数,它是被释放的缓冲器指标。


--------------------------------------------------------------------------------

您可能已经猜到,系统也提供了类似的函数,用来取得及设定群组信任成员帐户的资讯。要取得信任成员资讯,您可以呼叫NetLocalGroupGetInfo;而设定群组资讯则要呼叫NetLocalGroupSetInfo。这些函数定义如下:

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);如您所见,呼叫这些函数与呼叫取得及设定使用者帐户资讯的函数几乎完全相同,差别在于您要处理的群组资讯及先前讨论的LOCALGROUP_INFO_* 结构。

请记得群组成员资讯也与群组帐户一起储存,尽管名称及注解看起来可能是与群组帐户结合的最少资讯。由于它与SID主题密切相关,所以这个主题将延到本章的最后部分,在稍后的〈认识SIDs〉一节中将讨论SIDs内容。

列举使用者及群组
 

通常会在给定的系统上取得现存使用者或群组的清单。再一次地说明,Net API提供两个类似的函数来执行这些任务。您可以使用NetLocalGroupEnum去取得系统上的群组清单;同样地,也可以使用NetUserEnum取得系统上使用者帐户的清单。这些函数定义如下:

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);这些函数很类似,但有一些差别值得注意。从LOCALGROUP_INFO_* 结构集中传回结构到NetLocalGroupEnum的同时,将会从USER_INFO_* 结构集传回结构到NetUserEnum函数。此外,NetUserEnum可让您指定筛选条件,以减少使用者帐户清单传回的范围。传递0给NetUserEnum的filter参数,表示没有筛选条件,所以任何的帐户类型都会被传回。您通常要传递FILTER_NORMAL_ACCOUNT给filter参数;然而,您也可以传递表9-5中列出的任何一个值。

 表9-5 可以传递给NetUserEnum的filter参数的筛选条件值

值  意义
FILTER_NORMAL_ACCOUNT 传回系统上的通用使用者帐户资料
FILTER_TEMP_DUPLICATE_ACCOUNT 传回网域控制站上的本机使用者帐户资料
FILTER_INTER网域_TRUST_ACCOUNT 传回网域控制站上的网域信任帐户资料
FILTER_WORKSTATION_TRUST_ 传回网域控制站上的成员服务器或工作站帐 ACCOUNT 户资料
FILTER_SERVER_TRUST_ACCOUNT 传回网域控制站上的网域控制站帐户资料

除了回传filter参数及结构外,NetUserEnum及NetLocalGroupEnum的使用是完全相同的。就像我们先前看到的函数一样,pszServerName参数指出我们想要列举信任成员帐户的系统。dwLevel参数指出与ppbBuf参数相关的LOCALGROUP_ INFO_* 或USER_INFO_* 结构的版本。

您应该传递要求作为ppbBuf参数之结构类型指标变数的位址。根据NetUserEnum及NetLocalGroupEnum是否被使用的情形,系统将指派一个缓冲器以持有USER_INFO_* 或LOCALGROUP_INFO_* 结构的阵列,ppbBuf是一个缓冲器位址的指标。这个缓冲器将是函数列举的帐户。记得就像NetUserGetInfo一样,必须呼叫NetApiBufferFree来释放系统指派给您的缓冲器。

您应该指示出呼叫函数所传回的缓冲器最大容量,以位元组来传递这个大小以作为dwPrefMaxLen参数。假如您要系统尽可能指派更大的缓冲器,可以传递MAX_PREFERRED_LENGTH。这样的话,系统通常会个别呼叫函数来完成列举型别(Enumeration)的动作。

您可能会问为什么总是选择限制「Net*Enum」传回的缓冲器大小,从而必须多次呼叫函数。这里有几个原因:不保证少数的帐户,而大量的帐户可能会超出系统指派的最大缓冲器大小。此外,如果列举型别是个冗长的程序,您可能会想要应用程序定期地收回对它的控制。


--------------------------------------------------------------------------------

说明

因为您无法确定系统能够指派多少的内存,所以不管是否选择MAX_PREFERRED_LENGTH值为缓冲器的长度,您的程序代码都应该要能处理要求多次呼叫列举函数的情况。


--------------------------------------------------------------------------------

系统传回读取pdwEntriesRead参数所指的变数项目数量。其馀可用的项目(包括这些被传回的项目)总数会储存在pdwTotalEntries参数所指的变数中。

最后一个参数是系统传回的值,在随后呼叫的列举函数中可以被传递回系统,用来继续接收帐户资讯。在您最初呼叫列举函数时,应该将pdwResume Handle参数所指的变数设定为0,并且不应该修改传回的值。

假如已经成功地传回资料,可是有更多帐户被列举时,列举函数将传回ERROR_MORE_DATA。当最后的可用帐户被传回后,列举函数将回传NERR_ Success。

以下的程序代码说明了如何使用NetLocalGroupEnum列举本机系统上的群组,并显示在主控台视窗:

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);
}这个函数只需要稍微修改就可以使用NetUserEnum来列举系统上的使用者。

摧毁使用者及群组帐户
 

在切换工具及讨论识别信任成员帐户、SIDs的系统结构之前,我想要谈论如何摧毁使用者及群组帐户的部份,以涵盖使用者及群组管理的讨论。

在这个时候谈到SID,是因为它的确与信任成员帐户的解构有些关联。尽管Net函数能让您处理信任成员帐户的名称,而其馀的系统大部分皆忽视与信任成员帐户关联的名称。取而代之的是系统会使用与帐户关联的SID二进制值来识别某个帐户。这与摧毁信任成员帐户有什么关系呢?

假如您使用名称为「JClark」建立一个信任成员,然后再用这个帐户登入并建立一个文件,系统会主张我是这个物件的所有者。然而,如果您摧毁了我的使用者帐户,然后再以名称「JClark」重新建立一个新的使用者帐户,此时系统会指派不同的SID值,因此新的帐户不会被视为旧文件物件的所有者。

那就是说,您可以使用如下的函数来删除使用者帐户:

NET_API_STATUS NetUserDel(
 PCWSTR pszServerName,
 PCWSTR pszUsername);如您所看到的,这个简单的函数只使用系统名称及使用者帐户的名称作为参数。PszServerName参数可以为NULL,表示本机系统。假如这个函数执行成功,将会传回NERR_Success。

Net API为删除群组实作了一个类似的函数,其定义如下:

NET_API_STATUS NetLocalGroupDel(
 PCWSTR pszServerName,
 PCWSTR pszGroupname);
--------------------------------------------------------------------------------

说明

您也可以使用ADSI物件来列举及删除使用者及群组帐户,并取得以及设定使用者及群组资讯。有关IADsUser及IADsGroup介面和用来搜寻及列举IdirectorySearch介面的讨论部份,请参阅《Platform SDK》文件。


--------------------------------------------------------------------------------

管理群组成员
 

群组成员资讯能以几个方式修改,但是我们首先需要学习几个撷取群组成员资讯的方法。第一个方法使用NetUserGetLocalGroups函数撷取为群组成员之使用者清单;第二个方法使用NetLocalGroupGetMembers函数撷取与个别群组关联的成员组。

使用NetUserGetLocalGroups函数
 

NetUserGetLocalGroups函数的原型如下:

NET_API_STATUS NetUserGetLocalGroups(
 PCWSTR pszServerName,
 PCWSTR pszUsername,
 DWORD dwLevel,
 DWORD dwFlags,
 PBYTE* ppbBuf,
 DWORD dwPrefMaxLen,
 PDWORD pdwEntriesRead,
 PDWORD pdwTotalEntries);如您所见,这个函数与我们讨论过的NetLocalGroupEnum及NetUserEnum函数看起来很类似。事实上,主要的不同处在于这个函数使用了LOCALGROUP_USERS_INFO_* 结构集(LOCALGROUP_USERS_INFO_0至今仍存在)。

dwFlags唯一有效的值即是LG_INCLUDE_INDIRECT,它指示NetUserGetLocalGroups也应该传回为间接成员群组的使用者(由pszUsername指出),或者是0,即指示函数应该只传回为直接成员群组的使用者。当pszUsername为通用群组或是网域群组的成员时-即本机群组的成员,可能也会产生间接成员的情形。

您可以传递MAX_PREFERRED_LENGTH给dwPrefMaxLen参数,但是无论使用何种方式,您都必须注意经由pdwEntriesRead参数及pdwTotalEntries参数传回的值,以确定所有可能的项目皆被传回。

和之前的列举函数不同,它没有再次呼叫NetUserGetLocalGroups继续列举的方法。您不太可能会面临列举多于几十个群组的状况,而列举使用者可能会传回数千个帐户项目。

使用NetLocalGroupGetMembers函数
 

第二个列举成员资讯的方式是撷取与个别群组关联的成员组。您应该使用NetLocalGroupGetMembers函数这样做:

NET_API_STATUS NetLocalGroupGetMembers(
 PCWSTR pszServerName,
 PCWSTR pszLocalGroupName,
 DWORD  dwLevel,
 PBYTE* ppbBuf,
 DWORD  dwPrefMaxLen,
 PDWORD pdwEntriesRead,
 PDWORD pdwTotalEntries,
 PDWORD_PTR pdwResumeHandle);这个函数会取得服务器名称及本机群组名称以作为它的第一个参数。常见的dwLevel参数指出您想要经由ppbBuf参数传回的LOCALGROUP_MEMBERS_INFO_* 结构集level。

LOCALGROUP_MEMBERS_INFO_* 结构允许您处理信任成员名称及网域名称,或是它们的SIDs,下一节将作详细地说明。以下是LOCALGROUP_MEMBERS_ INFO_0 及 LOCALGROUP_MEMBERS_INFO_3 结构的定义:

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;
--------------------------------------------------------------------------------

说明

处理信任成员时,了解许多安全性函数所需的SID值是很重要的。再者,就SIDs来说,本机群组成员的操作函数可让您撷取信任成员项目。


--------------------------------------------------------------------------------

LOCALGROUP_MEMBERS_INFO_0中使用的PSID类型指出一个指向SID结构的指标。这些结构的任何一个都可根据您软件的需要而与NetLocalGroupGetMembers一起使用。

dwPrefMaxLen、pdwEntriesRead、pdwTotalEntries及pdwResumeHandle参数是惯用缓冲器大小、读取的项目、剩下的项目以及继续列举型别的值。这些参数和已经讨论过的NetUserEnum及NetLocalGroupEnum函数中相同名称的参数一样,以完全相同的方式运作。事实上,为了与NetLocalGroupGetMembers一起使用,NetLocalGroupEnum的一部分范例函数PrintLocalGroups,可以容易地修改如下:

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);
}使用NetLocalGroupSetMembers函数来设定本机群组的成员:

NET_API_STATUS NetLocalGroupSetMembers(
 PCWSTR pszServerName,
 PCWSTR pszGroupName,
 DWORD  dwLevel,
 PBYTE  pbBuf,
 DWORD  dwTotalEntries);PszServerName及pszGroupName参数指示您正在设定成员的系统名称及本机群组。DwLevel参数指出您想要使用的LOCALGROUP_MEMBERS_INFO_* 结构集level。您可以传递0或3,以分别表示LOCALGROUP_MEMBERS_INFO_0或LOCALGROUP_MEMBERS_INFO_3,两者在稍早都已定义过。这意味着您可以选择使用信任成员帐户名称字串或是SIDs来设定成员。

您应该传递一个指标到选择的结构类型阵列给pbBuf参数,以及阵列中项目的数量为dwTotalEntries参数的内容。假如函数执行成功,被pbBuf以阵列传递之表示信任成员清单将是这个群组的新成员清单。记住信任成员清单将会取代任一个现行的群组成员。如果函数执行成功,将传回NERR_Success。

其他有用的函数
 

现在您知道撷取及设定所有本机群组之信任成员的方法,还有两个有用的函数,可让您从现行成员的群组中,只新增及删除特定的信任成员清单。这两个函数分别是NetLocalGroupAddMembers及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);您可以使用跟NetLocalGroupSetMembers一样的方式使用这些函数。关于范例的部份,请参阅本章稍后叙述的

认识SIDs
 

SIDs对Windows 2000安全性来说是不可缺少的。SIDs在本章已有所提及,也后续的章节也将再次出现,而现在正是详细认识SIDs的时候。到目前为止,您知道SIDs是安全识别项,它向系统识别信任成员的身分。每个SID是一个代表信任成员的唯一二进制数值。

以下有几个您要熟悉的常见工作:

将一个使用者或群组名称转换成SID。
 
为信任成员将SID转换成文字名称。
 
从头建立一个众所周知的SID。这些SID存在所有的系统中,而且指出如Guest或Everybody帐户。
 
复制SID结构。
 
最后,来回地转换代表SID的二进制数值及字串。
 
SID是个安全识别项以及向系统识别信任成员身分的结构。SID由48个位元值跟随一个32位元之元件数字变数所组成。在文件中,SID通常会以S-R-I-S-S...格式表示(请参阅提要栏位)。


--------------------------------------------------------------------------------

SID格式

SID的格式通常是:S-R-I-S-S...

在这里:

 S 如字面所写的「S」,指示这些序号是SID。

 R 是SID的修订等级,以一个数字表示(一般是1)。

 I 是个48I位元的数字,指示授权单位。

 S 是个32位元数字,指示次要的授权单位,亦即大家所知的相对识别项或是RID。

 S 是另一个次要授权单位。在SID中可以有任一的次要授权单位数量。

 范例: S-1-5-11


--------------------------------------------------------------------------------

从Windows NT 3.1至今,SID结构的修订版本号码已经是1;然而,结构是可能会改变的,我们总有一天会看到2的修定版本。授权单位「号码」指出SID在哪个授权单位之下产生-即什么「实体」拥有SID及管理它的帐户。表9-6提供现在有支援之授权单位清单。

 表9-6 SID授权单位

授权单位  定义
SECURITY_NULL_SID_ AUTHORITY 定义为0,这个授权单位被用来建立一个指出无效群组或没有人的SID。
SECURITY_WORLD_SID_ AUTHORITY 定义为1,这个授权单位被用来建立Everybody群组帐户。
SECURITY_LOCAL_SID_ AUTHORITY 定义为2,这个授权单位被用来建立众所周知的LOCAL群组帐户。
SECURITY_CREATOR_SID_ AUTHORITY 定义为3,这个授权单位与Creator Owner SID一起使用。
SECURITY_NON_UNIQUE_ AUTHORITY 定义为4,这个授权单位被用来产生不是唯一的SIDs。 第一个次要授权单位总是设为SECURITY_NT_NON_UNIQUE,定义为0x15。
SECURITY_NT_AUTHORITY 定义为5,这个授权单位由Windows 2000系统建立的使用者及群组帐户所使用。

次要授权单位是唯一的32位元值,与授权单位的SID相关。下一个次要授权单位是唯一的,与先前的次要授权单位及最后授权单位的SID相关。次要授权单位即大家所知道的RIDs。

表9-7显示一些众所周知的RIDs。(完整的清单请参阅《Platform SDK》文件中的WinNT.h。)和先前列出之授权单位不同的是,这些RIDs并不完全是唯一的,因为它们是唯一与授权单位相关的,就其本身来说,不受授权单位的支配是没有意义的。

 表9-7 一些众所周知的RIDs

RID  有关的授权单位  定义
SECURITY_NULL_RID NULL 定义为0,这是NULL群组的个别次要授权单位。
SECURITY_WORLD_RID WORLD 定义为0,这是Everyone群组的个别次要授权单位。
SECURITY_CREATOR_ OWNER_RID CREATOR 定义为0,这是使用者Creator Owner的个别次要授权单位。
SECURITY_CREATOR_ GROUP_RID CREATOR 定义为1,这是使用者Creator Group的个别次要授权单位。
SECURITY_CREATOR_ OWNER_SERVER_RID CREATOR 定义为2,这是使用者Creator Owner Server的个别次要授权单位。
SECURITY_CREATOR_ GROUP_SERVER_RID CREATOR 定义为3,这是使用者Creator Group Server的个别次要授权单位。

经由把众所周知的RID与各自的授权单位结合,可以产生大家所熟知的SID。众所周知的SIDs识别系统辨别使用者及被系统定义且经由每个网路上安装Windows 2000的群组身分。表9-8列出一些大家所熟知的SIDs及用法。

 表9-8 众所周知的SIDs及用法

SID  常见名称  用法
S-1-0-0 Null SID 指出一个空的或为NULL的群组。它被定义为唯一没有使用者的群组,通常用来指示没有人。
S-1-1-0 Everyone 指出一个所有信任成员皆为暗示性成员的群组。这是一个非常重要的SID,对于在系统中建立安全物件的存取清单是有帮助的。它也就是World SID或World群组。
S-1-2-0 Local SID 指出一个包括所有本地或实际登入系统的使用者群组。
S-1-3-0 Creator Owner SID 为某个物件的建立者充当替代符号(Placeholder)。它与可继承的存取控制清单一起使用。您将在第十章找到更多有关此SID的资讯。
S-1-3-1 Creator Group SID 为某个物件的主要群组建立者充当替代符号。它与可继承的存取控制清单一起使用。您将在第十章找到更多有关此SID的资讯。
S-1-5-1 Dialup 指出当使用者帐户经由拨接登入Windows 2000系统时,所有使用者帐户自动成为成员的群组。
S-1-5-2 Network 指出当使用者帐户经由网路登入Windows 2000系统时,所有使用者帐户自动成为成员的群组。
S-1-5-3 Batch 指出当使用者帐户经由批次登入的方式登入Windows 2000系统时,所有使用者帐户自动成为成员的群组。
S-1-5-4 Interactive 指出当使用者帐户经由互动式登入Windows 2000系统时,所有使用者帐户自动成为成员的群组。
S-1-5-6 Service 指出当使用者帐户以服务的身分登入Windows 2000系统时,所有使用者帐户自动成为成员的群组。
S-1-5-7 AnonymousLogon 与无效的会议登入结合。
S-1-5-9 ServerLogon 与网域控制站帐户结合。
S-1-5-10 Self(或Principal Self) 充当替代符号,并且只适用于存取群组或使用者帐户的清单。当它出现时,指出存取清单申请的信任成员帐户。
S-1-5-11 Authenticated User 使用者指出一个所有目前被认证的使用者帐户是暗示性成员的群组。
S-1-5-13 Terminal Server 与登入终端服务器的使用者关联。
S-1-5-18 LocalSystem 这个有许多服务皆在其下执行的特别帐户,存在于所有的Windows 2000系统中。有关更多的资讯,请参阅 第十一章 。

您偶尔才会看到这些SIDs。熟悉这些系统内建的信任成员是好的,SID本身的资料结构定义如下:

typedef struct _SID {
 BYTE Revision;
 BYTE SubAuthorityCount;
 SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
 DWORD SubAuthority[ANYSIZE_ARRAY];
}SID;这个结构的每个成员现在对您来说应该是熟悉的。ANYSIZE_ARRAY定义为1,主要是表示结构不一定仅以单一的DWORD代表一个次要授权单位以作为结尾。尽管SID结构的本身非常清楚,它是个「不透明的」资料结构,而且应该只使用提供的系统函数来操作。这个不透明特性给予Microsoft开发人员在未来改变SID内部结构的自由。您的软件应该跟随这个规则。

建立SIDs
 

您通常会为众所周知的信任成员建立SIDs,例如Everyone群组。您会经由查询授权单位及信任成员的值来为讨论中的信任成员建立一个SID,并且将它们传递给AllocateAndInitializeSid函数:

BOOL AllocateAndInitializeSid(
 PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority,
 BYTE nSubAuthorityCount,
 DWORD dwSubAuthority0,
 DWORD dwSubAuthority1,
 DWORD dwSubAuthority2,
 DWORD dwSubAuthority3,
 DWORD dwSubAuthority4,
 DWORD dwSubAuthority5,
 DWORD dwSubAuthority6,
 DWORD dwSubAuthority7,
 PSID* ppSid);您应该认识这些参数的用途,值得一提的是,第一个PidentifierAuthority参数,该参数为您正在建立的SID识别授权单位的值。PidentifierAuthority参数是SID_IDENTIFIER_AUTHORITY的类型,被定义为6个位元组的阵列。这看起来有点棘手,但值得庆幸的是《Platform SDK》文件定义了这些有用的SID授权单位。您应该传递表9-6中显示的其中一个值,例如SECURITY_NT_AUTHORITY。

第二个值得注意的参数为nSubAuthorityCount,指出您为SID请求的次要授权单位数量。尽管系统已经定义这个值SID_MAX_SUB_AUTHORITIE为15,AllocateAndInitializeSid将只会以8或更少的次要授权单位来建立SIDs。


--------------------------------------------------------------------------------

说明

不管AllocateAndInitializeSid的限制,还是应该把SIDs当成可为任意大小的情形(即避免静态缓冲器)。


--------------------------------------------------------------------------------

接下来的八个参数为您新的SID指出次要授权单位。我常想知道为什么Microsoft的开发人员不选择将这些参数合并成为DWORD阵列的单一指标。无论如何,您应该传递给次要授权单位的值和您需要的一样多,并且传递0给剩馀的参数。

最后一个参数ppSid,值得额外的注意。它被定义为PSID*,是个指向SID指标的指标。和大部分Windows所提供的函数不同,AllocateAndInitializeSid会指派一个缓冲器给您,而非等待您提供一个缓冲器。被指派的内存位址则经由这个参数传回。

假如AllocateAndInitializeSid传回FALSE,表示您很有可能传递了一个大于8的次要授权单位数量。因为这个函数会指派内存给您,所以另一个失败的原因可能是产生内存不足的情形。假如AllocateAndInitilizeSid执行成功,PSID变数将会包含一个指向新SID的指标。当您完成SID后,应该把它传递给FreeSid,以使系统可以释放SID所使用的内存:

PVOID FreeSid(PSID pSid);使用AllocateAndInitializeSid,您不但可以建立众所周知的SIDs,也可以建立一个使您的软件能辨别授权单位及次要授权单位数值的动态SIDs。以下的范例显示了如何使用此函数建立一个代表Everyone群组的SID:

PSID BuildEveryoneSid(){
 SID_IDENTIFIER_AUTHORITY auth = SECURITY_WORLD_SID_AUTHORITY;
 PSID pSID = NULL;
 BOOL fSuccess = AllocateAndInitializeSid(&auth, 1,
  SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSID);
 return(fSuccess ? pSID : NULL);
}一旦您完成了此函数传回的SID后,必须将它的指标传递到FreeSid函数。您可以轻易地修改这个函数,并经由改变auth变数的初始值,以及传递不同的RID到AllocateAndInitializeSid,以建立另一个SID。

信任成员名称及二进制的SID转换
 

信任成员帐户名称及二进制SIDs之间的转换是您经常需要执行的有用工作,系统提供两个可以执行这些工作的函数,这些函数有令人困惑的名称,所以这是记得哪个是哪个的最佳方式:

假如您有一个信任成员的帐户名称,但是却需要一个二进制的SID,此时您可以呼叫LookupAccountName。
 
相反地,假如您有一个二进制的SID,但是却需要一个信任成员的帐户名称时,您可以呼叫LookupAccountSid。
 
如您所见,这些函数的名称就是您已经拥有的资讯。

撷取信任成员的二进制SID
 

LookupAccountName的函数原型如下:

BOOL LookupAccountName(
 PCTSTR pszSystemName,
 PCTSTR pszAccountName,
 PSID pSid,
 PDWORD pcbSid,
 PTSTR pszReferencedDomainName,
 PDWORD pcbReferencedDomainName,
 PSID_NAME_USE peUse);PszSystemName参数是系统名称,您想要在这个系统上查询pszAccountName参数中所指定的名称。您可以传递NULL给pszSystemName参数,以指出本地机器。

您应该在内存中传递一个指向足够为pSid参数所需之SID缓冲器指标,以及一个DWORD位址,包含作为pcbSid参数的缓冲器大小。假如您的缓冲器太小,系统将传回失败,而且GetLastError函数会传回ERROR_INSUFFICIENT_ BUFFER。LookupAccountName也将透过pcbSid参数以缓冲器应有的大小填满您提供的DWORD变数。

PszReferenced网域Name及pcbReferenced网域Name分别为缓冲器接收与帐户相关的网域名称,以及一个指向包含大小的DWORD变数指标。即使是不需要与信任成员相关的网域名称,也不能传递NULL给任何一个值。

您应该传递SID_NAME_USE变数的位址以作为peUse参数。LookupAccount Name将传回一个经由peUse指向变数中的值,它指出已经传回给您的SID类型。表9-9列出这些可能的值。

 表9-9 SID_NAME_US列举型别类型值

值  用法
SidTypeUser 指示一个SID给使用者信任成员帐户
SidTypeGroup 指示一个SID给群组信任成员帐户
SidTypeDomain 指示一个代表网域物件的SID
SidTypeAlias 指示一个代表内建群组如管理者群组的SID
SidTypeWellKnownGroup 指示一个SID给众所周知的群组,例如Everyone群组
SidTypeDeletedAccount 指示一个SID给删除的帐户
SidTypeInvalid 指示一个无效的SID
SidTypeUnknown 指示一个未知的SID类型
SidTypeComputer 指示一个SID给网域上的电脑帐户

尽管LookupAccountName取得了电脑名称作为参数,它的搜寻并不限于所指定的机器。搜寻应该依据指示机器的观点来执行。这么说,以下的清单即显示出信任成员的搜寻顺序:

众所周知的SID名称
在本地机器上内建及定义的帐户
这个系统的主要网域
受信任的网域
在网域树系中的任一个网域

--------------------------------------------------------------------------------

说明

尽管您可以使用LookupAccountName找出众所周知的SIDs,您还是应该经常使用AllocateAndInitializeSid来直接建立众所周知的SIDs。


--------------------------------------------------------------------------------

如您所看到的,信任成员的名称空间在使用者、群组、电脑及网域之间被分享。尽管系统不允许您使用与另一个使用者或群组相同的名称建立一位使用者或群组,但系统允许使用者或群组拥有与电脑或网域相同的名称。这产生了潜在的问题,因为LookupAccountName会在SID被回传给使用者之前即传给电脑。

您的软件必须为使用者或群组可能会有和电脑或网域相同的名称负责。处理这个情形的最好方法是明确地指出电脑名称给帐户,以作为帐户名称的一部份。举例来说,假如电脑和使用者都取名为「JClark」,而「JClark」帐户已存在「JClark」电脑上,以下的程序代码将传回SID给使用者帐户:

BOOL fRet = LookupAccountName(NULL, "JClark\\JClark",
 &sid, &dwSizeSid, szBuffer, &dwSizeBuf, &use);撷取信任成员的帐户名称
 

假如您的软件需要为信任成员找出帐户名称,在它已经拥有二进制的SID时应该使用LookupAccountSid函数:

BOOL LookupAccountSid(
 PCTSTR pszSystemName,
 PSID pSid,
 PTSTR pszName,
 PDWORD pcbName,
 PTSTR pszReferencedDomainName,
 PDWORD pcbReferencedDomainName,
 PSID_NAME_USE peUse);PszSystemName参数是您执行搜寻所在的机器名称。当LookupAccountSid搜寻一个信任成员名称时,它会与LookupAccountName使用相同的搜寻顺序及规则。

Psid是个指向您想撷取之与帐户名称一致的现行SID指标。这个SID可以是您建立的,或是系统事先在token以及其他结构中传回的任何一个有效SID。PszName及pszReferencedDomainName应该指向您提供用来从系统接收信任成员的名称及它的网域名称缓冲器。

这些缓冲器应该要足够大,以持有传回的值;它们的大小会经由指向DWORD变数的指标来传递,pcbName指出经由它所指向的缓冲器长度,而pcbReferencedDomainName则指出经由它指向的缓冲器长度。假如您不传递足够大小的缓冲器以从系统接收这些名称的话,系统会在这两个变数中传回所需的缓冲器大小。

您应该传递SID_NAME_USE变数的位址给peUse,以从系统接收使用的SID。有关SID_NAME_USE之列举型别类型的说明,请参阅

复制SIDs
 

许多系统函数会传回SIDs,同时有许多与安全性相关的函数会期待您建立包含SIDs或指向SIDs指标的结构。这不是问题,除了SID结构在长度上是可变的以外,系统设计者要求我们将结构看成是不透明的。幸运的是,系统提供了一个用来复制SID的函数:

BOOL CopySid(
 DWORD dwDestinationSidLength,
 PSID pSidDestination,
 PSID pSidSource);这个容易使用的函数简单地取得您的缓冲器长度(以位元组)、一个指向目的SID的指标及一个指向最初SID的指标。

尽管系统知道它正在复制的SID长度,它仍旧需要确定不会被写入超过缓冲器的长度。这就是为什么CopySid要求您传递一个长度以作为第一个参数的原因。更重要的是,您将会需要知道被指派的缓冲器有多大。可以使用GetLengthSid函数找出以位元组计算的SID长度:

DWORD GetLengthSid(PSID pSid);您可以使用这个函数传回的值指派一个缓冲器给您的新SID。

文字与二进制SID的转换
 

到了这个时候,您应该已经知道如何执行这些有关SIDs的经常需求工作部份。然而,我们对SIDs的讨论还不完整,除非我告诉您如何将代表SID的文字转换成以二进制表示,反之亦然。

在继续讨论前,先说明什么是所谓的「文字SID」。我不谈论代表信任成

[wangjy17908]
添加时间:2010-08-04
版权所有(C)2005-2015