"""
Implements a permissions editor for services.
Service can be specified as plain name for local machine,
or as a remote service of the form \\machinename\service
"""

import os
import win32com.server.policy
import win32security, ntsecuritycon, win32con
import pythoncom, win32api, win32service
from win32com.authorization import authorization

SERVICE_GENERIC_EXECUTE=win32service.SERVICE_START|win32service.SERVICE_STOP|win32service.SERVICE_PAUSE_CONTINUE|win32service.SERVICE_USER_DEFINED_CONTROL
SERVICE_GENERIC_READ=win32service.SERVICE_QUERY_CONFIG|win32service.SERVICE_QUERY_STATUS|win32service.SERVICE_INTERROGATE|win32service.SERVICE_ENUMERATE_DEPENDENTS
SERVICE_GENERIC_WRITE=win32service.SERVICE_CHANGE_CONFIG

from ntsecuritycon import STANDARD_RIGHTS_READ, STANDARD_RIGHTS_WRITE, STANDARD_RIGHTS_EXECUTE, \
    WRITE_OWNER, WRITE_DAC, READ_CONTROL, \
    SI_ADVANCED, SI_EDIT_AUDITS, SI_EDIT_PROPERTIES, SI_EDIT_ALL, SI_PAGE_TITLE, SI_RESET, \
    SI_ACCESS_SPECIFIC, SI_ACCESS_GENERAL, SI_ACCESS_CONTAINER, SI_ACCESS_PROPERTY, \
    OBJECT_INHERIT_ACE, CONTAINER_INHERIT_ACE, INHERIT_ONLY_ACE, \
    SI_PAGE_PERM, SI_PAGE_ADVPERM, SI_PAGE_AUDIT, SI_PAGE_OWNER, PSPCB_SI_INITDIALOG, \
    SI_CONTAINER
from win32security import OBJECT_INHERIT_ACE, CONTAINER_INHERIT_ACE, INHERIT_ONLY_ACE
from win32com.shell.shellcon import PSPCB_RELEASE, PSPCB_CREATE ## Msg parameter to PropertySheetPageCallback
from pythoncom import IID_NULL


