From 16128dd8ee6012fab40483ae50116a53b2b41595 Mon Sep 17 00:00:00 2001
From: Nikos Skalkotos <skalkoto@grnet.gr>
Date: Mon, 31 Mar 2014 16:51:18 +0300
Subject: [PATCH] windows: Create a new registry module

This modules hosts the Registry class which contains all the registry
manipulation code previously hosted under the Windows class
---
 image_creator/os_type/windows/__init__.py | 280 ++++------------------
 image_creator/os_type/windows/registry.py | 279 +++++++++++++++++++++
 2 files changed, 320 insertions(+), 239 deletions(-)
 create mode 100644 image_creator/os_type/windows/registry.py

diff --git a/image_creator/os_type/windows/__init__.py b/image_creator/os_type/windows/__init__.py
index 768fdc9..c8fb6f0 100644
--- a/image_creator/os_type/windows/__init__.py
+++ b/image_creator/os_type/windows/__init__.py
@@ -21,6 +21,7 @@ Windows OSs."""
 from image_creator.os_type import OSBase, sysprep, add_sysprep_param
 from image_creator.util import FatalError
 from image_creator.os_type.windows.vm import VM
+from image_creator.os_type.windows.registry import Registry
 from image_creator.os_type.windows.winexe import WinEXE
 
 import hivex
@@ -29,7 +30,6 @@ import os
 import time
 import random
 import string
-import struct
 import re
 
 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
@@ -147,6 +147,7 @@ class Windows(OSBase):
         self.product_name = self.image.g.inspect_get_product_name(self.root)
 
         self.vm = VM(self.image.device, self.sysprep_params)
+        self.registry = Registry(self.image.g, self.root)
 
         self.syspreped = False
 
@@ -311,11 +312,11 @@ class Windows(OSBase):
 
         self.mount(readonly=False)
         try:
-            disabled_uac = self._update_uac_remote_setting(1)
+            disabled_uac = self.registry.update_uac_remote_setting(1)
             token = self._enable_os_monitor()
 
             # disable the firewalls
-            firewall_states = self._update_firewalls(0, 0, 0)
+            firewall_states = self.registry.update_firewalls(0, 0, 0)
 
             # Delete the pagefile. It will be recreated when the system boots
             systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
@@ -367,9 +368,9 @@ class Windows(OSBase):
             self.mount(readonly=False)
             try:
                 if disabled_uac:
-                    self._update_uac_remote_setting(0)
+                    self.registry.update_uac_remote_setting(0)
 
-                self._update_firewalls(*firewall_states)
+                self.registry.update_firewalls(*firewall_states)
             finally:
                 self.umount()
 
@@ -406,43 +407,6 @@ class Windows(OSBase):
             self._shutdown()
         self.out.success("done")
 
-    def _open_hive(self, hive, write=False):
-        """Returns a context manager for opening a hive file of the image for
-        reading or writing.
-        """
-        g = self.image.g
-        systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
-        path = "%s/system32/config/%s" % (systemroot, hive)
-        try:
-            path = g.case_sensitive_path(path)
-        except RuntimeError as err:
-            raise FatalError("Unable to retrieve file: %s. Reason: %s" %
-                             (hive, str(err)))
-
-        class OpenHive:
-            """The OpenHive context manager"""
-            def __enter__(self):
-                localfd, self.localpath = tempfile.mkstemp()
-                try:
-                    os.close(localfd)
-                    g.download(path, self.localpath)
-
-                    hive = hivex.Hivex(self.localpath, write=write)
-                except:
-                    os.unlink(self.localpath)
-                    raise
-
-                return hive
-
-            def __exit__(self, exc_type, exc_value, traceback):
-                try:
-                    if write:
-                        g.upload(self.localpath, path)
-                finally:
-                    os.unlink(self.localpath)
-
-        return OpenHive()
-
     def _shutdown(self):
         """Shuts down the windows VM"""
         self.vm.rexec(r'shutdown /s /t 5')
@@ -464,210 +428,48 @@ class Windows(OSBase):
 
         token = "".join(random.choice(string.ascii_letters) for x in range(16))
 
-        with self._open_hive('SOFTWARE', write=True) as hive:
-            # Enable automatic logon.
-            # This is needed because we need to execute a script that we add in
-            # the RunOnce registry entry and those programs only get executed
-            # when a user logs on. There is a RunServicesOnce registry entry
-            # whose keys get executed in the background when the logon dialog
-            # box first appears, but they seem to only work with services and
-            # not arbitrary command line expressions :-(
-            #
-            # Instructions on how to turn on automatic logon in Windows can be
-            # found here: http://support.microsoft.com/kb/324737
-            #
-            # Warning: Registry change will not work if the β€œLogon Banner” is
-            # defined on the server either by a Group Policy object (GPO) or by
-            # a local policy.
-
-            winlogon = hive.root()
-            for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
-                          'Winlogon'):
-                winlogon = hive.node_get_child(winlogon, child)
-
-            hive.node_set_value(
-                winlogon,
-                {'key': 'DefaultUserName', 't': 1,
-                 'value': "Administrator".encode('utf-16le')})
-            hive.node_set_value(
-                winlogon,
-                {'key': 'DefaultPassword', 't': 1,
-                 'value':  self.sysprep_params['password'].encode('utf-16le')})
-            hive.node_set_value(
-                winlogon,
-                {'key': 'AutoAdminLogon', 't': 1,
-                 'value': "1".encode('utf-16le')})
-
-            key = hive.root()
-            for child in ('Microsoft', 'Windows', 'CurrentVersion'):
-                key = hive.node_get_child(key, child)
-
-            runonce = hive.node_get_child(key, "RunOnce")
-            if runonce is None:
-                runonce = hive.node_add_child(key, "RunOnce")
-
-            value = (
-                r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
-                r'-ExecutionPolicy RemoteSigned '
-                r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
-                r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
-                r'$port.Close()}"').encode('utf-16le')
-
-            hive.node_set_value(
-                runonce, {'key': "BootMonitor", 't': 1, 'value': value})
-
-            value = (
-                r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
-                r'\policies\system /v LocalAccountTokenFilterPolicy'
-                r' /t REG_DWORD /d 1 /f').encode('utf-16le')
-
-            hive.node_set_value(
-                runonce, {'key': "UpdateRegistry", 't': 1, 'value': value})
-
-            hive.commit(None)
+        # Enable automatic logon.
+        # This is needed because we need to execute a script that we add in the
+        # RunOnce registry entry and those programs only get executed when a
+        # user logs on. There is a RunServicesOnce registry entry whose keys
+        # get executed in the background when the logon dialog box first
+        # appears, but they seem to only work with services and not arbitrary
+        # command line expressions :-(
+        #
+        # Instructions on how to turn on automatic logon in Windows can be
+        # found here: http://support.microsoft.com/kb/324737
+        #
+        # Warning: Registry change will not work if the β€œLogon Banner” is
+        # defined on the server either by a Group Policy object (GPO) or by a
+        # local policy.
+
+        user = self.sysprep_params['admin']
+        passwd = self.sysprep_params['password']
+        self.registry.enable_autologon(user, passwd)
+
+        commands = {
+            "BootMonitor":
+            (r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
+             r'-ExecutionPolicy RemoteSigned '
+             r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
+             r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
+             r'$port.Close()}"'),
+            "UpdateRegistry":
+            (r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
+             r'\policies\system /v LocalAccountTokenFilterPolicy'
+             r' /t REG_DWORD /d 1 /f')}
+
+        self.registry.runonce(commands)
 
         return token
 
-    def _update_firewalls(self, domain, public, standard):
-        """Enables or disables the firewall for the Domain, the Public and the
-        Standard profile. Returns a triple with the old values.
-
-        1 will enable a firewall and 0 will disable it
-        """
-
-        if domain not in (0, 1):
-            raise ValueError("Valid values for domain parameter are 0 and 1")
-
-        if public not in (0, 1):
-            raise ValueError("Valid values for public parameter are 0 and 1")
-
-        if standard not in (0, 1):
-            raise ValueError("Valid values for standard parameter are 0 and 1")
-
-        with self._open_hive('SYSTEM', write=True) as hive:
-            select = hive.node_get_child(hive.root(), 'Select')
-            current_value = hive.node_get_value(select, 'Current')
-
-            # expecting a little endian dword
-            assert hive.value_type(current_value)[1] == 4
-            current = "%03d" % hive.value_dword(current_value)
-
-            firewall_policy = hive.root()
-            for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
-                          'Parameters', 'FirewallPolicy'):
-                firewall_policy = hive.node_get_child(firewall_policy, child)
-
-            old_values = []
-            new_values = [domain, public, standard]
-            for profile in ('Domain', 'Public', 'Standard'):
-                node = hive.node_get_child(firewall_policy,
-                                           '%sProfile' % profile)
-
-                old_value = hive.node_get_value(node, 'EnableFirewall')
-
-                # expecting a little endian dword
-                assert hive.value_type(old_value)[1] == 4
-                old_values.append(hive.value_dword(old_value))
-
-                hive.node_set_value(
-                    node, {'key': 'EnableFirewall', 't': 4L,
-                           'value': struct.pack("<I", new_values.pop(0))})
-            hive.commit(None)
-
-        return old_values
-
-    def _update_uac_remote_setting(self, value):
-        """Updates the registry key value:
-        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
-        \System]"LocalAccountTokenFilterPolicy"
-
-        value = 1 will disable the UAC remote restrictions
-        value = 0 will enable the UAC remote restrictions
-
-        For more info see here: http://support.microsoft.com/kb/951016
-
-        Returns:
-            True if the key is changed
-            False if the key is unchanged
-        """
-
-        if value not in (0, 1):
-            raise ValueError("Valid values for value parameter are 0 and 1")
-
-        with self._open_hive('SOFTWARE', write=True) as hive:
-            key = hive.root()
-            for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
-                          'System'):
-                key = hive.node_get_child(key, child)
-
-            policy = None
-            for val in hive.node_values(key):
-                if hive.value_key(val) == "LocalAccountTokenFilterPolicy":
-                    policy = val
-
-            if policy is not None:
-                dword = hive.value_dword(policy)
-                if dword == value:
-                    return False
-            elif value == 0:
-                return False
-
-            new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
-                         'value': struct.pack("<I", value)}
-
-            hive.node_set_value(key, new_value)
-            hive.commit(None)
-
-        return True
-
     def _do_collect_metadata(self):
         """Collect metadata about the OS"""
         super(Windows, self)._do_collect_metadata()
-        self.meta["USERS"] = " ".join(self._get_users())
-
-    def _get_users(self):
-        """Returns a list of users found in the images"""
 
-        with self._open_hive('SAM') as hive:
-            # Navigate to /SAM/Domains/Account/Users
-            users_node = hive.root()
-            for child in ('SAM', 'Domains', 'Account', 'Users'):
-                users_node = hive.node_get_child(users_node, child)
-
-            # Navigate to /SAM/Domains/Account/Users/Names
-            names_node = hive.node_get_child(users_node, 'Names')
-
-            # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
-            # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
-            #
-            # The RID (relative identifier) of each user is stored as the type!
-            # (not the value) of the default key of the node under Names whose
-            # name is the user's username. Under the RID node, there in a F
-            # value that contains information about this user account.
-            #
-            # See sam.h of the chntpw project on how to translate the F value
-            # of an account in the registry. Bytes 56 & 57 are the account type
-            # and status flags. The first bit is the 'account disabled' bit
-            disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
-
-            users = []
-            for user_node in hive.node_children(names_node):
-                username = hive.node_name(user_node)
-                rid = hive.value_type(hive.node_get_value(user_node, ""))[0]
-                # if RID is 500 (=0x1f4), the corresponding node name under
-                # Users is '000001F4'
-                key = ("%8.x" % rid).replace(' ', '0').upper()
-                rid_node = hive.node_get_child(users_node, key)
-                f_val = hive.value_value(hive.node_get_value(rid_node, 'F'))[1]
-
-                if disabled(f_val):
-                    self.out.warn("Found disabled `%s' account!" % username)
-                    continue
-
-                users.append(username)
-
-        # Filter out the guest account
-        return users
+        # We only care for active users
+        _, users = self.registry.enum_users()
+        self.meta["USERS"] = " ".join(users)
 
     def _check_connectivity(self):
         """Check if winexe works on the Windows VM"""
diff --git a/image_creator/os_type/windows/registry.py b/image_creator/os_type/windows/registry.py
new file mode 100644
index 0000000..d31c227
--- /dev/null
+++ b/image_creator/os_type/windows/registry.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011-2014 GRNET S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This package hosts code for accessing the windows registry"""
+
+from image_creator.util import FatalError
+
+import hivex
+import tempfile
+import os
+import struct
+
+
+class Registry(object):
+    """Windows Registry manipulation methods"""
+
+    def __init__(self, guestfs_handler, root_partition):
+        self.g = guestfs_handler
+        self.root = root_partition
+
+    def open_hive(self, hive, write=False):
+        """Returns a context manager for opening a hive file of the image for
+        reading or writing.
+        """
+        systemroot = self.g.inspect_get_windows_systemroot(self.root)
+        path = "%s/system32/config/%s" % (systemroot, hive)
+        try:
+            path = self.g.case_sensitive_path(path)
+        except RuntimeError as err:
+            raise FatalError("Unable to retrieve file: %s. Reason: %s" %
+                             (hive, str(err)))
+
+        g = self.g  # OpenHive class needs this since 'self' gets overwritten
+
+        class OpenHive:
+            """The OpenHive context manager"""
+            def __enter__(self):
+                localfd, self.localpath = tempfile.mkstemp()
+                try:
+                    os.close(localfd)
+                    g.download(path, self.localpath)
+
+                    hive = hivex.Hivex(self.localpath, write=write)
+                except:
+                    os.unlink(self.localpath)
+                    raise
+
+                return hive
+
+            def __exit__(self, exc_type, exc_value, traceback):
+                try:
+                    if write:
+                        g.upload(self.localpath, path)
+                finally:
+                    os.unlink(self.localpath)
+
+        return OpenHive()
+
+    def runonce(self, commands):
+        """Add commands to the RunOnce registry key"""
+
+        with self.open_hive('SOFTWARE', write=True) as hive:
+
+            key = hive.root()
+            for child in ('Microsoft', 'Windows', 'CurrentVersion'):
+                key = hive.node_get_child(key, child)
+
+            runonce = hive.node_get_child(key, "RunOnce")
+            if runonce is None:
+                runonce = hive.node_add_child(key, "RunOnce")
+
+            for desc, cmd in commands.items():
+                assert type(desc) is str and type(cmd) is str
+                value = {'key': desc, 't': 1, 'value': cmd.encode('utf-16le')}
+                hive.node_set_value(runonce, value)
+
+            hive.commit(None)
+
+    def enable_autologon(self, username, password="", autoadminlogon=True):
+        """Enable automatic logon for a specific user"""
+
+        assert type(username) is str and type(password) is str
+
+        with self.open_hive('SOFTWARE', write=True) as hive:
+
+            winlogon = hive.root()
+            for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
+                          'Winlogon'):
+                winlogon = hive.node_get_child(winlogon, child)
+
+            hive.node_set_value(winlogon,
+                                {'key': 'DefaultUserName', 't': 1,
+                                 'value': username.encode('utf-16le')})
+            hive.node_set_value(winlogon,
+                                {'key': 'DefaultPassword', 't': 1,
+                                 'value':  password.encode('utf-16le')})
+            hive.node_set_value(
+                winlogon,
+                {'key': 'AutoAdminLogon', 't': 1,
+                 'value': ("%d" % int(autoadminlogon)).encode('utf-16le')})
+
+            hive.commit(None)
+
+    def update_firewalls(self, domain, public, standard):
+        """Enables or disables the firewall for the Domain, the Public and the
+        Standard profile. Returns a triple with the old values.
+
+        1 will enable a firewall and 0 will disable it
+        """
+
+        if domain not in (0, 1):
+            raise ValueError("Valid values for domain parameter are 0 and 1")
+
+        if public not in (0, 1):
+            raise ValueError("Valid values for public parameter are 0 and 1")
+
+        if standard not in (0, 1):
+            raise ValueError("Valid values for standard parameter are 0 and 1")
+
+        with self.open_hive('SYSTEM', write=True) as hive:
+            select = hive.node_get_child(hive.root(), 'Select')
+            current_value = hive.node_get_value(select, 'Current')
+
+            # expecting a little endian dword
+            assert hive.value_type(current_value)[1] == 4
+            current = "%03d" % hive.value_dword(current_value)
+
+            firewall_policy = hive.root()
+            for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
+                          'Parameters', 'FirewallPolicy'):
+                firewall_policy = hive.node_get_child(firewall_policy, child)
+
+            old_values = []
+            new_values = [domain, public, standard]
+            for profile in ('Domain', 'Public', 'Standard'):
+                node = hive.node_get_child(firewall_policy,
+                                           '%sProfile' % profile)
+
+                old_value = hive.node_get_value(node, 'EnableFirewall')
+
+                # expecting a little endian dword
+                assert hive.value_type(old_value)[1] == 4
+                old_values.append(hive.value_dword(old_value))
+
+                hive.node_set_value(
+                    node, {'key': 'EnableFirewall', 't': 4L,
+                           'value': struct.pack("<I", new_values.pop(0))})
+            hive.commit(None)
+
+        return old_values
+
+    def update_uac_remote_setting(self, value):
+        r"""Updates the registry key value:
+        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
+        \System]"LocalAccountTokenFilterPolicy"
+
+        value = 1 will disable the UAC remote restrictions
+        value = 0 will enable the UAC remote restrictions
+
+        For more info see here: http://support.microsoft.com/kb/951016
+
+        Returns:
+            True if the key is changed
+            False if the key is unchanged
+        """
+
+        if value not in (0, 1):
+            raise ValueError("Valid values for value parameter are 0 and 1")
+
+        with self.open_hive('SOFTWARE', write=True) as hive:
+            key = hive.root()
+            for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
+                          'System'):
+                key = hive.node_get_child(key, child)
+
+            policy = None
+            for val in hive.node_values(key):
+                if hive.value_key(val) == "LocalAccountTokenFilterPolicy":
+                    policy = val
+
+            if policy is not None:
+                dword = hive.value_dword(policy)
+                if dword == value:
+                    return False
+            elif value == 0:
+                return False
+
+            new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
+                         'value': struct.pack("<I", value)}
+
+            hive.node_set_value(key, new_value)
+            hive.commit(None)
+
+        return True
+
+    def enum_users(self):
+        """Returns a list of users found on the system and a second list of
+        active users.
+        """
+
+        users = []
+        active = []
+
+        # Under HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID% there is
+        # an F field that contains information about this user account. Bytes
+        # 56 & 57 are the account type and status flags. The first bit is the
+        # 'account disabled' bit:
+        #
+        # http://www.beginningtoseethelight.org/ntsecurity/index.htm
+        #        #8603CF0AFBB170DD
+        #
+        disabled = lambda f: int(f[56].encode('hex'), 16) & 0x01
+
+        def collect_users(hive, username, rid_node):
+
+            f_val = hive.value_value(hive.node_get_value(rid_node, 'F'))[1]
+
+            if not disabled(f_val):
+                active.append(username)
+
+            users.append(username)
+
+        self._foreach_user([], collect_users)
+
+        return (users, active)
+
+    def _foreach_user(self, userlist, action, write=False):
+        """Performs an action on the RID node of a user in the registry, for
+        every user found in the userlist. If userlist is empty, it performs the
+        action on all users. The write flag determines if the registry is
+        opened for reading or writing.
+        """
+
+        with self.open_hive('SAM', write) as hive:
+            # Navigate to /SAM/Domains/Account/Users
+            users_node = hive.root()
+            for child in ('SAM', 'Domains', 'Account', 'Users'):
+                users_node = hive.node_get_child(users_node, child)
+
+            # Navigate to /SAM/Domains/Account/Users/Names
+            names_node = hive.node_get_child(users_node, 'Names')
+
+            # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\%RID%
+            # HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\Names\%Username%
+            #
+            # The RID (relative identifier) of each user is stored as the
+            # type!!!! (not the value) of the default key of the node under
+            # Names whose name is the user's username.
+            for user_node in hive.node_children(names_node):
+
+                username = hive.node_name(user_node)
+
+                if len(userlist) != 0 and username not in userlist:
+                    continue
+
+                rid = hive.value_type(hive.node_get_value(user_node, ""))[0]
+                # if RID is 500 (=0x1f4), the corresponding node name under
+                # Users is '000001F4'
+                key = ("%8.x" % rid).replace(' ', '0').upper()
+                rid_node = hive.node_get_child(users_node, key)
+
+                action(hive, username, rid_node)
+
+
+# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
-- 
GitLab