Files
openide/native/WinElevator/launcher.c
Vladislav Rassokhin cad28570bc IDEA-296416 WinElevator: convert to CMake
GitOrigin-RevId: f047b4332a47323b22b7e16681e0f2ae9df71252
2022-09-07 20:46:48 +00:00

351 lines
12 KiB
C

/*
* 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 <Windows.h>
#include <elevTools.h>
#include <stdio.h>
#include <assert.h>
// Elevation "frontend". Launched by user it starts elevator, connects to it and reads data from it
// Author: Ilya Kazakevich
#define ERR_FAIL_READ -1
#define ERR_FAIL_WRITE -2
#define ERR_GET_DIR -3
#define ERR_LAUNCH -4
#define ERR_FAIL_WAIT -5
#define ERR_CREATE_PIPE -6
#define ERR_WRITE_PIPE -7
// 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 __stdcall _CreateConnectPipe(_PIPE_CONNECTION_INFO* pPipeInfo)
{
WCHAR pipeName[40];
wsprintf(pipeName, L"JB-Launcher-Pipe-%ld", pPipeInfo->nDescriptor);
HANDLE eventSource = RegisterEventSourceW(NULL, pipeName);
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)
{
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_CREATE_PIPE, NULL, 0, 0, NULL, NULL);
fwprintf(stderr, L"Failed to create in pipe: %ld", GetLastError());
exit(ERR_CREATE_PIPE);
}
if (!ConnectNamedPipe(hExternalPipe, NULL))
{
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_FAIL_WAIT, NULL, 0, 0, NULL, NULL);
fwprintf(stderr, L"Failed to wait for in pipe: %ld", GetLastError());
exit(ERR_FAIL_WAIT);
}
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;
}
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_FAIL_READ, NULL, 0, 0, NULL, NULL);
fwprintf(stderr, L"Failed to read from %ld: %ld", pPipeInfo->nDescriptor, GetLastError());
exit(ERR_FAIL_READ);
}
if (!WriteFile(hToWrite, buffer, nBytesRead, &nBytesWritten, NULL))
{
DWORD nError = GetLastError();
if ((!pPipeInfo->bFromExternalProcess) && nError == ERROR_BROKEN_PIPE)
{
break;
}
ReportEvent(eventSource, EVENTLOG_WARNING_TYPE, 0, ERR_FAIL_WRITE, NULL, 0, 0, NULL, NULL);
fwprintf(stderr, L"Failed to write: %ld", GetLastError());
// Unability to write to remote pipe means there is nothing connected to stdout.
// Reason to report warning but not die (see IDEA-178958)
break;
}
FlushFileBuffers(hToWrite);
}
CloseHandle(hToWrite);
CloseHandle(hToRead);
DeregisterEventSource(eventSource);
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[])
{
HANDLE eventSource = RegisterEventSourceW(NULL, L"JB-Launcher");
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);
MemoryBarrier(); // To make sure threads has access to g_
ELEV_PIPE_NAME sEnvVarsPipeName;
ELEV_GEN_PIPE_NAME(sEnvVarsPipeName, nPid, ELEV_DESCR_ENVVAR);
HANDLE hEnvVarsPipe = CreateNamedPipe(
sEnvVarsPipeName,
PIPE_ACCESS_OUTBOUND,
PIPE_WAIT | PIPE_TYPE_MESSAGE,
1, _MAX_ENV, _MAX_ENV, 0, NULL
);
if (hEnvVarsPipe == INVALID_HANDLE_VALUE) {
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_CREATE_PIPE, NULL, 0, 0, NULL, NULL);
fprintf(stderr, "Failed to open env vars pipe: %ld", GetLastError());
return ERR_CREATE_PIPE;
}
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))
{
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_GET_DIR, NULL, 0, 0, NULL, NULL);
fprintf(stderr, "Failed to get directory: %ld", GetLastError());
return ERR_GET_DIR;
}
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))
{
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_LAUNCH, NULL, 0, 0, NULL, NULL);
fprintf(stderr, "Failed to launch process: %ld", GetLastError());
return ERR_LAUNCH;
}
{
// Waiting for the client to connect.
if (!ConnectNamedPipe(hEnvVarsPipe, NULL))
{
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_FAIL_WAIT, NULL, 0, 0, NULL, NULL);
fwprintf(stderr, L"Failed to wait for in pipe: %ld", GetLastError());
exit(ERR_FAIL_WAIT);
}
// Getting the env vars block.
LPWCH lpEnvString = GetEnvironmentStringsW();
LPWSTR lpszVariable = (LPWSTR)lpEnvString;
while (*lpszVariable)
{
size_t len = (wcslen(lpszVariable) + 1) * sizeof(WCHAR);
DWORD numWritten;
BOOL bWriteOk = WriteFile(hEnvVarsPipe, lpszVariable, (DWORD)len, &numWritten, NULL);
if (!bWriteOk)
{
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_WRITE_PIPE, NULL, 0, 0, NULL, NULL);
fprintf(stderr, "Failed to write to env vars pipe: %ld", GetLastError());
return ERR_WRITE_PIPE;
}
// Waiting for the client to read the message.
BOOL bFlushOk = FlushFileBuffers(hEnvVarsPipe);
if (!bFlushOk) {
ReportEvent(eventSource, EVENTLOG_ERROR_TYPE, 0, ERR_WRITE_PIPE, NULL, 0, 0, NULL, NULL);
fprintf(stderr, "Failed to flush the env vars pipe: %ld", GetLastError());
return ERR_WRITE_PIPE;
}
lpszVariable += wcslen(lpszVariable) + 1;
}
FreeEnvironmentStringsW(lpEnvString);
// Closing the pipe to signal the client to stop waiting for new msgs.
CloseHandle(hEnvVarsPipe);
}
// 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);
DeregisterEventSource(eventSource);
return nExitCode;
}