class ServiceSecurity(win32com.server.policy.DesignatedWrapPolicy):
    _com_interfaces_=[authorization.IID_ISecurityInformation]
    _public_methods_=['GetObjectInformation','GetSecurity','SetSecurity','GetAccessRights',
        'GetInheritTypes','MapGeneric','PropertySheetPageCallback']

    def __init__(self, ServiceName):
        self.ServiceName=ServiceName
        self._wrap_(self)

    def GetObjectInformation(self):
        """Identifies object whose security will be modified, and determines options available
           to the end user"""
        flags=SI_ADVANCED|SI_EDIT_ALL|SI_PAGE_TITLE|SI_RESET
        hinstance=0  ## handle to module containing string resources
        servername=''  ## name of authenticating server if not local machine
        
        ## service name can contain remote machine name of the form \\Server\ServiceName
        objectname=os.path.split(self.ServiceName)[1]
        pagetitle='Service Permissions for '+self.ServiceName
        objecttype=IID_NULL
        return flags, hinstance, servername, objectname, pagetitle, objecttype

    def GetSecurity(self, requestedinfo, bdefault):
        """Requests the existing permissions for object"""
        if bdefault:
            return win32security.SECURITY_DESCRIPTOR()
        else:
            return win32security.GetNamedSecurityInfo(self.ServiceName, win32security.SE_SERVICE, requestedinfo)

    def SetSecurity(self, requestedinfo, sd):
        """Applies permissions to the object"""
        owner=sd.GetSecurityDescriptorOwner()
        group=sd.GetSecurityDescriptorGroup()
        dacl=sd.GetSecurityDescriptorDacl()
        sacl=sd.GetSecurityDescriptorSacl()
        win32security.SetNamedSecurityInfo(self.ServiceName, win32security.SE_SERVICE, requestedinfo,
            owner, group, dacl, sacl)

    def GetAccessRights(self, objecttype, flags):
        """Returns a tuple of (AccessRights, DefaultAccess), where AccessRights is a sequence of tuples representing
            SI_ACCESS structs, containing (guid, access mask, Name, flags). DefaultAccess indicates which of the
            AccessRights will be used initially when a new ACE is added (zero based).
            Flags can contain SI_ACCESS_SPECIFIC,SI_ACCESS_GENERAL,SI_ACCESS_CONTAINER,SI_ACCESS_PROPERTY,
                  CONTAINER_INHERIT_ACE,INHERIT_ONLY_ACE,OBJECT_INHERIT_ACE
        """
        ## input flags: SI_ADVANCED,SI_EDIT_AUDITS,SI_EDIT_PROPERTIES indicating which property sheet is requesting the rights
        if (objecttype is not None) and (objecttype!=IID_NULL):
            ## Not relevent for services
            raise NotImplementedError("Object type is not supported")
        
        ## ???? for some reason, the DACL for a service will not retain ACCESS_SYSTEM_SECURITY in an ACE ????
        ## (IID_NULL, win32con.ACCESS_SYSTEM_SECURITY, 'View/change audit settings', SI_ACCESS_SPECIFIC),
        
        accessrights=[
            (IID_NULL, win32service.SERVICE_ALL_ACCESS, 'Full control', SI_ACCESS_GENERAL),
            (IID_NULL, SERVICE_GENERIC_READ, 'Generic read', SI_ACCESS_GENERAL),
            (IID_NULL, SERVICE_GENERIC_WRITE, 'Generic write', SI_ACCESS_GENERAL),
            (IID_NULL, SERVICE_GENERIC_EXECUTE, 'Start/Stop/Pause service', SI_ACCESS_GENERAL),
            (IID_NULL, READ_CONTROL, 'Read Permissions', SI_ACCESS_GENERAL),
            (IID_NULL, WRITE_DAC, 'Change permissions', SI_ACCESS_GENERAL),
            (IID_NULL, WRITE_OWNER, 'Change owner', SI_ACCESS_GENERAL),
            (IID_NULL, win32con.DELETE, 'Delete service', SI_ACCESS_GENERAL),
            (IID_NULL, win32service.SERVICE_START, 'Start service', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_STOP, 'Stop service', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_PAUSE_CONTINUE, 'Pause/unpause service', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_USER_DEFINED_CONTROL, 'Execute user defined operations', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_QUERY_CONFIG, 'Read configuration', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_CHANGE_CONFIG, 'Change configuration', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_ENUMERATE_DEPENDENTS, 'List dependent services', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_QUERY_STATUS, 'Query status', SI_ACCESS_SPECIFIC),
            (IID_NULL, win32service.SERVICE_INTERROGATE, 'Query status (immediate)', SI_ACCESS_SPECIFIC),
            ]
        return (accessrights, 0)

    def MapGeneric(self, guid, aceflags, mask):
        """ Converts generic access rights to specific rights.
        """
        return win32security.MapGenericMask(mask,
            (SERVICE_GENERIC_READ, SERVICE_GENERIC_WRITE, SERVICE_GENERIC_EXECUTE, win32service.SERVICE_ALL_ACCESS))

    def GetInheritTypes(self):
        """Specifies which types of ACE inheritance are supported.
            Services don't use any inheritance
         """
        return ((IID_NULL, 0, 'Only current object'),)

    def PropertySheetPageCallback(self, hwnd, msg, pagetype):
        """Invoked each time a property sheet page is created or destroyed."""
        ## page types from SI_PAGE_TYPE enum: SI_PAGE_PERM SI_PAGE_ADVPERM SI_PAGE_AUDIT SI_PAGE_OWNER
        ## msg: PSPCB_CREATE, PSPCB_RELEASE, PSPCB_SI_INITDIALOG
        return None

    def EditSecurity(self, owner_hwnd=0):
        """Creates an ACL editor dialog based on parameters returned by interface methods"""
        isi=pythoncom.WrapObject(self, authorization.IID_ISecurityInformation, pythoncom.IID_IUnknown)
        authorization.EditSecurity(owner_hwnd, isi)

if __name__=='__main__':
    # Find the first service on local machine and edit its permissions
    scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ENUMERATE_SERVICE)
    svcs=win32service.EnumServicesStatus(scm)
    win32service.CloseServiceHandle(scm)
    si=ServiceSecurity(svcs[0][0])
    si.EditSecurity()