# sham_winutils.py
# 
# Functions for doing user/domain stuff
# Part of permcheck
#

import win32api
import win32con
import win32net
import win32netcon
import pywintypes
import sys
import os
import logging
import win32process
import win32gui
import win32service

global GLOBAL_ISADMIN            # yeh yehglobals are bad, but this really is a global
GLOBAL_ISADMIN = None

global GLOBAL_SAFEDLLSEARCHMODE            # another global cache for efficiency
GLOBAL_SAFEDLLSEARCHMODE = None

global GLOBAL_WFPDATA            # another global cache for efficiency
GLOBAL_WFPDATA = None

def getcurrentuserinfo():
  ''' Get info about the current user
    '''
  user=win32api.GetUserName()
  return getuserinfo(user)

def getuserinfo(username):
  ''' Get info about a user
    '''
  dc = None

  # Attempt to find the user locally first
  try:
    safe_info = win32net.NetUserGetInfo(None,username,1)
  except pywintypes.error, e:
    logging.warn("Failed to retrieve user information for " + username + ": " + e.args[2])
    return None
  if safe_info != None:
    return safe_info

  # No local user, lets check for the domain
  try:
    # get the domain controller info
    dc=win32net.NetServerEnum(None,100,win32netcon.SV_TYPE_DOMAIN_CTRL)
  except win32net.error, e:
    #print "win32net error:" + repr(e)
    logging.debug("win32net error:" + repr(e))

  # this will return None, or the safe_info we got

  try:
    if dc != None and dc[0]:
      dcname=dc[0][0]['name']
      return win32net.NetUserGetInfo("\\\\"+dcname,username,1)
    else:
      # didn't exist on domain either
      return None
  except pywintypes.error, e:
    logging.warn("Failed to retrieve user information for " + username + ": " + e.args[2])
    return None

def getcurrentusername():
  import getpass  

  getpass_user = getpass.getuser()
  if getpass_user != None and getpass_user != '':
    return getpass_user
  else:
    # below is another option, not reliable without network
    user = getcurrentuserinfo()
    if user.has_key('name'):
      return user['name']
    else:
      return None

def isadmin():
  ''' Check whether the current user is an administrator
    ''' 
  ''' This makes use of a global to speed things up, as checking users can be slow if on domain etc
    '''
  global GLOBAL_ISADMIN
  if GLOBAL_ISADMIN == None:
    userinfo = getcurrentuserinfo() 
    if userinfo != None:
      if userinfo['priv'] == 2:
        GLOBAL_ISADMIN = True
        return True
      else:
        GLOBAL_ISADMIN = False
        return False
    else:
      logging.warning("Failed to get admin status, Defaulting to non-admin mode, please specify a user with -u if this is incorrect.")
      GLOBAL_ISADMIN = False
      return False
  else:
    return GLOBAL_ISADMIN


def DoesLocalUserExist(username):
  res=1
  users=[]
  try:
    while res:
      (users,total,res) = win32net.NetUserEnum(None,3,0)
      for user in users:
        if str(user['name']) == username:
          return True

  except win32net.error:
    return False

  return False

def getallusers():
  ''' Get a full list of users 
    ''' 
  res=1
  users=[]
  user_list=[]
  try:
    while res:
      (users,total,res) = win32net.NetUserEnum(None,3,0)
      for user in users:
        user_list.append(user)

  except win32net.error:
    logging.warn('getadminusers failed, this is often due to failed network connectivity\n' + 
                 traceback.format_tb(sys.exc_info()[2]),'\n',sys.exc_type,'\n',sys.exc_value)
  finally:
    return user_list


def getlocaladminusers():
  ''' Get a list of users with admin privs
    ''' 
  res=1
  users=[]
  user_list=[]
  try:
    while res:
      (users,total,res) = win32net.NetUserEnum(None,3,0)
      #(users,total,res) = win32net.NetUserEnum(None,3,win32netcon.NULL,res,win32netcon.MAX_PREFERRED_LENGTH)

      for i in users:
        add=0
        login=str(i['name'])
        priv=str(i['priv'])
        # filter out accounts we don't want
        # priv == '2' is admin privs
        if priv == '2' :
          user_list.append(login)

          return user_list
  except win32net.error:
    logging.warn('getadminusers failed, this is often due to failed network connectivity\n' + 
                 traceback.format_tb(sys.exc_info()[2]),'\n',sys.exc_type,'\n',sys.exc_value)
    return []


def getlowlyusers():
  ''' Get a list of lowly users 
    ''' 
  res=1
  users=[]
  user_list=[]
  try:
    while res:
      (users,total,res) = win32net.NetUserEnum(None,3,win32netcon.NULL)
      for i in users:
        add=0
        login=str(i['name'])
        priv=str(i['priv'])
        # filter out accounts we don't want
        # priv == '2' is admin privs
        if (login not in ['HelpAssistant']) and (not str(login).startswith('SUPPORT_')) and priv != '2' :
          user_list.append(login)


  except win32net.error:
    logging.warn('getlowlyusers failed, this is often due to failed network connectivity\n' + 
                 traceback.format_tb(sys.exc_info()[2]),'\n',sys.exc_type,'\n',sys.exc_value)
  finally:
    return user_list


