typedef struct _COAUTHIDENTITY {
   OLECHAR *User;
   // user account name
   // имя учетной записи пользователя
   ULONG UserLength;
   // wcslen(User)
   // длина имени пользователя
   OLECHAR *Domain;
   // Domain/Machine name
   // имя домена/машины
   ULONG DomainLength;
   // wcslen(Domain)
   // длина имени домена
   OLECHAR *Password;
   // cleartext password
   // пароль открытым текстом
   ULONG PasswordLength;
   // wcslen(Password)
   // длина пароля
   ULONG Flags;
   // must be SEC_WINNT_AUTH_IDENTITY_UNICODE
   // должно быть SEC_WINNT_AUTH_IDENTITY_UNICODE
   } COAUTHIDENTITY;
 
   Эта структура позволяет клиентам делать вызовы методов COM как любым принципалам защиты, при условии, что они знают открытые тексты паролей для желаемой учетной записи[1]. Если вместо указателя на явную структуру COAUTHIDENTITY передается нулевой указатель, то каждый внешний вызов будет делаться с использованием полномочий вызывающего процесса[2].
   Чаще всего метод IClientSecurity::SetBlanket применяется для повышения уровня аутентификации отдельного заместителя. Следующий код демонстрирует эту технологию:
 
   HRESULT Encrypt(IApe *pApe) {
   IClientSecurity *pcs = 0;
   // ask proxy manager for IClientSecurity interface
   // запрашиваем интерфейс IClientSecurity у администратора заместителей
   HRESULT hr = pApe->QueryInterface(IID_IClientSecurity, (void**)&pcs);
   if (SUCCEEDED(hr)) {
   hr = pcs->SetBlanket(pApe, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IDENTIFY, 0, EOAC_NONE);
   pcs->Release();
   }
   return hr;
   }
 
   В идеале вызывающая программа передаст этой функции скопированный интерфейсный заместитель. В противном случае с целью проведения операции копирования эту функцию можно было бы изменить следующим образом:
 
   HRESULT DupeAndEncrypt(IApe *pApe, IApe * &rpSecretApe) {
   rpSecretApe = 0;
   IClientSecurity *pcs = 0;
   // ask proxy manager for IClientSecurity interface
   // запрашиваем интерфейс IClientSecurity у администратора заместителей
   HRESULT hr = pApe->QueryInterface(IID_IClientSecurity, (void**)&pcs);
   if (SUCCEEDED(hr)) {
   hr = pcs->CopyProxy(pApe, (IUnknown**)&rpSecretApe);
   if (SUCCEEDED(hr))
   hr = pcs->SetBlanket (rpSecretApe, RPC_AUUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, RPC_С_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IDENTIFY, 0, EOAC_NONE);
   pcs->Release();
   }
   return hr;
   }
 
   Для удобства в COM API предусмотрены оберточные функции вокруг каждого из трех методов IClientSecurity , которые изнутри вызывают QueryInterface для нахождения соответствующего интерфейса IClientSecurity и затем вызывают нужный метод:
 
   // get security settings for interface proxy pProxy
   // получаем установки защиты для интерфейсного заместителя
   pProxy HRESULT CoQueryProxyBlanket([in] IUnknown *pProxy, [out] DWORD *pAuthnSvc, [out] DWORD *pAuthzSvc, [out] OLECHAR **pServerPrincName, [out] DWORD *pAuthnLevel, [out] DWORD *pImpLevel, [out] void **pAuthInfo, [out] DWORD *Capabilities);
   // change security settings for interface proxy pProxy
   // изменяем установки защиты для интерфейсного заместителя
   pProxy HRESULT CoSetProxyBlanket([in] IUnknown *pProxy, [in] DWORD AuthnSvc, [in] DWORD AuthzSvc, [in] OLECHAR *pServerPrincName, [in] DWORD AuthnLevel, [in] DWORD ImpLevel, [in] void *pAuthInfo, [in] DWORD Capabilities);
   // duplicate an interface proxy
   // копируем интерфейсный заместитель
   HRESULT CoCopyProxy([in] IUnknown *pProxy, [out] IUnknown **ppCopy);
 
   Следующий код представляет собой модифицированную версию предыдущей функции, где используются эти удобные процедуры:
 
   HRESULT DupeAndEncrypt(IApe *pApe, IАре *ArpSecretApe) {
   rpSecretApe = 0; HRESULT hr = СоСоруProxy(pApe, (IUnknown**)&rpSecretApe);
   if (SUCCEEDED(hr))
   hr = CoSetProxyBlanket(rpSecretApe, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IDENTIFY, 0, EOAC_NONE);
   return hr;
   }
 
   Первая версия несколько эффективнее, так как для нахождения интерфейса IClientSecurity в ней использован только один вызов QueryInterface. Для последней версии требуется меньше кода, поэтому и вероятность ошибок в ней меньше.
   Важно отметить, что методы IClientSecurity могут применяться только в тех интерфейсах, которые используют интерфейсные заместители. Это означает, что те интерфейсы, которые реализованы локально администратором заместителей (например, IMultiQI, IClientSecurity), не могут использоваться с методами IClientSecurity. Интерфейс IUnknown – это особый случай. IUnknown фактически является локальным интерфейсом, реализованным администратором заместителей. Однако администратору заместителей часто требуется связаться с апартаментом сервера для запроса новых интерфейсов и для освобождения ресурсов, хранящихся в соответствующем администраторе заглушек. Эта связь осуществляется через закрытый интерфейс IRemUnknown, который реализуется библиотекой COM отдельно внутри каждого апартамента. Разработчики могут контролировать полную защиту, использованную для этих вызовов IRemUnknown путем передачи реализации IUnknown администратора заместителей в IClientSecurity::SetBlanket (подобно интерфейсному заместителю, администратор заместителей использует общие для процесса автоматические установки защиты в случае, если функция SetBlanket не вызывается)[3]. Поскольку все интерфейсные заместители агрегированы администратором заместителей, то это, в сущности, означает, что вызовы IClientSecurity::SetBlanket на какой-либо определенный интерфейсный заместитель не влияют на функции QueryInterface, AddRef и Release. Скорее, на них влияют установки, примененные к реализации IUnknown администратором заместителей. Для того чтобы получить указатель на реализацию IUnknown администратором заместителей, можно просто запросить с помощью QueryInterface интерфейсный заместитель для IID_IUnknown. Следующий фрагмент кода демонстрирует эту технологию, отключая защиту как для интерфейсного заместителя, так и для его администратора заместителей:
 
   void TurnOffAllSecurity(IApe *pApe) {
   IUnknown *pUnkProxyManager = 0;
   // get a pointer to the proxy manager
   // получаем указатель на администратор заместителей
   HRESULT hr = pApe->QueryInterface(IID_IUnknown, (void**)&pUnkProxyManager);
   assert(SUCCEEDED(hr));
   // set blanket for proxy manager
   // устанавливаем защиту для администратора заместителей
   hr = CoSetProxyBlanket(pUnkProxyManager, RPC_C_AUTHN_NONE, RPC_C_AUTHZ_NONE, О, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS, 0, EOAC_NONE);
   assert(SUCCEEDED(hr));
   // set blanket for interface proxy
   // устанавливаем защиту для интерфейсного заместителя
   hr = CoSetProxyBlanket(pApe, RPC_C_AUTHN_NONE, RPC_C_AUTHZ_NONE, 0, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS, 0, EOAC_NONE);
   assert(SUCCEEDED(hr));
   // release temporary pointer to proxy manager
   // освобождаем временный указатель на администратор заместителей
   pUnkProxyManager->Release();
   }
 
   Хотя представляется возможным установить и запросить защиту для администратора заместителей, невозможно скопировать администратор заместителей с помощью IClientSecurity::CopyProxy, так как это нарушило бы правила идентификации COM.
   Когда ORPC-запрос направляется интерфейсной заглушке, COM создает объект контекста вызова (call context object), представляющий различные аспекты вызова, в том числе установки защиты того интерфейсного заместителя, который отправил этот запрос. COM связывает этот контекстный объект с потоком, который будет выполнять вызов метода. Библиотека COM выставляет API-функцию CoGetCallContext, позволяющую реализациям метода получить контекст для текущего вызова метода:
   HRESULT CoGetCallContext ([in] REFIID riid, [out, iid_is(riid)] void **ppv);
   В Windows NT 4.0 единственным интерфейсом, доступным для контекстного объекта вызова, является интерфейс IServerSecurity:
 
   [local, object, uuid(0000013E-0000-0000-C000-000000000046)]
   interface IServerSecurity : IUnknown {
   // get caller's security settings
   // получаем установки защиты вызывающей программы
   HRESULT QueryBlanket( [out] DWORD *pAuthnSvc,
   // authentication pkg
   // модуль аутентификации
   [out] DWORD *pAuthzSvc,
   // authorization pkg
   // модуль авторизации
   [out] OLECHAR **pServerName,
   // server principal
   // серверный принципал
   [out] DWORD *pAuthnLevel,
   // authentication level
   // уровень аутентификации
   [out] DWORD *pImpLevel,
   // impersonation level
   // уровень заимствования прав
   [out] void *pPrivs,
   // client principal
   // клиентский принципал
   [out] DWORD *pCaps
   // EOAC flags
   // флаги EOAC
   );
 
   // start running with credentials of caller
   // начинаем выполнение с полномочиями вызывающей программы
   HRESULT ImpersonateClent(void);
   // stop running with credentials of caller
   // заканчиваем выполнение с полномочиями вызывающей программы
   HRESULT RevertToSelf(void);
   // test for Impersonation
   // тест для заимствования прав BOOL
   IsImpersonating(void);
   }
 
   IServerSecurity::QueryBlanket возвращает установки полной защиты, фактически использованные для текущего ORPC-вызова (которые могут несколько отличаться от клиентских установок благодаря специфическому для SSP повышению уровней). Как было в случае с IClientSecurity::QueryBlanket, функции IServerSecurity::QueryBlanket также разрешается передавать нуль вместо неиспользуемых параметров. Ниже приведен пример реализации метода, которая гарантирует, что вызывающая программа обеспечила возможность шифрования перед обработкой вызова:
 
   STDMETHODIMP Gorilla::SwingFromTree(/*(in]*/ long nTreeID) {
   // get current call context
   // получаем контекст текущего вызова IServerSecurity *pss = 0;
   HRESULT hr = CoGetCallContext(IID_IServerSecurity, (void**)&pss);
   DWORD dwAuthnLevel;
   if (SUCCEEDED(hr)) {
   // get authentication level of current call
   // получаем уровень аутентификации текущего вызова
   hr = pss->QueryBlanket(0, 0, 0, &dwAuthnLevel, 0, 0, 0);
   pss->Release(); }
   // verify proper authentication level
   // проверяем правильность уровня аутентификации
   if (FAILED(hr) || dwAuthnLevel != RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
   hr = APE_E_NOPUBLICTREE;
   else hr = this->ActuallySwingFromTree(nTreeID);
   return hr;
   }
 
   Как было в случае с IClientSecurity, каждый метод IServerSecurity доступен в качестве удобной API-функции. Приводимая ниже реализация метода использует удобную подпрограмму вместо явного вызова интерфейса IServerSecurity
 
   STDMETHODIMP Gorilla::SwingFromTree(/*[in]*/ long nTreeID) {
   DWORD dwAuthnLevel;
   // get authentication level of current call
   // получаем уровень аутентификации текущего вызова
   HRESULT hr = CoQueryClientBlanket(0, 0, 0, &dwAuthnLevel, 0, 0, 0);
   // verify proper authentication level
   // проверяем правильность уровня аутентификации
   if (FAILED(hr) || dwAuthnLevel != RPC_C_AUTHN_LEVEL_РКТ_PRIVACY)
   hr = АРЕ_Е_NOPUBLICTREE;
   else hr = this->ActuallySwingFromTree(nTreeID);
   return hr;
   }
 
   И снова мы видим, что последняя версия требует меньше кода и поэтому вероятность ошибок в ней меньше.
   Метод IServerSecurity::QueryBlanket также позволяет разработчику объекта находить идентификатор защиты вызывающей программы через параметр pPrivs. Как и в случае с полномочиями, передаваемыми в IClientSecurity::SetBlanket , точный формат этого идентификатора является специфическим для конкретного модуля защиты. Для NTLM этот формат является просто строкой вида Authority\AccountName
   Следующая реализация метода отыскивает идентификатор защиты вызывающей программы с помощью API-функции CoQueryClientBlanket:
 
   STDMETHODIMP Gorilla::EatBanana() {
   OLECHAR *pwszClientPrincipal = 0;
   // get security identifier of caller
   // получаем идентификатор защиты вызывающей программы
   HRESULT hr = CoQueryClientBlanket(0, 0, 0, 0, 0, (void**)&pwszClientPrincipal, 0);
   // log user name
   // регистрируем имя пользователя
   if (SUCCEEDED(hr)) {
   this->LogCallerIDToFile(pwszClientPrincipal);
   hr = this->ActuallyEatBanana();
   }
   return hr;
   }
 
   При вызове CoQueryClientBlanket для успешного возвращения идентификатора защиты вызывающей программы последняя должна определить:
   По крайней мере RPC_C_IMP_LEVEL_IDENTIFY как автоматический (или явный) уровень заимствования прав;
   По крайней мере RPC_C_AUTHN_LEVEL_CONNECT как автоматический (или явный) уровень аутентификации.
   Если вызывающая программа явно изменила вызывающий принципал в установках полной защиты заместителя с помощью функции COAUTHIDENTITY, то вместо него будет возвращено имя явно заданного принципала.
   Точно так же, как можно полностью контролировать установки защиты, использующиеся при вызове метода с помощью интерфейса IClientSecurity , представляется полезным контролировать установки защиты, использованные при вызове на активацию. К сожалению, активационные вызовы являются глобальными API-функциями, не имеющими соответствующего администратора заместителей, откуда можно было бы получить интерфейс IClientSecurity. Для того чтобы позволить вызывающим программам задавать установки защиты для активационных вызовов, каждый активационный вызов принимает структуру СОSERVERINFO:
 
   typedef struct _COSERVERINFO {
   DWORD dwReserved1;
   LPWSTR pwszName; COAUTHINFO * pAuthInfo;
   DWORD * dwReserved2;
   } COSERVERINFO;
 
   В одной из предыдущих глав было отмечено, что элемент данных pwszName позволяет вызывающей программе осуществлять явный контроль того, какая хост-машина будет обслуживать активационный запрос. Третий элемент данных, pAuthInfo, указывает на структуру данных, которая позволяет вызывающей программе контролировать установки защиты, использованные при осуществлении активационного вызова. Этот параметр является указателем на структуру COAUTHINFO, определенную следующим образом:
 
   typedef struct _COAUTHINFO {
   DWORD dwAuthnSvc;
   DWORD dwAuthzSvc;
   LPWSTR pwszServerPrincName;
   DWORD dwAuthnLevel;
   DWORD dwImpersonationLevel;
   COAUTHIDENTITY * pAuthIdentityData;
   DWORD dwCapabilities;
   } COAUTHINFO;
 
   Эти элементы данных соответствуют параметрам IClientSecurity::SetВlanket, однако используются только во время активационного вызова и не влияют на результирующий интерфейсный заместитель[4].
   Следующий фрагмент кода осуществляет активационный вызов, используя структуру COAUTHINFO, чтобы заставить SCM использовать при активационном вызове шифрование (RPC_C_AUTHN_LEVEL_PKT_PRIVACY):
 
   void CreateSecretChimp(IApe *&rpApe) {
   rpApe = 0;
   // create a COAUTHINFO that specifies privacy
   // создаем COAUTHINFO, которая определяет секретность
   COAUTHINFO cai = { RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IDENTIFY, 0, 0 };
   // issue an activation call using the COAUTHINFO
   // осуществляем активационный вызов с использованием COAUTHINFO
   COSERVERINFO csi = { 0, 0, &cai, 0 };
   IApeClass *pac = 0;
   hr = CoGetClassObject(CLSID_Chimp, CLSCTX_ALL, &csi, IID_IApeClass, (void**)&pac);
   assert(SUCCEEDED(hr));
   // the activation call occurred with encryption,
   // but рас is using automatic security settings
   // активационный вызов произошел с шифрованием,
   // но пакет использует автоматические установки защиты
   hr = pac->CreateApe(&rpApe);
   pac->Release();
   return hr;
   }
 
   Важно отметить, что, поскольку структура COAUTHINFO оказывает воздействие только на сам активационный вызов, результирующий интерфейсный заместитель IApeClass будет использовать автоматические установки защиты, установленные более ранним вызовом CoInitializeSecurity. Это означает, что вызов метода IApeClass::CreateApe будет использовать автоматические установки защиты, а не те, которые определены структурой COAUTHINFO . Для того чтобы гарантировать применение шифрования во время создания или обработки нового Chimp, необходимо модифицировать функцию так, чтобы она ставила полную защиту на заместители обоих интерфейсов – IApeClass и IАре:
 
   // encrypt calls on IApeClass reference
   // зашифровываем вызовы на ссылку на IApeClass CoSetProxyBlanket(pac, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_ANONYMOUS, 0, EOAC_NONE);
   // issue call to create object
   // осуществляем вызов для создания объекта
   pac->CreateApe(&rpApe);
   // encrypt calls on IApe reference
   // зашифровываем вызовы на ссылку на IApe
   CoSetProxyBlanket(rpApe, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, 0, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_ANONYMOUS, 0, EOAC_NONE);
 
   Использование явного вызова COAUTHIDENTITY во время активации может позволить вызывающей программе создавать объекты в процессах, которые в противном случае были бы недоступны принципалу вызывающего процесса. Однако в этом случае вызывающая программа должна гарантировать, что администратор заместителей использует эти же самые полномочия при освобождении интерфейсного указателя, иначе будет утечка ресурсов со стороны сервера. Как уже упоминалось ранее в этой главе, полная защита администратора заместителей контролируется отдельно путем вызова метода IClientSecurity::SetBlanket на реализацию IUnknown администратором заместителей.
 

Контроль доступа

   Как уже упоминалось ранее в этой главе, каждый процесс COM может защитить сам себя от несанкционированного доступа. COM рассматривает контроль доступа на двух уровнях: права запуска (launch permissions) и права доступа (access permissions). Права запуска используются, чтобы определить, какие пользователи могут запускать серверные процессы при осуществлении активационных вызовов к SCM. Права доступа определяют, какие пользователи могут обращаться к объектам процесса после того, как сервер уже запущен. Оба типа контроля доступа можно сконфигурировать при помощи DCOMCNFG.EXE, но только права доступа могут быть заданы программно на этапе выполнения (поскольку после того, как сервер запущен, уже слишком поздно отказывать пользователю в правах запуска). Вместо этого право запуска предоставляется диспетчеру управления сервнсами SCM во время активации.
   Когда SCM решает, что должен быть запущен новый серверный процесс, он пытается получить дескриптор защиты NT SECURITY_DESCRIPTOR, описывающий, каким пользователям разрешено запускать серверный процесс. В первую очередь SCM проверяет AppID класса для явной установки прав запуска. Эта установка приходит в форме сериализованного дескриптора защиты NT, который хранится в именованной величине LaunchPermission AppID:
   [HKCR\AppID\{27EE6A4D-DF65-1d0-8C5F-0080C73925BA}]
   LaunchPermission=<serialized NT security descriptor>
   Если эта именованная величина отсутствует, SCM пытается прочитать общие для всей машины права запуска из такой именованной величины:
   [HKEY_LOCAL_MACHINE\Software\Microsoft\OLE]
   DefaultLaunchPermission=<serialized NT security descriptor>
   Обе эти установки могут быть модифицированы с помощью DCOMCNFG.EXE. Если не найден ни один из этих ключей реестра, то COM запретит запуск кому бы то ни было. Если же SECURITY_DESCRIPTOR найден, SCM проверяет идентификатор защиты активизирующей вызывающей программы (формально называемой активизатором – actiuator) по списку разграничительного контроля доступа DACL (Discretionary Access Control List), имеющемуся в дескрипторе, чтобы определить, имеет ли активизатор полномочия на запуск сервера. Если активизатор не имеет необходимых полномочий, то следует отказ на активационный вызов с HRESULT E_ACCESSDENIED, и никаких процессов не запускается. В случае успешной проверки SCM запускает серверный процесс и продолжает выполнение активационного запроса.
   Права запуска определяют только, какие пользователи могут или не могут начинать серверные процессы во время активации. Эта проверка всегда выполняется SCM на основе информации, записанной в реестре. Права доступа определяют, какие пользователи могут действительно связываться с объектами серверного процесса. Эта проверка осуществляется библиотекой COM при каждом запросе на установку соединения, приходящем от клиента. Для контроля установок прав доступа к процессу разработчики могут использовать API-функцию CoIntializeSecurity.
   Напомним, что процессы, не вызывающие явно функцию CoInitializeSecurity, автоматически используют список контроля доступа, записанный под ключом реестра AppID приложения:
   [HKCR\AppIO\{27EE6A4D-DF65-11d0-8C5F-0080C73925BA}]
   AccessPermission=<serialized NT security descriptor>
   Ранее объяснялось, что если этот элемент реестра отсутствует, то COM ищет установку по умолчанию для всей машины, а если она также отсутствует, то создается новый список контроля доступа, который включает только принципала серверных процессов и встроенной учетной записи SYSTEM.
   Приложения, явно вызывающие CoInitializeSecurity, могут вручную контролировать, каким вызывающим программам разрешен доступ к объектам, экспортируемым данным процессом. По умолчанию первый параметр CoIntializeSecurity принимает указатель на SECURITY_DESCRIPTOR NT. Если вызывающая программа передает в качестве этого параметра нулевой указатель, то COM не будет осуществлять никакого контроля входящих вызовов. Это разрешает вызовы от любого аутентифицированного принципала защиты. Если и клиент, и сервер укажут RPC_C_AUTHN_LEVEL_NONE, то COM разрешит вызовы от кого угодно, независимо от его аутентификации. Если же в вызывающей программе имеется легальный указатель на дескриптор защиты, то COM с помощью DACL этого дескриптора защиты определит, каким вызывающим программам разрешен доступ к объектам процесса. Заголовки SDK определяют так называемый флаг прав (COM_RIGHTS_EXECUTE), который используется при создании DACL для явного разрешения или запрета пользователям на связь с объектами процесса.
   Хотя и допускается использовать API-функции Win32 для создания SECURITY_DESCRIPTOR с целью передачи его в CoInitializeSecurity, этот способ контроля доступа к объектам процесса не является предпочтительным, в основном по причине темной природы API-функций защиты Win32. Для упрощения программирования в COM контроля доступа в реализации COM для Windows NT 4.0 Service Pack 2 разработчикам разрешено указывать тот объект COM, который будет использоваться для выполнения проверки доступа при установке новых соединений. Этот объект регистрируется библиотекой COM во время выполнения CoInitializeSecurity и должен реализовать интерфейс IAccessControl:
   [object, uuid(EEDD23EO-8410-11CE-A1C3-08002B2B8D8F)]
   interface IAccessControl : IUnknown {
   // add access allowed rights for a list of users
   // добавляем разрешенные права доступа для списка пользователей
   HRESULT GrantAccessRights([in] PACTRL_ACCESSW pAccessList);
   // explicitly set the access rights for a list of users
   // явно устанавливаем права доступа для списка пользователей
   HRESULT SetAccessRights([in] PACTRL_ACCESSW pAccessList
   // users+rights
   // пользователи + Права
   );
   // set the owner/group IDs of the descriptor
   // устанавливаем идентификаторы владельца/группы для дескриптора
   HRESULT Set0wner(
   [in] PTRUSTEEW pOwner, // owner ID
   // ID владельца
   [in] PTRUSTEEW pGroup // group ID
   // ID группы
   );
   // remove access rights for a list of users
   // удаляем права доступа для списка пользователей
   HRESULT RevokeAccessRights(
   [in] LPWSTR lpProperty, // not used
   // не используется
   [in] ULONG cTrustees, // how many users
   // сколько имеется пользователей
   [in, size_is(cTrustees)] TRUSTEEW prgTrustees[] // users
   // пользователи
   );
   // get list of users and their rights
   // получаем список пользователей и их прав
   HRESULT GetAllAccessRights(
   [in] LPWSTR lpProperty, // not used
   // не используется
   [out] PACTRL_ACCESSW *ppAccessList, // users+rights
   // пользователи + права
   [out] PTRUSTEEW *ppOwner, // owner ID
   // ID владельца
   [out] PTRUSTEEW *ppGroup // group ID
   // ID группы
   );
   // called by COM to allow/deny access to an object
   // вызывается COM для разрешения/запрета доступа к объекту
   HRESULT IsAccessAllowed(
   [in] PTRUSTEEW pTrustee, // caller's ID
   // ID вызывающей программы
   [in] LPWSTR lpProperty, // not used
   // не используется
   [in] ACCESS_RIGHTS Rights, // COM_RIGHTS_EXECUTE
   [out] BOOL *pbAllowed // yes/no!
   // да/нет!
   );
   }
   Этот интерфейс предназначен для того, чтобы разработчики могли создавать объекты контроля доступа на основе статических таблиц данных, преобразующих имена принципалов в права доступа. Интерфейс основывается на новом Windows NT 4.0 API защиты на базе опекуна (trustee), то есть пользователя, обладающего правами доступа к объекту. Основным типом данных, используемым этим API, является TRUSTEE:
   typedef struct _TRUSTEE_W {
   struct _TRUSTEE_W *pMultipleTrustee;
   MULTIPLE_TRUSTEE_OPERATION MultipleTrusteeOperation;
   TRUSTEE_FORM TrusteeForm;
   TRUSTEE_TYPE TrusteeType;
   switch_is(TrusteeForm)]
   union {
   [case(TRUSTEE_IS_NAME)]
   LPWSTR ptstrName;
   [case(TRUSTEE_IS_SID)]
   SID *pSid;
   };
   } TRUSTEE_W, *PTRUSTEE_W, TRUSTEEW, *PTRUSTEEW;
   Этот тип данных используется для описания принципала защиты. Первые два параметра, pMultipleTrustee и MultipleTrusteeOperation, позволяют вызывающей программе отличать настоящие регистрационные имена (logins – логины) от попыток заимствования прав. Пятый параметр, ptstrName/pSid, содержит либо идентификатор защиты NT (security identifier – SID), либо текстовое имя учетной записи, подлежащее идентификации. При этом третий параметр, TrusteeForm, указывает, какой именно член объединения (union member) используется. Четвертый параметр, TrusteeType, указывает, является ли данный принципал учетной записью пользователя или группы.
   Для связывания опекуна с полномочиями, которые ему даны или в которых ему отказано, в Win32 API предусмотрен тип данных ACTRL_ACCESS_ENTRY:
   typedef struct _ACTRL_ACCESS_ENTRYW {
   TRUSTEE_W Trustee; // who?
   // кто?
   ULONG fAccessFlags; // allowed/denied?
   // разрешено/запрещено?
   ACCESSRIGHTS Access;// which rights?
   // какие права?
   ACCESSRIGHTS ProvSpecificAccess; // not used by COM
   // в COM не используется
   INHERIT_FLAGS Inheritance; // not used by COM
   // в COM не используется
   LPWSTR lpInheritProperty; // not used by COM
   // в COM не используется
   } ACTRL_ACCESS_ENTRYW, *PACTRL_ACCESS_ENTRYW;
   а также тип данных для создания списков элементов для опекунов/полномочий:
   typedef struct _ACTRL_ACCESS_ENTRY_LISTW {
   ULONG cEntries;
   [size_is(cEntries)] ACTRL_ACCESS_ENTRYW *pAccessList;
   } ACTRL_ACCESS_ENTRY_LISTW, *PACTRL_ACCESS_ENTRY_LISTW;
   И наконец, в Win32 предусмотрено еще два дополнительных типа данных, которые позволяют связывать элементы списков доступа с именованными признаками.
   typedef struct _ACTRL_PROPERTY_ENTRYW {
   LPWSTR lpProperty; // not used by COM
   // не используется в COM
   ACTRL_ACCESS_ENTRY_LISW *pAccessEntryList;
   ULONG fListFlags; // not used by COM
   // не используется в COM
   } ACTRL_PROPERTY_ENTRYW, *PACTRL_PROPERTY_ENTRYW;
   typedef struct _ACTRL_ALISTW {
   ULONG cEntries;
   [size_is(cEntries)]
   ACTRL_PROPERTY_ENTRYW *pPropertyAccessList;
   } ACTRL_ACCESSW, *PACTRL_ACCESSW;