【问题标题】:How can my program detect whether it's running on a particular domain?我的程序如何检测它是否在特定域上运行?
【发布时间】:2011-01-17 16:37:21
【问题描述】:

我需要根据当前登录用户的位置来限制应用程序的特定功能。因为我必须在 Delphi 中实现这个逻辑,所以我不希望过度使用完整的活动目录/LDAP 查询。

我目前的想法是利用 DsGetDcName,并使用 DOMAIN_CONTROLLER_INFO 结构中返回的 GUID 并将其与硬编码常量进行比较。似乎只有在重新创建域时域 GUID 才会更改,因此这将提供我想要的功能,并且开销有限。我唯一担心的是我在 MSDN 上找不到任何文档来证实我的假设。

type
  EAccessDenied = Exception;
  EInvalidOwner = Exception;
  EInsufficientBuffer = Exception;
  ELibraryNotFound = Exception;

  NET_API_STATUS = Integer;

  TDomainControllerInfoA = record
    DomainControllerName: LPSTR;
    DomainControllerAddress: LPSTR;
    DomainControllerAddressType: ULONG;
    DomainGuid: TGUID;
    DomainName: LPSTR;
    DnsForestName: LPSTR;
    Flags: ULONG;
    DcSiteName: LPSTR;
    ClientSiteName: LPSTR;
  end;
  PDomainControllerInfoA = ^TDomainControllerInfoA;

const
  NERR_Success = 0;

procedure NetCheck(ErrCode: NET_API_STATUS);
begin
  if ErrCode <> NERR_Success then
  begin
    case ErrCode of
      ERROR_ACCESS_DENIED:
        raise EAccessDenied.Create('Access is Denied');
      ERROR_INVALID_OWNER:
        raise EInvalidOwner.Create('Cannot assign the owner of this object.');
      ERROR_INSUFFICIENT_BUFFER:
        raise EInsufficientBuffer.Create('Buffer passed was too small');
      else
        raise Exception.Create('Error Code: ' + IntToStr(ErrCode) + #13 +
          SysErrorMessage(ErrCode));
    end;
  end;
end;

function IsInternalDomain: Boolean;
var
  NTNetDsGetDcName: function(ComputerName, DomainName: PChar; DomainGuid: PGUID; SiteName: PChar; Flags: ULONG; var DomainControllerInfo: PDomainControllerInfoA): NET_API_STATUS; stdcall;
  NTNetApiBufferFree: function (lpBuffer: Pointer): NET_API_STATUS; stdcall;
  LibHandle: THandle;
  DomainControllerInfo: PDomainControllerInfoA;
  ErrMode: Word;
const
  NTlib = 'NETAPI32.DLL';
  DS_IS_FLAT_NAME = $00010000;
  DS_RETURN_DNS_NAME = $40000000;
  INTERNAL_DOMAIN_GUID: TGUID = '{????????-????-????-????-????????????}';
begin
 if Win32Platform = VER_PLATFORM_WIN32_NT then
    begin
    ErrMode := SetErrorMode(SEM_NOOPENFILEERRORBOX);
    LibHandle := LoadLibrary(NTlib);
    SetErrorMode(ErrMode);
    if LibHandle = 0 then
        raise ELibraryNotFound.Create('Unable to map library: ' + NTlib);
    try
      @NTNetDsGetDcName := GetProcAddress(Libhandle, 'DsGetDcNameA');
      @NTNetApiBufferFree       := GetProcAddress(Libhandle,'NetApiBufferFree');
      try
        NetCheck(NTNetDsGetDcName(nil, nil, nil, nil, DS_IS_FLAT_NAME or DS_RETURN_DNS_NAME, DomainControllerInfo));
        Result := (DomainControllerInfo.DomainName = 'foo.com') and (CompareMem(@DomainControllerInfo.DomainGuid,@INTERNAL_DOMAIN_GUID, SizeOf(TGuid)));//WideCharToString(pDomain);
      finally
        NetCheck(NTNetApiBufferFree(DomainControllerInfo));
      end;
    finally
      FreeLibrary(LibHandle);
    end;
    end
 else
  Result := False;