def isdomain():
  ''' Check whether this machine is part of a domain
    '''
  try:
    dc=win32net.NetServerEnum(None,100,win32netcon.SV_TYPE_DOMAIN_CTRL)
  except win32net.error, e:
    #print "win32net error:" + repr(e)
    logging.debug("isdomain: no domain found: " + repr(e))
    return False

  if dc[0]:
    return True
  else:
    return False

def validate_users(userlist):
  '''
    Ensure that the users we are testing as are real users
    takes a list of user names and returns True or False

    '''

  validusers = []
  # get a list of valid local users 
  userinfo = getallusers()    
  for user in userinfo:
    validusers.append(str(user['name']))

  if not isdomain():
    # testing only local users
    for user in userlist:
      if not user in validusers:
        logging.debug('Invalid user provided: ' + user)
        return False

    return True
  else:
    # we are in a domain, need to do things the hard way
    dc = None
    try:
      # because we have just checked isdomain() this should work reasonably reliably
      dc=win32net.NetServerEnum(None,100,win32netcon.SV_TYPE_DOMAIN_CTRL)
    except win32net.error, e:
      #print "win32net error:" + repr(e)
      logging.debug("win32net error:" + repr(e))
      logging.error("validate users failed with unexpected failed domain error")
      return False
      # this will return None, or the safe_info we got

    # Domain attach worked 
    if dc != None and dc[0]:
      dcname=dc[0][0]['name']
      # Loop through each user, checking to see if they exist on the domain
      # if we find one that doesn't exists we exit, otherwise keep looping
      for user in userlist:
        try:
          userinfo = win32net.NetUserGetInfo("\\\\"+dcname,user,1)
        except:
          userinfo = None

        # if it doesn't exist on the domain, perhaps it exists locally
        if userinfo == None:
          if not user in validusers:
            logging.error('Invalid user provided: ' + user)
            return False

      return True

    else:
      # Domain attach failed
      logging.error("Domain attach failed unexpectedly in validate_users")
      return False



def getregvalues(key):
  ''' Gets the data from a given registry key and returns as a list of dictionaries
        [
        {'name':'foo', 'value':'bla', 'type':}, 
        {'name':'foo2', 'value':1, 'type':}
        ]
    '''
  values = []
  (a,b,c) = win32api.RegQueryInfoKey(key)
  for i in range(0, b):
    servicesubvalue = win32api.RegEnumValue(key, i)
    values.append({'name':servicesubvalue[0], 'value':servicesubvalue[1], 'type':servicesubvalue[2]})
  return values

def GetRegValuesAsTuples(key):
  ''' Gets the data from a given registry key and returns as a list of (name,value,type) tuples
        [
        {'foo', 'value', ''), 
        {'foo2', 'value2', '')
        ]
    '''
  values = []
  (a,b,c) = win32api.RegQueryInfoKey(key)
  for i in range(0, b):
    servicesubvalue = win32api.RegEnumValue(key, i)
    values.append((servicesubvalue[0], servicesubvalue[1], servicesubvalue[2]))
  return values

def we_are_frozen():
  """Returns whether we are frozen via py2exe.
        This will affect how we find out where we are located."""

  return hasattr(sys, "frozen")

def current_exe():
  """ This will get us the program's path,
        even if we are frozen using py2exe"""

  if we_are_frozen():
    return unicode(sys.executable, sys.getfilesystemencoding())

  return unicode(__file__, sys.getfilesystemencoding())



def wait_remove(path):
  ''' Deletes a file, but waits until it is unlocked before continuing
        '''
  if not os.path.exists(path):
    return

  count = 200
  removed = False

  while (not removed):
    if not (count > 0):
      raise IOError, "timed out waiting for file to be removed"
    try:
      os.remove(path)
      removed = True
    except Exception, e:
      win32api.Sleep(100)
      count = count - 1
      #pass

