Launch games under the correct user account on Windows (#600)
This commit is contained in:
@@ -388,7 +388,8 @@ jobs:
|
|||||||
libboost-filesystem1.71-dev \
|
libboost-filesystem1.71-dev \
|
||||||
libboost-log1.71-dev \
|
libboost-log1.71-dev \
|
||||||
libboost-regex1.71-dev \
|
libboost-regex1.71-dev \
|
||||||
libboost-thread1.71-dev
|
libboost-thread1.71-dev \
|
||||||
|
libboost-program-options1.71-dev
|
||||||
|
|
||||||
# Install cmake
|
# Install cmake
|
||||||
wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh
|
wget https://cmake.org/files/v3.22/cmake-3.22.2-linux-x86_64.sh
|
||||||
@@ -412,7 +413,8 @@ jobs:
|
|||||||
cmake \
|
cmake \
|
||||||
libboost-filesystem-dev \
|
libboost-filesystem-dev \
|
||||||
libboost-log-dev \
|
libboost-log-dev \
|
||||||
libboost-thread-dev
|
libboost-thread-dev \
|
||||||
|
libboost-program-options-dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
|
|||||||
+4
-3
@@ -68,7 +68,7 @@ pkg_check_modules (CURL REQUIRED libcurl)
|
|||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
set(Boost_USE_STATIC_LIBS ON)
|
set(Boost_USE_STATIC_LIBS ON)
|
||||||
endif()
|
endif()
|
||||||
find_package(Boost COMPONENTS log filesystem REQUIRED)
|
find_package(Boost COMPONENTS log filesystem program_options REQUIRED)
|
||||||
|
|
||||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
list(APPEND SUNSHINE_COMPILE_OPTIONS -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||||
|
|
||||||
@@ -122,6 +122,7 @@ if(WIN32)
|
|||||||
d3d11 dxgi D3DCompiler
|
d3d11 dxgi D3DCompiler
|
||||||
setupapi
|
setupapi
|
||||||
dwmapi
|
dwmapi
|
||||||
|
userenv
|
||||||
synchronization.lib
|
synchronization.lib
|
||||||
${CURL_STATIC_LIBRARIES}
|
${CURL_STATIC_LIBRARIES}
|
||||||
)
|
)
|
||||||
@@ -658,8 +659,8 @@ elseif(UNIX)
|
|||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libcurl4, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl, libboost-thread1.67.0 | libboost-thread1.71.0 | libboost-thread1.74.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0 | libboost-filesystem1.74.0, libboost-log1.67.0 | libboost-log1.71.0 | libboost-log1.74.0, libboost-program-options1.67.0 | libboost-program-options1.71.0 | libboost-program-options1.74.0, libcurl4, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2, libcap2")
|
||||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, libcurl >= 7.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22")
|
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, boost-thread >= 1.67.0, boost-filesystem >= 1.67.0, boost-log >= 1.67.0, boost-program-options >= 1.67.0, libcurl >= 7.0, pulseaudio-libs >= 10.0, libopusenc >= 0.2.1, libxcb >= 1.13, libXtst >= 1.2.3, libevdev >= 1.5.6, libdrm >= 2.4.97, libcap >= 2.22")
|
||||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config
|
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS OFF) # This should automatically figure out dependencies, doesn't work with the current config
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ RUN apt-get update -y \
|
|||||||
libboost-filesystem-dev=1.74.0* \
|
libboost-filesystem-dev=1.74.0* \
|
||||||
libboost-log-dev=1.74.0* \
|
libboost-log-dev=1.74.0* \
|
||||||
libboost-thread-dev=1.74.0* \
|
libboost-thread-dev=1.74.0* \
|
||||||
|
libboost-program-options-dev=1.74.0* \
|
||||||
libcap-dev=1:2.44* \
|
libcap-dev=1:2.44* \
|
||||||
libcurl4-openssl-dev=7.81.0* \
|
libcurl4-openssl-dev=7.81.0* \
|
||||||
libdrm-dev=2.4.110* \
|
libdrm-dev=2.4.110* \
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Install Requirements
|
|||||||
libboost-filesystem-dev \
|
libboost-filesystem-dev \
|
||||||
libboost-log-dev \
|
libboost-log-dev \
|
||||||
libboost-thread-dev \
|
libboost-thread-dev \
|
||||||
|
libboost-program-options-dev \
|
||||||
libcap-dev \ # KMS
|
libcap-dev \ # KMS
|
||||||
libdrm-dev \ # KMS
|
libdrm-dev \ # KMS
|
||||||
libevdev-dev \
|
libevdev-dev \
|
||||||
@@ -104,6 +105,7 @@ Install Requirements
|
|||||||
libboost-log1.71-dev \
|
libboost-log1.71-dev \
|
||||||
libboost-regex1.71-dev \
|
libboost-regex1.71-dev \
|
||||||
libboost-thread1.71-dev \
|
libboost-thread1.71-dev \
|
||||||
|
libboost-program-options1.71-dev \
|
||||||
libcap-dev \ # KMS
|
libcap-dev \ # KMS
|
||||||
libdrm-dev \ # KMS
|
libdrm-dev \ # KMS
|
||||||
libevdev-dev \
|
libevdev-dev \
|
||||||
@@ -160,6 +162,7 @@ Install Requirements
|
|||||||
libboost-filesystem-dev \
|
libboost-filesystem-dev \
|
||||||
libboost-log-dev \
|
libboost-log-dev \
|
||||||
libboost-thread-dev \
|
libboost-thread-dev \
|
||||||
|
libboost-program-options-dev \
|
||||||
libcap-dev \ # KMS
|
libcap-dev \ # KMS
|
||||||
libdrm-dev \ # KMS
|
libdrm-dev \ # KMS
|
||||||
libevdev-dev \
|
libevdev-dev \
|
||||||
@@ -206,6 +209,7 @@ Install Requirements
|
|||||||
libboost-filesystem-dev \
|
libboost-filesystem-dev \
|
||||||
libboost-log-dev \
|
libboost-log-dev \
|
||||||
libboost-thread-dev \
|
libboost-thread-dev \
|
||||||
|
libboost-program-options-dev \
|
||||||
libcap-dev \ # KMS
|
libcap-dev \ # KMS
|
||||||
libdrm-dev \ # KMS
|
libdrm-dev \ # KMS
|
||||||
libevdev-dev \
|
libevdev-dev \
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ modules:
|
|||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
- cd tools/build && bison -y -d -o src/engine/jamgram.cpp src/engine/jamgram.y
|
- cd tools/build && bison -y -d -o src/engine/jamgram.cpp src/engine/jamgram.y
|
||||||
- ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log || cat bootstrap.log
|
- ./bootstrap.sh --prefix=$FLATPAK_DEST --with-libraries=system,thread,log,program_options || cat bootstrap.log
|
||||||
- ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS"
|
- ./b2 install variant=release link=static,shared runtime-link=shared cxxflags="$CXXFLAGS" linkflags="$LDFLAGS"
|
||||||
-j $FLATPAK_BUILDER_N_JOBS
|
-j $FLATPAK_BUILDER_N_JOBS
|
||||||
sources:
|
sources:
|
||||||
|
|||||||
@@ -17,6 +17,20 @@
|
|||||||
struct sockaddr;
|
struct sockaddr;
|
||||||
struct AVFrame;
|
struct AVFrame;
|
||||||
|
|
||||||
|
// Forward declarations of boost classes to avoid having to include boost headers
|
||||||
|
// here, which results in issues with Windows.h and WinSock2.h include order.
|
||||||
|
namespace boost {
|
||||||
|
namespace filesystem {
|
||||||
|
class path;
|
||||||
|
}
|
||||||
|
namespace process {
|
||||||
|
class child;
|
||||||
|
template<typename Char>
|
||||||
|
class basic_environment;
|
||||||
|
typedef basic_environment<char> environment;
|
||||||
|
} // namespace process
|
||||||
|
} // namespace boost
|
||||||
|
|
||||||
namespace platf {
|
namespace platf {
|
||||||
constexpr auto MAX_GAMEPADS = 32;
|
constexpr auto MAX_GAMEPADS = 32;
|
||||||
|
|
||||||
@@ -289,6 +303,8 @@ std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &
|
|||||||
// A list of names of displays accepted as display_name with the mem_type_e
|
// A list of names of displays accepted as display_name with the mem_type_e
|
||||||
std::vector<std::string> display_names(mem_type_e hwdevice_type);
|
std::vector<std::string> display_names(mem_type_e hwdevice_type);
|
||||||
|
|
||||||
|
boost::process::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, boost::process::environment &env, FILE *file, std::error_code &ec);
|
||||||
|
|
||||||
input_t input();
|
input_t input();
|
||||||
void move_mouse(input_t &input, int deltaX, int deltaY);
|
void move_mouse(input_t &input, int deltaX, int deltaY);
|
||||||
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
|
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y);
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include "src/main.h"
|
#include "src/main.h"
|
||||||
#include "src/platform/common.h"
|
#include "src/platform/common.h"
|
||||||
|
|
||||||
|
#include <boost/process.hpp>
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#define SUNSHINE_GNUC_EXTENSION __extension__
|
#define SUNSHINE_GNUC_EXTENSION __extension__
|
||||||
#else
|
#else
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
namespace bp = boost::process;
|
||||||
|
|
||||||
window_system_e window_system;
|
window_system_e window_system;
|
||||||
|
|
||||||
@@ -140,6 +143,16 @@ std::string get_mac_address(const std::string_view &address) {
|
|||||||
return "00:00:00:00:00:00"s;
|
return "00:00:00:00:00:00"s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) {
|
||||||
|
BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
|
||||||
|
if(!file) {
|
||||||
|
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace source {
|
namespace source {
|
||||||
enum source_e : std::size_t {
|
enum source_e : std::size_t {
|
||||||
#ifdef SUNSHINE_BUILD_CUDA
|
#ifdef SUNSHINE_BUILD_CUDA
|
||||||
|
|||||||
@@ -9,10 +9,14 @@
|
|||||||
#include "src/main.h"
|
#include "src/main.h"
|
||||||
#include "src/platform/common.h"
|
#include "src/platform/common.h"
|
||||||
|
|
||||||
|
#include <boost/process.hpp>
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
namespace bp = boost::process;
|
||||||
|
|
||||||
namespace platf {
|
namespace platf {
|
||||||
|
|
||||||
std::unique_ptr<deinit_t> init() {
|
std::unique_ptr<deinit_t> init() {
|
||||||
if(!CGPreflightScreenCaptureAccess()) {
|
if(!CGPreflightScreenCaptureAccess()) {
|
||||||
BOOST_LOG(error) << "No screen capture permission!"sv;
|
BOOST_LOG(error) << "No screen capture permission!"sv;
|
||||||
@@ -116,6 +120,17 @@ std::string get_mac_address(const std::string_view &address) {
|
|||||||
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
|
BOOST_LOG(warning) << "Unable to find MAC address for "sv << address;
|
||||||
return "00:00:00:00:00:00"s;
|
return "00:00:00:00:00:00"s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) {
|
||||||
|
BOOST_LOG(warning) << "run_unprivileged() is not yet implemented for this platform. The new process will run with Sunshine's permissions."sv;
|
||||||
|
if(!file) {
|
||||||
|
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return bp::child(cmd, env, bp::start_dir(working_dir), bp::std_out > file, bp::std_err > file, ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace platf
|
} // namespace platf
|
||||||
|
|
||||||
namespace dyn {
|
namespace dyn {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <boost/process.hpp>
|
||||||
|
|
||||||
// prevent clang format from "optimizing" the header include order
|
// prevent clang format from "optimizing" the header include order
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -10,11 +12,14 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winuser.h>
|
#include <winuser.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
|
#include <userenv.h>
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
#include "src/main.h"
|
#include "src/main.h"
|
||||||
#include "src/utility.h"
|
#include "src/utility.h"
|
||||||
|
|
||||||
|
namespace bp = boost::process;
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace platf {
|
namespace platf {
|
||||||
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
using adapteraddrs_t = util::c_ptr<IP_ADAPTER_ADDRESSES>;
|
||||||
@@ -120,4 +125,277 @@ void print_status(const std::string_view &prefix, HRESULT status) {
|
|||||||
|
|
||||||
BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes };
|
BOOST_LOG(error) << prefix << ": "sv << std::string_view { err_string, bytes };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::wstring utf8_to_wide_string(const std::string &str) {
|
||||||
|
// Determine the size required for the destination string
|
||||||
|
int chars = MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), NULL, 0);
|
||||||
|
|
||||||
|
// Allocate it
|
||||||
|
wchar_t buffer[chars] = {};
|
||||||
|
|
||||||
|
// Do the conversion for real
|
||||||
|
chars = MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), buffer, chars);
|
||||||
|
return std::wstring(buffer, chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string wide_to_utf8_string(const std::wstring &str) {
|
||||||
|
// Determine the size required for the destination string
|
||||||
|
int bytes = WideCharToMultiByte(CP_UTF8, 0, str.data(), str.length(), NULL, 0, NULL, NULL);
|
||||||
|
|
||||||
|
// Allocate it
|
||||||
|
char buffer[bytes] = {};
|
||||||
|
|
||||||
|
// Do the conversion for real
|
||||||
|
bytes = WideCharToMultiByte(CP_UTF8, 0, str.data(), str.length(), buffer, bytes, NULL, NULL);
|
||||||
|
return std::string(buffer, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE duplicate_shell_token() {
|
||||||
|
// Get the shell window (will usually be owned by explorer.exe)
|
||||||
|
HWND shell_window = GetShellWindow();
|
||||||
|
if(!shell_window) {
|
||||||
|
BOOST_LOG(error) << "No shell window found. Is explorer.exe running?"sv;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a handle to the explorer.exe process
|
||||||
|
DWORD shell_pid;
|
||||||
|
GetWindowThreadProcessId(shell_window, &shell_pid);
|
||||||
|
HANDLE shell_process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, shell_pid);
|
||||||
|
if(!shell_process) {
|
||||||
|
BOOST_LOG(error) << "Failed to open shell process: "sv << GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open explorer's token to clone for process creation
|
||||||
|
HANDLE shell_token;
|
||||||
|
BOOL ret = OpenProcessToken(shell_process, TOKEN_DUPLICATE, &shell_token);
|
||||||
|
CloseHandle(shell_process);
|
||||||
|
if(!ret) {
|
||||||
|
BOOST_LOG(error) << "Failed to open shell process token: "sv << GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate the token to make it usable for process creation
|
||||||
|
HANDLE new_token;
|
||||||
|
ret = DuplicateTokenEx(shell_token, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &new_token);
|
||||||
|
CloseHandle(shell_token);
|
||||||
|
if(!ret) {
|
||||||
|
BOOST_LOG(error) << "Failed to duplicate shell process token: "sv << GetLastError();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool merge_user_environment_block(bp::environment &env, HANDLE shell_token) {
|
||||||
|
// Get the target user's environment block
|
||||||
|
PVOID env_block;
|
||||||
|
if(!CreateEnvironmentBlock(&env_block, shell_token, FALSE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the environment block and populate env
|
||||||
|
for(auto c = (PWCHAR)env_block; *c != UNICODE_NULL; c += wcslen(c) + 1) {
|
||||||
|
// Environment variable entries end with a null-terminator, so std::wstring() will get an entire entry.
|
||||||
|
std::string env_tuple = wide_to_utf8_string(std::wstring { c });
|
||||||
|
std::string env_name = env_tuple.substr(0, env_tuple.find('='));
|
||||||
|
std::string env_val = env_tuple.substr(env_tuple.find('=') + 1);
|
||||||
|
|
||||||
|
// Perform a case-insensitive search to see if this variable name already exists
|
||||||
|
auto itr = std::find_if(env.cbegin(), env.cend(),
|
||||||
|
[&](const auto &e) { return boost::iequals(e.get_name(), env_name); });
|
||||||
|
if(itr != env.cend()) {
|
||||||
|
// Use this existing name if it is already present to ensure we merge properly
|
||||||
|
env_name = itr->get_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the PATH variable, we will merge the values together
|
||||||
|
if(boost::iequals(env_name, "PATH")) {
|
||||||
|
env[env_name] = env_val + ";" + env[env_name].to_string();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Other variables will be superseded by those in the user's environment block
|
||||||
|
env[env_name] = env_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyEnvironmentBlock(env_block);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This does NOT append a null terminator
|
||||||
|
void append_string_to_environment_block(wchar_t *env_block, int &offset, const std::wstring &wstr) {
|
||||||
|
std::memcpy(&env_block[offset], wstr.data(), wstr.length() * sizeof(wchar_t));
|
||||||
|
offset += wstr.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring create_environment_block(bp::environment &env) {
|
||||||
|
int size = 0;
|
||||||
|
for(const auto &entry : env) {
|
||||||
|
auto name = entry.get_name();
|
||||||
|
auto value = entry.to_string();
|
||||||
|
size += utf8_to_wide_string(name).length() + 1 /* L'=' */ + utf8_to_wide_string(value).length() + 1 /* L'\0' */;
|
||||||
|
}
|
||||||
|
|
||||||
|
size += 1 /* L'\0' */;
|
||||||
|
|
||||||
|
wchar_t env_block[size];
|
||||||
|
int offset = 0;
|
||||||
|
for(const auto &entry : env) {
|
||||||
|
auto name = entry.get_name();
|
||||||
|
auto value = entry.to_string();
|
||||||
|
|
||||||
|
// Construct the NAME=VAL\0 string
|
||||||
|
append_string_to_environment_block(env_block, offset, utf8_to_wide_string(name));
|
||||||
|
env_block[offset++] = L'=';
|
||||||
|
append_string_to_environment_block(env_block, offset, utf8_to_wide_string(value));
|
||||||
|
env_block[offset++] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append a final null terminator
|
||||||
|
env_block[offset++] = L'\0';
|
||||||
|
|
||||||
|
return std::wstring(env_block, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
LPPROC_THREAD_ATTRIBUTE_LIST allocate_proc_thread_attr_list(DWORD attribute_count) {
|
||||||
|
SIZE_T size;
|
||||||
|
InitializeProcThreadAttributeList(NULL, attribute_count, 0, &size);
|
||||||
|
|
||||||
|
auto list = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, size);
|
||||||
|
if(list == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!InitializeProcThreadAttributeList(list, attribute_count, 0, &size)) {
|
||||||
|
HeapFree(GetProcessHeap(), 0, list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_proc_thread_attr_list(LPPROC_THREAD_ATTRIBUTE_LIST list) {
|
||||||
|
DeleteProcThreadAttributeList(list);
|
||||||
|
HeapFree(GetProcessHeap(), 0, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
bp::child run_unprivileged(const std::string &cmd, boost::filesystem::path &working_dir, bp::environment &env, FILE *file, std::error_code &ec) {
|
||||||
|
HANDLE shell_token = duplicate_shell_token();
|
||||||
|
if(!shell_token) {
|
||||||
|
// This can happen if the shell has crashed. Fail the launch rather than risking launching with
|
||||||
|
// Sunshine's permissions unmodified.
|
||||||
|
ec = std::make_error_code(std::errc::no_such_process);
|
||||||
|
return bp::child();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto token_close = util::fail_guard([shell_token]() {
|
||||||
|
CloseHandle(shell_token);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Populate env with user-specific environment variables
|
||||||
|
if(!merge_user_environment_block(env, shell_token)) {
|
||||||
|
ec = std::make_error_code(std::errc::not_enough_memory);
|
||||||
|
return bp::child();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most Win32 APIs can't consume UTF-8 strings directly, so we must convert them into UTF-16
|
||||||
|
std::wstring wcmd = utf8_to_wide_string(cmd);
|
||||||
|
std::wstring env_block = create_environment_block(env);
|
||||||
|
std::wstring start_dir = utf8_to_wide_string(working_dir.string());
|
||||||
|
|
||||||
|
STARTUPINFOEXW startup_info = {};
|
||||||
|
startup_info.StartupInfo.cb = sizeof(startup_info);
|
||||||
|
|
||||||
|
// Allocate a process attribute list with space for 1 element
|
||||||
|
startup_info.lpAttributeList = allocate_proc_thread_attr_list(1);
|
||||||
|
if(startup_info.lpAttributeList == NULL) {
|
||||||
|
ec = std::make_error_code(std::errc::not_enough_memory);
|
||||||
|
return bp::child();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attr_list_free = util::fail_guard([list = startup_info.lpAttributeList]() {
|
||||||
|
free_proc_thread_attr_list(list);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(file) {
|
||||||
|
HANDLE log_file_handle = (HANDLE)_get_osfhandle(_fileno(file));
|
||||||
|
|
||||||
|
// Populate std handles if the caller gave us a log file to use
|
||||||
|
startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||||
|
startup_info.StartupInfo.hStdInput = NULL;
|
||||||
|
startup_info.StartupInfo.hStdOutput = log_file_handle;
|
||||||
|
startup_info.StartupInfo.hStdError = log_file_handle;
|
||||||
|
|
||||||
|
// Allow the log file handle to be inherited by the child process (without inheriting all of
|
||||||
|
// our inheritable handles, such as our own log file handle created by SunshineSvc).
|
||||||
|
UpdateProcThreadAttribute(startup_info.lpAttributeList,
|
||||||
|
0,
|
||||||
|
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
|
||||||
|
&log_file_handle,
|
||||||
|
sizeof(log_file_handle),
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Impersonate the user when launching the process. This will ensure that appropriate access
|
||||||
|
// checks are done against the user token, not our SYSTEM token. It will also allow network
|
||||||
|
// shares and mapped network drives to be used as launch targets, since those credentials
|
||||||
|
// are stored per-user.
|
||||||
|
if(!ImpersonateLoggedOnUser(shell_token)) {
|
||||||
|
auto winerror = GetLastError();
|
||||||
|
BOOST_LOG(error) << "Failed to impersonate user: "sv << winerror;
|
||||||
|
ec = std::make_error_code(std::errc::permission_denied);
|
||||||
|
return bp::child();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch the process with the duplicated shell token.
|
||||||
|
// Set CREATE_BREAKAWAY_FROM_JOB to avoid the child being killed if SunshineSvc.exe is terminated.
|
||||||
|
// Set CREATE_NEW_CONSOLE to avoid writing stdout to Sunshine's log if 'file' is not specified.
|
||||||
|
PROCESS_INFORMATION process_info;
|
||||||
|
BOOL ret = CreateProcessAsUserW(shell_token,
|
||||||
|
NULL,
|
||||||
|
(LPWSTR)wcmd.c_str(),
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
!!(startup_info.StartupInfo.dwFlags & STARTF_USESTDHANDLES),
|
||||||
|
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB,
|
||||||
|
env_block.data(),
|
||||||
|
start_dir.empty() ? NULL : start_dir.c_str(),
|
||||||
|
(LPSTARTUPINFOW)&startup_info,
|
||||||
|
&process_info);
|
||||||
|
|
||||||
|
// End impersonation of the logged on user. If this fails (which is extremely unlikely),
|
||||||
|
// we will be running with an unknown user token. The only safe thing to do in that case
|
||||||
|
// is terminate ourselves.
|
||||||
|
if(!RevertToSelf()) {
|
||||||
|
auto winerror = GetLastError();
|
||||||
|
BOOST_LOG(fatal) << "Failed to revert to self after impersonation: "sv << winerror;
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ret) {
|
||||||
|
// Since we are always spawning a process with a less privileged token than ourselves,
|
||||||
|
// bp::child() should have no problem opening it with any access rights it wants.
|
||||||
|
auto child = bp::child((bp::pid_t)process_info.dwProcessId);
|
||||||
|
|
||||||
|
// Only close handles after bp::child() has opened the process. If the process terminates
|
||||||
|
// quickly, the PID could be reused if we close the process handle.
|
||||||
|
CloseHandle(process_info.hThread);
|
||||||
|
CloseHandle(process_info.hProcess);
|
||||||
|
|
||||||
|
BOOST_LOG(info) << cmd << " running with PID "sv << child.id();
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We must NOT try bp::child() here, since this case can potentially be induced by ACL
|
||||||
|
// manipulation (denying yourself execute permission) to cause an escalation of privilege.
|
||||||
|
auto winerror = GetLastError();
|
||||||
|
BOOST_LOG(error) << "Failed to launch process: "sv << winerror;
|
||||||
|
ec = std::make_error_code(std::errc::invalid_argument);
|
||||||
|
return bp::child();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace platf
|
} // namespace platf
|
||||||
+75
-31
@@ -10,12 +10,19 @@
|
|||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/program_options/parsers.hpp>
|
||||||
#include <boost/property_tree/json_parser.hpp>
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "platform/common.h"
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// _SH constants for _wfsopen()
|
||||||
|
#include <share.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace proc {
|
namespace proc {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace bp = boost::process;
|
namespace bp = boost::process;
|
||||||
@@ -35,7 +42,7 @@ void process_end(bp::child &proc, bp::group &proc_handle) {
|
|||||||
proc.wait();
|
proc.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_code &ec) {
|
int exe_with_full_privs(const std::string &cmd, bp::environment &env, file_t &file, std::error_code &ec) {
|
||||||
if(!file) {
|
if(!file) {
|
||||||
return bp::system(cmd, env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
return bp::system(cmd, env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
||||||
}
|
}
|
||||||
@@ -43,23 +50,46 @@ int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_c
|
|||||||
return bp::system(cmd, env, bp::std_out > file.get(), bp::std_err > file.get(), ec);
|
return bp::system(cmd, env, bp::std_out > file.get(), bp::std_err > file.get(), ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
int proc_t::execute(int app_id) {
|
boost::filesystem::path find_working_directory(const std::string &cmd, bp::environment &env) {
|
||||||
if(!running() && _app_id != -1) {
|
// Parse the raw command string into parts to get the actual command portion
|
||||||
// previous process exited on its own, reset _process_handle
|
#ifdef _WIN32
|
||||||
_process_handle = bp::group();
|
auto parts = boost::program_options::split_winmain(cmd);
|
||||||
|
#else
|
||||||
_app_id = -1;
|
auto parts = boost::program_options::split_unix(cmd);
|
||||||
|
#endif
|
||||||
|
if(parts.empty()) {
|
||||||
|
BOOST_LOG(error) << "Unable to parse command: "sv << cmd;
|
||||||
|
return boost::filesystem::path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(debug) << "Parsed executable ["sv << parts.at(0) << "] from command ["sv << cmd << ']';
|
||||||
|
|
||||||
|
// If the cmd path is not a complete path, resolve it using our PATH variable
|
||||||
|
boost::filesystem::path cmd_path(parts.at(0));
|
||||||
|
if(!cmd_path.is_complete()) {
|
||||||
|
cmd_path = boost::process::search_path(parts.at(0));
|
||||||
|
if(cmd_path.empty()) {
|
||||||
|
BOOST_LOG(error) << "Unable to find executable ["sv << parts.at(0) << "]. Is it in your PATH?"sv;
|
||||||
|
return boost::filesystem::path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(debug) << "Resolved executable ["sv << parts.at(0) << "] to path ["sv << cmd_path << ']';
|
||||||
|
|
||||||
|
// Now that we have a complete path, we can just use parent_path()
|
||||||
|
return cmd_path.parent_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
int proc_t::execute(int app_id) {
|
||||||
|
// Ensure starting from a clean slate
|
||||||
|
terminate();
|
||||||
|
|
||||||
if(app_id < 0 || app_id >= _apps.size()) {
|
if(app_id < 0 || app_id >= _apps.size()) {
|
||||||
BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']';
|
BOOST_LOG(error) << "Couldn't find app with ID ["sv << app_id << ']';
|
||||||
|
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure starting from a clean slate
|
|
||||||
terminate();
|
|
||||||
|
|
||||||
_app_id = app_id;
|
_app_id = app_id;
|
||||||
auto &proc = _apps[app_id];
|
auto &proc = _apps[app_id];
|
||||||
|
|
||||||
@@ -67,7 +97,19 @@ int proc_t::execute(int app_id) {
|
|||||||
_undo_it = _undo_begin;
|
_undo_it = _undo_begin;
|
||||||
|
|
||||||
if(!proc.output.empty() && proc.output != "null"sv) {
|
if(!proc.output.empty() && proc.output != "null"sv) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
// fopen() interprets the filename as an ANSI string on Windows, so we must convert it
|
||||||
|
// to UTF-16 and use the wchar_t variants for proper Unicode log file path support.
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
|
||||||
|
auto woutput = converter.from_bytes(proc.output);
|
||||||
|
|
||||||
|
// Use _SH_DENYNO to allow us to open this log file again for writing even if it is
|
||||||
|
// still open from a previous execution. This is required to handle the case of a
|
||||||
|
// detached process executing again while the previous process is still running.
|
||||||
|
_pipe.reset(_wfsopen(woutput.c_str(), L"a", _SH_DENYNO));
|
||||||
|
#else
|
||||||
_pipe.reset(fopen(proc.output.c_str(), "a"));
|
_pipe.reset(fopen(proc.output.c_str(), "a"));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
@@ -80,7 +122,7 @@ int proc_t::execute(int app_id) {
|
|||||||
auto &cmd = _undo_it->do_cmd;
|
auto &cmd = _undo_it->do_cmd;
|
||||||
|
|
||||||
BOOST_LOG(info) << "Executing: ["sv << cmd << ']';
|
BOOST_LOG(info) << "Executing: ["sv << cmd << ']';
|
||||||
auto ret = exe(cmd, _env, _pipe, ec);
|
auto ret = exe_with_full_privs(cmd, _env, _pipe, ec);
|
||||||
|
|
||||||
if(ec) {
|
if(ec) {
|
||||||
BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
|
BOOST_LOG(error) << "Couldn't run ["sv << cmd << "]: System: "sv << ec.message();
|
||||||
@@ -94,17 +136,17 @@ int proc_t::execute(int app_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(auto &cmd : proc.detached) {
|
for(auto &cmd : proc.detached) {
|
||||||
BOOST_LOG(info) << "Spawning ["sv << cmd << ']';
|
boost::filesystem::path working_dir = proc.working_dir.empty() ?
|
||||||
if(proc.output.empty() || proc.output == "null"sv) {
|
find_working_directory(cmd, _env) :
|
||||||
bp::spawn(cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
boost::filesystem::path(proc.working_dir);
|
||||||
}
|
BOOST_LOG(info) << "Spawning ["sv << cmd << "] in ["sv << working_dir << ']';
|
||||||
else {
|
auto child = platf::run_unprivileged(cmd, working_dir, _env, _pipe.get(), ec);
|
||||||
bp::spawn(cmd, _env, bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ec) {
|
if(ec) {
|
||||||
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
|
BOOST_LOG(warning) << "Couldn't spawn ["sv << cmd << "]: System: "sv << ec.message();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
child.detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(proc.cmd.empty()) {
|
if(proc.cmd.empty()) {
|
||||||
@@ -113,23 +155,18 @@ int proc_t::execute(int app_id) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
boost::filesystem::path working_dir = proc.working_dir.empty() ?
|
boost::filesystem::path working_dir = proc.working_dir.empty() ?
|
||||||
boost::filesystem::path(proc.cmd).parent_path() :
|
find_working_directory(proc.cmd, _env) :
|
||||||
boost::filesystem::path(proc.working_dir);
|
boost::filesystem::path(proc.working_dir);
|
||||||
if(proc.output.empty() || proc.output == "null"sv) {
|
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << "] in ["sv << working_dir << ']';
|
||||||
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
|
_process = platf::run_unprivileged(proc.cmd, working_dir, _env, _pipe.get(), ec);
|
||||||
_process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > bp::null, bp::std_err > bp::null, ec);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
BOOST_LOG(info) << "Executing: ["sv << proc.cmd << ']';
|
|
||||||
_process = bp::child(_process_handle, proc.cmd, _env, bp::start_dir(working_dir), bp::std_out > _pipe.get(), bp::std_err > _pipe.get(), ec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ec) {
|
if(ec) {
|
||||||
BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
|
BOOST_LOG(warning) << "Couldn't run ["sv << proc.cmd << "]: System: "sv << ec.message();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_process_handle.add(_process);
|
||||||
|
}
|
||||||
|
|
||||||
fg.disable();
|
fg.disable();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -140,6 +177,11 @@ int proc_t::running() {
|
|||||||
return _app_id;
|
return _app_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform cleanup actions now if needed
|
||||||
|
if(_process) {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +191,8 @@ void proc_t::terminate() {
|
|||||||
// Ensure child process is terminated
|
// Ensure child process is terminated
|
||||||
placebo = false;
|
placebo = false;
|
||||||
process_end(_process, _process_handle);
|
process_end(_process, _process_handle);
|
||||||
|
_process = bp::child();
|
||||||
|
_process_handle = bp::group();
|
||||||
_app_id = -1;
|
_app_id = -1;
|
||||||
|
|
||||||
if(ec) {
|
if(ec) {
|
||||||
@@ -166,7 +210,7 @@ void proc_t::terminate() {
|
|||||||
|
|
||||||
BOOST_LOG(debug) << "Executing: ["sv << cmd << ']';
|
BOOST_LOG(debug) << "Executing: ["sv << cmd << ']';
|
||||||
|
|
||||||
auto ret = exe(cmd, _env, _pipe, ec);
|
auto ret = exe_with_full_privs(cmd, _env, _pipe, ec);
|
||||||
|
|
||||||
if(ec) {
|
if(ec) {
|
||||||
BOOST_LOG(fatal) << "System: "sv << ec.message();
|
BOOST_LOG(fatal) << "System: "sv << ec.message();
|
||||||
|
|||||||
Reference in New Issue
Block a user