
Since the sethostname plugin was moved in plugins.common, there was a bit of Windows specific functionality inside it, the fact that it returns reboot_required in order to set the hostname on Windows. Now this choice is moved to osutils.set_host_name, which will simply return False for non-Windows systems. Change-Id: Ic7774f2c7dd86f3ade82a19520df4301bc9674a7
995 lines
37 KiB
Python
995 lines
37 KiB
Python
# Copyright 2012 Cloudbase Solutions Srl
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
import os
|
|
import re
|
|
import struct
|
|
import time
|
|
|
|
import pywintypes
|
|
import six
|
|
from six.moves import winreg
|
|
from win32com import client
|
|
import win32process
|
|
import win32security
|
|
import wmi
|
|
|
|
from cloudbaseinit import exception
|
|
from cloudbaseinit.openstack.common import log as logging
|
|
from cloudbaseinit.osutils import base
|
|
from cloudbaseinit.utils import encoding
|
|
from cloudbaseinit.utils.windows import network
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
advapi32 = ctypes.windll.advapi32
|
|
kernel32 = ctypes.windll.kernel32
|
|
netapi32 = ctypes.windll.netapi32
|
|
userenv = ctypes.windll.userenv
|
|
iphlpapi = ctypes.windll.iphlpapi
|
|
Ws2_32 = ctypes.windll.Ws2_32
|
|
setupapi = ctypes.windll.setupapi
|
|
msvcrt = ctypes.cdll.msvcrt
|
|
|
|
|
|
class Win32_PROFILEINFO(ctypes.Structure):
|
|
_fields_ = [
|
|
('dwSize', wintypes.DWORD),
|
|
('dwFlags', wintypes.DWORD),
|
|
('lpUserName', wintypes.LPWSTR),
|
|
('lpProfilePath', wintypes.LPWSTR),
|
|
('lpDefaultPath', wintypes.LPWSTR),
|
|
('lpServerName', wintypes.LPWSTR),
|
|
('lpPolicyPath', wintypes.LPWSTR),
|
|
('hprofile', wintypes.HANDLE)
|
|
]
|
|
|
|
|
|
class Win32_LOCALGROUP_MEMBERS_INFO_3(ctypes.Structure):
|
|
_fields_ = [
|
|
('lgrmi3_domainandname', wintypes.LPWSTR)
|
|
]
|
|
|
|
|
|
class Win32_MIB_IPFORWARDROW(ctypes.Structure):
|
|
_fields_ = [
|
|
('dwForwardDest', wintypes.DWORD),
|
|
('dwForwardMask', wintypes.DWORD),
|
|
('dwForwardPolicy', wintypes.DWORD),
|
|
('dwForwardNextHop', wintypes.DWORD),
|
|
('dwForwardIfIndex', wintypes.DWORD),
|
|
('dwForwardType', wintypes.DWORD),
|
|
('dwForwardProto', wintypes.DWORD),
|
|
('dwForwardAge', wintypes.DWORD),
|
|
('dwForwardNextHopAS', wintypes.DWORD),
|
|
('dwForwardMetric1', wintypes.DWORD),
|
|
('dwForwardMetric2', wintypes.DWORD),
|
|
('dwForwardMetric3', wintypes.DWORD),
|
|
('dwForwardMetric4', wintypes.DWORD),
|
|
('dwForwardMetric5', wintypes.DWORD)
|
|
]
|
|
|
|
|
|
class Win32_MIB_IPFORWARDTABLE(ctypes.Structure):
|
|
_fields_ = [
|
|
('dwNumEntries', wintypes.DWORD),
|
|
('table', Win32_MIB_IPFORWARDROW * 1)
|
|
]
|
|
|
|
|
|
class Win32_OSVERSIONINFOEX_W(ctypes.Structure):
|
|
_fields_ = [
|
|
('dwOSVersionInfoSize', wintypes.DWORD),
|
|
('dwMajorVersion', wintypes.DWORD),
|
|
('dwMinorVersion', wintypes.DWORD),
|
|
('dwBuildNumber', wintypes.DWORD),
|
|
('dwPlatformId', wintypes.DWORD),
|
|
('szCSDVersion', wintypes.WCHAR * 128),
|
|
('wServicePackMajor', wintypes.DWORD),
|
|
('wServicePackMinor', wintypes.DWORD),
|
|
('wSuiteMask', wintypes.DWORD),
|
|
('wProductType', wintypes.BYTE),
|
|
('wReserved', wintypes.BYTE)
|
|
]
|
|
|
|
|
|
class GUID(ctypes.Structure):
|
|
_fields_ = [
|
|
("data1", ctypes.wintypes.DWORD),
|
|
("data2", ctypes.wintypes.WORD),
|
|
("data3", ctypes.wintypes.WORD),
|
|
("data4", ctypes.c_byte * 8)]
|
|
|
|
def __init__(self, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8):
|
|
self.data1 = l
|
|
self.data2 = w1
|
|
self.data3 = w2
|
|
self.data4[0] = b1
|
|
self.data4[1] = b2
|
|
self.data4[2] = b3
|
|
self.data4[3] = b4
|
|
self.data4[4] = b5
|
|
self.data4[5] = b6
|
|
self.data4[6] = b7
|
|
self.data4[7] = b8
|
|
|
|
|
|
class Win32_SP_DEVICE_INTERFACE_DATA(ctypes.Structure):
|
|
_fields_ = [
|
|
('cbSize', wintypes.DWORD),
|
|
('InterfaceClassGuid', GUID),
|
|
('Flags', wintypes.DWORD),
|
|
('Reserved', ctypes.POINTER(wintypes.ULONG))
|
|
]
|
|
|
|
|
|
class Win32_SP_DEVICE_INTERFACE_DETAIL_DATA_W(ctypes.Structure):
|
|
_fields_ = [
|
|
('cbSize', wintypes.DWORD),
|
|
('DevicePath', ctypes.c_byte * 2)
|
|
]
|
|
|
|
|
|
class Win32_STORAGE_DEVICE_NUMBER(ctypes.Structure):
|
|
_fields_ = [
|
|
('DeviceType', wintypes.DWORD),
|
|
('DeviceNumber', wintypes.DWORD),
|
|
('PartitionNumber', wintypes.DWORD)
|
|
]
|
|
|
|
|
|
msvcrt.malloc.argtypes = [ctypes.c_size_t]
|
|
msvcrt.malloc.restype = ctypes.c_void_p
|
|
|
|
msvcrt.free.argtypes = [ctypes.c_void_p]
|
|
msvcrt.free.restype = None
|
|
|
|
kernel32.VerifyVersionInfoW.argtypes = [
|
|
ctypes.POINTER(Win32_OSVERSIONINFOEX_W),
|
|
wintypes.DWORD, wintypes.ULARGE_INTEGER]
|
|
kernel32.VerifyVersionInfoW.restype = wintypes.BOOL
|
|
|
|
kernel32.VerSetConditionMask.argtypes = [wintypes.ULARGE_INTEGER,
|
|
wintypes.DWORD,
|
|
wintypes.BYTE]
|
|
kernel32.VerSetConditionMask.restype = wintypes.ULARGE_INTEGER
|
|
|
|
kernel32.SetComputerNameExW.argtypes = [ctypes.c_int, wintypes.LPCWSTR]
|
|
kernel32.SetComputerNameExW.restype = wintypes.BOOL
|
|
|
|
kernel32.GetLogicalDriveStringsW.argtypes = [wintypes.DWORD, wintypes.LPWSTR]
|
|
kernel32.GetLogicalDriveStringsW.restype = wintypes.DWORD
|
|
|
|
kernel32.GetDriveTypeW.argtypes = [wintypes.LPCWSTR]
|
|
kernel32.GetDriveTypeW.restype = wintypes.UINT
|
|
|
|
kernel32.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD,
|
|
wintypes.DWORD, wintypes.LPVOID,
|
|
wintypes.DWORD, wintypes.DWORD,
|
|
wintypes.HANDLE]
|
|
kernel32.CreateFileW.restype = wintypes.HANDLE
|
|
|
|
kernel32.DeviceIoControl.argtypes = [wintypes.HANDLE, wintypes.DWORD,
|
|
wintypes.LPVOID, wintypes.DWORD,
|
|
wintypes.LPVOID, wintypes.DWORD,
|
|
ctypes.POINTER(wintypes.DWORD),
|
|
wintypes.LPVOID]
|
|
kernel32.DeviceIoControl.restype = wintypes.BOOL
|
|
|
|
kernel32.GetProcessHeap.argtypes = []
|
|
kernel32.GetProcessHeap.restype = wintypes.HANDLE
|
|
|
|
kernel32.HeapAlloc.argtypes = [wintypes.HANDLE, wintypes.DWORD,
|
|
ctypes.c_size_t]
|
|
kernel32.HeapAlloc.restype = wintypes.LPVOID
|
|
|
|
kernel32.HeapFree.argtypes = [wintypes.HANDLE, wintypes.DWORD,
|
|
wintypes.LPVOID]
|
|
kernel32.HeapFree.restype = wintypes.BOOL
|
|
|
|
iphlpapi.GetIpForwardTable.argtypes = [
|
|
ctypes.POINTER(Win32_MIB_IPFORWARDTABLE),
|
|
ctypes.POINTER(wintypes.ULONG),
|
|
wintypes.BOOL]
|
|
iphlpapi.GetIpForwardTable.restype = wintypes.DWORD
|
|
|
|
Ws2_32.inet_ntoa.restype = ctypes.c_char_p
|
|
|
|
setupapi.SetupDiGetClassDevsW.argtypes = [ctypes.POINTER(GUID),
|
|
wintypes.LPCWSTR,
|
|
wintypes.HANDLE,
|
|
wintypes.DWORD]
|
|
setupapi.SetupDiGetClassDevsW.restype = wintypes.HANDLE
|
|
|
|
setupapi.SetupDiEnumDeviceInterfaces.argtypes = [
|
|
wintypes.HANDLE,
|
|
wintypes.LPVOID,
|
|
ctypes.POINTER(GUID),
|
|
wintypes.DWORD,
|
|
ctypes.POINTER(Win32_SP_DEVICE_INTERFACE_DATA)]
|
|
setupapi.SetupDiEnumDeviceInterfaces.restype = wintypes.BOOL
|
|
|
|
setupapi.SetupDiGetDeviceInterfaceDetailW.argtypes = [
|
|
wintypes.HANDLE,
|
|
ctypes.POINTER(Win32_SP_DEVICE_INTERFACE_DATA),
|
|
ctypes.POINTER(Win32_SP_DEVICE_INTERFACE_DETAIL_DATA_W),
|
|
wintypes.DWORD,
|
|
ctypes.POINTER(wintypes.DWORD),
|
|
wintypes.LPVOID]
|
|
setupapi.SetupDiGetDeviceInterfaceDetailW.restype = wintypes.BOOL
|
|
|
|
setupapi.SetupDiDestroyDeviceInfoList.argtypes = [wintypes.HANDLE]
|
|
setupapi.SetupDiDestroyDeviceInfoList.restype = wintypes.BOOL
|
|
|
|
VER_MAJORVERSION = 1
|
|
VER_MINORVERSION = 2
|
|
VER_BUILDNUMBER = 4
|
|
|
|
VER_GREATER_EQUAL = 3
|
|
|
|
GUID_DEVINTERFACE_DISK = GUID(0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2,
|
|
0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b)
|
|
|
|
|
|
class WindowsUtils(base.BaseOSUtils):
|
|
NERR_GroupNotFound = 2220
|
|
ERROR_ACCESS_DENIED = 5
|
|
ERROR_INSUFFICIENT_BUFFER = 122
|
|
ERROR_NO_DATA = 232
|
|
ERROR_NO_SUCH_MEMBER = 1387
|
|
ERROR_MEMBER_IN_ALIAS = 1378
|
|
ERROR_INVALID_MEMBER = 1388
|
|
ERROR_OLD_WIN_VERSION = 1150
|
|
ERROR_NO_MORE_FILES = 18
|
|
|
|
INVALID_HANDLE_VALUE = 0xFFFFFFFF
|
|
|
|
FILE_SHARE_READ = 1
|
|
FILE_SHARE_WRITE = 2
|
|
|
|
OPEN_EXISTING = 3
|
|
|
|
IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x002D1080
|
|
|
|
MAX_PATH = 260
|
|
|
|
DIGCF_PRESENT = 2
|
|
DIGCF_DEVICEINTERFACE = 0x10
|
|
|
|
DRIVE_CDROM = 5
|
|
|
|
SERVICE_STATUS_STOPPED = "Stopped"
|
|
SERVICE_STATUS_START_PENDING = "Start Pending"
|
|
SERVICE_STATUS_STOP_PENDING = "Stop Pending"
|
|
SERVICE_STATUS_RUNNING = "Running"
|
|
SERVICE_STATUS_CONTINUE_PENDING = "Continue Pending"
|
|
SERVICE_STATUS_PAUSE_PENDING = "Pause Pending"
|
|
SERVICE_STATUS_PAUSED = "Paused"
|
|
SERVICE_STATUS_UNKNOWN = "Unknown"
|
|
|
|
SERVICE_START_MODE_AUTOMATIC = "Automatic"
|
|
SERVICE_START_MODE_MANUAL = "Manual"
|
|
SERVICE_START_MODE_DISABLED = "Disabled"
|
|
|
|
ComputerNamePhysicalDnsHostname = 5
|
|
|
|
_config_key = 'SOFTWARE\\Cloudbase Solutions\\Cloudbase-Init\\'
|
|
_service_name = 'cloudbase-init'
|
|
|
|
_FW_IP_PROTOCOL_TCP = 6
|
|
_FW_IP_PROTOCOL_UDP = 17
|
|
_FW_SCOPE_ALL = 0
|
|
_FW_SCOPE_LOCAL_SUBNET = 1
|
|
|
|
def _enable_shutdown_privilege(self):
|
|
process = win32process.GetCurrentProcess()
|
|
token = win32security.OpenProcessToken(
|
|
process,
|
|
win32security.TOKEN_ADJUST_PRIVILEGES |
|
|
win32security.TOKEN_QUERY)
|
|
priv_luid = win32security.LookupPrivilegeValue(
|
|
None, win32security.SE_SHUTDOWN_NAME)
|
|
privilege = [(priv_luid, win32security.SE_PRIVILEGE_ENABLED)]
|
|
win32security.AdjustTokenPrivileges(token, False, privilege)
|
|
|
|
def reboot(self):
|
|
self._enable_shutdown_privilege()
|
|
|
|
ret_val = advapi32.InitiateSystemShutdownW(0, "Cloudbase-Init reboot",
|
|
0, True, True)
|
|
if not ret_val:
|
|
raise exception.CloudbaseInitException("Reboot failed")
|
|
|
|
def _get_user_wmi_object(self, username):
|
|
conn = wmi.WMI(moniker='//./root/cimv2')
|
|
username_san = self._sanitize_wmi_input(username)
|
|
q = conn.query('SELECT * FROM Win32_Account where name = '
|
|
'\'%s\'' % username_san)
|
|
if len(q) > 0:
|
|
return q[0]
|
|
return None
|
|
|
|
def user_exists(self, username):
|
|
return self._get_user_wmi_object(username) is not None
|
|
|
|
def _get_adsi_object(self, hostname='.', object_name=None,
|
|
object_type='computer'):
|
|
adsi = client.Dispatch("ADsNameSpaces")
|
|
winnt = adsi.GetObject("", "WinNT:")
|
|
query = "WinNT://%s" % hostname
|
|
if object_name:
|
|
object_name_san = self.sanitize_shell_input(object_name)
|
|
query += "/%s" % object_name_san
|
|
query += ",%s" % object_type
|
|
return winnt.OpenDSObject(query, "", "", 0)
|
|
|
|
def _create_or_change_user(self, username, password, create,
|
|
password_expires):
|
|
try:
|
|
if create:
|
|
host = self._get_adsi_object()
|
|
user = host.Create('user', username)
|
|
else:
|
|
user = self._get_adsi_object(object_name=username,
|
|
object_type='user')
|
|
|
|
user.setpassword(password)
|
|
user.SetInfo()
|
|
|
|
self._set_user_password_expiration(username, password_expires)
|
|
except pywintypes.com_error as ex:
|
|
if create:
|
|
msg = "Create user failed: %s"
|
|
else:
|
|
msg = "Set user password failed: %s"
|
|
raise exception.CloudbaseInitException(msg % ex.excepinfo[2])
|
|
|
|
def _sanitize_wmi_input(self, value):
|
|
return value.replace('\'', '\'\'')
|
|
|
|
def _set_user_password_expiration(self, username, password_expires):
|
|
r = self._get_user_wmi_object(username)
|
|
if not r:
|
|
return False
|
|
r.PasswordExpires = password_expires
|
|
r.Put_()
|
|
return True
|
|
|
|
def create_user(self, username, password, password_expires=False):
|
|
self._create_or_change_user(username, password, create=True,
|
|
password_expires=password_expires)
|
|
|
|
def set_user_password(self, username, password, password_expires=False):
|
|
self._create_or_change_user(username, password, create=False,
|
|
password_expires=password_expires)
|
|
|
|
def _get_user_sid_and_domain(self, username):
|
|
sid = ctypes.create_string_buffer(1024)
|
|
cbSid = wintypes.DWORD(ctypes.sizeof(sid))
|
|
domainName = ctypes.create_unicode_buffer(1024)
|
|
cchReferencedDomainName = wintypes.DWORD(
|
|
ctypes.sizeof(domainName) / ctypes.sizeof(wintypes.WCHAR))
|
|
sidNameUse = wintypes.DWORD()
|
|
|
|
ret_val = advapi32.LookupAccountNameW(
|
|
0, six.text_type(username), sid, ctypes.byref(cbSid), domainName,
|
|
ctypes.byref(cchReferencedDomainName), ctypes.byref(sidNameUse))
|
|
if not ret_val:
|
|
raise exception.CloudbaseInitException("Cannot get user SID")
|
|
|
|
return (sid, domainName.value)
|
|
|
|
def add_user_to_local_group(self, username, groupname):
|
|
|
|
lmi = Win32_LOCALGROUP_MEMBERS_INFO_3()
|
|
lmi.lgrmi3_domainandname = six.text_type(username)
|
|
|
|
ret_val = netapi32.NetLocalGroupAddMembers(0, six.text_type(groupname),
|
|
3, ctypes.addressof(lmi), 1)
|
|
|
|
if ret_val == self.NERR_GroupNotFound:
|
|
raise exception.CloudbaseInitException('Group not found')
|
|
elif ret_val == self.ERROR_ACCESS_DENIED:
|
|
raise exception.CloudbaseInitException('Access denied')
|
|
elif ret_val == self.ERROR_NO_SUCH_MEMBER:
|
|
raise exception.CloudbaseInitException('Username not found')
|
|
elif ret_val == self.ERROR_MEMBER_IN_ALIAS:
|
|
# The user is already a member of the group
|
|
pass
|
|
elif ret_val == self.ERROR_INVALID_MEMBER:
|
|
raise exception.CloudbaseInitException('Invalid user')
|
|
elif ret_val != 0:
|
|
raise exception.CloudbaseInitException('Unknown error')
|
|
|
|
def get_user_sid(self, username):
|
|
r = self._get_user_wmi_object(username)
|
|
if not r:
|
|
return None
|
|
return r.SID
|
|
|
|
def create_user_logon_session(self, username, password, domain='.',
|
|
load_profile=True):
|
|
token = wintypes.HANDLE()
|
|
ret_val = advapi32.LogonUserW(six.text_type(username),
|
|
six.text_type(domain),
|
|
six.text_type(password), 2, 0,
|
|
ctypes.byref(token))
|
|
if not ret_val:
|
|
raise exception.CloudbaseInitException("User logon failed")
|
|
|
|
if load_profile:
|
|
pi = Win32_PROFILEINFO()
|
|
pi.dwSize = ctypes.sizeof(Win32_PROFILEINFO)
|
|
pi.lpUserName = six.text_type(username)
|
|
ret_val = userenv.LoadUserProfileW(token, ctypes.byref(pi))
|
|
if not ret_val:
|
|
kernel32.CloseHandle(token)
|
|
raise exception.CloudbaseInitException(
|
|
"Cannot load user profile")
|
|
|
|
return token
|
|
|
|
def close_user_logon_session(self, token):
|
|
kernel32.CloseHandle(token)
|
|
|
|
def get_user_home(self, username):
|
|
user_sid = self.get_user_sid(username)
|
|
if user_sid:
|
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\'
|
|
'Microsoft\\Windows NT\\CurrentVersion\\'
|
|
'ProfileList\\%s' % user_sid) as key:
|
|
return winreg.QueryValueEx(key, 'ProfileImagePath')[0]
|
|
LOG.debug('Home directory not found for user \'%s\'' % username)
|
|
return None
|
|
|
|
def sanitize_shell_input(self, value):
|
|
return value.replace('"', '\\"')
|
|
|
|
def set_host_name(self, new_host_name):
|
|
ret_val = kernel32.SetComputerNameExW(
|
|
self.ComputerNamePhysicalDnsHostname,
|
|
six.text_type(new_host_name))
|
|
if not ret_val:
|
|
raise exception.CloudbaseInitException("Cannot set host name")
|
|
return True
|
|
|
|
def get_network_adapters(self):
|
|
"""Return available adapters as a list of tuples of (name, mac)."""
|
|
conn = wmi.WMI(moniker='//./root/cimv2')
|
|
# Get Ethernet adapters only
|
|
wql = ('SELECT * FROM Win32_NetworkAdapter WHERE '
|
|
'AdapterTypeId = 0 AND MACAddress IS NOT NULL')
|
|
|
|
if self.check_os_version(6, 0):
|
|
wql += ' AND PhysicalAdapter = True'
|
|
|
|
q = conn.query(wql)
|
|
return [(r.Name, r.MACAddress) for r in q]
|
|
|
|
def get_dhcp_hosts_in_use(self):
|
|
dhcp_hosts = []
|
|
for net_addr in network.get_adapter_addresses():
|
|
if net_addr["dhcp_enabled"] and net_addr["dhcp_server"]:
|
|
dhcp_hosts.append((net_addr["mac_address"],
|
|
net_addr["dhcp_server"]))
|
|
return dhcp_hosts
|
|
|
|
def set_ntp_client_config(self, ntp_hosts):
|
|
base_dir = self._get_system_dir()
|
|
w32tm_path = os.path.join(base_dir, "w32tm.exe")
|
|
|
|
# Convert the NTP hosts list to a string, in order to pass
|
|
# it to w32tm.
|
|
ntp_hosts = ",".join(ntp_hosts)
|
|
|
|
args = [w32tm_path, '/config', '/manualpeerlist:%s' % ntp_hosts,
|
|
'/syncfromflags:manual', '/update']
|
|
|
|
(out, err, ret_val) = self.execute_process(args, shell=False)
|
|
if ret_val:
|
|
raise exception.CloudbaseInitException(
|
|
'w32tm failed to configure NTP.\nOutput: %(out)s\nError:'
|
|
' %(err)s' % {'out': out, 'err': err})
|
|
|
|
def set_network_adapter_mtu(self, mac_address, mtu):
|
|
if not self.check_os_version(6, 0):
|
|
raise exception.CloudbaseInitException(
|
|
'Setting the MTU is currently not supported on Windows XP '
|
|
'and Windows Server 2003')
|
|
|
|
iface_index_list = [
|
|
net_addr["interface_index"] for net_addr
|
|
in network.get_adapter_addresses()
|
|
if net_addr["mac_address"] == mac_address]
|
|
|
|
if not iface_index_list:
|
|
raise exception.CloudbaseInitException(
|
|
'Network interface with MAC address "%s" not found' %
|
|
mac_address)
|
|
else:
|
|
iface_index = iface_index_list[0]
|
|
|
|
LOG.debug('Setting MTU for interface "%(mac_address)s" with '
|
|
'value "%(mtu)s"' %
|
|
{'mac_address': mac_address, 'mtu': mtu})
|
|
|
|
base_dir = self._get_system_dir()
|
|
netsh_path = os.path.join(base_dir, 'netsh.exe')
|
|
|
|
args = [netsh_path, "interface", "ipv4", "set", "subinterface",
|
|
str(iface_index), "mtu=%s" % mtu,
|
|
"store=persistent"]
|
|
(out, err, ret_val) = self.execute_process(args, shell=False)
|
|
if ret_val:
|
|
raise exception.CloudbaseInitException(
|
|
'Setting MTU for interface "%(mac_address)s" with '
|
|
'value "%(mtu)s" failed' % {'mac_address': mac_address,
|
|
'mtu': mtu})
|
|
|
|
def set_static_network_config(self, mac_address, address, netmask,
|
|
broadcast, gateway, dnsnameservers):
|
|
conn = wmi.WMI(moniker='//./root/cimv2')
|
|
|
|
q = conn.query("SELECT * FROM Win32_NetworkAdapter WHERE "
|
|
"MACAddress = '{}'".format(mac_address))
|
|
if not len(q):
|
|
raise exception.CloudbaseInitException(
|
|
"Network adapter not found")
|
|
|
|
adapter_config = q[0].associators(
|
|
wmi_result_class='Win32_NetworkAdapterConfiguration')[0]
|
|
|
|
LOG.debug("Setting static IP address")
|
|
(ret_val,) = adapter_config.EnableStatic([address], [netmask])
|
|
if ret_val > 1:
|
|
raise exception.CloudbaseInitException(
|
|
"Cannot set static IP address on network adapter")
|
|
reboot_required = (ret_val == 1)
|
|
|
|
if gateway:
|
|
LOG.debug("Setting static gateways")
|
|
(ret_val,) = adapter_config.SetGateways([gateway], [1])
|
|
if ret_val > 1:
|
|
raise exception.CloudbaseInitException(
|
|
"Cannot set gateway on network adapter")
|
|
reboot_required = reboot_required or ret_val == 1
|
|
|
|
if dnsnameservers:
|
|
LOG.debug("Setting static DNS servers")
|
|
(ret_val,) = adapter_config.SetDNSServerSearchOrder(dnsnameservers)
|
|
if ret_val > 1:
|
|
raise exception.CloudbaseInitException(
|
|
"Cannot set DNS on network adapter")
|
|
reboot_required = reboot_required or ret_val == 1
|
|
|
|
return reboot_required
|
|
|
|
def _get_config_key_name(self, section):
|
|
key_name = self._config_key
|
|
if section:
|
|
key_name += section.replace('/', '\\') + '\\'
|
|
return key_name
|
|
|
|
def set_config_value(self, name, value, section=None):
|
|
key_name = self._get_config_key_name(section)
|
|
|
|
with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE,
|
|
key_name) as key:
|
|
if type(value) == int:
|
|
regtype = winreg.REG_DWORD
|
|
else:
|
|
regtype = winreg.REG_SZ
|
|
winreg.SetValueEx(key, name, 0, regtype, value)
|
|
|
|
def get_config_value(self, name, section=None):
|
|
key_name = self._get_config_key_name(section)
|
|
|
|
try:
|
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
|
key_name) as key:
|
|
(value, regtype) = winreg.QueryValueEx(key, name)
|
|
return value
|
|
except WindowsError:
|
|
return None
|
|
|
|
def wait_for_boot_completion(self):
|
|
try:
|
|
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
|
|
"SYSTEM\\Setup\\Status\\SysprepStatus", 0,
|
|
winreg.KEY_READ) as key:
|
|
while True:
|
|
gen_state = winreg.QueryValueEx(key,
|
|
"GeneralizationState")[0]
|
|
if gen_state == 7:
|
|
break
|
|
time.sleep(1)
|
|
LOG.info('Waiting for sysprep completion. '
|
|
'GeneralizationState: %d' % gen_state)
|
|
except WindowsError as ex:
|
|
if ex.winerror == 2:
|
|
LOG.debug('Sysprep data not found in the registry, '
|
|
'skipping sysprep completion check.')
|
|
else:
|
|
raise ex
|
|
|
|
def _get_service(self, service_name):
|
|
conn = wmi.WMI(moniker='//./root/cimv2')
|
|
service_list = conn.Win32_Service(Name=service_name)
|
|
if len(service_list):
|
|
return service_list[0]
|
|
|
|
def check_service_exists(self, service_name):
|
|
return self._get_service(service_name) is not None
|
|
|
|
def get_service_status(self, service_name):
|
|
service = self._get_service(service_name)
|
|
return service.State
|
|
|
|
def get_service_start_mode(self, service_name):
|
|
service = self._get_service(service_name)
|
|
return service.StartMode
|
|
|
|
def set_service_start_mode(self, service_name, start_mode):
|
|
# TODO(alexpilotti): Handle the "Delayed Start" case
|
|
service = self._get_service(service_name)
|
|
(ret_val,) = service.ChangeStartMode(start_mode)
|
|
if ret_val != 0:
|
|
raise exception.CloudbaseInitException(
|
|
'Setting service %(service_name)s start mode failed with '
|
|
'return value: %(ret_val)d' % {'service_name': service_name,
|
|
'ret_val': ret_val})
|
|
|
|
def start_service(self, service_name):
|
|
LOG.debug('Starting service %s' % service_name)
|
|
service = self._get_service(service_name)
|
|
(ret_val,) = service.StartService()
|
|
if ret_val != 0:
|
|
raise exception.CloudbaseInitException(
|
|
'Starting service %(service_name)s failed with return value: '
|
|
'%(ret_val)d' % {'service_name': service_name,
|
|
'ret_val': ret_val})
|
|
|
|
def stop_service(self, service_name):
|
|
LOG.debug('Stopping service %s' % service_name)
|
|
service = self._get_service(service_name)
|
|
(ret_val,) = service.StopService()
|
|
if ret_val != 0:
|
|
raise exception.CloudbaseInitException(
|
|
'Stopping service %(service_name)s failed with return value:'
|
|
' %(ret_val)d' % {'service_name': service_name,
|
|
'ret_val': ret_val})
|
|
|
|
def terminate(self):
|
|
# Wait for the service to start. Polling the service "Started" property
|
|
# is not enough
|
|
time.sleep(3)
|
|
self.stop_service(self._service_name)
|
|
|
|
def get_default_gateway(self):
|
|
default_routes = [r for r in self._get_ipv4_routing_table()
|
|
if r[0] == '0.0.0.0']
|
|
if default_routes:
|
|
return (default_routes[0][3], default_routes[0][2])
|
|
else:
|
|
return (None, None)
|
|
|
|
def _get_ipv4_routing_table(self):
|
|
routing_table = []
|
|
|
|
heap = kernel32.GetProcessHeap()
|
|
|
|
size = wintypes.ULONG(ctypes.sizeof(Win32_MIB_IPFORWARDTABLE))
|
|
p = kernel32.HeapAlloc(heap, 0, ctypes.c_size_t(size.value))
|
|
if not p:
|
|
raise exception.CloudbaseInitException(
|
|
'Unable to allocate memory for the IP forward table')
|
|
p_forward_table = ctypes.cast(
|
|
p, ctypes.POINTER(Win32_MIB_IPFORWARDTABLE))
|
|
|
|
try:
|
|
err = iphlpapi.GetIpForwardTable(p_forward_table,
|
|
ctypes.byref(size), 0)
|
|
if err == self.ERROR_INSUFFICIENT_BUFFER:
|
|
kernel32.HeapFree(heap, 0, p_forward_table)
|
|
p = kernel32.HeapAlloc(heap, 0, ctypes.c_size_t(size.value))
|
|
if not p:
|
|
raise exception.CloudbaseInitException(
|
|
'Unable to allocate memory for the IP forward table')
|
|
p_forward_table = ctypes.cast(
|
|
p, ctypes.POINTER(Win32_MIB_IPFORWARDTABLE))
|
|
|
|
err = iphlpapi.GetIpForwardTable(p_forward_table,
|
|
ctypes.byref(size), 0)
|
|
if err != self.ERROR_NO_DATA:
|
|
if err:
|
|
raise exception.CloudbaseInitException(
|
|
'Unable to get IP forward table. Error: %s' % err)
|
|
|
|
forward_table = p_forward_table.contents
|
|
table = ctypes.cast(
|
|
ctypes.addressof(forward_table.table),
|
|
ctypes.POINTER(Win32_MIB_IPFORWARDROW *
|
|
forward_table.dwNumEntries)).contents
|
|
|
|
i = 0
|
|
while i < forward_table.dwNumEntries:
|
|
row = table[i]
|
|
routing_table.append((
|
|
encoding.get_as_string(Ws2_32.inet_ntoa(
|
|
row.dwForwardDest)),
|
|
encoding.get_as_string(Ws2_32.inet_ntoa(
|
|
row.dwForwardMask)),
|
|
encoding.get_as_string(Ws2_32.inet_ntoa(
|
|
row.dwForwardNextHop)),
|
|
row.dwForwardIfIndex,
|
|
row.dwForwardMetric1))
|
|
i += 1
|
|
|
|
return routing_table
|
|
finally:
|
|
kernel32.HeapFree(heap, 0, p_forward_table)
|
|
|
|
def check_static_route_exists(self, destination):
|
|
return len([r for r in self._get_ipv4_routing_table()
|
|
if r[0] == destination]) > 0
|
|
|
|
def add_static_route(self, destination, mask, next_hop, interface_index,
|
|
metric):
|
|
args = ['ROUTE', 'ADD', destination, 'MASK', mask, next_hop]
|
|
(out, err, ret_val) = self.execute_process(args)
|
|
# Cannot use the return value to determine the outcome
|
|
if ret_val or err:
|
|
raise exception.CloudbaseInitException(
|
|
'Unable to add route: %s' % err)
|
|
|
|
def check_os_version(self, major, minor, build=0):
|
|
vi = Win32_OSVERSIONINFOEX_W()
|
|
vi.dwOSVersionInfoSize = ctypes.sizeof(Win32_OSVERSIONINFOEX_W)
|
|
|
|
vi.dwMajorVersion = major
|
|
vi.dwMinorVersion = minor
|
|
vi.dwBuildNumber = build
|
|
|
|
mask = 0
|
|
for type_mask in [VER_MAJORVERSION, VER_MINORVERSION, VER_BUILDNUMBER]:
|
|
mask = kernel32.VerSetConditionMask(mask, type_mask,
|
|
VER_GREATER_EQUAL)
|
|
|
|
type_mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER
|
|
ret_val = kernel32.VerifyVersionInfoW(ctypes.byref(vi), type_mask,
|
|
mask)
|
|
if ret_val:
|
|
return True
|
|
else:
|
|
err = kernel32.GetLastError()
|
|
if err == self.ERROR_OLD_WIN_VERSION:
|
|
return False
|
|
else:
|
|
raise exception.CloudbaseInitException(
|
|
"VerifyVersionInfo failed with error: %s" % err)
|
|
|
|
def get_volume_label(self, drive):
|
|
max_label_size = 261
|
|
label = ctypes.create_unicode_buffer(max_label_size)
|
|
ret_val = kernel32.GetVolumeInformationW(six.text_type(drive), label,
|
|
max_label_size, 0, 0, 0, 0, 0)
|
|
if ret_val:
|
|
return label.value
|
|
|
|
def generate_random_password(self, length):
|
|
while True:
|
|
pwd = super(WindowsUtils, self).generate_random_password(length)
|
|
# Make sure that the Windows complexity requirements are met:
|
|
# http://technet.microsoft.com/en-us/library/cc786468(v=ws.10).aspx
|
|
valid = True
|
|
for r in ["[a-z]", "[A-Z]", "[0-9]"]:
|
|
if not re.search(r, pwd):
|
|
valid = False
|
|
if valid:
|
|
return pwd
|
|
|
|
def _split_str_buf_list(self, buf, buf_len):
|
|
i = 0
|
|
value = ''
|
|
values = []
|
|
while i < buf_len:
|
|
c = buf[i]
|
|
if c != '\x00':
|
|
value += c
|
|
else:
|
|
values.append(value)
|
|
value = ''
|
|
i += 1
|
|
|
|
return values
|
|
|
|
def _get_logical_drives(self):
|
|
buf_size = self.MAX_PATH
|
|
buf = ctypes.create_unicode_buffer(buf_size + 1)
|
|
buf_len = kernel32.GetLogicalDriveStringsW(buf_size, buf)
|
|
if not buf_len:
|
|
raise exception.CloudbaseInitException(
|
|
"GetLogicalDriveStringsW failed")
|
|
|
|
return self._split_str_buf_list(buf, buf_len)
|
|
|
|
def get_cdrom_drives(self):
|
|
drives = self._get_logical_drives()
|
|
return [d for d in drives if kernel32.GetDriveTypeW(d) ==
|
|
self.DRIVE_CDROM]
|
|
|
|
def _is_64bit_arch(self):
|
|
# interpreter's bits
|
|
return struct.calcsize("P") == 8
|
|
|
|
def get_physical_disks(self):
|
|
physical_disks = []
|
|
|
|
disk_guid = GUID_DEVINTERFACE_DISK
|
|
handle_disks = setupapi.SetupDiGetClassDevsW(
|
|
ctypes.byref(disk_guid), None, None,
|
|
self.DIGCF_PRESENT | self.DIGCF_DEVICEINTERFACE)
|
|
if handle_disks == self.INVALID_HANDLE_VALUE:
|
|
raise exception.CloudbaseInitException(
|
|
"SetupDiGetClassDevs failed")
|
|
|
|
try:
|
|
did = Win32_SP_DEVICE_INTERFACE_DATA()
|
|
did.cbSize = ctypes.sizeof(Win32_SP_DEVICE_INTERFACE_DATA)
|
|
|
|
index = 0
|
|
while setupapi.SetupDiEnumDeviceInterfaces(
|
|
handle_disks, None, ctypes.byref(disk_guid), index,
|
|
ctypes.byref(did)):
|
|
index += 1
|
|
handle_disk = self.INVALID_HANDLE_VALUE
|
|
|
|
required_size = wintypes.DWORD()
|
|
if not setupapi.SetupDiGetDeviceInterfaceDetailW(
|
|
handle_disks, ctypes.byref(did), None, 0,
|
|
ctypes.byref(required_size), None):
|
|
if (kernel32.GetLastError() !=
|
|
self.ERROR_INSUFFICIENT_BUFFER):
|
|
raise exception.CloudbaseInitException(
|
|
"SetupDiGetDeviceInterfaceDetailW failed")
|
|
|
|
pdidd = ctypes.cast(
|
|
msvcrt.malloc(ctypes.c_size_t(required_size.value)),
|
|
ctypes.POINTER(Win32_SP_DEVICE_INTERFACE_DETAIL_DATA_W))
|
|
|
|
try:
|
|
pdidd.contents.cbSize = ctypes.sizeof(
|
|
Win32_SP_DEVICE_INTERFACE_DETAIL_DATA_W)
|
|
if not self._is_64bit_arch():
|
|
# NOTE(cpoieana): For some reason, on x86 platforms
|
|
# the alignment or content of the struct
|
|
# is not taken into consideration.
|
|
pdidd.contents.cbSize = 6
|
|
|
|
if not setupapi.SetupDiGetDeviceInterfaceDetailW(
|
|
handle_disks, ctypes.byref(did), pdidd,
|
|
required_size, None, None):
|
|
raise exception.CloudbaseInitException(
|
|
"SetupDiGetDeviceInterfaceDetailW failed")
|
|
|
|
device_path = ctypes.cast(
|
|
pdidd.contents.DevicePath, wintypes.LPWSTR).value
|
|
|
|
handle_disk = kernel32.CreateFileW(
|
|
device_path, 0, self.FILE_SHARE_READ,
|
|
None, self.OPEN_EXISTING, 0, 0)
|
|
if handle_disk == self.INVALID_HANDLE_VALUE:
|
|
raise exception.CloudbaseInitException(
|
|
'CreateFileW failed')
|
|
|
|
sdn = Win32_STORAGE_DEVICE_NUMBER()
|
|
|
|
b = wintypes.DWORD()
|
|
if not kernel32.DeviceIoControl(
|
|
handle_disk, self.IOCTL_STORAGE_GET_DEVICE_NUMBER,
|
|
None, 0, ctypes.byref(sdn), ctypes.sizeof(sdn),
|
|
ctypes.byref(b), None):
|
|
raise exception.CloudbaseInitException(
|
|
'DeviceIoControl failed')
|
|
|
|
physical_disks.append(
|
|
r"\\.\PHYSICALDRIVE%d" % sdn.DeviceNumber)
|
|
finally:
|
|
msvcrt.free(pdidd)
|
|
if handle_disk != self.INVALID_HANDLE_VALUE:
|
|
kernel32.CloseHandle(handle_disk)
|
|
finally:
|
|
setupapi.SetupDiDestroyDeviceInfoList(handle_disks)
|
|
|
|
return physical_disks
|
|
|
|
def _get_fw_protocol(self, protocol):
|
|
if protocol == self.PROTOCOL_TCP:
|
|
fw_protocol = self._FW_IP_PROTOCOL_TCP
|
|
elif protocol == self.PROTOCOL_UDP:
|
|
fw_protocol = self._FW_IP_PROTOCOL_UDP
|
|
else:
|
|
raise NotImplementedError("Unsupported protocol")
|
|
return fw_protocol
|
|
|
|
def firewall_create_rule(self, name, port, protocol, allow=True):
|
|
if not allow:
|
|
raise NotImplementedError()
|
|
|
|
fw_port = client.Dispatch("HNetCfg.FWOpenPort")
|
|
fw_port.Name = name
|
|
fw_port.Protocol = self._get_fw_protocol(protocol)
|
|
fw_port.Port = port
|
|
fw_port.Scope = self._FW_SCOPE_ALL
|
|
fw_port.Enabled = True
|
|
|
|
fw_mgr = client.Dispatch("HNetCfg.FwMgr")
|
|
fw_profile = fw_mgr.LocalPolicy.CurrentProfile
|
|
fw_profile = fw_profile.GloballyOpenPorts.Add(fw_port)
|
|
|
|
def firewall_remove_rule(self, name, port, protocol, allow=True):
|
|
if not allow:
|
|
raise NotImplementedError()
|
|
|
|
fw_mgr = client.Dispatch("HNetCfg.FwMgr")
|
|
fw_profile = fw_mgr.LocalPolicy.CurrentProfile
|
|
|
|
fw_protocol = self._get_fw_protocol(protocol)
|
|
fw_profile = fw_profile.GloballyOpenPorts.Remove(port, fw_protocol)
|
|
|
|
def is_wow64(self):
|
|
return win32process.IsWow64Process()
|
|
|
|
def get_system32_dir(self):
|
|
return os.path.expandvars('%windir%\\system32')
|
|
|
|
def get_sysnative_dir(self):
|
|
return os.path.expandvars('%windir%\\sysnative')
|
|
|
|
def check_sysnative_dir_exists(self):
|
|
sysnative_dir_exists = os.path.isdir(self.get_sysnative_dir())
|
|
if not sysnative_dir_exists and self.is_wow64():
|
|
LOG.warning('Unable to validate sysnative folder presence. '
|
|
'If Target OS is Server 2003 x64, please ensure '
|
|
'you have KB942589 installed')
|
|
return sysnative_dir_exists
|
|
|
|
def _get_system_dir(self, sysnative=True):
|
|
if sysnative and self.check_sysnative_dir_exists():
|
|
return self.get_sysnative_dir()
|
|
else:
|
|
return self.get_system32_dir()
|
|
|
|
def execute_powershell_script(self, script_path, sysnative=True):
|
|
base_dir = self._get_system_dir(sysnative)
|
|
powershell_path = os.path.join(base_dir,
|
|
'WindowsPowerShell\\v1.0\\'
|
|
'powershell.exe')
|
|
|
|
args = [powershell_path, '-ExecutionPolicy', 'RemoteSigned',
|
|
'-NonInteractive', '-File', script_path]
|
|
|
|
return self.execute_process(args, shell=False)
|
|
|
|
def execute_system32_process(self, args, shell=True, decode_output=False,
|
|
sysnative=True):
|
|
base_dir = self._get_system_dir(sysnative)
|
|
process_path = os.path.join(base_dir, args[0])
|
|
return self.execute_process([process_path] + args[1:],
|
|
decode_output=decode_output, shell=shell)
|