def SplitCommand(command):
  #split command into command and params
  # e.g. c:\windows\system32.exe /k bla.foo asfadsfds ksdafjadsf
  # returns c:\windows\system32.exe and  /k bla.foo asfadsfds ksdafjadsf
  dropped_quote = ''

  if command[0] == '"' and command[-1] == '"':
    command = command[1:-1]
  if command[0] == "'" and command[-1] == "'":
    command = command[1:-1]

  if command[0] == "'" or command[0] == '"':
    # drop the first quote
    dropped_quote = command[0]
    command = command[1:]


  if command.lower().find(".exe") != -1:
    # cases with the exe, this is easy
    if dropped_quote: 
      # remove the quote
      a,b,c = command.partition(dropped_quote)
      command = a + c

    a,b,c = command.lower().partition(".exe")
    command_file = a + b 
    params = c
  elif command.lower().find(".sys") != -1:
    # cases with the exe, this is easy
    if dropped_quote: 
      # remove the quote
      a,b,c = command.partition(dropped_quote)
      command = a + c

    a,b,c = command.partition(".sys")
    command_file = a + b 
    params = c
  
  elif dropped_quote:
    # no exe, but we dropped a quote
    # first remove quote first quote
    a,b,c = command.partition(dropped_quote)
    command_file = a + ".exe"
    params = c

  else:
    # must be a broken issue
    # e.g. c:\program files\foo\test /bla /foo
    # this is actually a vuln in many cases as it will try to exec c:\program.exe
    # theres a good chance that anything after the space will be - or / 
    spacepos = command.find(' ')
    while spacepos != -1:
      if os.path.exists(command[0:spacepos]):
        command_file = command[0:spacepos]
        params = command[spacepos:]
      elif os.path.exists(command[0:spacepos] + ".exe"):
        command_file = command[0:spacepos] + ".exe"
        params = command[spacepos:]
        break
      spacepos = command.find(' ',spacepos + 1)

    if spacepos != -1:
      # hit end of loop without breaking, we has file
      #command_file = command[0:spacepos]
      #params = command[spacepos:]
      pass
    else:
      logging.error("SplitCommand failed on " + command)
      return '',''

  return command_file, params



def get_processinfo(ProcessID):
  import win32com
  WMI = win32com.client.GetObject('winmgmts:')
  p=None

  if ProcessID == "" or ProcessID == None:
    return None

  p = WMI.ExecQuery('select * from Win32_Process where ProcessId=%s' % ProcessID)
  try:
    if len(p) > 0 :
      procinfo = {
        'Name' : p[0].Properties_('Name').Value, 
        'CommandLine' : p[0].Properties_('CommandLine').Value, 
        'ExecutablePath' : p[0].Properties_('ExecutablePath').Value , 
        'ParentProcessId' : p[0].Properties_('ParentProcessId').Value ,
      }
    else:
      return None

  except IndexError, e:
    logging.debug("get_piduser lost pid")
    procinfo = None

  return procinfo

def getowner(ProcessID):
  from win32com.client import GetObject
  WMI = GetObject('winmgmts:')
  p = None
  p = WMI.ExecQuery('select * from Win32_Process where ProcessId=%s' % ProcessID)


  try:
    Owner = p[0].execMethod_('GetOwner')
    user = str(Owner.User)
    domain = str(Owner.Domain)

  except (IndexError, pywintypes.com_error), e:
    logging.debug("getowner failed")
    return (None, None)

  return (user, domain)


def isuseradmin(user):
  logging.debug("checking is user admin: " + str(user))
  if user in ("SYSTEM", "", None):
    return True
  else:
    userinfo = getuserinfo(user)
    if userinfo != None:
      if userinfo['priv'] > 1:
        return True
    else: 
      return False

def EnumerateURLHandlers():
  reghandle1 = win32api.RegOpenKeyEx(win32con.HKEY_CLASSES_ROOT, "\\", 0, 
                                     win32con.KEY_READ)
  handlers = []
  reglist = win32api.RegEnumKeyEx(reghandle1)
  for regentry in reglist:
    commandloc = "\\" + regentry[0] + r"\shell\open\command"
    reg_command = win32api.RegOpenKeyEx(win32con.HKEY_CLASSES_ROOT, commandloc, 0, win32con.KEY_READ)
    if reg_command:
      # get the registry values from this key
      thesevalues = getregvalues(reg_command)


    thisservice = {}
    for thisvalue in thesevalues:
      #print thisvalue
      if thisvalue['name'] in ("Type", "ImagePath", "DisplayName"):
        thisservice[thisvalue['name']] = thisvalue['value']

    if thisservice.has_key('DisplayName') and thisservice.has_key('Type'):
      #print str(thisservice['DisplayName']) + ' ' + str(thisservice['Type'])
      # only append a service if it is in the requested types
      if thisservice['Type'] in types:
        services.append(thisservice)


  return services



