1""" 2Run processes as a different user in Windows 3""" 4 5# Import Python Libraries 6import ctypes 7import logging 8import os 9import time 10 11from salt.exceptions import CommandExecutionError 12 13try: 14 import psutil 15 16 HAS_PSUTIL = True 17except ImportError: 18 HAS_PSUTIL = False 19 20try: 21 import win32api 22 import win32con 23 import win32process 24 import win32security 25 import win32pipe 26 import win32event 27 import win32profile 28 import msvcrt 29 import salt.platform.win 30 import pywintypes 31 32 HAS_WIN32 = True 33except ImportError: 34 HAS_WIN32 = False 35 36 37log = logging.getLogger(__name__) 38 39 40# Although utils are often directly imported, it is also possible to use the 41# loader. 42def __virtual__(): 43 """ 44 Only load if Win32 Libraries are installed 45 """ 46 if not HAS_WIN32 or not HAS_PSUTIL: 47 return False, "This utility requires pywin32 and psutil" 48 49 return "win_runas" 50 51 52def split_username(username): 53 # TODO: Is there a windows api for this? 54 domain = "." 55 if "@" in username: 56 username, domain = username.split("@") 57 if "\\" in username: 58 domain, username = username.split("\\") 59 return username, domain 60 61 62def create_env(user_token, inherit, timeout=1): 63 """ 64 CreateEnvironmentBlock might fail when we close a login session and then 65 try to re-open one very quickly. Run the method multiple times to work 66 around the async nature of logoffs. 67 """ 68 start = time.time() 69 env = None 70 exc = None 71 while True: 72 try: 73 env = win32profile.CreateEnvironmentBlock(user_token, False) 74 except pywintypes.error as exc: 75 pass 76 else: 77 break 78 if time.time() - start > timeout: 79 break 80 if env is not None: 81 return env 82 raise exc 83 84 85def runas(cmdLine, username, password=None, cwd=None): 86 """ 87 Run a command as another user. If the process is running as an admin or 88 system account this method does not require a password. Other non 89 privileged accounts need to provide a password for the user to runas. 90 Commands are run in with the highest level privileges possible for the 91 account provided. 92 """ 93 # Validate the domain and sid exist for the username 94 username, domain = split_username(username) 95 try: 96 _, domain, _ = win32security.LookupAccountName(domain, username) 97 except pywintypes.error as exc: 98 message = win32api.FormatMessage(exc.winerror).rstrip("\n") 99 raise CommandExecutionError(message) 100 101 # Elevate the token from the current process 102 access = win32security.TOKEN_QUERY | win32security.TOKEN_ADJUST_PRIVILEGES 103 th = win32security.OpenProcessToken(win32api.GetCurrentProcess(), access) 104 salt.platform.win.elevate_token(th) 105 106 # Try to impersonate the SYSTEM user. This process needs to be running as a 107 # user who as been granted the SeImpersonatePrivilege, Administrator 108 # accounts have this permission by default. 109 try: 110 impersonation_token = salt.platform.win.impersonate_sid( 111 salt.platform.win.SYSTEM_SID, 112 session_id=0, 113 privs=["SeTcbPrivilege"], 114 ) 115 except OSError: 116 log.debug("Unable to impersonate SYSTEM user") 117 impersonation_token = None 118 win32api.CloseHandle(th) 119 120 # Impersonation of the SYSTEM user failed. Fallback to an un-privileged 121 # runas. 122 if not impersonation_token: 123 log.debug("No impersonation token, using unprivileged runas") 124 return runas_unpriv(cmdLine, username, password, cwd) 125 126 if domain == "NT AUTHORITY": 127 # Logon as a system level account, SYSTEM, LOCAL SERVICE, or NETWORK 128 # SERVICE. 129 user_token = win32security.LogonUser( 130 username, 131 domain, 132 "", 133 win32con.LOGON32_LOGON_SERVICE, 134 win32con.LOGON32_PROVIDER_DEFAULT, 135 ) 136 elif password: 137 # Login with a password. 138 user_token = win32security.LogonUser( 139 username, 140 domain, 141 password, 142 win32con.LOGON32_LOGON_INTERACTIVE, 143 win32con.LOGON32_PROVIDER_DEFAULT, 144 ) 145 else: 146 # Login without a password. This always returns an elevated token. 147 user_token = salt.platform.win.logon_msv1_s4u(username).Token 148 149 # Get a linked user token to elevate if needed 150 elevation_type = win32security.GetTokenInformation( 151 user_token, win32security.TokenElevationType 152 ) 153 if elevation_type > 1: 154 user_token = win32security.GetTokenInformation( 155 user_token, win32security.TokenLinkedToken 156 ) 157 158 # Elevate the user token 159 salt.platform.win.elevate_token(user_token) 160 161 # Make sure the user's token has access to a windows station and desktop 162 salt.platform.win.grant_winsta_and_desktop(user_token) 163 164 # Create pipes for standard in, out and error streams 165 security_attributes = win32security.SECURITY_ATTRIBUTES() 166 security_attributes.bInheritHandle = 1 167 168 stdin_read, stdin_write = win32pipe.CreatePipe(security_attributes, 0) 169 stdin_read = salt.platform.win.make_inheritable(stdin_read) 170 171 stdout_read, stdout_write = win32pipe.CreatePipe(security_attributes, 0) 172 stdout_write = salt.platform.win.make_inheritable(stdout_write) 173 174 stderr_read, stderr_write = win32pipe.CreatePipe(security_attributes, 0) 175 stderr_write = salt.platform.win.make_inheritable(stderr_write) 176 177 # Run the process without showing a window. 178 creationflags = ( 179 win32process.CREATE_NO_WINDOW 180 | win32process.CREATE_NEW_CONSOLE 181 | win32process.CREATE_SUSPENDED 182 ) 183 184 startup_info = salt.platform.win.STARTUPINFO( 185 dwFlags=win32con.STARTF_USESTDHANDLES, 186 hStdInput=stdin_read.handle, 187 hStdOutput=stdout_write.handle, 188 hStdError=stderr_write.handle, 189 ) 190 191 # Create the environment for the user 192 env = create_env(user_token, False) 193 194 hProcess = None 195 try: 196 # Start the process in a suspended state. 197 process_info = salt.platform.win.CreateProcessWithTokenW( 198 int(user_token), 199 logonflags=1, 200 applicationname=None, 201 commandline=cmdLine, 202 currentdirectory=cwd, 203 creationflags=creationflags, 204 startupinfo=startup_info, 205 environment=env, 206 ) 207 208 hProcess = process_info.hProcess 209 hThread = process_info.hThread 210 dwProcessId = process_info.dwProcessId 211 dwThreadId = process_info.dwThreadId 212 213 # We don't use these so let's close the handle 214 salt.platform.win.kernel32.CloseHandle(stdin_write.handle) 215 salt.platform.win.kernel32.CloseHandle(stdout_write.handle) 216 salt.platform.win.kernel32.CloseHandle(stderr_write.handle) 217 218 ret = {"pid": dwProcessId} 219 # Resume the process 220 psutil.Process(dwProcessId).resume() 221 222 # Wait for the process to exit and get its return code. 223 if ( 224 win32event.WaitForSingleObject(hProcess, win32event.INFINITE) 225 == win32con.WAIT_OBJECT_0 226 ): 227 exitcode = win32process.GetExitCodeProcess(hProcess) 228 ret["retcode"] = exitcode 229 230 # Read standard out 231 fd_out = msvcrt.open_osfhandle(stdout_read.handle, os.O_RDONLY | os.O_TEXT) 232 with os.fdopen(fd_out, "r") as f_out: 233 stdout = f_out.read() 234 ret["stdout"] = stdout 235 236 # Read standard error 237 fd_err = msvcrt.open_osfhandle(stderr_read.handle, os.O_RDONLY | os.O_TEXT) 238 with os.fdopen(fd_err, "r") as f_err: 239 stderr = f_err.read() 240 ret["stderr"] = stderr 241 finally: 242 if hProcess is not None: 243 salt.platform.win.kernel32.CloseHandle(hProcess) 244 win32api.CloseHandle(th) 245 win32api.CloseHandle(user_token) 246 if impersonation_token: 247 win32security.RevertToSelf() 248 win32api.CloseHandle(impersonation_token) 249 250 return ret 251 252 253def runas_unpriv(cmd, username, password, cwd=None): 254 """ 255 Runas that works for non-privileged users 256 """ 257 # Validate the domain and sid exist for the username 258 username, domain = split_username(username) 259 try: 260 _, domain, _ = win32security.LookupAccountName(domain, username) 261 except pywintypes.error as exc: 262 message = win32api.FormatMessage(exc.winerror).rstrip("\n") 263 raise CommandExecutionError(message) 264 265 # Create a pipe to set as stdout in the child. The write handle needs to be 266 # inheritable. 267 c2pread, c2pwrite = salt.platform.win.CreatePipe( 268 inherit_read=False, 269 inherit_write=True, 270 ) 271 errread, errwrite = salt.platform.win.CreatePipe( 272 inherit_read=False, 273 inherit_write=True, 274 ) 275 276 # Create inheritable copy of the stdin 277 stdin = salt.platform.win.kernel32.GetStdHandle( 278 salt.platform.win.STD_INPUT_HANDLE, 279 ) 280 dupin = salt.platform.win.DuplicateHandle(srchandle=stdin, inherit=True) 281 282 # Get startup info structure 283 startup_info = salt.platform.win.STARTUPINFO( 284 dwFlags=win32con.STARTF_USESTDHANDLES, 285 hStdInput=dupin, 286 hStdOutput=c2pwrite, 287 hStdError=errwrite, 288 ) 289 290 try: 291 # Run command and return process info structure 292 process_info = salt.platform.win.CreateProcessWithLogonW( 293 username=username, 294 domain=domain, 295 password=password, 296 logonflags=salt.platform.win.LOGON_WITH_PROFILE, 297 commandline=cmd, 298 startupinfo=startup_info, 299 currentdirectory=cwd, 300 ) 301 salt.platform.win.kernel32.CloseHandle(process_info.hThread) 302 finally: 303 salt.platform.win.kernel32.CloseHandle(dupin) 304 salt.platform.win.kernel32.CloseHandle(c2pwrite) 305 salt.platform.win.kernel32.CloseHandle(errwrite) 306 307 # Initialize ret and set first element 308 ret = {"pid": process_info.dwProcessId} 309 310 # Get Standard Out 311 fd_out = msvcrt.open_osfhandle(c2pread, os.O_RDONLY | os.O_TEXT) 312 with os.fdopen(fd_out, "r") as f_out: 313 ret["stdout"] = f_out.read() 314 315 # Get Standard Error 316 fd_err = msvcrt.open_osfhandle(errread, os.O_RDONLY | os.O_TEXT) 317 with os.fdopen(fd_err, "r") as f_err: 318 ret["stderr"] = f_err.read() 319 320 # Get Return Code 321 if ( 322 salt.platform.win.kernel32.WaitForSingleObject( 323 process_info.hProcess, win32event.INFINITE 324 ) 325 == win32con.WAIT_OBJECT_0 326 ): 327 exitcode = salt.platform.win.wintypes.DWORD() 328 salt.platform.win.kernel32.GetExitCodeProcess( 329 process_info.hProcess, ctypes.byref(exitcode) 330 ) 331 ret["retcode"] = exitcode.value 332 333 # Close handle to process 334 salt.platform.win.kernel32.CloseHandle(process_info.hProcess) 335 336 return ret 337