end;

按照建议在ServerFault 上添加了相关问题。

Technet 上发现了另一篇有趣的文章,这似乎也暗示我是对的,但并未专门针对域 SID。

【问题讨论】:

  • 我同意 GUID 不会更改,但我想指出,如果域控制器发生更改,您肯定会收到不同的 GUID。
  • 注意我使用的是域 GUID,而不是 DC guid。因此(有人会认为)它需要降级/删除所有 DC 和工作站,并将它们迁移到新域。我假设域 GUID 是在您将第一台服务器提升为域控制器时创建的,然后它在域的生命周期内一直存在。
  • 好吧,我的错,对不起。我同意你的假设,不幸的是我没有在任何地方看到它。也许您想通过serverfault 提问。

标签: delphi winapi active-directory dns


【解决方案1】:

在域上创建服务帐户;

获取服务帐户的 GUID 并对其进行加密并将其保存在某处(注册表),可能作为企业安装过程的一部分以验证许可协议。

在启动客户端应用程序时查询域服务帐户 GUID 并使用保存的 GUID 对其进行验证。

或者创建您自己的企业“关键”服务器。

执行 LDAP 查询比执行所有域控制器的废话要容易。

【讨论】:

    【解决方案2】:

    如果我正确理解您的要求,那么在您的情况下最好的 API 是 GetUserNameEx。您可以选择EXTENDED_NAME_FORMAT 类型的NameFormat 参数的值,您可以更好地验证。如果您想额外验证有关程序运行所在计算机的信息,另一个函数GetComputerNameEx 会很有帮助。

    【讨论】:

    • 但是,我并没有尝试在此处直接验证用户。虽然我可以得到各种回复,但我的潜在偏执狂是有人仍然可以生成一个具有相同名称的虚拟内部域名并获得不正确的结果。如果我可以根据 Domain SID 进行限制,我会感觉更安全(可能是错误的)。
    【解决方案3】:

    我需要限制特定的 基于应用程序的功能 当前记录的位置 在用户中

    如果您试图找出当前登录的用户的位置,则不应使用DsGetDcName。 p>

    您的计算机可以加入 domainA。您的登录用户可以来自 domainB。在您的计算机上调用 DsGetDcName 不会为您提供 domainB GUID,但它会为您提供 domainA GUID

    因此,我认为您应该改用LookupAccountNameLookupAccountName 为您提供当前登录用户的 SID。然后,您可以从用户 SID 中提取域 SID。该域 SID 实际上是该用户来自的域。关于如何从用户SID中提取域SID的详细信息,请查看here

    关于您关于域 GUID 唯一性的原始问题,很抱歉我没有答案。 AFAIK,没有可用的工具允许您更改域 SID 或 GUID。我不确定破解和更改它有多难。

    【讨论】:

    • 我没有考虑过这条路线,但是域 SID 只保证在单个企业中是唯一的。我的印象是域 GUID 应该是全球唯一的,因为 CoCreateGUID 调用返回重复 GUID 的可能性非常小。也许我应该使用这两种方法的组合,域 GUID 检查以确保正确的域(不是幻像/虚拟域),然后使用 LookupAccountName 来确保当前用户是该域的成员?
    • @jchoover 是的,我想我明白你在担心什么。您不希望有人设置具有相同域名和相同域 SID 的虚拟域。正如我所说,我没有看到任何工具可以帮助您确定域 SID 的外观。如果他可以更改域 SID(例如,通过直接更改 NTDS.DIT​​ 文件),他可能也可以使用相同的技巧来更改域 GUID。我认为你只是过度偏执。黑客也可以简单地反汇编您的代码并修补您的代码以检查他自己的域 GUID。
    • @jchoover 顺便说一句,我同意 GUID 与 SID 相比不太可能意外相同。
    猜你喜欢
    • 1970-01-01
    • 2015-10-31
    • 2014-11-18
    • 1970-01-01
    • 1970-01-01
    • 2012-01-19
    • 1970-01-01
    • 1970-01-01
    • 2020-01-16
    相关资源
    最近更新 更多