class WindowHunter():

  def __init__(self):
    self.vulnprocesses = []              # used for cached process priv checks
    self.notvulnprocesses = []           # used for cached process priv checks
    self.notadminusers = []                 # used for caching isadminuser checks

    self.vulnwindows = {}

  def enumerate_highprivwindows(self):
    # Enumerate the windows on the current desktop and hit the callback with the HWND
    win32gui.EnumWindows(self.callback_enumerate_highprivwindows, None)
    return self.vulnwindows

  def callback_enumerate_highprivwindows(self, handle, foo):
    # take a window handle and determine if it is running under high privs
    threadid,processid = win32process.GetWindowThreadProcessId(handle)
    sys.stdout.write(".")
    if processid in self.vulnprocesses:
      # we've already confirmed the process this window belongs to 
      # doesn't have elevated privs
      self.vulnwindows[processid]["Windows"].append((handle, threadid))
    elif processid in self.notvulnprocesses: 
      # add to cache so we speed up checks for this process
      self.notvulnprocesses.append(processid)
      return
    else:
      logging.debug(str(handle) + " " +  str(threadid) + " " + str(processid))
      user, domain = getowner(processid)

      if user not in self.notadminusers:
        # we haven't tested this user yet
        if isuseradmin(user):
          # we have a window, running under high privileges
          #procinfo = get_processinfo(processid)
          #print "Looks like high priv process on our winstation"
          #print str(procinfo)
          self.vulnwindows[processid] = {"User":user, 
                                         "Windows": [(handle, threadid)] }

          # add to cache so we speed up checks for this process
          self.vulnprocesses.append(processid)
        else:
          self.notadminusers.append(user)


#def RegGetPermissionPath(reg_path):
  #''' Returns reg_path if its a key, or parent of regpath if its a value
    #Effectively finds the place where the permissions for the path is stored, or returns false if neither exist

    #HKLM\System\CurrentControlSet\Value returns HKLM\System\CurrentControlSet
    #HKLM\System\CurrentControlSet returns HKLM\System\CurrentControlSet
    #HKLM\System\NotHERE returns None

    #Problem
    #HKLM\System\CurrentControlSet\Value
    #Value could be a value or a key

  #'''

  ## accesschk only checks keys not values
  ## perhaps if its a key , end it with a backslash, otherwise its a value
  #### BUG THIS IS TOTALLY BROKEN
  #### Need some better logic kthx
  ## we're gonna have issues because


  #(base_key, rest_of_path) = RegSplitRegistryPath(reg_path)
  #reghandle1 = None
  #try:
    #reghandle1 = win32api.RegOpenKeyEx(base_key, rest_of_path, 0, win32con.KEY_READ)
  #except pywintypes.error, e:
    #if e.args[2] == 'The system cannot find the file specified.':
      ## get the parent path
      #new_path = os.path.dirname(rest_of_path)
  #if reghandle1:
    ## we could open the reg key, we're good
    #win32api.RegCloseKey(reghandle1)
    #return reg_path
  #else: 
    ## lets try again with the new path
    #try:
      #reghandle1 = win32api.RegOpenKeyEx(base_key, new_path, 0, win32con.KEY_READ)
    #except pywintypes.error, e:
      #if e.args[2] == 'The system cannot find the file specified.':
        ## clearly doesn't exist
        #return None

    #if reghandle1:
      ## it worked
      #win32api.RegCloseKey(reghandle1)
      #return RegGetStringFromBaseKey(base_key) + '\\' + new_path
    #else:
      ## something else went wrong
      #return None



def RegGetPermissionPath(regpath):
  '''
    Get the key which contains the security atributes for a specified registry path (entry does not have 
    permissions of its own)

    Keyword Arguments:
    regpath - the registry path for an entry or key

    Returns:
    Registry Key path string or None
    '''
  basekey = None        # the reg base
  returnpath = None
  reghandle1 = None

  (basekey, newpath) = RegSplitRegistryPath(regpath)

  # conversion dict to convert between constant and string
  convdict = { 'HKLM':win32con.HKEY_LOCAL_MACHINE, 
               'HKCU':win32con.HKEY_CURRENT_USER,
               'HKU':win32con.HKEY_USERS }

  # attempt to open the raw key
  # note that this is looped, this is kind of a dirty hack to get around
  # registry keys with [] or command line parameters that include \ such as bootexecute
  while reghandle1 == None and len(newpath) > 4:
    try:
      reghandle1 = win32api.RegOpenKeyEx(basekey, newpath, 0, win32con.KEY_READ)
    except pywintypes.error, e:
      if e.args[2] == 'The system cannot find the file specified.':
        # truncate 
        # os.path.dirname breaks if ther is a / in the path

        newpath = os.path.dirname(newpath)
      else:
        break

  basekeystr = ''
  if reghandle1 != None:

    # convert back from constant to string
    for key,value in convdict.iteritems():
      if value == basekey:
        basekeystr = key
        break

    if basekeystr != '':
      # we have a real result
      returnpath = basekeystr + '\\' + newpath
  else:
    returnpath = regpath
    logging.debug('RegGetPermissionPath could not get reg entry: ' + regpath)

  return returnpath


def GetServices():
  ''' Retrieves details about windows services

    Args:
        None

    Returns:
        List of Tuples of ServiceName, DisplayName, ServiceStatus
        [ (u'NetDDE', u'Network DDE', (32, 1, 0, 1077, 0, 0, 0)),
        (u'Netlogon', u'Net Logon', (32, 1, 0, 1077, 0, 0, 0)) ]

    '''
  scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ENUMERATE_SERVICE)
  services = win32service.EnumServicesStatus(scm, win32service.SERVICE_WIN32, win32service.SERVICE_STATE_ALL)
  return services

