http://blog.csdn.net/showna/article/details/1543523
在「安全性」这个主题下,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」。我不谈论代表信任成员帐户名称的字串;而谈论代表二进制SID结构的字串。
举例来说,「S-1-1-0」是一个代表SID的文字SID,它的信任成员帐户名称是Everyone。
当 储存SID名称到持久的储存器中,例如系统登录,或是在使用者介面中表示一个SID时,文字SIDs会是非常有帮助的。您可以用来转换文字SIDs的函 数,分别是ConvertSidToStringSid及ConvertStringSidToSid。以下是它们的函数原型:
BOOL ConvertSidToStringSid( PSID pSid, PTSTR* StringSid); BOOL ConvertStringSidToSid( PCTSTR StringSid, PSID* ppSid);
这两个函数都会指派缓冲器给您,当您完成传回的资料时,释放缓冲器即是您的工作。可以使用LocalFree函数来释放这些函数传回的缓冲器。
现在您对系统信任成员帐户的二进制识别资料已有彻底的认识,所以您已经拥有所有本章之前叙述之利用本机群组操作成员方法的必要资讯。让我们开始讨论指派及移除信任成员的权限。
认识权限与帐户权利
权限是指派权利给广大系统分枝的信任成员(相对之下,指派给信任成员的存取权利则定义允许存取系统中的特定物件)。权限的例子包括登入本机的权利、备份文件与目录,以及关闭系统。
另一个察看权限的方式是使用保护系统函数的安全方式。举例来说,某些系统函数,例如LogonUser及InitiateSystemShutdown,要求呼叫的软件必须拥有适当的权限,否则函数将会执行失败。
有两种您应该知道的权限文字代表。这些文字代表即是Windows使用者介面中显示的易记名称以及软件使用的程序名称。每个程序名称都定义在SDK的标头档中。
表9-10列出了在本书印刷时,Windows 2000中可用的所有权限。部分的清单在《Platform SDK》文件中。
表9-10 权限及帐户权利 |
程序名称 | 显示的名称 | 标头档 |
---|---|---|
SeAssignPrimaryTokenPrivilege SE_ASSIGNPRIMARYTOKEN_NAME |
取代一个程序层级的 (处理程序等级的token) | WinNT.h |
SeAuditPrivilege SE_AUDIT_NAME |
产生安全性稽核 | WinNT.h |
SeBackupPrivilege SE_BACKUP_NAME |
备份文件及目录 | WinNT.h |
SeChangeNotifyPrivilege SE_CHANGE_NOTIFY_NAME |
略过往返检查 | WinNT.h |
SeCreatePagefilePrivilege SE_CREATE_PAGEFILE_NAME |
建立一个分页档 | WinNT.h |
SeCreatePermanentPrivilege SE_CREATE_PERMANENT_NAME |
建立永久分享的物件 | WinNT.h |
SeCreateTokenPrivilege SE_CREATE_TOKEN_NAME |
建立一个token物件 | WinNT.h |
SeDebugPrivilege SE_DEBUG_NAME |
除错程序 | WinNT.h |
SeEnableDelegationPrivilege SE_ENABLE_DELEGATION_NAME |
信任委派的电脑及使用者帐户 | WinNT.h |
SeIncreaseBasePriorityPrivilege SE_INC_BASE_PRIORITY_NAME |
增加优先权清单 | WinNT.h |
SeIncreaseQuotaPrivilege SE_INCREASE_QUOTA_NAME |
增加配额 | WinNT.h |
SeLoadDriverPrivilege SE_LOAD_DRIVER_NAME |
载入及卸载设备驱动程序 | WinNT.h |
SeLockMemoryPrivilege SE_LOCK_MEMORY_NAME |
锁住在内存中的页面 | WinNT.h |
SeMachineAccountPrivilege SE_MACHINE_ACCOUNT_NAME |
增加工作站到网域 | WinNT.h |
SeProfileSingleProcessPrivilege SE_PROF_SINGLE_PROCESS_NAME |
设定档个别的程序 | WinNT.h |
SeRemoteShutdownPrivilege SE_REMOTE_SHUTDOWN_NAME |
强迫从远端系统关机 | WinNT.h |
SeRestorePrivilege E SE_RESTORE_NAM |
回存文件及目录 | WinNT.h |
SeSecurityPrivilege SE_SECURITY_NAME |
管理稽核及安全性日志 | WinNT.h |
SeShutdownPrivilege SE_SHUTDOWN_NAME |
关闭系统 | WinNT.h |
SeSyncAgentPrivilege SE_SYNC_AGENT_NAME |
使目录服务资料同步 | WinNT.h |
SeSystemEnvironmentPrivilege SE_SYSTEM_ENVIRONMENT_NAME |
修改韧体环境值 | WinNT.h |
SeSystemProfilePrivilege SE_SYSTEM_PROFILE_NAME |
设定档系统效能 | WinNT.h |
SeSystemtimePrivilege SE_SYSTEMTIME_NAME |
改变系统时间 | WinNT.h |
SeTakeOwnershipPrivilege SE_TAKE_OWNERSHIP_NAME |
取得文件或其他物件的所有权 | WinNT.h |
SeTcbPrivilege SE_TCB_NAME |
扮演操作系统的一部份 | WinNT.h |
SeUndockPrivilege SE_UNDOCK_NAME |
从母机中移除电脑 | WinNT.h |
SeUnsolicitedInputPrivilege SE_UNSOLICITED_INPUT_NAME |
接收未经请求的设备输入 | WinNT.h |
SeBatchLogonRight SE_BATCH_LOGON_NAME |
以批次工作的身分登入 | NTSecAPI.h |
SeDenyBatchLogonRight SE_DENY_BATCH_LOGON_NAME |
拒绝批次工作的登入 | NTSecAPI.h |
SeDenyInteractiveLogonRight SE_DENY_INTERACTIVE_LOGON_NAME |
拒绝本机登入 | NTSecAPI.h |
SeDenyNetworkLogonRight SE_DENY_NETWORK_LOGON_NAME |
拒绝从网路存取这台电脑 | NTSecAPI.h |
SeDenyServiceLogonRight SE_DENY_SERVICE_LOGON_NAME |
拒绝以服务的身分登入 | NTSecAPI.h |
SeInteractiveLogonRight SE_INTERACTIVE_LOGON_NAME |
本机登入 | NTSecAPI.h |
SeNetworkLogonRight SE_NETWORK_LOGON_NAME |
从网路存取这台电脑 | NTSecAPI.h |
SeServiceLogonRight SE_ SERVICE_LOGON_NAME |
以服务的身分登入 | NTSecAPI.h |
系统实际上会区别权限和它的近亲,即帐户权利,他们被同等地指派及撤销。如表9-10所示,权限定义在WinNT.h中,而帐户权利则在NTSecAPI.h中定义。
现在您对于权限以及系统如何表示他们的方法应该已有稍微的认识,让我们开始研究在系统上指派及撤销权限的部份。
LSA函数
为 了有计划性地指派权限,您需要熟悉一组称为LSA的函数。LSA代表Local Security Authority,它处理本地机器上的使用者登入及认证。LSA函数可用来作一些与本章有关之权限指派工作。使用LSA函数的第一步是撷取原则物件的 handle。原则物件代表您将执行帐户管理函数的系统。为了获得原则物件的handle,您必须呼叫LsaOpenPolicy。
NTSTATUS LsaOpenPolicy( PLSA_UNICODE_STRING plsastrSystemName, PLSA_OBJECT_ATTRIBUTES pObjectAttributes, ACCESS_MASK DesiredAccess, PLSA_HANDLE pPolicyHandle);
这个函数起先看起来可能有点奇怪,因为这些资料型态在其他的Win32函数中不常被使用,所以让我们一个一个地检查。
PlsastrSystemName是指向LSA_UNICODE_STRING结构的指标,定义如下:
typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; }LSA_UNICODE_STRING
跟Net函数一样,LSA函数只处理Unicode格式的字串。然而,和Net函数不同的是,LSA函数要求以LSA_UNICODE_STRING结构管理所有的字串,此结构包括字串长度、缓冲器长度以及指向缓冲器的指标。
说明
要记得Length(指示字串的长度)及MaximumLength(指示缓冲器的长度)成员是以位元组的方式储存,而非字元。
经 由LSA_UNICODE_STRING结构之Buffer成员指向的字串没有明确地定义以0作结束。您可以选择用0来结束字串,但必须确定在字串长度的 计算中没有包括无效的结尾。请记住当系统传回LSA_UNICODE_STRING给软件时,它不能指望经由Buffer成员指向的字串以0结束。
处 理LSA_UNICODE_STRING类型可能会有点不熟练,因为它是以长度分隔而不是以0作为分隔。正因如此,LSA_UNICODE_STRING 实际上是被包含在C++ 类别中。我建立了一个称为CLSAStr的类别,在本章稍后叙述的TrusteeMan范例应用程序中会大量地使用到它。
要呼叫LsaOpenPolicy,必须初始化LSA_UNICODE_STRING结构的实例并将它指向一个包含想操作之权限的Unicode字串系统名称。传递NULL以作为plsastrSystemName参数值,表示为本地机器。
PobjectAttributes参数指向LSA_OBJECT_ATTRIBUTES结构的实例,在此时还未使用,因此要将它的每个成员初始化为0。
DesiredAccess 参数被宣告为ACCESS_MASK类型,对应到32位元无号整数。假如您要在系统上查看权限的资讯,则须结合此讨论后之范例中的 POLICY_VIEW_LOCAL_INFORMATION及POLICY_LOOKUP_NAMES。假如您要设定权限资讯,应该要结合 POLICY_CREATE_ACCOUNT存取标记。
最后一个LsaOpenPolicy的参数要求您传递LSA_HANDLE类型变数的位址。执行成功后,系统会将此handle放置到此变数中开放的LSA原则物件。一旦您完成了这个原则物件,即应该传递此handle到LsaClose函数:
NTSTATUS LsaClose(LSA_HANDLE hObjectHandle);
所有的LSA函数皆传回NTSTATUS类型的值,您应该将这个值传递到LsaNtStatusToWinError中:
ULONG LsaNtStatusToWinError(NTSTATUS Status);
LsaNtStatusToWinError函数将NT状态程序代码转变成熟悉的Win32错误程序代码,就像从GetLastError传回的一样。在转变后,成功的呼叫LsaOpenPolicy,以传回一个ERROR_SUCCESS值。
以下的程序代码显示开启一个在网路上,系统名称为「JasonsComputer」之LSA原则物件的适当方法:
LSA_OBJECT_ATTRIBUTES lsaOA = {0}; LSA_UNICODE_STRING lsastrComputer = {0}; LSA_HANDLE hPolicy = NULL; // 电脑名称 WCHAR* pstrName = L"JasonsComputer"; // 设定LSA_OBJECT_ATTRIBUTES结构的大小 lsaOA.Length = sizeof(lsaOA); // 填入LSA_UNICODE_STRING结构的成员 lsastrComputer.Length = (USHORT) (lstrlen(pstrName) * sizeof(WCHAR)); lsastrComputer.MaximumLength = lsastrComputer.Length + sizeof(WCHAR); lsastrComputer.Buffer = pstrName; // 撷取原则的handle NTSTATUS ntStatus = LsaOpenPolicy(&lsastrComputer, &lsaOA, POLICY_VIEW_LOCAL_INFORMATION | POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, &hPolicy); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS){ // 处理错误 }
现在您已经知道如何取得LSA原则物件开启的handle,所以您已准备好开始管理系统上的权限。
列举权限
列举系统权限的方法有两种:获得特定信任成员所持有的权限清单,或者请求持有特定权限的信任成员清单。
信任成员持有的权限
让我们开始讨论如何获得系统特定的信任成员持有的权限清单。使用LsaEnumerateAccountRights函数来列举权限:
NTSTATUS LsaEnumerateAccountRights( LSA_HANDLE hPolicy, PSID psidTrustee, PLSA_UNICODE_STRING* pplsastrUserRights, PULONG plCountOfRights);
您 必须传递一个与POLICY_LOOKUP_NAMES存取一起建立的开放原则物件,以作为LSAEnumerateAccountRights的 hPolicy参数。PsidTrustee参数是个指向您想要列举权限之信任成员SID的指标。您可以使用LookupAccountName(在先前 的〈认识SIDs〉一节中有讨论)从信任成员的帐户名称获得一个SID。这个信任成员可以是使用者帐户、群组帐户或者电脑帐户。
您 应该传递LSA_UNICODE_STRING型态的变数位址给LsaEnumerateAccountRights的 pplsastrUserRights参数。系统会产生LSA_UNICODE_STRING结构的阵列,并指派一个缓冲器包含此阵列,然后在 pplsastrUserRights参数指向的变数中放置一个指向缓冲器的指标。系统会传回plCountOfRights指向的ULONG阵列变数中 的权限号码。
因为系统已经了指派一个代表您的缓冲器,所以当您完成它时,必须释放这个缓冲器。传递一个指标到LsaFreeMemory函数的缓冲器中:
NTSTATUS LsaFreeMemory(PVOID pvBuffer);
假如LsaEnumerateAccountRights执行成功了,那么转变的状态程序代码将是ERROR_SUCCESS;假如帐户没有被指派权限,则转变的错误程序代码将会是ERROR_FILE_NOT_FOUND。
经 由LsaEnumerateAccountRights传回的阵列元素是LSA_UNICODE_STRING(在先前的〈LSA函数〉一节中定义)。每 个元素会指向一个包含代表帐户权利的Unicode字串缓冲器,包括SeDebugPrivilege及 SeEnableDelegationPrivilege等值。您可以参考先前的
虽然LookupPrivilegeDisplayName函数和列举权限并无直接的关系,但是在此仍应该提到它。这个函数将计划性的权限名称,例如SeTcbPrivilege,译成易记的显示名称,在这个实例中则被译为「扮演作业系统的一部份」。
BOOL LookupPrivilegeDisplayName( PCTSTR pszSystemName, PCTSTR pszName, PTSTR pszDisplayName, PDWORD cbDisplayName, PDWORD pLanguageId);
这个函数为权限取得系统名称及计划性名称,然后再将易记的名称传到您提供的缓冲器中。
说明
LookupPrivilegeDisplayName不会为帐户权利传回显示名称。它只为权限工作。我们在此处的讨论并不区分这两种帐户权利的类型,这个主题在第十一章会有更完整讨论。您可以参考 表9-10 来决定帐户权利是否为权限。假如所讨论的权利在WinNT.h标头档中被定义,则它是个权限。如果它被定义在NTSecAPI.h标头档中,则它只是个帐户权利,并非实际的权限。
以下的范例函数显示如何列出信任成员持有的权限清单。它取得LSA原则handle及PSID以作为它的参数。
BOOL PrintTrusteePrivs(LSA_HANDLE hPolicy, PSID psid) { BOOL fSuccess = FALSE; WCHAR szTempPrivBuf[256]; WCHAR szPrivDispBuf[1024]; PLSA_UNICODE_STRING plsastrPrivs = NULL; __try { // 撷取SID的权限阵列 ULONG lCount = 0; NTSTATUS ntStatus = LsaEnumerateAccountRights(hPolicy, psid, &plsastrPrivs, &lCount); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS) { plsastrPrivs = NULL; __leave; } ULONG lDispLen = 0; ULONG lDispLang = 0; for (ULONG lIndex = 0;lIndex < lCount; lIndex++) { // 确保0的结束 lstrcpyn(szTempPrivBuf, plsastrPrivs[lIndex].Buffer, plsastrPrivs[lIndex].Length); szTempPrivBuf[plsastrPrivs[lIndex].Length] == 0; wprintf(L"Programmatic Name: %s/n", szTempPrivBuf); // 转译成显示的名称 lDispLen = 1024; // Size of static Display buffer if (LookupPrivilegeDisplayName(NULL, szTempPrivBuf, szPrivDispBuf, &lDispLen, &lDispLang)) wprintf(L"Display Name: %s/n/n", szPrivDispBuf); } fSuccess = TRUE; } __finally { if (plsastrPrivs) LsaFreeMemory(plsastrPrivs); } return(fSuccess); }
持有特定权限的信任成员
第二种撷取Windows 2000系统权限资讯的方式为:经由对LsaEnumerate AccountsWithUserRight的呼叫请求持有特定权限的信任成员清单。
NTSTATUS LsaEnumerateAccountsWithUserRight( LSA_HANDLE hPolicy, PLSA_UNICODE_STRING plsastrUserRight, PVOID* ppvEnumerationBuffer, PULONG CountReturned);
这 个函数请求一个LSA原则物件的handle,与LsaEnumerateAccountRights很类似。然而,它取代了 LSA_UNICODE_STRING结构的信任成员名称,您应该传递一个附带权限或帐户权利的计划性名称的LSA_UNICODE_STRING结构指 标。
系 统经由被分派的缓冲器传回信任成员资讯到您的软件中,以及透过ppvEnumerationBuffer参数传回它的指标。尽管这个参数被定义成 PVOID指标,您还是应该传递指标的位址给PLSA_ENUMERATION_INFORMATION类型之变数,因为系统将以 LSA_ENUMERATION_INFORMATION结构阵列的方式传回资讯。假如Windows开发人员定义了 LsaEnumerateAccountsWithUserRight取得正确类型的指标的话,情况将会更好,但是他们选择要求一个PVOID指标作为替 代,所以您必须自行将这个参数转型。
LSA_ENUMERATION_INFORMATION结构真的非常简单,而且只包含单一的成员-即一个SID指标:
typedef struct _LSA_ENUMERATION_INFORMATION { PSID Sid; } LSA_ENUMERATION_INFORMATION;
阵 列中元素的数量被传回到CountReturned指向的变数中,为LsaEnumerateAccountsWithUserRight的最后一个参 数。在传回阵列中的SIDs,经由传递SID到LookupAccountSid中,可被用来产生信任成员名称。当您完成 LsaEnumerateAccountsWithUserRight传回的缓冲器后,应该要将它的指标传递给LsaFreeMemory。
权限的指派与移除
建立单独的软件来列举权限给信任成员帐户的情形是罕见的。然而,几乎任何一个建立信任成员帐户的软件皆必须从帐户中指派(或移除)权利。幸运的是,LSA提供了两个简单的函数来执行这些任务。首先,LsaAddAccountRights用来将权限授予信任成员:
NTSTATUS LsaAddAccountRights( LSA_HANDLE hPolicy, PSID psidTrustee, PLSA_UNICODE_STRING plsastrUserRights, ULONG lCountOfRights);
这个函数很容易使用。您必须传递开启LSA原则物件的handle,以及指向您要修改权限之信任成员SID指标(本章先前的讨论中曾提到,您可以经由呼叫LookupAccountName而获得使用者或群组帐户的SID)。
然 而,在呼叫LsaAddAccountRights之前,您应该建立一个LSA_UNICODE_ STRING结构的阵列,并加上您想要信任成员持有的权限名称。传递这个阵列的指标作为LsaAddAccountRights的 plsastrUserRights参数值。最后,传递阵列中元素的数量给最后一个lCountOfRights参数。
LsaAddAccountRights函数忽略已信任成员持有的任何帐户权利或权限。然而,若阵列中的任何一个权利对系统来说为无效的名称,则会失去作用。
以下的程序代码提供了一个如何指派SeInteractiveLogonRight帐户权利及SeTcbPrivilege权限给经由变数psid指出SID的信任成员。
LSA_UNICODE_STRING lsastrPrivs[2] = { 0 }; lsastrPrivs[0].Buffer = SE_INTERACTIVE_LOGON_NAME; lsastrPrivs[0].Length = lstrlen(lsastrPrivs[0].Buffer) * sizeof(WCHAR); lsastrPrivs[0].MaximumLength = lsastrPrivs[0].Length + sizeof(WCHAR); lsastrPrivs[1].Buffer = SE_TCB_NAME; lsastrPrivs[1].Length = lstrlen(lsastrPrivs[1].Buffer) * sizeof(WCHAR); lsastrPrivs[1].MaximumLength = lsastrPrivs[1].Length + sizeof(WCHAR); NTSTATUS ntStatus = LsaAddAccountRights(hPolicy, psid, lsastrPrivs, 2); ULONG lErr = LsaNtStatusToWinError(ntStatus);
这个用来从信任成员帐户中移除权限的函数与LsaAddAccountRights非常相似,称LsaRemoveAccountRights,其函数原型如下:
NTSTATUS LsaRemoveAccountRights( LSA_HANDLE hPolicy, PSID psidTrustee, BOOLEAN fAllRights, PLSA_UNICODE_STRING plsastrUserRights, ULONG lCountOfRights);
它除了从psidTrustee指示的信任成员中移除权利清单以及拥有一个附加参数BooleanfAllRights外,此函数与LsaAddAccountRights一样。
FallRights 可让您从信任成员中移除所有的权限,而不用建立信任成员持有的权限清单。假如您传递TRUE给此参数,则plsastrUserRights及 lCountOfRights参数应该分别为NULL和0。如果您传递FALSE给fAllRights参数,则 LsRemoveAccountRights与LsaAddAccountRights的使用方式相同。
在 结束LsaRemoveAccountRights的讨论前,我应该指出从帐户中移除所有权利的相关细节。《Platform SDK》文件中说明如果您传递TRUE给fAllRights参数的话,系统将会从帐户中移除所有的权限,然后再从系统中删除这个帐户;然而,这只有从某 个观点来说才是正确的。每个执行Windows 2000的系统必须维持一个帐户权限项目的本机资料库(内部实作技术如同本机帐户),即使信任成员依靠在另一台机器上,例如网域控制站。 LsaRemoveAccountRights函数不会从主帐户的系统中删除信任成员帐户(即使是本地帐户);它只从它的本机资料库中删除帐户权限项目。
建立信任成员的理由
在 结束本章前,我想要谈论一些信任成员帐户的观点。我们已经讨论过如何建立及摧毁使用者及群组帐户以及如何指派权限给这些帐户的内容。此外,也浏览了安全识 别项或SIDs的关键主题。然而,您可能还想知道您的服务器软件必须建立信任成员帐户的原因,简单来说,这是因为许多服务器应用程序从不建立信任成员帐户 以及从现存的信任成员帐户指派或撤销权限。
这里有几个原因:
- 限制或管理资源的存取。这就是您的服务器软件如何利用信任成员管理的部份。
- 为使用者建立帐户,以作为登入识别的使用或为管理现存的使用者而建立群组帐户。尽管这是建立信任成员帐户常见的原因,但服务器软件并非如此。MMC或其他管理的工具通常会管理这些函数。
下一章我们将讨论信任成员及安全物件存取权利之间的关系。第十一章将谈到您的服务器软件可用来代表客户端或任何随意选择的信任成员帐户行为的方法,您也将学会经由使用第二个信任成员帐户调整现存信任成员权利的方法。
随 着这些主题的揭露,您将学会使用Windows环境中可得的各种技巧,并以更有创造性的方式来限制及增强对物件的存取,如果必要的话,使您的服务器软件拥 有建立信任成员帐户的权力是很重要的。在接下来的几章中讨论到这些主题时,我将提出经由您的服务器软件建立唯一信任成员的适当案例。
TrusteeMan范例应用程序
TrusteeMan 范例应用程序(「09 TrusteeMan.exe」)示范Net函数的使用及用来建立和管理使用者及群组帐户,与指派及撤销系统权限的LSA函数。这个应用程序的原始码及文 件,存放在随书光碟中的09-TrusteeMan目录下。图9-2显示了TrusteeMan范例应用程序的使用者介面。
图9-2 TrusteeMan范例应用程序之使用者介面 |
可 用性是覆写TrusteeMan范例应用程序的目标。因为以许多方面来说,必须探究与了解安全性的内容,而一个可用的工具能帮助您浏览系统的特征。这个范 例应用程序可让您尽情地建立使用者及群组帐户。也可让您修改群组的成员,并授予及否决权限给系统上的信任成员帐户。您可以输入要使用的电脑名称且在具有足 够权利的任何系统上管理信任成员。
说明
当您使用TrusteeMan建立使用者帐户时,应用程序会使用「Pass2000」作为您新帐户的密码。这个密码简化了使用者介面并集中在重要的主题上。假如您是系统的管理者,您可以使用MMC嵌入式管理单元为使用者帐户手动地设定密码。
建议您花些时间看TrusteeMan范例应用程序,以熟悉Windows 2000的信任成员管理的运作方式。本书其馀的章节皆假定您会使用TrusteeMan、MMC嵌入式管理单元,或其他管理使用者帐户及指派权限的工具。
TrusteeMan范例应用程序除了是个有用的工具外,它的原始码也提供了许多关于信任成员管理的Net及LSA函数功能参考。这个范例应用程序使用Net函数执行应用程序的左窗格或信任成员侧,而使用LSA函数实作应用程序的权利则在右窗格或权限侧。
这个范例也为编辑信任成员清单实作一个可重覆使用的通用对话方块。此范例使用这个对话方块来管理群组帐户及特定权限的持有者。系统没有为此提供内建的介面,所以在范例中包含一个。
表9-10 中所有可传回的权限清单内容。表9-9 。TrusteeMan范例应用程序 。表9-1 中所讨论的LOCALGROUP_INFO_* 结构集。您可以传递0或者1以作为此函数的level值。这两个结构的定义如下: