2008-10-16 73 views
4

我有一个网站,它启用了Windows身份验证。从网站的一个页面,用户可以启动一个服务来处理数据库中的某些内容。.Net 2.0 ServiceController.GetServices()

它对我启动服务很好,因为我是服务器上的本地管理员。但我只是让用户测试它,并且他们无法启动服务。

我的问题是:


有谁知道的方式,通过名称中使用不同的Windows帐户比他们目前正在与登录的一个来获得指定的计算机上的服务列表?


我真的不希望添加所有需要启动该服务为Windows组,并将它们全部设置为我的IIS服务器上的本地管理员的用户.....

下面是我得到的一些代码:

public static ServiceControllerStatus FindService() 
     { 
      ServiceControllerStatus status = ServiceControllerStatus.Stopped; 

      try 
      { 
       string machineName = ConfigurationManager.AppSettings["ServiceMachineName"]; 
       ServiceController[] services = ServiceController.GetServices(machineName); 
       string serviceName = ConfigurationManager.AppSettings["ServiceName"].ToLower(); 

       foreach (ServiceController service in services) 
       { 
        if (service.ServiceName.ToLower() == serviceName) 
        { 
         status = service.Status; 
         break; 
        } 
       } 
      } 
      catch(Exception ex) 
      { 
       status = ServiceControllerStatus.Stopped; 
       SaveError(ex, "Utilities - FindService()"); 
      } 

      return status; 
     } 

我的例外来自try块中的第二行。这里的错误:

System.InvalidOperationException: Cannot open Service Control Manager on computer 'server.domain.com'. This operation might require other privileges. ---> System.ComponentModel.Win32Exception: Access is denied --- End of inner exception stack trace --- at System.ServiceProcess.ServiceController.GetDataBaseHandleWithAccess(String machineName, Int32 serviceControlManaqerAccess) at System.ServiceProcess.ServiceController.GetServicesOfType(String machineName, Int32 serviceType) at TelemarketingWebSite.Utilities.StartService()

感谢您的帮助/信息

回答

5

注意:这并没有解决枚举服务作为一个不同的用户,但考虑到你在做什么的更广泛的描述,我认为这是一个很好的答案。

我认为如果直接转到感兴趣的服务,您可以简化很多,并且可能避免部分安全问题。而不是调用GetServices,试试这个:

string machineName = ConfigurationManager.AppSettings["ServiceMachineName"]; 
string serviceName = ConfigurationManager.AppSettings["ServiceName"]; 
ServiceController service = new ServiceController(serviceName, machineName); 
return service.Status; 

这直接连接到感兴趣的服务并绕过枚举/搜索步骤。因此,它不需要调用方在服务控制管理器(SCM)上拥有SC_MANAGER_ENUMERATE_SERVICE权限,远程用户默认情况下不具有该权限。它仍然需要SC_MANAGER_CONNECT,但是according to MSDN应该授予远程认证用户。

一旦找到感兴趣的服务,您仍需要能够停止并启动它,远程用户可能无权执行此操作。但是,可以修改单个服务上的安全描述符(DACL),这将允许您授予远程用户访问权限,以停止并启动该服务,而不要求它们是本地管理员。这是通过SetNamedSecurityInfo API函数完成的。您需要授予的访问权限为SERVICE_STARTSERVICE_STOP。根据这些用户属于哪个组,您可能还需要授予他们GENERIC_READ。所有这些权利是described in MSDN

假设感兴趣的用户位于“远程服务控制器”组(您将创建)并且服务名称为“我的服务名称”,以下是一些将执行此设置的C++代码。请注意,如果您想授予访问权限的用户(不一定是个好主意),而不是您创建的群组,则需要将TRUSTEE_IS_GROUP更改为TRUSTEE_IS_WELL_KNOWN_GROUP

该代码没有错误检查,您希望添加。所有三个失败的函数(Get/SetNamedSecurityInfo和SetEntriesInAcl)都会返回0来表示成功。

另注:您还可以设置使用the SC tool服务的安全描述符,它可以在%WINDIR%\ System32下被发现,但不涉及任何编程。

#include "windows.h" 
#include "accctrl.h" 
#include "aclapi.h" 