def GetDrivers():
  ''' Retrieves details about windows services

    Args:
        None

    Returns:
        List of Tuples of ServiceName, DisplayName, ServiceStatus
        [ (u'NetDDE', u'Network DDE', (32, 1, 0, 1077, 0, 0, 0)),
        (u'Netlogon', u'Net Logon', (32, 1, 0, 1077, 0, 0, 0)) ]

    '''
  scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ENUMERATE_SERVICE)
  services = win32service.EnumServicesStatus(scm, win32service.SERVICE_DRIVER, win32service.SERVICE_STATE_ALL)

  return services


def PredictDLLSearchPath(dll_name, calling_file, current_dir=None):
  ''' Gets a list of potential paths for an import in the order Windows will look for them
    Args:
      dll_name: the file to get the path for
      calling_file: the full path to the file that will be importing the dll
      current_dir: the working directory if known, otherwise will default to the directory the calling_file is in

    Returns:
      A list of paths, which will end in the dll_name, ordered by how Windows should look for it

    From http://msdn2.microsoft.com/en-us/library/ms682586.aspx
    If SafeDllSearchMode is enabled, the search order is as follows:
    1. The directory from which the application loaded.
    2. The system directory. Use the GetSystemDirectory function to get the path of this directory.
    3. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
    4. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
    5. The current directory.
    6. The directories that are listed in the PATH environment variable. Note that this does not include the per-application path 
    specified by the App Paths registry key.

    If SafeDllSearchMode is disabled, the search order is as follows:
    1. The directory from which the application loaded.
    2. The current directory.
    3. The system directory. Use the GetSystemDirectory function to get the path of this directory.
    4. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
    5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
    6. The directories that are listed in the PATH environment variable. Note that this does not include the per-application path 
    specified by the App Paths registry key    

Note that this function does not know if SetDllDirectory is called before the import, which could make this function
 lie to you. 

    '''
  dll_paths = []
  safe_dll_search_mode = SafeDLLSearchModeEnabled()

  if current_dir == None:
    current_dir = os.path.dirname(calling_file)

  system_directory = win32api.GetSystemDirectory()
  system_directory_16 = os.path.join(os.path.dirname(system_directory), 'system')
  windows_directory = win32api.GetWindowsDirectory()
  full_win_paths = []
  win_path_list = str(os.environ['path']).split(os.path.pathsep)
  for path in win_path_list:
    full_win_paths.append(os.path.join(path, dll_name))

  # SafeDllSearch is On
  if safe_dll_search_mode:
    dll_paths.append(os.path.join(os.path.dirname(calling_file), dll_name))
    dll_paths += [os.path.join(system_directory, dll_name), 
                  os.path.join(system_directory_16, dll_name), 
                  os.path.join(windows_directory, dll_name)]
    # add current dir if its different from importing file dir
    if current_dir != os.path.dirname(calling_file):
      dll_paths.append(os.path.join(current_dir, dll_name))
    dll_paths += full_win_paths


  # SafeDllSearch is Off
  else:
    dll_paths.append(os.path.join(os.path.dirname(calling_file), dll_name))
    # add current dir if its different from importing file dir
    if current_dir != os.path.dirname(calling_file):
      dll_paths.append(os.path.join(current_dir, dll_name))
    dll_paths += [os.path.join(system_directory, dll_name), 
                  os.path.join(system_directory_16, dll_name), 
                  os.path.join(windows_directory, dll_name)]
    dll_paths += full_win_paths

  return dll_paths


def WaitForServiceStatus(service, status, waitSecs):
  """Waits for the service to return the specified status.  You
    should have already requested the service to enter that state"""
  for i in range(waitSecs*4):
    now_status = win32service.QueryServiceStatus(service)[1]
    if now_status == status:
      break
    win32api.Sleep(250)
  else:
    raise pywintypes.error, (winerror.ERROR_SERVICE_REQUEST_TIMEOUT, "QueryServiceStatus", win32api.FormatMessage(winerror.ERROR_SERVICE_REQUEST_TIMEOUT)[:-2])





class ServiceError(Exception):
  """ Exception generated when an error occurs during Service grokking """
  pass


