diff --git a/bin/win/elevator.exe b/bin/win/elevator.exe new file mode 100644 index 000000000000..0ce89bbdd1dd Binary files /dev/null and b/bin/win/elevator.exe differ diff --git a/bin/win/launcher.exe b/bin/win/launcher.exe new file mode 100644 index 000000000000..bca7846e27a0 Binary files /dev/null and b/bin/win/launcher.exe differ diff --git a/native/WinElevator/Elevator.sln b/native/WinElevator/Elevator.sln new file mode 100644 index 000000000000..cc42445ac66e --- /dev/null +++ b/native/WinElevator/Elevator.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "launcher", "launcher\launcher.vcxproj", "{6631215A-50A4-49BE-A0B5-BDAC4C75B955}" + ProjectSection(ProjectDependencies) = postProject + {9D71A73C-2570-488A-958B-A126F4EEECB7} = {9D71A73C-2570-488A-958B-A126F4EEECB7} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "elevator", "elevator\elevator.vcxproj", "{9D71A73C-2570-488A-958B-A126F4EEECB7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "elevShared", "elevShared\elevShared.vcxitems", "{45D41ACC-2C3C-43D2-BC10-02AA73FFC7C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0AFC8DF7-8237-4909-8993-9CF44995AA42}" + ProjectSection(SolutionItems) = preProject + README.txt = README.txt + EndProjectSection +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + elevShared\elevShared.vcxitems*{45d41acc-2c3c-43d2-bc10-02aa73ffc7c7}*SharedItemsImports = 9 + elevShared\elevShared.vcxitems*{6631215a-50a4-49be-a0b5-bdac4c75b955}*SharedItemsImports = 4 + elevShared\elevShared.vcxitems*{9d71a73c-2570-488a-958b-a126f4eeecb7}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Debug|x64.ActiveCfg = Debug|x64 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Debug|x64.Build.0 = Debug|x64 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Debug|x86.ActiveCfg = Debug|Win32 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Debug|x86.Build.0 = Debug|Win32 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Release|x64.ActiveCfg = Release|x64 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Release|x64.Build.0 = Release|x64 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Release|x86.ActiveCfg = Release|Win32 + {6631215A-50A4-49BE-A0B5-BDAC4C75B955}.Release|x86.Build.0 = Release|Win32 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Debug|x64.ActiveCfg = Debug|x64 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Debug|x64.Build.0 = Debug|x64 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Debug|x86.ActiveCfg = Debug|Win32 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Debug|x86.Build.0 = Debug|Win32 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x64.ActiveCfg = Release|x64 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x64.Build.0 = Release|x64 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x86.ActiveCfg = Release|Win32 + {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/native/WinElevator/README.txt b/native/WinElevator/README.txt new file mode 100644 index 000000000000..926f9cb1c5ae --- /dev/null +++ b/native/WinElevator/README.txt @@ -0,0 +1,37 @@ +With UAC enabled even administrator has limited access token and can't modify some folders like "Program Files". +The only official way to elevate token for process is to launch app as elevated using shell api so user will have chance to accept it. +Since it may lead to security risks it is not recomended to run whole app as elevated, but run only certain tools instead. + +This app consists of 2 apps: +* launcher: accepts command line to run as elevated and launches "elevator" using shell api +* elevator: has "UAC execution level" set "administrator" in its manifest, so UAC is displayed. It then runs provided command line. + +Since elevator is launched with elevated priviliges, it has separate console in conhost (technically it is not child of launcher but of AppInfo instead), +so there is some machinery to connect elevated process to console and its pipes. + +Launcher provides its pid to elevator and elevator attaches to its console. + +But if std(out|err|in) are redirected to files or pipes, attaching to console is not enough. +In this case launcher creates named pipes, elevator connects to them and provides their handlers as handlers for newly created process. +Launcher then creates threads to read/write them to console. + +------- +How to build. +You may open .sln from Visual Studio or use msbuild from VS command prompt: + msbuild Elevator.sln /p:Configuration=release + +------- +How to test. +From unelevated command.com run commands and click "yes" + +Testing console +> launcher.exe %ComSpec% +You should be taken to admin console (i.e. has write access to c:\windows\) + +Testing output +> echo spam | launcher.exe python.exe -c "import sys; sys.stderr.write('err'); print(sys.stdin.read()); " > out.txt 2> err.txt +Check you got "spam" in out.txt and "err" in "err.txt" + +Ensure permissions +> launcher.exe python.exe -c "print(open('c:\\windows\\eggs.txt', 'w'))" +Check no error diff --git a/native/WinElevator/elevShared/elevShared.vcxitems b/native/WinElevator/elevShared/elevShared.vcxitems new file mode 100644 index 000000000000..c5ac1ff70529 --- /dev/null +++ b/native/WinElevator/elevShared/elevShared.vcxitems @@ -0,0 +1,19 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {45d41acc-2c3c-43d2-bc10-02aa73ffc7c7} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + \ No newline at end of file diff --git a/native/WinElevator/elevShared/elevShared.vcxitems.filters b/native/WinElevator/elevShared/elevShared.vcxitems.filters new file mode 100644 index 000000000000..cdd7d46ee8f4 --- /dev/null +++ b/native/WinElevator/elevShared/elevShared.vcxitems.filters @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/native/WinElevator/elevShared/elevTools.h b/native/WinElevator/elevShared/elevTools.h new file mode 100644 index 000000000000..2c43ec6dff11 --- /dev/null +++ b/native/WinElevator/elevShared/elevTools.h @@ -0,0 +1,42 @@ +#pragma once +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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. + */ + +// Shared file for elevator and launcher + +// Author: Ilya Kazakevich + + +// So called "descriptors". Used as arguments in many macros and can also be used as binary flags +#define ELEV_DESCR_STDOUT 1 +#define ELEV_DESCR_STDERR 2 +#define ELEV_DESCR_STDIN 4 + +// Rules to generate pipe name +#define ELEV_GEN_PIPE_NAME(sDest, nPid, nDescriptor) wsprintf(sDest, L"\\\\.\\pipe\\_jetbrains%ld_%d", nPid, nDescriptor) + +#define ELEV_BUF_SIZE 1024 //Buf to read/write between processes + +// Convert descriptor to Win32API handler +#define ELEV_DESCR_GET_HANDLE(nDescriptor) (nDescriptor == ELEV_DESCR_STDOUT ? STD_OUTPUT_HANDLE : \ + (nDescriptor == ELEV_DESCR_STDERR ? STD_ERROR_HANDLE : STD_INPUT_HANDLE)) + +// Pipe name +typedef wchar_t ELEV_PIPE_NAME[32]; + +// Separates arguments provided to elevator and user command line +#define ELEV_COMMAND_LINE_SEPARATOR L"--::--" + diff --git a/native/WinElevator/elevator/Resource.rc b/native/WinElevator/elevator/Resource.rc new file mode 100644 index 000000000000..324bb62a5ba2 Binary files /dev/null and b/native/WinElevator/elevator/Resource.rc differ diff --git a/native/WinElevator/elevator/elevator.c b/native/WinElevator/elevator/elevator.c new file mode 100644 index 000000000000..bad1ed9b0f2c --- /dev/null +++ b/native/WinElevator/elevator/elevator.c @@ -0,0 +1,193 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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. + */ + +#include +#include +#include +#include +#include + + +// UAC-enabled (in manifset) tool to launch ptocesses. +// Connects to pipes and console, and then launches new process using CreateProcess + +// Author: Ilya Kazakevich + + +// Connects and waits for pipe if required by descriptor flags +// nDescriptor ELEV_DESC_* +// nDescriptorFlags flags passed from launcher to check if descriptor should be connected +// Returns 0 if ok, error otherwise. Could be windows error, EBADF or EMFILE for dup2 +static DWORD _ConnectIfNeededPipe(DWORD nParentPid, DWORD nDescriptor, FILE* stream, int nDescriptorFlags, _Out_ PHANDLE pRemoteProcessHandle) +{ + if (!(nDescriptorFlags & nDescriptor)) + { + *pRemoteProcessHandle = GetStdHandle(ELEV_DESCR_GET_HANDLE(nDescriptor)); + return 0; // Not needed to connect pipe, use real descriptor + } + ELEV_PIPE_NAME sPipeName; + ELEV_GEN_PIPE_NAME(sPipeName, nParentPid, nDescriptor); + WaitNamedPipe(sPipeName, INFINITE); + BOOL bStdIn = nDescriptor == ELEV_DESCR_STDIN; + unsigned long access = (bStdIn ? GENERIC_READ : GENERIC_WRITE); + HANDLE hPipe = CreateFile(sPipeName, access, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hPipe == INVALID_HANDLE_VALUE || hPipe == NULL) + { + return GetLastError(); + } + + // Make inheritable by remote process + if (!SetHandleInformation(hPipe, HANDLE_FLAG_INHERIT, TRUE)) + { + return GetLastError(); + } + + // Fix CRT + if (_dup2(_open_osfhandle((intptr_t)hPipe, _O_WTEXT | _O_TEXT), _fileno(stream)) != 0) + { + return errno; + } + + // Fix Win32API + DWORD hStdHandleToChange = ELEV_DESCR_GET_HANDLE(nDescriptor); + if (!SetStdHandle(hStdHandleToChange, hPipe)) + { + return GetLastError(); + } + + *pRemoteProcessHandle = hPipe; + return 0; +} + +// PID Directory DescriptorFlags ProgramToRun Arguments +#define _ARG_PID 1 +#define _ARG_DIR 2 +#define _ARG_DESCRIPTORS 3 + +int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) +{ + if (argc <= _ARG_DESCRIPTORS) + { + fwprintf(stderr, L"Bad command line"); + return -1; + } + if (!SetCurrentDirectory(argv[_ARG_DIR])) + { + fwprintf(stderr, L"Failed to set directory to %s : %ld", argv[_ARG_DIR], GetLastError()); + return -1; + } + DWORD nParentPid = _wtol(argv[_ARG_PID]); + if (!nParentPid) + { + fwprintf(stderr, L"Failed to get parent pid from %s", argv[_ARG_PID]); + return -1; + } + + wchar_t* sDescriptorsStr = argv[_ARG_DESCRIPTORS]; + size_t nDescriptorsLen = wcslen(sDescriptorsStr); + if (!nDescriptorsLen) + { + fwprintf(stderr, L"Failed to get descriptors from %s", sDescriptorsStr); + return -1; + } + for(int i = 0; i < nDescriptorsLen; i++) + { + if (! iswdigit(sDescriptorsStr[i])) + { + fwprintf(stderr, L"Bad descriptor %s", sDescriptorsStr); + return -1; + } + } + int nDescriptorFlags = _wtoi(sDescriptorsStr); + + + + + wchar_t* sFromSeparator = wcsstr(GetCommandLine(), ELEV_COMMAND_LINE_SEPARATOR); + if (! sFromSeparator) + { + fwprintf(stderr, L"Failed to find %s in %s", ELEV_COMMAND_LINE_SEPARATOR, GetCommandLine()); + return -1; + } + // Add rest commandline + WCHAR* sCommandLine = sFromSeparator + wcslen(ELEV_COMMAND_LINE_SEPARATOR); + + // Fix console + FreeConsole(); + if (!AttachConsole(nParentPid)) + { + fwprintf(stderr, L"Failed to attach console: %d", GetLastError()); + return 1; + } + + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.dwFlags = STARTF_USESTDHANDLES; // To pass handles to remote process + + DWORD nError; // No place to output errors yet. Event log is overkill here, so we use exit code. + + nError = _ConnectIfNeededPipe(nParentPid, ELEV_DESCR_STDIN, stdin, nDescriptorFlags, &startupInfo.hStdInput); + if (nError != 0) + { + exit(nError); + } + nError = _ConnectIfNeededPipe(nParentPid, ELEV_DESCR_STDOUT, stdout, nDescriptorFlags, &startupInfo.hStdOutput); + if (nError != 0) + { + exit(nError); + } + nError = _ConnectIfNeededPipe(nParentPid, ELEV_DESCR_STDERR, stderr, nDescriptorFlags, &startupInfo.hStdError); + if (nError != 0) + { + exit(nError); + } + + HANDLE parentProcess = OpenProcess(SYNCHRONIZE, FALSE, nParentPid); + if (!parentProcess) + { + exit(GetLastError()); // If parent process can't be opened it probably dead + } + + PROCESS_INFORMATION processInfo; + + if (!CreateProcess(NULL, sCommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startupInfo, &processInfo)) + { + fwprintf(stderr, L"Error launching process. Exit code %ld, command was %ls", GetLastError(), sCommandLine); + return 1; + } + HANDLE processesToWait[] = { parentProcess, processInfo.hProcess }; + + DWORD nWaitResult = WaitForMultipleObjects(2, processesToWait, FALSE, INFINITE); + if (WAIT_FAILED == nWaitResult) + { + fwprintf(stderr, L"Error waiting processes: %ld", GetLastError()); + return -1; + } + if (nWaitResult - WAIT_OBJECT_0 == 0) + { + fwprintf(stderr, L"Parent process (launcher) died?"); + TerminateProcess(processInfo.hProcess, -1); + return -1; + } + + + + DWORD nExitCode = 0; + GetExitCodeProcess(processInfo.hProcess, &nExitCode); + + return nExitCode; +} diff --git a/native/WinElevator/elevator/elevator.vcxproj b/native/WinElevator/elevator/elevator.vcxproj new file mode 100644 index 000000000000..b862b3ffdfee --- /dev/null +++ b/native/WinElevator/elevator/elevator.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {9D71A73C-2570-488A-958B-A126F4EEECB7} + Win32Proj + elevator + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + true + $(IncludePath); + $(VC_SourcePath); + + + true + $(IncludePath); + $(VC_SourcePath); + + + false + $(IncludePath); + $(VC_SourcePath); + + + false + $(IncludePath); + $(VC_SourcePath); + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + RequireAdministrator + + + + + NotUsing + Level3 + Disabled + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + RequireAdministrator + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + RequireAdministrator + + + + + Level3 + NotUsing + MaxSpeed + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + RequireAdministrator + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/native/WinElevator/elevator/elevator.vcxproj.filters b/native/WinElevator/elevator/elevator.vcxproj.filters new file mode 100644 index 000000000000..38ea8fb79f5f --- /dev/null +++ b/native/WinElevator/elevator/elevator.vcxproj.filters @@ -0,0 +1,23 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + + + + + + + + + + \ No newline at end of file diff --git a/native/WinElevator/elevator/elevator.vcxproj.user b/native/WinElevator/elevator/elevator.vcxproj.user new file mode 100644 index 000000000000..abe8dd8961e3 --- /dev/null +++ b/native/WinElevator/elevator/elevator.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/native/WinElevator/elevator/jb.ico b/native/WinElevator/elevator/jb.ico new file mode 100644 index 000000000000..325ab6b7de42 Binary files /dev/null and b/native/WinElevator/elevator/jb.ico differ diff --git a/native/WinElevator/elevator/resource.h b/native/WinElevator/elevator/resource.h new file mode 100644 index 000000000000..661a5bafe934 Binary files /dev/null and b/native/WinElevator/elevator/resource.h differ diff --git a/native/WinElevator/launcher/Resource.rc b/native/WinElevator/launcher/Resource.rc new file mode 100644 index 000000000000..d4c6613e7a62 Binary files /dev/null and b/native/WinElevator/launcher/Resource.rc differ diff --git a/native/WinElevator/launcher/launcher.c b/native/WinElevator/launcher/launcher.c new file mode 100644 index 000000000000..54247c0a794b --- /dev/null +++ b/native/WinElevator/launcher/launcher.c @@ -0,0 +1,275 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * 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. + */ +#include +#include +#include + +// Elevation "frontend". Launched by user it starts elevator, connects to it and reads data from it + +// Author: Ilya Kazakevich + +// Pipe that should be connected to remote process +typedef struct +{ + DWORD nRemoteProcessPid; + DWORD nDescriptor; //One of ELEV_* descriptors + BOOL bFromExternalProcess; // True if pipe is for READING from EXTERNAL process. Otherwise to write to it +} _PIPE_CONNECTION_INFO; + + +// Pipes to remote process. Accessed from another threads so they are global +static _PIPE_CONNECTION_INFO g_stdOutPipe, g_stdErrPipe, g_stdInPipe; + + +#define _CONFIGURE_PIPE_INFO(pipeInfo, nDescriptorToSet, nPid, bFromExternalProcessDirection) { \ + pipeInfo.nDescriptor = nDescriptorToSet; \ + pipeInfo.nRemoteProcessPid = nPid; \ + pipeInfo.bFromExternalProcess = bFromExternalProcessDirection; \ +} + +// Returns full command line excluding program itself +static WCHAR* _GetCommandLineNoProgram() +{ + WCHAR* sCommandLine = GetCommandLine(); + int nNumberOfArgs; + WCHAR** args = CommandLineToArgvW(sCommandLine, &nNumberOfArgs); + WCHAR* sProgram = args[0]; + size_t nProgramLengthChars = wcslen(sProgram); + LocalFree(args); + if (sCommandLine[0] == L'"') + { + nProgramLengthChars += 2; //Program name is in quotes + } + WCHAR * sCommandLineAfterProgram = sCommandLine + nProgramLengthChars; + for (; sCommandLineAfterProgram[0] == L' '; sCommandLineAfterProgram++) {} // Remove spaces after program + + return sCommandLineAfterProgram; +} + +// Adds argument to command line. +// pchCurrentBufferSize should be *psCommandLine size. Incremeted automatically. +// psCommandLine to append to +// sStringToAdd arugment to add +// Escaping is not supported, so string can't have quotes +static void _AddStringToCommandLine(_Inout_ size_t* pchCurrentBufferChars, _Inout_ WCHAR** psCommandLine, _In_ WCHAR* sStringToAdd, _In_ BOOL bAddQuotes) +{ + // TODO: Doc suboptimal. Use line length instead of wcslen(*psCommandLine) ("shlemiel the painter algorithm") + + size_t nCurrentLineLengthChars = wcslen(*psCommandLine); + size_t nStringToAddLengthChars = wcslen(sStringToAdd); + size_t nSpaceLeftInBufferChars = (*pchCurrentBufferChars) - nCurrentLineLengthChars; + // "\"new_string_goes_here\" " + size_t nRequiredSizeChars = (*pchCurrentBufferChars) + nStringToAddLengthChars; + if (bAddQuotes) + { + nRequiredSizeChars += wcslen(L"\"\" "); + } + if (nSpaceLeftInBufferChars < nRequiredSizeChars) + { + // Not enough space, add more space + (*pchCurrentBufferChars) = nRequiredSizeChars; + *psCommandLine = realloc(*psCommandLine, sizeof(WCHAR) * (*pchCurrentBufferChars)); + } + WCHAR* endOfCommandLine = (*psCommandLine) + nCurrentLineLengthChars; + if (bAddQuotes) { + wsprintf(endOfCommandLine, L"\"%ls\" ", sStringToAdd); + } + else + { + wcscat_s((*psCommandLine), (*pchCurrentBufferChars), sStringToAdd); + } +} + + +// ThreadProc to connect pipe to remote process +static DWORD _CreateConnectPipe(_PIPE_CONNECTION_INFO* pPipeInfo) +{ + ELEV_PIPE_NAME sPipeName; + ELEV_GEN_PIPE_NAME(sPipeName, pPipeInfo->nRemoteProcessPid, pPipeInfo->nDescriptor); + int access = (pPipeInfo->bFromExternalProcess ? PIPE_ACCESS_INBOUND : PIPE_ACCESS_OUTBOUND); + HANDLE hExternalPipe = CreateNamedPipe( + sPipeName, + access, + PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS | PIPE_TYPE_BYTE, + 1, + ELEV_BUF_SIZE, + ELEV_BUF_SIZE, + 0, + NULL); + if (hExternalPipe == NULL || hExternalPipe == INVALID_HANDLE_VALUE) + { + fwprintf(stderr, L"Failed to create in pipe: %ld", GetLastError()); + exit(-1); + } + + if (!ConnectNamedPipe(hExternalPipe, NULL)) + { + fwprintf(stderr, L"Failed to wait for in pipe: %ld", GetLastError()); + exit(-1); + } + + + char buffer[ELEV_BUF_SIZE]; + DWORD nBytesRead; + DWORD nBytesWritten; + HANDLE hInternal = GetStdHandle(ELEV_DESCR_GET_HANDLE(pPipeInfo->nDescriptor)); + HANDLE hToRead = (pPipeInfo->bFromExternalProcess ? hExternalPipe : hInternal); + HANDLE hToWrite = ((! pPipeInfo->bFromExternalProcess) ? hExternalPipe : hInternal); + + while (1) + { + if (!ReadFile(hToRead, buffer, ELEV_BUF_SIZE, &nBytesRead, NULL)) + { + DWORD nError = GetLastError(); + + if (nError == ERROR_BROKEN_PIPE) + { + break; + } + fwprintf(stderr, L"Failed to read from %ld: %ld", pPipeInfo->nDescriptor, GetLastError()); + exit(-1); + } + if (!WriteFile(hToWrite, buffer, nBytesRead, &nBytesWritten, NULL)) + { + DWORD nError = GetLastError(); + + if ((!pPipeInfo->bFromExternalProcess) && nError == ERROR_BROKEN_PIPE) + { + break; + } + fwprintf(stderr, L"Failed to write: %ld", GetLastError()); + exit(-1); + } + FlushFileBuffers(hToWrite); + } + CloseHandle(hToWrite); + CloseHandle(hToRead); + return 0; +} + +// If pipe is not console this function connects it, sets thread handler and configures descriptor flags to mark this descriptor is connected +static void _LaunchPipeThread(_PIPE_CONNECTION_INFO* pPipeInfo, _Out_opt_ PHANDLE pThreadHandle, _Inout_ int* pDescriptorFlags) +{ + HANDLE hHandle = GetStdHandle(ELEV_DESCR_GET_HANDLE(pPipeInfo->nDescriptor)); + + // Console apps may act differently if its stream is not connected to console, so we only connect when file or pipe is used + if (GetFileType(hHandle) == FILE_TYPE_CHAR ) // Console (same as *nix istty()), do not touch + { + if (pThreadHandle) { + *pThreadHandle = NULL; + } + return; + } + HANDLE hThread = CreateThread(NULL, 0, _CreateConnectPipe, pPipeInfo, 0, NULL); + if (pThreadHandle) + { + *pThreadHandle = hThread; + } + *pDescriptorFlags |= pPipeInfo->nDescriptor; +} + +int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) +{ + + + DWORD nExitCode = 0; + + // Get pids + DWORD nPid = GetCurrentProcessId(); + WCHAR sPid[20]; + _ltow_s(nPid, sPid, 20, 10); + + _CONFIGURE_PIPE_INFO(g_stdOutPipe, ELEV_DESCR_STDOUT, nPid, TRUE); + _CONFIGURE_PIPE_INFO(g_stdErrPipe, ELEV_DESCR_STDERR, nPid, TRUE); + _CONFIGURE_PIPE_INFO(g_stdInPipe, ELEV_DESCR_STDIN, nPid, FALSE); + + + _mm_mfence(); // To make sure threads has access to g_ + + HANDLE arHandlesToWait[] = {NULL, NULL}; + int nDescriptorFlags = 0; + _LaunchPipeThread(&g_stdInPipe, NULL, &nDescriptorFlags); // No need to wait stdin thread so we do not need its handle + _LaunchPipeThread(&g_stdOutPipe, &arHandlesToWait[0], &nDescriptorFlags); + _LaunchPipeThread(&g_stdErrPipe, &arHandlesToWait[1], &nDescriptorFlags); + + + //Get current dir + WCHAR sCurrentDirectory[MAX_PATH + 1]; + GetCurrentDirectory(MAX_PATH, sCurrentDirectory); + + //Build commandline + size_t chCurrentSize = 1; + WCHAR* sNewCommandLine = calloc(1, sizeof(WCHAR)); + + _AddStringToCommandLine(&chCurrentSize, &sNewCommandLine, sPid, TRUE); + _AddStringToCommandLine(&chCurrentSize, &sNewCommandLine, sCurrentDirectory, TRUE); + WCHAR sDescriptorFlags[3]; + _itow_s(nDescriptorFlags, sDescriptorFlags, 2, 10); + _AddStringToCommandLine(&chCurrentSize, &sNewCommandLine, sDescriptorFlags, TRUE); + + + _AddStringToCommandLine(&chCurrentSize, &sNewCommandLine, ELEV_COMMAND_LINE_SEPARATOR, FALSE); + + + // Add arguments provided by user to the tail of command line + // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + WCHAR * sOriginalCommandLineNoProgram = _GetCommandLineNoProgram(); + size_t nNewBufferSizeChars = wcslen(sOriginalCommandLineNoProgram) + wcslen(sNewCommandLine) + 1; + sNewCommandLine = realloc(sNewCommandLine, nNewBufferSizeChars * sizeof(WCHAR)); + wcscat_s(sNewCommandLine, nNewBufferSizeChars, sOriginalCommandLineNoProgram); + + // Get full path to elevator + WCHAR sPath[MAX_PATH + 1]; + if(!GetModuleFileName(NULL, sPath, MAX_PATH)) + { + fprintf(stderr, "Failed to get directory: %ld", GetLastError()); + return 1; + } + + WCHAR sDrive[_MAX_DRIVE], sDir[_MAX_DIR], sFile[_MAX_FNAME], sExt[_MAX_EXT]; + _wsplitpath_s(sPath, sDrive, _MAX_DRIVE, sDir, _MAX_DIR, sFile, _MAX_FNAME, sExt, _MAX_EXT); + + swprintf_s(sPath, MAX_PATH, L"%ls%lselevator.exe", sDrive, sDir); + + //Execute elevator + SHELLEXECUTEINFO execInfo; + ZeroMemory(&execInfo, sizeof(SHELLEXECUTEINFO)); + execInfo.cbSize = sizeof(SHELLEXECUTEINFO); + execInfo.lpParameters = sNewCommandLine; + execInfo.lpFile = sPath; + execInfo.lpDirectory = sCurrentDirectory; + execInfo.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE; + + if (!ShellExecuteEx(&execInfo)) + { + fprintf(stderr, "Failed to launch process: %ld", GetLastError()); + return 1; + } + + // Wait for all threads + for(int i = 0; i < 2; i++) + { + if (arHandlesToWait[i]) + { + WaitForSingleObject(arHandlesToWait[i], INFINITE); + } + } + + // Process should be ended here, lets wait + WaitForSingleObject(execInfo.hProcess, INFINITE); + GetExitCodeProcess(execInfo.hProcess, &nExitCode); + return nExitCode; +} diff --git a/native/WinElevator/launcher/launcher.vcxproj b/native/WinElevator/launcher/launcher.vcxproj new file mode 100644 index 000000000000..f100fc0ca5f3 --- /dev/null +++ b/native/WinElevator/launcher/launcher.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {6631215A-50A4-49BE-A0B5-BDAC4C75B955} + Win32Proj + launcher + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + true + $(IncludePath); + $(VC_SourcePath); + + + true + $(IncludePath); + $(VC_SourcePath); + + + false + $(IncludePath); + $(VC_SourcePath); + + + false + $(IncludePath); + $(VC_SourcePath); + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + AsInvoker + + + + + NotUsing + Level3 + Disabled + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + AsInvoker + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + AsInvoker + + + + + Level3 + NotUsing + MaxSpeed + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + AsInvoker + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/native/WinElevator/launcher/launcher.vcxproj.filters b/native/WinElevator/launcher/launcher.vcxproj.filters new file mode 100644 index 000000000000..dc9b18d9ad11 --- /dev/null +++ b/native/WinElevator/launcher/launcher.vcxproj.filters @@ -0,0 +1,20 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/native/WinElevator/launcher/launcher.vcxproj.user b/native/WinElevator/launcher/launcher.vcxproj.user new file mode 100644 index 000000000000..abe8dd8961e3 --- /dev/null +++ b/native/WinElevator/launcher/launcher.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/native/WinElevator/launcher/resource.h b/native/WinElevator/launcher/resource.h new file mode 100644 index 000000000000..647c9bd9480d Binary files /dev/null and b/native/WinElevator/launcher/resource.h differ diff --git a/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java b/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java index b78203d1ef69..430da9276eab 100644 --- a/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java +++ b/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java @@ -144,8 +144,17 @@ public class ExecUtil { command.add(commandLine.getExePath()); command.addAll(commandLine.getParametersList().getList()); - GeneralCommandLine sudoCommandLine; - if (SystemInfo.isMac) { + final GeneralCommandLine sudoCommandLine; + if (SystemInfo.isWinVistaOrNewer) { + // launcher.exe process with elevated permissions on UAC. + final File launcherExe = PathManager.findBinFileWithException("launcher.exe"); + sudoCommandLine = new GeneralCommandLine(launcherExe.getPath()); + sudoCommandLine.setWorkDirectory(commandLine.getWorkDirectory()); + sudoCommandLine.addParameter(commandLine.getExePath()); + sudoCommandLine.addParameters(commandLine.getParametersList().getParameters()); + sudoCommandLine.getEnvironment().putAll(commandLine.getEffectiveEnvironment()); + } + else if (SystemInfo.isMac) { String escapedCommandLine = StringUtil.join(command, ExecUtil::escapeAppleScriptArgument, " & \" \" & "); String escapedScript = "tell current application\n" + " activate\n" + diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java index cf768491038f..d2c1c45fab48 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java @@ -118,7 +118,7 @@ public class PyPackageManagerImpl extends PyPackageManager { private boolean refreshAndCheckForSetuptools() throws ExecutionException { try { final List packages = refreshAndGetPackages(false); - return PyPackageUtil.findPackage(packages, PyPackageUtil.SETUPTOOLS) != null || + return PyPackageUtil.findPackage(packages, PyPackageUtil.SETUPTOOLS) != null || PyPackageUtil.findPackage(packages, PyPackageUtil.DISTRIBUTE) != null; } catch (PyExecutionException e) { @@ -233,7 +233,7 @@ public class PyPackageManagerImpl extends PyPackageManager { if (canModify) { final String location = pkg.getLocation(); if (location != null) { - canModify = FileUtil.ensureCanCreateFile(new File(location)); + canModify = ensureCanCreateFile(new File(location)); } } args.add(pkg.getName()); @@ -249,6 +249,29 @@ public class PyPackageManagerImpl extends PyPackageManager { } } + // TODO: Move to FileUtil.ensureCanCreateFile ? + + /** + * When file it protected with UAC on Windows, you can't relay on {@link File#canWrite()}. + * + * @param file file to check if writable (works in UAC too) + */ + private static boolean ensureCanCreateFile(@NotNull final File file) { + if (SystemInfo.isWinVistaOrNewer) { + try { + final File folder = (file.isFile() ? file.getParentFile() : file); + final File tmpFile = File.createTempFile("pycharm", null, folder); + tmpFile.deleteOnExit(); + tmpFile.delete(); + } + catch (final IOException ignored) { + return false; + } + return true; + } + return FileUtil.ensureCanCreateFile(file); + } + @Nullable @Override @@ -439,8 +462,8 @@ public class PyPackageManagerImpl extends PyPackageManager { cmdline.addAll(args); LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " ")); - final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath)); - final boolean useSudo = !canCreate && !SystemInfo.isWindows && askForSudo; + final boolean canCreate = ensureCanCreateFile(new File(homePath)); + final boolean useSudo = !canCreate && askForSudo; try { final GeneralCommandLine commandLine = new GeneralCommandLine(cmdline).withWorkDirectory(workingDir);