int main() 
{ 
    char serviceName[] = "my-service-name"; 
    char userGroup[] = "Remote Service Controllers"; 

    // retrieve the security info 
    PACL pDacl = NULL; 
    PSECURITY_DESCRIPTOR pDescriptor = NULL; 
    GetNamedSecurityInfo(serviceName, SE_SERVICE, 
     DACL_SECURITY_INFORMATION, NULL, NULL, 
     &pDacl, NULL, &pDescriptor); 

    // add an entry to allow the users to start and stop the service 
    EXPLICIT_ACCESS access; 
    ZeroMemory(&access, sizeof(access)); 
    access.grfAccessMode = GRANT_ACCESS; 
    access.grfAccessPermissions = SERVICE_START | SERVICE_STOP; 
    access.Trustee.TrusteeForm = TRUSTEE_IS_NAME; 
    access.Trustee.TrusteeType = TRUSTEE_IS_GROUP; 
    access.Trustee.ptstrName = userGroup; 
    PACL pNewDacl; 
    SetEntriesInAcl(1, &access, pDacl, &pNewDacl); 

    // write the changes back to the service 
    SetNamedSecurityInfo(serviceName, SE_SERVICE, 
     DACL_SECURITY_INFORMATION, NULL, NULL, 
     pNewDacl, NULL); 

    LocalFree(pNewDacl); 
    LocalFree(pDescriptor); 
} 

这也可以从使用P/Invoke C#完成,但是这是一个有点更多的工作。

如果您仍然希望能够以这些用户身份枚举服务,则需要在SCM上向他们授予SC_MANAGER_ENUMERATE_SERVICE权限。不幸的是,只有在Windows Server 2003 sp1或更高版本上才能修改SCM的安全性。

1

您可以尝试在你的web.config文件中使用ASP.NET模拟,并指定具有相应权限的用户帐户:

<system.web> 
     <identity impersonate="true" userName="Username" password="Password" /> 
    </system.web 

看看this article on MSDN。我相信还有其他选项不需要将密码存储在web.config文件中,而是将其放入注册表项中。

这将导致ASP.NET工作进程在指定用户的上下文中运行,而不是登录到Web应用程序的用户。 但是,这提出了一个安全问题,我会强烈反思你的设计。您可能想要考虑让ASP.NET网页反过来将请求发送到实际控制服务的其他进程的请求,即使是另一个Windows服务或将请求写入到Windows服务定期轮询的数据库表中。

+0

我必须有Windows用户名,因为我根据自己的Windows登录显示的网站的东西。我不想让用户有一个额外的登录。 – Miles 2008-10-17 15:30:58

+0

您可以继续使用他们的Windows帐户进行身份验证。我假设你在IIS和ASP.NET身份验证模型中使用集成身份验证。 ()。您可以一起使用两者,并且IIS仍将协商用户凭据,而无需其他登录。 – Rich 2008-10-17 22:34:57

1

谢谢你的代码行查理。这是我最终做的。我从这个网站得到了这个想法:http://www.codeproject.com/KB/cs/svcmgr.aspx?display=Print

我还必须将我正在访问的帐户添加到服务器上的Power Users组。

public static ServiceControllerStatus FindService() 
     { 
      ServiceControllerStatus status = ServiceControllerStatus.Stopped; 
    try 
      { 
       string machineName = ConfigurationManager.AppSettings["ServiceMachineName"]; 
       string serviceName = ConfigurationManager.AppSettings["ServiceName"].ToLower(); 

       ImpersonationUtil.Impersonate(); 

       ServiceController service = new ServiceController(serviceName, machineName); 
       status = service.Status; 
      } 
      catch(Exception ex) 
      { 
       status = ServiceControllerStatus.Stopped; 
       SaveError(ex, "Utilities - FindService()"); 
      } 

      return status; 
     } 

这是我的其他类的ImpersonationUtil.Impersonate():

public static class ImpersonationUtil 
    { 
     public static bool Impersonate() 
     { 
      string logon = ConfigurationManager.AppSettings["ImpersonationUserName"]; 
      string password = ConfigurationManager.AppSettings["ImpersonationPassword"]; 
      string domain = ConfigurationManager.AppSettings["ImpersonationDomain"]; 

      IntPtr token = IntPtr.Zero; 
      IntPtr tokenDuplicate = IntPtr.Zero; 
      WindowsImpersonationContext impersonationContext = null; 

      if (LogonUser(logon, domain, password, 2, 0, ref token) != 0) 
       if (DuplicateToken(token, 2, ref tokenDuplicate) != 0) 
        impersonationContext = new WindowsIdentity(tokenDuplicate).Impersonate(); 
      // 

      return (impersonationContext != null); 
     } 

     [DllImport("advapi32.dll", CharSet = CharSet.Auto)] 
     public static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); 

     [DllImport("advapi32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)] 
     public extern static int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken); 
    }