def GetWindowsVersion():
  ''' Get the windows version information
    Returns:
       (release, version, major, minor, buildno)
    '''

  # Import the needed APIs

  from win32api import RegQueryValueEx,RegOpenKeyEx,RegCloseKey,GetVersionEx
  from win32con import HKEY_LOCAL_MACHINE,VER_PLATFORM_WIN32_NT,\
       VER_PLATFORM_WIN32_WINDOWS

  # Find out the registry key and some general version infos
  maj,min,buildno,plat,csd = GetVersionEx()
  version = '%i.%i.%i' % (maj,min,buildno & 0xFFFF)
  if plat == VER_PLATFORM_WIN32_WINDOWS:
    regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion'
    # Try to guess the release name
    if maj == 4:
      if min == 0:
        release = '95'
      elif min == 10:
        release = '98'
      elif min == 90:
        release = 'Me'
      else:
        release = 'postMe'
    elif maj == 5:
      release = '2000'
  elif plat == VER_PLATFORM_WIN32_NT:
    regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion'
    if maj <= 4:
      release = 'NT'
    elif maj == 5:
      if min == 0:
        release = '2000'
      elif min == 1:
        release = 'XP'
      elif min == 2:
        release = '2003Server'
      else:
        release = 'post2003'
  else:
    if not release:
      # E.g. Win3.1 with win32s
      release = '%i.%i' % (maj,min)

  return release,version,maj, min, buildno

def SafeDLLSearchModeEnabled():
  '''
    Determine is SafeDLLSearchMode is enabled or not

    Returns:
      True or False
    '''

  # use a global so we don't hit the registry over and over again
  global GLOBAL_SAFEDLLSEARCHMODE
  if GLOBAL_SAFEDLLSEARCHMODE != None:
    return GLOBAL_SAFEDLLSEARCHMODE

  (release, version, major, minor, buildno) = GetWindowsVersion()

  if release in ['XP', '2003Server', 'post2003']:
    # on by default in XP and above
    keystring = r"System\CurrentControlSet\Control\Session Manager"
    try:
      reghandle1 = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, keystring, 0, 
                                         win32con.KEY_READ)
      value,type = win32api.RegQueryValueEx(reghandle1, 'SafeDllSearchMode')
      if value == 1:
        GLOBAL_SAFEDLLSEARCHMODE = True
        return True
      else:
        GLOBAL_SAFEDLLSEARCHMODE = False
        return False

    except pywintypes.error, e:
      # key doesn't exist, this means it is enabled
      #print repr(sys.exc_info())
      GLOBAL_SAFEDLLSEARCHMODE = True
      return True

  else: 
    # no option to turn it on on older OS
    return False

def SetSafeDLLSearchMode(enable):
  '''
    Sets SafeDLLSearchMode - requires admin privileges (unless someone really messed up)

    Returns:
      Returns True on success
    '''

  # use a global so we don't hit the registry over and over again
  global GLOBAL_SAFEDLLSEARCHMODE

  if enable:
    new_value = 1
  else:
    new_value = 0

  # on by default in XP and above
  keystring = r'HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode'

  if RegUpdateEntry(keystring, win32con.REG_DWORD, new_value):
    rval = True
  else:
    logging.warn('Could not write to SafeDllSearchMode key')
    rval = False

  # update the global if we updated the value successfully
  if rval:
    GLOBAL_SAFEDLLSEARCHMODE = enable

  return rval


def RegUpdateEntry(reg_path, type, data, check_type=False):
  ''' Updates the registry entry at reg_path with data, optionally enforcing the type check


    Returns:
      True on success, otherwise false

    This function is intelligent enough to know if it is updating a value or a key and to behave appropriately

    '''
  # split off the base_key
  (base_key , reg_path_rest) = RegSplitRegistryPath(reg_path)
  if not base_key: return False

  key = None
  #        value,type = win32api.RegQueryValueEx(reghandle1, 'SafeDllSearchMode')

  reghandle1 = None
  # first try and read a full path
  try:
    reghandle1 = win32api.RegOpenKeyEx(base_key, reg_path_rest, 0, 
                                       win32con.KEY_SET_VALUE)
  except pywintypes.error:
    # if we failed, theres a good chance it is because we have a subkey
    # use os path cos it seems to be reliable
    key = os.path.basename(reg_path_rest)
    reg_path_rest = os.path.dirname(reg_path_rest)

  if key != None:
    try:
      reghandle1 = win32api.RegOpenKeyEx(base_key, reg_path_rest, 0, 
                                         win32con.KEY_SET_VALUE)
    except pywintypes.error:
      # still can't get the key, we're done
      logging.debug("Couldn't open registry key for writing")
      rval = False

  # we have a valid handle
  if reghandle1:
    #print (base_key, reg_path_rest, key)
    try:
      win32api.RegSetValueEx(reghandle1, key, None, type, data)
      # if no execption it worked
      rval = True

    except pywintypes.error, e:
      # key doesn't exist, this means it is enabled
      #print repr(sys.exc_info()[1])
      logging.debug('Could not write to registry key ', reg_path + '\\' + sub_key_name)
      rval = False

  return rval


