From 648d39f9f7adb16aebacd6082e22f08dff9b2bad Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 28 May 2017 06:09:28 +0000 Subject: [PATCH] #1527: run the shadow server via LsaLogonUser + CreateProcessWithTokenW git-svn-id: https://xpra.org/svn/Xpra/trunk@15980 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/setup.py | 1 + src/xpra/platform/win32/common.py | 32 +- src/xpra/platform/win32/create_process_lib.py | 509 +++++++++++++ src/xpra/platform/win32/lsa_logon_lib.py | 678 ++++++++++++++++++ src/xpra/server/proxy/proxy_server.py | 60 +- 5 files changed, 1273 insertions(+), 7 deletions(-) create mode 100755 src/xpra/platform/win32/create_process_lib.py create mode 100755 src/xpra/platform/win32/lsa_logon_lib.py diff --git a/src/setup.py b/src/setup.py index d1c45a3691..a3aab79122 100755 --- a/src/setup.py +++ b/src/setup.py @@ -1359,6 +1359,7 @@ def add_service_exe(script, icon, base_name): add_console_exe("xpra/server/auth/sqlite_auth.py", "sqlite.ico", "SQLite_auth_tool") add_console_exe("xpra/server/auth/win32_auth.py", "authentication.ico", "System-Auth-Test") add_console_exe("win32/service/proxy.py", "xpra_txt.ico", "Xpra-Proxy") + add_console_exe("xpra/platform/win32/lsa_logon_lib.py", "xpra_txt.ico", "System-Logon-Test") if client_ENABLED: add_console_exe("xpra/codecs/loader.py", "encoding.ico", "Encoding_info") add_console_exe("xpra/platform/paths.py", "directory.ico", "Path_info") diff --git a/src/xpra/platform/win32/common.py b/src/xpra/platform/win32/common.py index c0dd6aa882..c76a2b9571 100644 --- a/src/xpra/platform/win32/common.py +++ b/src/xpra/platform/win32/common.py @@ -6,9 +6,9 @@ import ctypes -from ctypes import WinDLL, Structure, c_ulong, c_ushort, c_ubyte, c_int, c_long, c_void_p +from ctypes import WinDLL, Structure, c_ulong, c_ushort, c_ubyte, c_int, c_long, c_void_p, c_size_t from ctypes.wintypes import HWND, DWORD, WPARAM, LPARAM, HDC, HMONITOR, HMODULE, SHORT, ATOM, POINTER, RECT -from ctypes.wintypes import HANDLE, LPCWSTR, UINT, INT, WINFUNCTYPE, BOOL, HGDIOBJ, LONG, LPVOID, HBITMAP, LPCSTR, LPWSTR +from ctypes.wintypes import HANDLE, LPCWSTR, UINT, INT, WINFUNCTYPE, BOOL, HGDIOBJ, LONG, LPVOID, HBITMAP, LPCSTR, LPWSTR, HWINSTA LRESULT = c_long DEVMODE = c_void_p @@ -25,6 +25,13 @@ GetComputerNameW.argtypes = [LPWSTR, LPDWORD] GetCurrentProcess = kernel32.GetCurrentProcess GetCurrentProcess.restype = HANDLE +HeapAlloc = kernel32.HeapAlloc +HeapAlloc.restype = LPVOID +HeapAlloc.argtypes = [HANDLE, DWORD, c_size_t] +GetProcessHeap = kernel32.GetProcessHeap +GetProcessHeap.restype = HANDLE +GetProcessHeap.argtypes = [] + user32 = WinDLL("user32", use_last_error=True) RegisterClassExW = user32.RegisterClassExW @@ -94,6 +101,27 @@ GetDC.restype = HDC ReleaseDC = user32.ReleaseDC PostQuitMessage = user32.PostQuitMessage +OpenWindowStationW = user32.OpenWindowStationW +OpenWindowStationW.restype = HWINSTA +ACCESS_MASK = DWORD +OpenWindowStationW.argtypes = [LPWSTR, BOOL, ACCESS_MASK] +GetProcessWindowStation = user32.GetProcessWindowStation +GetProcessWindowStation.restype = HWINSTA +GetProcessWindowStation.argtypes = [] +SetProcessWindowStation = user32.SetProcessWindowStation +SetProcessWindowStation.restype = BOOL +SetProcessWindowStation.argtypes = [HWINSTA] +CloseWindowStation = user32.CloseWindowStation +CloseWindowStation.restype = BOOL +CloseWindowStation.argtypes = [HWINSTA] +HDESK = HANDLE +OpenDesktopW = user32.OpenDesktopW +OpenDesktopW.restype = HDESK +OpenDesktopW.argtypes = [LPWSTR, DWORD, BOOL, ACCESS_MASK] +CloseDesktop = user32.CloseDesktop +CloseDesktop.restype = BOOL +CloseDesktop.argtypes = [HDESK] + gdi32 = WinDLL("gdi32", use_last_error=True) CreateCompatibleDC = gdi32.CreateCompatibleDC diff --git a/src/xpra/platform/win32/create_process_lib.py b/src/xpra/platform/win32/create_process_lib.py new file mode 100755 index 0000000000..1da927301f --- /dev/null +++ b/src/xpra/platform/win32/create_process_lib.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python +# This file is part of Xpra. +# This code is released under the terms of the CC-BY-SA license: +# https://creativecommons.org/licenses/by-sa/2.0/legalcode +# This subprocess.Popen code was found here: +# https://stackoverflow.com/questions/29566330/ + +import os +import sys +import types +import ctypes +import subprocess + +from ctypes import wintypes + +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +advapi32 = ctypes.WinDLL('advapi32', use_last_error=True) + +ERROR_INVALID_HANDLE = 0x0006 +INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value +INVALID_DWORD_VALUE = wintypes.DWORD(-1).value + +DEBUG_PROCESS = 0x00000001 +DEBUG_ONLY_THIS_PROCESS = 0x00000002 +CREATE_SUSPENDED = 0x00000004 +DETACHED_PROCESS = 0x00000008 +CREATE_NEW_CONSOLE = 0x00000010 +CREATE_NEW_PROCESS_GROUP = 0x00000200 +CREATE_UNICODE_ENVIRONMENT = 0x00000400 +CREATE_SEPARATE_WOW_VDM = 0x00000800 +CREATE_SHARED_WOW_VDM = 0x00001000 +INHERIT_PARENT_AFFINITY = 0x00010000 +CREATE_PROTECTED_PROCESS = 0x00040000 +EXTENDED_STARTUPINFO_PRESENT = 0x00080000 +CREATE_BREAKAWAY_FROM_JOB = 0x01000000 +CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000 +CREATE_DEFAULT_ERROR_MODE = 0x04000000 +CREATE_NO_WINDOW = 0x08000000 + +STARTF_USESHOWWINDOW = 0x00000001 +STARTF_USESIZE = 0x00000002 +STARTF_USEPOSITION = 0x00000004 +STARTF_USECOUNTCHARS = 0x00000008 +STARTF_USEFILLATTRIBUTE = 0x00000010 +STARTF_RUNFULLSCREEN = 0x00000020 +STARTF_FORCEONFEEDBACK = 0x00000040 +STARTF_FORCEOFFFEEDBACK = 0x00000080 +STARTF_USESTDHANDLES = 0x00000100 +STARTF_USEHOTKEY = 0x00000200 +STARTF_TITLEISLINKNAME = 0x00000800 +STARTF_TITLEISAPPID = 0x00001000 +STARTF_PREVENTPINNING = 0x00002000 + +SW_HIDE = 0 +SW_SHOWNORMAL = 1 +SW_SHOWMINIMIZED = 2 +SW_SHOWMAXIMIZED = 3 +SW_SHOWNOACTIVATE = 4 +SW_SHOW = 5 +SW_MINIMIZE = 6 +SW_SHOWMINNOACTIVE = 7 +SW_SHOWNA = 8 +SW_RESTORE = 9 +SW_SHOWDEFAULT = 10 # ~STARTUPINFO +SW_FORCEMINIMIZE = 11 + +LOGON_WITH_PROFILE = 0x00000001 +LOGON_NETCREDENTIALS_ONLY = 0x00000002 + +STD_INPUT_HANDLE = wintypes.DWORD(-10).value +STD_OUTPUT_HANDLE = wintypes.DWORD(-11).value +STD_ERROR_HANDLE = wintypes.DWORD(-12).value + +class HANDLE(wintypes.HANDLE): + __slots__ = 'closed', + + def __int__(self): + return self.value or 0 + + def Detach(self): + if not getattr(self, 'closed', False): + self.closed = True + value = int(self) + self.value = None + return value + raise ValueError("already closed") + + def Close(self, CloseHandle=kernel32.CloseHandle): + if self and not getattr(self, 'closed', False): + CloseHandle(self.Detach()) + + __del__ = Close + + def __repr__(self): + return "%s(%d)" % (self.__class__.__name__, int(self)) + +class PROCESS_INFORMATION(ctypes.Structure): + """https://msdn.microsoft.com/en-us/library/ms684873""" + __slots__ = '_cached_hProcess', '_cached_hThread' + + _fields_ = (('_hProcess', HANDLE), + ('_hThread', HANDLE), + ('dwProcessId', wintypes.DWORD), + ('dwThreadId', wintypes.DWORD)) + + @property + def hProcess(self): + if not hasattr(self, '_cached_hProcess'): + self._cached_hProcess = self._hProcess + return self._cached_hProcess + + @property + def hThread(self): + if not hasattr(self, '_cached_hThread'): + self._cached_hThread = self._hThread + return self._cached_hThread + + def __del__(self): + try: + self.hProcess.Close() + finally: + self.hThread.Close() + +LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION) + +LPBYTE = ctypes.POINTER(wintypes.BYTE) + +class STARTUPINFO(ctypes.Structure): + """https://msdn.microsoft.com/en-us/library/ms686331""" + _fields_ = (('cb', wintypes.DWORD), + ('lpReserved', wintypes.LPWSTR), + ('lpDesktop', wintypes.LPWSTR), + ('lpTitle', wintypes.LPWSTR), + ('dwX', wintypes.DWORD), + ('dwY', wintypes.DWORD), + ('dwXSize', wintypes.DWORD), + ('dwYSize', wintypes.DWORD), + ('dwXCountChars', wintypes.DWORD), + ('dwYCountChars', wintypes.DWORD), + ('dwFillAttribute', wintypes.DWORD), + ('dwFlags', wintypes.DWORD), + ('wShowWindow', wintypes.WORD), + ('cbReserved2', wintypes.WORD), + ('lpReserved2', LPBYTE), + ('hStdInput', wintypes.HANDLE), + ('hStdOutput', wintypes.HANDLE), + ('hStdError', wintypes.HANDLE)) + + def __init__(self, **kwds): + self.cb = ctypes.sizeof(self) + super(STARTUPINFO, self).__init__(**kwds) + +class PROC_THREAD_ATTRIBUTE_LIST(ctypes.Structure): + pass + +PPROC_THREAD_ATTRIBUTE_LIST = ctypes.POINTER(PROC_THREAD_ATTRIBUTE_LIST) + +class STARTUPINFOEX(STARTUPINFO): + _fields_ = (('lpAttributeList', PPROC_THREAD_ATTRIBUTE_LIST),) + +LPSTARTUPINFO = ctypes.POINTER(STARTUPINFO) +LPSTARTUPINFOEX = ctypes.POINTER(STARTUPINFOEX) + +class SECURITY_ATTRIBUTES(ctypes.Structure): + _fields_ = (('nLength', wintypes.DWORD), + ('lpSecurityDescriptor', wintypes.LPVOID), + ('bInheritHandle', wintypes.BOOL)) + def __init__(self, **kwds): + self.nLength = ctypes.sizeof(self) + super(SECURITY_ATTRIBUTES, self).__init__(**kwds) + +LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES) + +class HANDLE_IHV(HANDLE): + pass + +class DWORD_IDV(wintypes.DWORD): + pass + +def _check_ihv(result, func, args): + if result.value == INVALID_HANDLE_VALUE: + raise ctypes.WinError(ctypes.get_last_error()) + return result.value + +def _check_idv(result, func, args): + if result.value == INVALID_DWORD_VALUE: + raise ctypes.WinError(ctypes.get_last_error()) + return result.value + +def _check_bool(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + +def WIN(func, restype, *argtypes): + func.restype = restype + func.argtypes = argtypes + if issubclass(restype, HANDLE_IHV): + func.errcheck = _check_ihv + elif issubclass(restype, DWORD_IDV): + func.errcheck = _check_idv + else: + func.errcheck = _check_bool + +# https://msdn.microsoft.com/en-us/library/ms724211 +WIN(kernel32.CloseHandle, wintypes.BOOL, + wintypes.HANDLE,) # _In_ HANDLE hObject + +# https://msdn.microsoft.com/en-us/library/ms685086 +WIN(kernel32.ResumeThread, DWORD_IDV, + wintypes.HANDLE,) # _In_ hThread + +# https://msdn.microsoft.com/en-us/library/ms682425 +WIN(kernel32.CreateProcessW, wintypes.BOOL, + wintypes.LPCWSTR, # _In_opt_ lpApplicationName + wintypes.LPWSTR, # _Inout_opt_ lpCommandLine + LPSECURITY_ATTRIBUTES, # _In_opt_ lpProcessAttributes + LPSECURITY_ATTRIBUTES, # _In_opt_ lpThreadAttributes + wintypes.BOOL, # _In_ bInheritHandles + wintypes.DWORD, # _In_ dwCreationFlags + wintypes.LPCWSTR, # _In_opt_ lpEnvironment + wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory + LPSTARTUPINFO, # _In_ lpStartupInfo + LPPROCESS_INFORMATION) # _Out_ lpProcessInformation + +# https://msdn.microsoft.com/en-us/library/ms682429 +WIN(advapi32.CreateProcessAsUserW, wintypes.BOOL, + wintypes.HANDLE, # _In_opt_ hToken + wintypes.LPCWSTR, # _In_opt_ lpApplicationName + wintypes.LPWSTR, # _Inout_opt_ lpCommandLine + LPSECURITY_ATTRIBUTES, # _In_opt_ lpProcessAttributes + LPSECURITY_ATTRIBUTES, # _In_opt_ lpThreadAttributes + wintypes.BOOL, # _In_ bInheritHandles + wintypes.DWORD, # _In_ dwCreationFlags + wintypes.LPCWSTR, # _In_opt_ lpEnvironment + wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory + LPSTARTUPINFO, # _In_ lpStartupInfo + LPPROCESS_INFORMATION) # _Out_ lpProcessInformation + +# https://msdn.microsoft.com/en-us/library/ms682434 +WIN(advapi32.CreateProcessWithTokenW, wintypes.BOOL, + wintypes.HANDLE, # _In_ hToken + wintypes.DWORD, # _In_ dwLogonFlags + wintypes.LPCWSTR, # _In_opt_ lpApplicationName + wintypes.LPWSTR, # _Inout_opt_ lpCommandLine + wintypes.DWORD, # _In_ dwCreationFlags + wintypes.LPCWSTR, # _In_opt_ lpEnvironment + wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory + LPSTARTUPINFO, # _In_ lpStartupInfo + LPPROCESS_INFORMATION) # _Out_ lpProcessInformation + +# https://msdn.microsoft.com/en-us/library/ms682431 +WIN(advapi32.CreateProcessWithLogonW, wintypes.BOOL, + wintypes.LPCWSTR, # _In_ lpUsername + wintypes.LPCWSTR, # _In_opt_ lpDomain + wintypes.LPCWSTR, # _In_ lpPassword + wintypes.DWORD, # _In_ dwLogonFlags + wintypes.LPCWSTR, # _In_opt_ lpApplicationName + wintypes.LPWSTR, # _Inout_opt_ lpCommandLine + wintypes.DWORD, # _In_ dwCreationFlags + wintypes.LPCWSTR, # _In_opt_ lpEnvironment + wintypes.LPCWSTR, # _In_opt_ lpCurrentDirectory + LPSTARTUPINFO, # _In_ lpStartupInfo + LPPROCESS_INFORMATION) # _Out_ lpProcessInformation + + +CREATION_TYPE_NORMAL = 0 +CREATION_TYPE_LOGON = 1 +CREATION_TYPE_TOKEN = 2 +CREATION_TYPE_USER = 3 + +class CREATIONINFO(object): + __slots__ = ('dwCreationType', + 'lpApplicationName', 'lpCommandLine', 'bUseShell', + 'lpProcessAttributes', 'lpThreadAttributes', 'bInheritHandles', + 'dwCreationFlags', 'lpEnvironment', 'lpCurrentDirectory', + 'hToken', 'lpUsername', 'lpDomain', 'lpPassword', 'dwLogonFlags') + + def __init__(self, dwCreationType=CREATION_TYPE_NORMAL, + lpApplicationName=None, lpCommandLine=None, bUseShell=False, + lpProcessAttributes=None, lpThreadAttributes=None, + bInheritHandles=False, dwCreationFlags=0, lpEnvironment=None, + lpCurrentDirectory=None, hToken=None, dwLogonFlags=0, + lpUsername=None, lpDomain=None, lpPassword=None): + self.dwCreationType = dwCreationType + self.lpApplicationName = lpApplicationName + self.lpCommandLine = lpCommandLine + self.bUseShell = bUseShell + self.lpProcessAttributes = lpProcessAttributes + self.lpThreadAttributes = lpThreadAttributes + self.bInheritHandles = bInheritHandles + self.dwCreationFlags = dwCreationFlags + self.lpEnvironment = lpEnvironment + self.lpCurrentDirectory = lpCurrentDirectory + self.hToken = hToken + self.lpUsername = lpUsername + self.lpDomain = lpDomain + self.lpPassword = lpPassword + self.dwLogonFlags = dwLogonFlags + +def create_environment(environ): + if environ is not None: + items = ['%s=%s' % (k, environ[k]) for k in sorted(environ)] + buf = '\x00'.join(items) + length = len(buf) + 2 if buf else 1 + return ctypes.create_unicode_buffer(buf, length) + +def create_process(commandline=None, creationinfo=None, startupinfo=None): + if creationinfo is None: + creationinfo = CREATIONINFO() + + if startupinfo is None: + startupinfo = STARTUPINFO() + elif isinstance(startupinfo, subprocess.STARTUPINFO): + startupinfo = STARTUPINFO(dwFlags=startupinfo.dwFlags, + hStdInput=startupinfo.hStdInput, + hStdOutput=startupinfo.hStdOutput, + hStdError=startupinfo.hStdError, + wShowWindow=startupinfo.wShowWindow) + + si, ci, pi = startupinfo, creationinfo, PROCESS_INFORMATION() + + if commandline is None: + commandline = ci.lpCommandLine + + if commandline is not None: + if ci.bUseShell: + si.dwFlags |= STARTF_USESHOWWINDOW + si.wShowWindow = SW_HIDE + comspec = os.environ.get("ComSpec", os.path.join( + os.environ["SystemRoot"], "System32", "cmd.exe")) + commandline = '"{}" /c "{}"'.format(comspec, commandline) + commandline = ctypes.create_unicode_buffer(commandline) + + dwCreationFlags = ci.dwCreationFlags | CREATE_UNICODE_ENVIRONMENT + lpEnvironment = create_environment(ci.lpEnvironment) + + if (dwCreationFlags & DETACHED_PROCESS and + ((dwCreationFlags & CREATE_NEW_CONSOLE) or + (ci.dwCreationType == CREATION_TYPE_LOGON) or + (ci.dwCreationType == CREATION_TYPE_TOKEN))): + raise RuntimeError('DETACHED_PROCESS is incompatible with ' + 'CREATE_NEW_CONSOLE, which is implied for ' + 'the logon and token creation types') + + if ci.dwCreationType == CREATION_TYPE_NORMAL: + + kernel32.CreateProcessW( + ci.lpApplicationName, commandline, + ci.lpProcessAttributes, ci.lpThreadAttributes, ci.bInheritHandles, + dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, + ctypes.byref(si), ctypes.byref(pi)) + + elif ci.dwCreationType == CREATION_TYPE_LOGON: + + advapi32.CreateProcessWithLogonW( + ci.lpUsername, ci.lpDomain, ci.lpPassword, ci.dwLogonFlags, + ci.lpApplicationName, commandline, + dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, + ctypes.byref(si), ctypes.byref(pi)) + + elif ci.dwCreationType == CREATION_TYPE_TOKEN: + + advapi32.CreateProcessWithTokenW( + ci.hToken, ci.dwLogonFlags, + ci.lpApplicationName, commandline, + dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, + ctypes.byref(si), ctypes.byref(pi)) + + elif ci.dwCreationType == CREATION_TYPE_USER: + + advapi32.CreateProcessAsUserW( + ci.hToken, + ci.lpApplicationName, commandline, + ci.lpProcessAttributes, ci.lpThreadAttributes, ci.bInheritHandles, + dwCreationFlags, lpEnvironment, ci.lpCurrentDirectory, + ctypes.byref(si), ctypes.byref(pi)) + + else: + raise ValueError('invalid process creation type') + + return pi + + +class Popen(subprocess.Popen): + def __init__(self, *args, **kwds): + ci = self._creationinfo = kwds.pop('creationinfo', CREATIONINFO()) + if kwds.pop('suspended', False): + ci.dwCreationFlags |= CREATE_SUSPENDED + self._child_started = False + super(Popen, self).__init__(*args, **kwds) + + if sys.version_info[0] == 2: + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, startupinfo, + creationflags, shell, to_close, p2cread, p2cwrite, + c2pread, c2pwrite, errread, errwrite): + """Execute program (MS Windows version)""" + commandline = (args if isinstance(args, types.StringTypes) else + subprocess.list2cmdline(args)) + self._common_execute_child(executable, commandline, shell, + close_fds, creationflags, env, cwd, + startupinfo, p2cread, c2pwrite, errwrite, to_close) + else: + + def _execute_child(self, args, executable, preexec_fn, close_fds, + pass_fds, cwd, env, startupinfo, creationflags, + shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, + errwrite, restore_signals, start_new_session): + """Execute program (MS Windows version)""" + assert not pass_fds, "pass_fds not supported on Windows." + commandline = (args if isinstance(args, str) else + subprocess.list2cmdline(args)) + self._common_execute_child(executable, commandline, shell, + close_fds, creationflags, env, cwd, + startupinfo, p2cread, c2pwrite, errwrite) + + def _common_execute_child(self, executable, commandline, shell, + close_fds, creationflags, env, cwd, + startupinfo, p2cread, c2pwrite, errwrite, + to_close=()): + + ci = self._creationinfo + if executable is not None: + ci.lpApplicationName = executable + if commandline: + ci.lpCommandLine = commandline + if shell: + ci.bUseShell = shell + if not close_fds: + ci.bInheritHandles = int(not close_fds) + if creationflags: + ci.dwCreationFlags |= creationflags + if env is not None: + ci.lpEnvironment = env + if cwd is not None: + ci.lpCurrentDirectory = cwd + + if startupinfo is None: + startupinfo = STARTUPINFO() + si = self._startupinfo = startupinfo + + default = None if sys.version_info[0] == 2 else -1 + if default not in (p2cread, c2pwrite, errwrite): + si.dwFlags |= STARTF_USESTDHANDLES + si.hStdInput = int( p2cread) + si.hStdOutput = int(c2pwrite) + si.hStdError = int(errwrite) + + try: + pi = create_process(creationinfo=ci, startupinfo=si) + finally: + if sys.version_info[0] == 2: + if p2cread is not None: + p2cread.Close() + to_close.remove(p2cread) + if c2pwrite is not None: + c2pwrite.Close() + to_close.remove(c2pwrite) + if errwrite is not None: + errwrite.Close() + to_close.remove(errwrite) + else: + if p2cread != -1: + p2cread.Close() + if c2pwrite != -1: + c2pwrite.Close() + if errwrite != -1: + errwrite.Close() + if hasattr(self, '_devnull'): + os.close(self._devnull) + + if not ci.dwCreationFlags & CREATE_SUSPENDED: + self._child_started = True + + # Retain the process handle, but close the thread handle + # if it's no longer needed. + self._processinfo = pi + self._handle = pi.hProcess.Detach() + self.pid = pi.dwProcessId + if self._child_started: + pi.hThread.Close() + + def start(self): + if self._child_started: + raise RuntimeError("processes can only be started once") + hThread = self._processinfo.hThread + prev_count = kernel32.ResumeThread(hThread) + if prev_count > 1: + for _ in range(1, prev_count): + if kernel32.ResumeThread(hThread) <= 1: + break + else: + raise RuntimeError('cannot start the main thread') + # The thread's previous suspend count was 0 or 1, + # so it should be running now. + self._child_started = True + hThread.Close() + + def __del__(self): + if not self._child_started: + try: + if hasattr(self, '_processinfo'): + self._processinfo.hThread.Close() + finally: + if hasattr(self, '_handle'): + self.terminate() + super(Popen, self).__del__() diff --git a/src/xpra/platform/win32/lsa_logon_lib.py b/src/xpra/platform/win32/lsa_logon_lib.py new file mode 100755 index 0000000000..cdaf83b152 --- /dev/null +++ b/src/xpra/platform/win32/lsa_logon_lib.py @@ -0,0 +1,678 @@ +#!/usr/bin/env python +# This file is part of Xpra. +# This code is released under the terms of the MIT license: +# https://opensource.org/licenses/MIT +# This LSA code was found here: +# https://stackoverflow.com/questions/43114209/ + +import os +import sys +import ctypes +import collections + +from ctypes import wintypes + +ntdll = ctypes.WinDLL('ntdll') +secur32 = ctypes.WinDLL('secur32') +kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) +advapi32 = ctypes.WinDLL('advapi32', use_last_error=True) + +MAX_COMPUTER_NAME_LENGTH = 15 + +SECURITY_LOGON_TYPE = wintypes.ULONG +Interactive = 2 +Network = 3 +Batch = 4 +Service = 5 + +LOGON_SUBMIT_TYPE = wintypes.ULONG +PROFILE_BUFFER_TYPE = wintypes.ULONG + +MsV1_0InteractiveLogon = 2 +MsV1_0Lm20Logon = 3 +MsV1_0NetworkLogon = 4 +MsV1_0WorkstationUnlockLogon = 7 +MsV1_0S4ULogon = 12 +MsV1_0NoElevationLogon = 82 + +KerbInteractiveLogon = 2 +KerbWorkstationUnlockLogon = 7 +KerbS4ULogon = 12 + +MSV1_0_S4U_LOGON_FLAG_CHECK_LOGONHOURS = 0x2 + +KERB_S4U_LOGON_FLAG_CHECK_LOGONHOURS = 0x2 +KERB_S4U_LOGON_FLAG_IDENTITY = 0x8 + +TOKEN_SOURCE_LENGTH = 8 + +NEGOTIATE_PACKAGE_NAME = b'Negotiate' +MICROSOFT_KERBEROS_NAME = b'Kerberos' +MSV1_0_PACKAGE_NAME = b'MICROSOFT_AUTHENTICATION_PACKAGE_V1_0' + +DELETE = 0x00010000 +READ_CONTROL = 0x00020000 +WRITE_DAC = 0x00040000 +WRITE_OWNER = 0x00080000 + +STANDARD_RIGHTS_REQUIRED = (DELETE | + READ_CONTROL | + WRITE_DAC | + WRITE_OWNER) + +TOKEN_ASSIGN_PRIMARY = 0x0001 +TOKEN_DUPLICATE = 0x0002 +TOKEN_IMPERSONATE = 0x0004 +TOKEN_QUERY = 0x0008 +TOKEN_QUERY_SOURCE = 0x0010 +TOKEN_ADJUST_PRIVILEGES = 0x0020 +TOKEN_ADJUST_GROUPS = 0x0040 +TOKEN_ADJUST_DEFAULT = 0x0080 +TOKEN_ADJUST_SESSIONID = 0x0100 + +TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | + TOKEN_ASSIGN_PRIMARY | + TOKEN_DUPLICATE | + TOKEN_IMPERSONATE | + TOKEN_QUERY | + TOKEN_QUERY_SOURCE | + TOKEN_ADJUST_PRIVILEGES | + TOKEN_ADJUST_GROUPS | + TOKEN_ADJUST_DEFAULT | + TOKEN_ADJUST_SESSIONID) + +DUPLICATE_CLOSE_SOURCE = 0x00000001 +DUPLICATE_SAME_ACCESS = 0x00000002 + +TOKEN_TYPE = wintypes.ULONG +TokenPrimary = 1 +TokenImpersonation = 2 + +SECURITY_IMPERSONATION_LEVEL = wintypes.ULONG +SecurityAnonymous = 0 +SecurityIdentification = 1 +SecurityImpersonation = 2 +SecurityDelegation = 3 + +class NTSTATUS(wintypes.LONG): + + def to_error(self): + return ntdll.RtlNtStatusToDosError(self) + + def __repr__(self): + name = self.__class__.__name__ + status = wintypes.ULONG.from_buffer(self) #@UndefinedVariable + return '%s(%#010x)' % (name, status.value) + +PNTSTATUS = ctypes.POINTER(NTSTATUS) + +class BOOL(wintypes.BOOL): + def __repr__(self): + name = self.__class__.__name__ + return '%s(%s)' % (name, bool(self)) + +class HANDLE(wintypes.HANDLE): + __slots__ = 'closed', + + def __int__(self): + return self.value or 0 + + def Detach(self): + if not getattr(self, 'closed', False): + self.closed = True + value = int(self) + self.value = None + return value + raise ValueError("already closed") + + def Close(self, CloseHandle=kernel32.CloseHandle): + if self and not getattr(self, 'closed', False): + CloseHandle(self.Detach()) + + __del__ = Close + + def __repr__(self): + return "%s(%d)" % (self.__class__.__name__, int(self)) + +class LARGE_INTEGER(wintypes.LARGE_INTEGER): + # https://msdn.microsoft.com/en-us/library/ff553204 + ntdll.RtlSecondsSince1970ToTime.restype = None + _unix_epoch = wintypes.LARGE_INTEGER() + ntdll.RtlSecondsSince1970ToTime(0, ctypes.byref(_unix_epoch)) + _unix_epoch = _unix_epoch.value + + def __int__(self): + return self.value + + def __repr__(self): + name = self.__class__.__name__ + return '%s(%d)' % (name, self.value) + + def as_time(self): + time100ns = self.value - self._unix_epoch + if time100ns >= 0: + return time100ns / 1e7 + raise ValueError('value predates the Unix epoch') + + @classmethod + def from_time(cls, t): + time100ns = int(t * 10**7) + return cls(time100ns + cls._unix_epoch) + +CHAR = ctypes.c_char +WCHAR = ctypes.c_wchar +PCHAR = ctypes.POINTER(CHAR) +PWCHAR = ctypes.POINTER(WCHAR) + +class STRING(ctypes.Structure): + _fields_ = (('Length', wintypes.USHORT), + ('MaximumLength', wintypes.USHORT), + ('Buffer', PCHAR)) + +PSTRING = ctypes.POINTER(STRING) + +class UNICODE_STRING(ctypes.Structure): + _fields_ = (('Length', wintypes.USHORT), + ('MaximumLength', wintypes.USHORT), + ('Buffer', PWCHAR)) + +PUNICODE_STRING = ctypes.POINTER(UNICODE_STRING) + +class LUID(ctypes.Structure): + _fields_ = (('LowPart', wintypes.DWORD), + ('HighPart', wintypes.LONG)) + + def __new__(cls, value=0): + return cls.from_buffer_copy(ctypes.c_ulonglong(value)) + + def __int__(self): + return ctypes.c_ulonglong.from_buffer(self).value #@UndefinedVariable + + def __repr__(self): + name = self.__class__.__name__ + return '%s(%#x)' % (name, int(self)) + +PLUID = ctypes.POINTER(LUID) +PSID = wintypes.LPVOID + +class SID_AND_ATTRIBUTES(ctypes.Structure): + _fields_ = (('Sid', PSID), + ('Attributes', wintypes.DWORD)) + +PSID_AND_ATTRIBUTES = ctypes.POINTER(SID_AND_ATTRIBUTES) + +class TOKEN_GROUPS(ctypes.Structure): + _fields_ = (('GroupCount', wintypes.DWORD), + ('Groups', SID_AND_ATTRIBUTES * 1)) + +PTOKEN_GROUPS = ctypes.POINTER(TOKEN_GROUPS) + +class TOKEN_SOURCE(ctypes.Structure): + _fields_ = (('SourceName', CHAR * TOKEN_SOURCE_LENGTH), + ('SourceIdentifier', LUID)) + def __init__(self, SourceName=None, SourceIdentifier=None): + if SourceName is not None: + if not isinstance(SourceName, bytes): + SourceName = SourceName.encode('mbcs') + self.SourceName = SourceName + if SourceIdentifier is None: + luid = self.SourceIdentifier + ntdll.NtAllocateLocallyUniqueId(ctypes.byref(luid)) + else: + self.SourceIdentifier = SourceIdentifier + +PTOKEN_SOURCE = ctypes.POINTER(TOKEN_SOURCE) + +py_source_context = TOKEN_SOURCE(b"PYTHON ") +py_origin_name = b"Python-%d" % os.getpid() +py_logon_process_name = b"PythonLogonProcess-%d" % os.getpid() + +SIZE_T = ctypes.c_size_t + +class QUOTA_LIMITS(ctypes.Structure): + _fields_ = (('PagedPoolLimit', SIZE_T), + ('NonPagedPoolLimit', SIZE_T), + ('MinimumWorkingSetSize', SIZE_T), + ('MaximumWorkingSetSize', SIZE_T), + ('PagefileLimit', SIZE_T), + ('TimeLimit', wintypes.LARGE_INTEGER)) + +PQUOTA_LIMITS = ctypes.POINTER(QUOTA_LIMITS) + +PULONG = ctypes.POINTER(wintypes.ULONG) +LSA_OPERATIONAL_MODE = wintypes.ULONG +PLSA_OPERATIONAL_MODE = PULONG +PHANDLE = ctypes.POINTER(wintypes.HANDLE) +PLPVOID = ctypes.POINTER(wintypes.LPVOID) +LPDWORD = ctypes.POINTER(wintypes.DWORD) + +class ContiguousUnicode(ctypes.Structure): + # _string_names_: sequence matched to underscore-prefixed fields + + def _get_unicode_string(self, name): + wchar_size = ctypes.sizeof(WCHAR) + s = getattr(self, '_%s' % name) + length = s.Length // wchar_size + buf = s.Buffer + if buf: + return buf[:length] + return None + + def _set_unicode_buffer(self, value): + cls = type(self) + wchar_size = ctypes.sizeof(WCHAR) + bufsize = (len(value) + 1) * wchar_size + ctypes.resize(self, ctypes.sizeof(cls) + bufsize) + addr = ctypes.addressof(self) + ctypes.sizeof(cls) + ctypes.memmove(addr, value, bufsize) + + def _set_unicode_string(self, name, value): + values = [] + for n in self._string_names_: + if n == name: + values.append(value or u'') + else: + values.append(getattr(self, n) or u'') + self._set_unicode_buffer(u'\x00'.join(values)) + + cls = type(self) + wchar_size = ctypes.sizeof(WCHAR) + addr = ctypes.addressof(self) + ctypes.sizeof(cls) + for n, v in zip(self._string_names_, values): + ptr = ctypes.cast(addr, PWCHAR) + ustr = getattr(self, '_%s' % n) + length = ustr.Length = len(v) * wchar_size + full_length = length + wchar_size + if ((n == name and value is None) or + (n != name and not (length or ustr.Buffer))): + ustr.Buffer = None + ustr.MaximumLength = 0 + else: + ustr.Buffer = ptr + ustr.MaximumLength = full_length + addr += full_length + + def __getattr__(self, name): + if name not in self._string_names_: + raise AttributeError + return self._get_unicode_string(name) + + def __setattr__(self, name, value): + if name in self._string_names_: + self._set_unicode_string(name, value) + else: + super(ContiguousUnicode, self).__setattr__(name, value) + + @classmethod + def from_address_copy(cls, address, size=None): + x = ctypes.Structure.__new__(cls) #@UndefinedVariable + if size is not None: + ctypes.resize(x, size) + ctypes.memmove(ctypes.byref(x), address, ctypes.sizeof(x)) + delta = ctypes.addressof(x) - address + for n in cls._string_names_: + ustr = getattr(x, '_%s' % n) + addr = ctypes.c_void_p.from_buffer(ustr.Buffer) #@UndefinedVariable + if addr: + addr.value += delta + return x + +class AuthInfo(ContiguousUnicode): + # _message_type_: from a logon-submit-type enumeration + def __init__(self): + self.MessageType = self._message_type_ + +class MSV1_0_INTERACTIVE_LOGON(AuthInfo): + _message_type_ = MsV1_0InteractiveLogon + _string_names_ = 'LogonDomainName', 'UserName', 'Password' + + _fields_ = (('MessageType', LOGON_SUBMIT_TYPE), + ('_LogonDomainName', UNICODE_STRING), + ('_UserName', UNICODE_STRING), + ('_Password', UNICODE_STRING)) + + def __init__(self, UserName=None, Password=None, LogonDomainName=None): + super(MSV1_0_INTERACTIVE_LOGON, self).__init__() + if LogonDomainName is not None: + self.LogonDomainName = LogonDomainName + if UserName is not None: + self.UserName = UserName + if Password is not None: + self.Password = Password + +class S4ULogon(AuthInfo): + _string_names_ = 'UserPrincipalName', 'DomainName' + + _fields_ = (('MessageType', LOGON_SUBMIT_TYPE), + ('Flags', wintypes.ULONG), + ('_UserPrincipalName', UNICODE_STRING), + ('_DomainName', UNICODE_STRING)) + + def __init__(self, UserPrincipalName=None, DomainName=None, Flags=0): + super(S4ULogon, self).__init__() + self.Flags = Flags + if UserPrincipalName is not None: + self.UserPrincipalName = UserPrincipalName + if DomainName is not None: + self.DomainName = DomainName + +class MSV1_0_S4U_LOGON(S4ULogon): + _message_type_ = MsV1_0S4ULogon + +class KERB_S4U_LOGON(S4ULogon): + _message_type_ = KerbS4ULogon + +PMSV1_0_S4U_LOGON = ctypes.POINTER(MSV1_0_S4U_LOGON) +PKERB_S4U_LOGON = ctypes.POINTER(KERB_S4U_LOGON) + +class ProfileBuffer(ContiguousUnicode): + # _message_type_ + def __init__(self): + self.MessageType = self._message_type_ + +class MSV1_0_INTERACTIVE_PROFILE(ProfileBuffer): + _message_type_ = MsV1_0InteractiveLogon + _string_names_ = ('LogonScript', 'HomeDirectory', 'FullName', + 'ProfilePath', 'HomeDirectoryDrive', 'LogonServer') + _fields_ = (('MessageType', PROFILE_BUFFER_TYPE), + ('LogonCount', wintypes.USHORT), + ('BadPasswordCount', wintypes.USHORT), + ('LogonTime', LARGE_INTEGER), + ('LogoffTime', LARGE_INTEGER), + ('KickOffTime', LARGE_INTEGER), + ('PasswordLastSet', LARGE_INTEGER), + ('PasswordCanChange', LARGE_INTEGER), + ('PasswordMustChange', LARGE_INTEGER), + ('_LogonScript', UNICODE_STRING), + ('_HomeDirectory', UNICODE_STRING), + ('_FullName', UNICODE_STRING), + ('_ProfilePath', UNICODE_STRING), + ('_HomeDirectoryDrive', UNICODE_STRING), + ('_LogonServer', UNICODE_STRING), + ('UserFlags', wintypes.ULONG)) + +class SECURITY_ATTRIBUTES(ctypes.Structure): + _fields_ = (('nLength', wintypes.DWORD), + ('lpSecurityDescriptor', wintypes.LPVOID), + ('bInheritHandle', wintypes.BOOL)) + def __init__(self, **kwds): + self.nLength = ctypes.sizeof(self) + super(SECURITY_ATTRIBUTES, self).__init__(**kwds) + +LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES) + +def _check_status(result, func, args): + if result.value < 0: + raise ctypes.WinError(result.to_error()) + return args + +def _check_bool(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + +def WIN(func, restype, *argtypes): + func.restype = restype + func.argtypes = argtypes + if issubclass(restype, NTSTATUS): + func.errcheck = _check_status + elif issubclass(restype, BOOL): + func.errcheck = _check_bool + +# https://msdn.microsoft.com/en-us/library/ms683179 +WIN(kernel32.GetCurrentProcess, wintypes.HANDLE) + +# https://msdn.microsoft.com/en-us/library/ms724251 +WIN(kernel32.DuplicateHandle, BOOL, + wintypes.HANDLE, # _In_ hSourceProcessHandle + wintypes.HANDLE, # _In_ hSourceHandle + wintypes.HANDLE, # _In_ hTargetProcessHandle + PHANDLE, # _Out_ lpTargetHandle + wintypes.DWORD, # _In_ dwDesiredAccess + wintypes.BOOL, # _In_ bInheritHandle + wintypes.DWORD) # _In_ dwOptions + +# https://msdn.microsoft.com/en-us/library/ms724295 +WIN(kernel32.GetComputerNameW, BOOL, + wintypes.LPWSTR, # _Out_ lpBuffer + LPDWORD) # _Inout_ lpnSize + +# https://msdn.microsoft.com/en-us/library/aa379295 +WIN(advapi32.OpenProcessToken, BOOL, + wintypes.HANDLE, # _In_ ProcessHandle + wintypes.DWORD, # _In_ DesiredAccess + PHANDLE) # _Out_ TokenHandle + +# https://msdn.microsoft.com/en-us/library/aa446617 +WIN(advapi32.DuplicateTokenEx, BOOL, + wintypes.HANDLE, # _In_ hExistingToken + wintypes.DWORD, # _In_ dwDesiredAccess + LPSECURITY_ATTRIBUTES, # _In_opt_ lpTokenAttributes + SECURITY_IMPERSONATION_LEVEL, # _In_ ImpersonationLevel + TOKEN_TYPE, # _In_ TokenType + PHANDLE) # _Out_ phNewToken + +# https://msdn.microsoft.com/en-us/library/ff566415 +WIN(ntdll.NtAllocateLocallyUniqueId, NTSTATUS, + PLUID) # _Out_ LUID + +# https://msdn.microsoft.com/en-us/library/aa378279 +WIN(secur32.LsaFreeReturnBuffer, NTSTATUS, + wintypes.LPVOID,) # _In_ Buffer + +# https://msdn.microsoft.com/en-us/library/aa378265 +WIN(secur32.LsaConnectUntrusted, NTSTATUS, + PHANDLE,) # _Out_ LsaHandle + +#https://msdn.microsoft.com/en-us/library/aa378318 +WIN(secur32.LsaRegisterLogonProcess, NTSTATUS, + PSTRING, # _In_ LogonProcessName + PHANDLE, # _Out_ LsaHandle + PLSA_OPERATIONAL_MODE) # _Out_ SecurityMode + +# https://msdn.microsoft.com/en-us/library/aa378269 +WIN(secur32.LsaDeregisterLogonProcess, NTSTATUS, + wintypes.HANDLE) # _In_ LsaHandle + +# https://msdn.microsoft.com/en-us/library/aa378297 +WIN(secur32.LsaLookupAuthenticationPackage, NTSTATUS, + wintypes.HANDLE, # _In_ LsaHandle + PSTRING, # _In_ PackageName + PULONG) # _Out_ AuthenticationPackage + +# https://msdn.microsoft.com/en-us/library/aa378292 +WIN(secur32.LsaLogonUser, NTSTATUS, + wintypes.HANDLE, # _In_ LsaHandle + PSTRING, # _In_ OriginName + SECURITY_LOGON_TYPE, # _In_ LogonType + wintypes.ULONG, # _In_ AuthenticationPackage + wintypes.LPVOID, # _In_ AuthenticationInformation + wintypes.ULONG, # _In_ AuthenticationInformationLength + PTOKEN_GROUPS, # _In_opt_ LocalGroups + PTOKEN_SOURCE, # _In_ SourceContext + PLPVOID, # _Out_ ProfileBuffer + PULONG, # _Out_ ProfileBufferLength + PLUID, # _Out_ LogonId + PHANDLE, # _Out_ Token + PQUOTA_LIMITS, # _Out_ Quotas + PNTSTATUS) # _Out_ SubStatus + + +def duplicate_token(source_token=None, access=TOKEN_ALL_ACCESS, + impersonation_level=SecurityImpersonation, + token_type=TokenPrimary, attributes=None): + close_source = False + if source_token is None: + close_source = True + source_token = HANDLE() + advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), + TOKEN_ALL_ACCESS, ctypes.byref(source_token)) + token = HANDLE() + try: + advapi32.DuplicateTokenEx(source_token, access, attributes, + impersonation_level, token_type, ctypes.byref(token)) + finally: + if close_source: + source_token.Close() + return token + +def lsa_connect_untrusted(): + handle = wintypes.HANDLE() + secur32.LsaConnectUntrusted(ctypes.byref(handle)) + return handle.value + +def lsa_register_logon_process(logon_process_name): + if not isinstance(logon_process_name, bytes): + logon_process_name = logon_process_name.encode('mbcs') + logon_process_name = logon_process_name[:127] + buf = ctypes.create_string_buffer(logon_process_name, 128) + name = STRING(len(logon_process_name), len(buf), buf) + handle = wintypes.HANDLE() + mode = LSA_OPERATIONAL_MODE() + secur32.LsaRegisterLogonProcess(ctypes.byref(name), + ctypes.byref(handle), ctypes.byref(mode)) + return handle.value + +def lsa_lookup_authentication_package(lsa_handle, package_name): + if not isinstance(package_name, bytes): + package_name = package_name.encode('mbcs') + package_name = package_name[:127] + buf = ctypes.create_string_buffer(package_name) + name = STRING(len(package_name), len(buf), buf) + package = wintypes.ULONG() + secur32.LsaLookupAuthenticationPackage(lsa_handle, ctypes.byref(name), + ctypes.byref(package)) + return package.value + + +# Low-level LSA logon + +LOGONINFO = collections.namedtuple('LOGONINFO', + ('Token', 'LogonId', 'Profile', 'Quotas') + ) + +def lsa_logon_user(auth_info, local_groups=None, origin_name=py_origin_name, + source_context=None, auth_package=None, logon_type=None, + lsa_handle=None): + from xpra.log import Logger + log = Logger("win32") + log("lsa_logon_user%s", (auth_info, local_groups, origin_name, + source_context, auth_package, logon_type, + lsa_handle)) + if local_groups is None: + plocal_groups = PTOKEN_GROUPS() + else: + plocal_groups = ctypes.byref(local_groups) + if source_context is None: + source_context = py_source_context + if not isinstance(origin_name, bytes): + origin_name = origin_name.encode('mbcs') + buf = ctypes.create_string_buffer(origin_name) + origin_name = STRING(len(origin_name), len(buf), buf) + if auth_package is None: + if isinstance(auth_info, MSV1_0_S4U_LOGON): + auth_package = NEGOTIATE_PACKAGE_NAME + elif isinstance(auth_info, KERB_S4U_LOGON): + auth_package = MICROSOFT_KERBEROS_NAME + else: + auth_package = MSV1_0_PACKAGE_NAME + if logon_type is None: + if isinstance(auth_info, S4ULogon): + logon_type = Batch + else: + logon_type = Interactive + profile_buffer = wintypes.LPVOID() + profile_buffer_length = wintypes.ULONG() + profile = None + logonid = LUID() + htoken = HANDLE() + quotas = QUOTA_LIMITS() + substatus = NTSTATUS() + deregister = False + if lsa_handle is None: + lsa_handle = lsa_connect_untrusted() + deregister = True + try: + if isinstance(auth_package, (str, bytes)): + auth_package = lsa_lookup_authentication_package(lsa_handle, + auth_package) + try: + args = (lsa_handle, ctypes.byref(origin_name), + logon_type, auth_package, ctypes.byref(auth_info), + ctypes.sizeof(auth_info), plocal_groups, + ctypes.byref(source_context), ctypes.byref(profile_buffer), + ctypes.byref(profile_buffer_length), ctypes.byref(logonid), + ctypes.byref(htoken), ctypes.byref(quotas), + ctypes.byref(substatus)) + log("LsaLogonUser%s", args) + secur32.LsaLogonUser(*args) + except WindowsError: #@UndefinedVariable + if substatus.value: + raise ctypes.WinError(substatus.to_error()) + raise + finally: + if profile_buffer: + address = profile_buffer.value + buftype = PROFILE_BUFFER_TYPE.from_address(address).value + if buftype == MsV1_0InteractiveLogon: + profile = MSV1_0_INTERACTIVE_PROFILE.from_address_copy( + address, profile_buffer_length.value) + secur32.LsaFreeReturnBuffer(address) + finally: + if deregister: + secur32.LsaDeregisterLogonProcess(lsa_handle) + return LOGONINFO(htoken, logonid, profile, quotas) + + +# High-level LSA logons + +def logon_msv1(name, password, domain=None, local_groups=None, + origin_name=py_origin_name, source_context=None): + return lsa_logon_user(MSV1_0_INTERACTIVE_LOGON(name, password, domain), + local_groups, origin_name, source_context) + +def logon_msv1_s4u(name, local_groups=None, origin_name=py_origin_name, + source_context=None): + domain = ctypes.create_unicode_buffer(MAX_COMPUTER_NAME_LENGTH + 1) + length = wintypes.DWORD(len(domain)) + kernel32.GetComputerNameW(domain, ctypes.byref(length)) + return lsa_logon_user(MSV1_0_S4U_LOGON(name, domain.value), + local_groups, origin_name, source_context) + +def logon_kerb_s4u(name, realm=None, local_groups=None, + origin_name=py_origin_name, + source_context=None, + logon_process_name=py_logon_process_name): + lsa_handle = lsa_register_logon_process(logon_process_name) + try: + return lsa_logon_user(KERB_S4U_LOGON(name, realm), + local_groups, origin_name, source_context, + lsa_handle=lsa_handle) + finally: + secur32.LsaDeregisterLogonProcess(lsa_handle) + + +def main(): + from xpra.platform import program_context + from xpra.log import enable_color, Logger, enable_debug_for + log = Logger("win32") + with program_context("LSA-Logon-Test", "LSA Logon Test"): + enable_color() + for x in ("-v", "--verbose"): + if x in list(sys.argv): + enable_debug_for("win32") + sys.argv.remove(x) + if len(sys.argv)!=2: + log.warn("invalid number of arguments") + log.warn("usage: %s [--verbose] username", sys.argv[0]) + return 1 + username = sys.argv[1] + try: + logon_msv1_s4u(username) + return 0 + except Exception as e: + log.error("Logon failed: %s", e) + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/xpra/server/proxy/proxy_server.py b/src/xpra/server/proxy/proxy_server.py index a4e0493e6d..1f8c2178af 100644 --- a/src/xpra/server/proxy/proxy_server.py +++ b/src/xpra/server/proxy/proxy_server.py @@ -12,7 +12,7 @@ glib.threads_init() gobject = import_gobject() gobject.threads_init() -from multiprocessing import Queue as MQueue, freeze_support +from multiprocessing import Queue as MQueue, freeze_support #@UnresolvedImport freeze_support() from xpra.log import Logger @@ -21,7 +21,7 @@ from xpra.util import LOGIN_TIMEOUT, AUTHENTICATION_ERROR, SESSION_NOT_FOUND, SERVER_ERROR, repr_ellipsized, print_nested_dict, csv, typedict -from xpra.os_util import get_username_for_uid, get_groups +from xpra.os_util import get_username_for_uid, get_groups, WIN32 from xpra.server.proxy.proxy_instance_process import ProxyInstanceProcess from xpra.server.server_core import ServerCore from xpra.server.control_command import ArgsControlCommand, ControlError @@ -210,6 +210,9 @@ def disconnect(reason, *extras): username = get_username_for_uid(uid) groups = get_groups(username) log("username(%i)=%s, groups=%s", username, groups) + else: + #the auth module recorded the username we authenticate against + username = getattr(client_proto.authenticator, "username", "") #ensure we don't loop back to the proxy: proxy_virtual_display = os.environ.get("DISPLAY") if proxy_virtual_display in displays: @@ -226,7 +229,8 @@ def disconnect(reason, *extras): disconnect(SESSION_NOT_FOUND, "no displays found") return try: - display = self.start_new_session(uid, gid, sns, displays) + display = self.start_new_session(username, uid, gid, sns, displays) + log("start_new_session%s=%s", (username, uid, gid, sns, displays), display) except Exception as e: log("start_server_subprocess failed", exc_info=True) log.error("Error: failed to start server subprocess:") @@ -326,9 +330,11 @@ def do_start_proxy(): message_queue.put("socket-handover-complete") start_thread(do_start_proxy, "start_proxy(%s)" % client_conn) - def start_new_session(self, uid, gid, new_session_dict={}, displays=()): - log("start_new_session%s", (uid, gid, new_session_dict)) + def start_new_session(self, username, uid, gid, new_session_dict={}, displays=()): + log("start_new_session%s", (username, uid, gid, new_session_dict, displays)) sns = typedict(new_session_dict) + if WIN32: + return self.start_win32_shadow(username, new_session_dict) mode = sns.get("mode", "start") assert mode in ("start", "start-desktop", "shadow"), "invalid start-new-session mode '%s'" % mode display = sns.get("display") @@ -359,6 +365,50 @@ def start_new_session(self, uid, gid, new_session_dict={}, displays=()): log("start_new_session(..)=%s", display) return display + def start_win32_shadow(self, username, new_session_dict): + log("start_win32_shadow%s", (username, new_session_dict)) + from xpra.platform.paths import get_app_dir + from xpra.platform.win32.lsa_logon_lib import logon_msv1_s4u + logon_info = logon_msv1_s4u(username) + log("logon_msv1_s4u(%s)=%s", username, logon_info) + #hwinstaold = set_window_station("winsta0") + def exec_command(command): + log("exec_command(%s)", command) + from xpra.platform.win32.create_process_lib import Popen, CREATIONINFO, CREATION_TYPE_TOKEN, LOGON_WITH_PROFILE, CREATE_NEW_PROCESS_GROUP, STARTUPINFO + creation_info = CREATIONINFO() + creation_info.dwCreationType = CREATION_TYPE_TOKEN + creation_info.dwLogonFlags = LOGON_WITH_PROFILE + creation_info.dwCreationFlags = CREATE_NEW_PROCESS_GROUP + creation_info.hToken = logon_info.Token + log("creation_info=%s", creation_info) + startupinfo = STARTUPINFO() + startupinfo.lpDesktop = "WinSta0\\Default" + startupinfo.lpTitle = "Xpra-Shadow" + cwd = get_app_dir() + from subprocess import PIPE + env = os.environ.copy() + log("env=%s", env) + proc = Popen(command, stdout=PIPE, stderr=PIPE, cwd=cwd, env=env, startupinfo=startupinfo, creationinfo=creation_info) + log("Popen(%s)=%s", command, proc) + log("poll()=%s", proc.poll()) + try: + log("stdout=%s", proc.stdout.read()) + log("stderr=%s", proc.stderr.read()) + except: + pass + if proc.poll() is not None: + return None + self.child_reaper.add_process(proc, "server-%s" % username, "xpra shadow", True, True) + return proc + #whoami = os.path.join(get_app_dir(), "whoami.exe") + #exec_command([whoami]) + port = 10000 + xpra_command = os.path.join(get_app_dir(), "xpra.exe") + command = [xpra_command, "shadow", "--bind-tcp=0.0.0.0:%i" % port, "-d", "win32"] + if not exec_command(command): + return None + exec_command(["C:\\Windows\notepad.exe"]) + return "tcp/localhost:%i" % port def reap(self, *args): log("reap%s", args)