def RegSplitRegistryPath(reg_path):
  ''' Takes a full registry path and returns a tuple of key type, and the path within the key type 

    e.g. HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
    would return (win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run')
    '''
  # conversion dict to convert between constant and string
  convdict = { 'HKLM':win32con.HKEY_LOCAL_MACHINE, 
               'HKCU':win32con.HKEY_CURRENT_USER,
               'HKU':win32con.HKEY_USERS }

  newpath = ''
  basekey = None
  for thiskey in convdict.iterkeys():
    if reg_path.startswith(thiskey):
      basekey = convdict[thiskey]
      # remove the HKLM\\ bit 
      newpath = reg_path[len(thiskey)+1:]
      break


  if basekey == None:
    logging.debug('Invalid registry entry passed to RegSplitRegistryPath: ' + reg_path)
    return (None, '')
  return (basekey, newpath)

def RegGetStringFromBaseKey(base_key):
  ''' Convert base key to its string representation
        e.g win32con.HKEY_LOCAL_MACHINE to 'HKLM'
    '''
  convdict = { win32con.HKEY_LOCAL_MACHINE:'HKLM', 
               win32con.HKEY_CURRENT_USER:'HKCU',
               win32con.HKEY_USERS:'HKU' }
  if convdict.has_key(base_key):
    return convdict[base_key]


def ProcessGetRunning():
  ''' 
    Get the running processes

    One wierd thing about this, if you are running as standard user, you can't retrieve the executable path
    This is annoying, because you can usually figure out the path, but it makes it tricky to check for here. 
    '''
  
  from win32com.client import GetObject
  
  #TODO add failure - can't get running process paths of admin processes if not admin
  WMI = GetObject('winmgmts:')
  processes = WMI.InstancesOf('Win32_Process')
  processinfo = []
  iamadmin = isadmin()
  #ourprocessname = "permcheck.exe"
  ourprocessname = os.path.basename(current_exe())
  logging.debug("Current process is: " + ourprocessname)

  adminusers = getlocaladminusers()
  adminusers.append("SYSTEM")

  if not iamadmin:
    logging.info("Not admin, image paths will not available for process executables, we will attempt to guess, but this is very unreliable")    

  for process in processes:
    try:
      owner = process.execMethod_('GetOwner')
    except pywintypes.com_error, e:

      logging.warn("Process error (two processes running?): owner is " + str(owner.User) + "\r\n" + str(e))
      return processinfo

    #logging.debug(process.Properties_('Name').Value)
    #logging.debug(process.Properties_('ExecutablePath').Value)
    #if owner.User != None:
    #   logging.debug('"' + str(owner.User))
    thisuser = str(owner.User)

    try:
      # we only want processes with higher privs than our own
      if (thisuser in adminusers or thisuser == "None"):
        # we don't want pseudo processes
        if (not process.Properties_('Name').Value in ["System Idle Process", "System", ourprocessname]):
          if iamadmin:
            imagepath = str(process.Properties_('ExecutablePath').Value).lower()
            imagepath = os.path.normpath(imagepath)
          else:
            # can't get image path, we dont have perms, set it to name, this is crap
            # but best we can do
            imagepath = str(process.Properties_('Name').Value).lower()
            imagepath = os.path.normpath(imagepath)
            guesspath = FileFindInPath(imagepath, str(os.environ['path']).split(os.path.pathsep))
            if guesspath != None:
              logging.debug('Guessed path: ' + guesspath)
              imagepath = os.path.normpath(guesspath)

          if (imagepath != 'none'):
            logging.debug("Found Running Process" + " " + imagepath)

            processinfo.append({"pid":process.Properties_('ProcessId').Value, 
                                "name":process.Properties_('Name').Value, 
                                "running_as":thisuser, 
                                "imagepath":imagepath, 
                                })
    except WindowsError, e:
      logging.warn("Process error: " + str(e))

  return processinfo


def FileIsLocked(file_path):
  ''' Checks to see if a file is locked for write
  '''
  try:
    f = open(file_path, 'wb')
  except IOError, e:
    logging.debug("File is locked?: " + repr(sys.exc_info()) + repr(e))
    return True

  if f:
    f.close()
    return False

def FileUnlock(file_path):
  ''' Attempts to unlock a file using regsvr32
  '''
  import win32pipe
  command = 'regsvr32 /u /s ' + file_path
  logging.debug(command)
  x = win32pipe.popen(command)
  x.close()

#### 
#### Objects
####



def RegNormalisePath(path):
  ''' Take a path retrieved from registry and normalise it into a real path
    '''
  if path != "":
    lowerpath = path.lower()
    if lowerpath.startswith("system32\\"):
      return  os.environ['SYSTEMDRIVE'] + '\\' + path
    if lowerpath.startswith("\\systemroot\\"):
      return  lowerpath.replace("\\systemroot", os.environ['SYSTEMROOT'])
    elif path.startswith("\\??\\"):
      return path.replace("\\??\\", "")
    else:
      return path

def ServicesGetImagePaths():
  '''Get the imagepaths for the services under hklm\system\currentcontrolset\services
     ie a list of files we can check perms on
  '''

  imagepaths = []

  services = getregservices((1,2))
  for thisservice in services:
    if thisservice.has_key('ImagePath'):
      #print thisservice['ImagePath']
      logging.info(thisservice['ImagePath'])
      thispath = RegNormalisePath(thisservice['ImagePath'])
      imagepaths.append(thispath)

  return imagepaths


def FileFindInPath(partial_path, path):
  ''' 
    Hunts to see if the file partial_path exists in the environment path
    This assumes that the current environment path matches the path for the user we're 
    checking for, which may not be true, but is often a pretty good guess

    Keyword arguments:
    partial_path -- the thing we're looking for e.g. cmd.exe
    path -- a list of paths

    Returns:
    None or the discovered absolute path
    '''
  for thispath in path:
    newpath = thispath + os.path.sep + partial_path
    if os.path.exists(newpath):
      return newpath 

  return None 

class WindowsServiceObject():

  def __init__(self, name, auto_init=True):
    '''
        Args
            name - the short name of the service
            auto_init - run init_from_system automatically

        '''
    self.name = name
    if auto_init:
      self.init_from_system()

  def init_from_system(self):
    srv = None
    scm = None
    try:
      scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ENUMERATE_SERVICE)
      srv = win32service.OpenService(scm, self.name , win32con.GENERIC_READ)

      
      srvconfig = win32service.QueryServiceConfig(srv)
      if srvconfig != None:  
        self.service_type = srvconfig[0]
        self.start_type = srvconfig[1]
        self.error_control = srvconfig[2]
        
        
        if srvconfig[3] != "":
          a,b = SplitCommand(srvconfig[3])
          self.binary_path_name = a
          self.binary_args = b
          self.binary_path_name_with_args = srvconfig[3]
        else:
          # empty binary, common with drivers
          self.binary_path_name = ""
          self.binary_args = ""
          self.binary_path_name_with_args = ""

        self.load_order_group = srvconfig[4]
        self.dependencies = srvconfig[6]
        self.service_start_name = srvconfig[7]
        self.display_name = srvconfig[8]

      self.registry_path = "HKLM\system\currentcontrolset\services" + '\\' + win32service.GetServiceKeyName(scm, self.display_name)
      self.description = win32service.QueryServiceConfig2(srv, win32service.SERVICE_CONFIG_DESCRIPTION)
    except:
      raise ServiceError("Failed to initialize service")
    finally:
      if srv != None:
        win32service.CloseServiceHandle(srv)

    return True
  

def FileIsWFPProtected(file_path):
  ''' Check if a file is under WFP protection
  
  Reference: http://www.bitsum.com/aboutwfp.asp
  '''
  global GLOBAL_WFPDATA
  
  #WFPProcessPaths
  if not GLOBAL_WFPDATA:
    GLOBAL_WFPDATA = WFPProcessPaths()
    
  if file_path not in GLOBAL_WFPDATA[0] and os.path.basename(file_path) not in GLOBAL_WFPDATA[1]:
    return False
  else:
    return True
   

def WFPProcessPaths():
  ''' Check if a file is under WFP protection
  
  Reference: http://www.bitsum.com/aboutwfp.asp
  '''
  import wfp_data
  
  wfp_paths_list = wfp_data.GetRawPaths().splitlines()
  wfp_files_list = wfp_data.GetRawFiles().splitlines()  
  
  replace_dict = {
    '%systemroot%':os.environ['systemroot'],
    '%commonprogramfiles%':os.environ['commonprogramfiles'],
    '%programfiles%':os.environ['ProgramFiles'],
    }

  new_wfp_paths_list = []
  for path in wfp_paths_list:
    if path.startswith("%"):  # speed issue
      for key,value in replace_dict.items():
        path = path.lower().replace(key, value)
      new_wfp_paths_list.append(path)
      
  return new_wfp_paths_list, wfp_files_list

#########################################################################################
#   OBSOLETE CODE
##########################################################################################


def getregservices(types):
  """ returns a list of dicts containing the services on this machine
        takes a list of types as defined in http://support.microsoft.com/kb/103000

    """

  reghandle1 = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, "system\currentcontrolset\services", 0, 
                                     win32con.KEY_READ)
  services = []
  reglist = win32api.RegEnumKeyEx(reghandle1)
  for regentry in reglist:
    serviceloc= "system\\currentcontrolset\\services\\" + regentry[0]
    servicereghandle2 = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, serviceloc, 0, win32con.KEY_READ)
    # get the registry values from this key
    thesevalues = getregvalues(servicereghandle2)

    thisservice = {}
    for thisvalue in thesevalues:
      #print thisvalue
      if thisvalue['name'] in ("Type", "ImagePath", "DisplayName"):
        thisservice[thisvalue['name']] = thisvalue['value']

    if thisservice.has_key('DisplayName') and thisservice.has_key('Type'):
      #print str(thisservice['DisplayName']) + ' ' + str(thisservice['Type'])
      # only append a service if it is in the requested types
      if thisservice['Type'] in types:
        services.append(thisservice)


  return services


