added process elevation support to the relauncher, very experimental, has some bugs.

This commit is contained in:
Nick Bolton 2012-07-28 02:59:20 +00:00
parent cf5a7d297d
commit 268f3a99bb
16 changed files with 246 additions and 150 deletions

View File

@ -27,14 +27,14 @@
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QGridLayout"> <layout class="QGridLayout">
<item row="6" column="6"> <item row="6" column="7">
<widget class="QPushButton" name="m_pButtonToggleStart"> <widget class="QPushButton" name="m_pButtonToggleStart">
<property name="text"> <property name="text">
<string>&amp;Start</string> <string>&amp;Start</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="7"> <item row="1" column="0" colspan="8">
<widget class="QGroupBox" name="m_pGroupServer"> <widget class="QGroupBox" name="m_pGroupServer">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -127,7 +127,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="7"> <item row="2" column="0" colspan="8">
<widget class="QGroupBox" name="m_pGroupClient"> <widget class="QGroupBox" name="m_pGroupClient">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -168,7 +168,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="7"> <item row="3" column="0" colspan="8">
<widget class="QGroupBox" name="m_pGroupLog"> <widget class="QGroupBox" name="m_pGroupLog">
<property name="title"> <property name="title">
<string>Log</string> <string>Log</string>
@ -223,7 +223,7 @@
<string/> <string/>
</property> </property>
<property name="pixmap"> <property name="pixmap">
<pixmap resource="Synergy.qrc">:/res/icons/16x16/warning.png</pixmap> <pixmap>:/res/icons/16x16/warning.png</pixmap>
</property> </property>
</widget> </widget>
</item> </item>
@ -237,6 +237,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="6">
<widget class="QCheckBox" name="m_pElevateCheckBox">
<property name="text">
<string>&amp;Elevate</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<action name="m_pActionAbout"> <action name="m_pActionAbout">
@ -323,9 +330,7 @@
</property> </property>
</action> </action>
</widget> </widget>
<resources> <resources/>
<include location="Synergy.qrc"/>
</resources>
<connections> <connections>
<connection> <connection>
<sender>m_pButtonToggleStart</sender> <sender>m_pButtonToggleStart</sender>

View File

@ -15,9 +15,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// this class is a duplicate of /src/lib/ipc/Ipc.cpp
#include "Ipc.h" #include "Ipc.h"
const char* kIpcMsgHello = "IHEL%1i"; const char* kIpcMsgHello = "IHEL%1i";
const char* kIpcMsgLogLine = "ILOG%s"; const char* kIpcMsgLogLine = "ILOG%s";
const char* kIpcMsgCommand = "ICMD%s"; const char* kIpcMsgCommand = "ICMD%s%1i";
const char* kIpcMsgShutdown = "ISDN"; const char* kIpcMsgShutdown = "ISDN";

View File

@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
// this class is a duplicate of /src/lib/ipc/Ipc.h
#pragma once #pragma once
#define IPC_HOST "127.0.0.1" #define IPC_HOST "127.0.0.1"

View File

@ -43,7 +43,7 @@ void IpcClient::connected()
{ {
char typeBuf[1]; char typeBuf[1];
typeBuf[0] = kIpcClientGui; typeBuf[0] = kIpcClientGui;
write(kIpcHello, 1, typeBuf); sendHello();
infoMessage("connection established"); infoMessage("connection established");
} }
@ -89,30 +89,34 @@ void IpcClient::retryConnect()
} }
} }
void IpcClient::write(int code, int length, const char* data) void IpcClient::sendHello()
{
QDataStream stream(m_Socket);
stream.writeRawData(kIpcMsgHello, 4);
char typeBuf[1];
typeBuf[0] = kIpcClientGui;
stream.writeRawData(typeBuf, 1);
}
void IpcClient::sendCommand(const QString& command, bool elevate)
{ {
QDataStream stream(m_Socket); QDataStream stream(m_Socket);
switch (code) { stream.writeRawData(kIpcMsgCommand, 4);
case kIpcHello:
stream.writeRawData(kIpcMsgHello, 4);
stream.writeRawData(data, 1);
break;
case kIpcCommand: { std::string stdStringCommand = command.toStdString();
char lenBuf[4]; const char* charCommand = stdStringCommand.c_str();
intToBytes(length, lenBuf, 4); int length = strlen(charCommand);
stream.writeRawData(kIpcMsgCommand, 4); char lenBuf[4];
stream.writeRawData(lenBuf, 4); intToBytes(length, lenBuf, 4);
stream.writeRawData(data, length); stream.writeRawData(lenBuf, 4);
break; stream.writeRawData(charCommand, length);
}
default: char elevateBuf[1];
std::cerr << "message type not supported: " << code << std::endl; elevateBuf[0] = elevate ? 1 : 0;
break; stream.writeRawData(elevateBuf, 1);
}
} }
void IpcClient::handleReadLogLine(const QString& text) void IpcClient::handleReadLogLine(const QString& text)

View File

@ -31,7 +31,8 @@ public:
IpcClient(); IpcClient();
virtual ~IpcClient(); virtual ~IpcClient();
void write(int code, int length, const char* data); void sendHello();
void sendCommand(const QString& command, bool elevate);
void connectToHost(); void connectToHost();
void disconnectFromHost(); void disconnectFromHost();

View File

@ -64,7 +64,9 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig) :
m_pTrayIcon(NULL), m_pTrayIcon(NULL),
m_pTrayIconMenu(NULL), m_pTrayIconMenu(NULL),
m_alreadyHidden(false), m_alreadyHidden(false),
m_SetupWizard(NULL) m_SetupWizard(NULL),
m_ElevateProcess(false),
m_SuppressElevateWarning(false)
{ {
setupUi(this); setupUi(this);
@ -85,6 +87,9 @@ MainWindow::MainWindow(QSettings& settings, AppConfig& appConfig) :
connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&))); connect(&m_IpcClient, SIGNAL(errorMessage(const QString&)), this, SLOT(appendLogError(const QString&)));
connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogNote(const QString&))); connect(&m_IpcClient, SIGNAL(infoMessage(const QString&)), this, SLOT(appendLogNote(const QString&)));
m_IpcClient.connectToHost(); m_IpcClient.connectToHost();
#else
// elevate checkbox is only useful on ms windows.
m_pElevateCheckBox->hide();
#endif #endif
} }
@ -133,13 +138,15 @@ void MainWindow::onModeChanged(bool firstRun, bool forceServiceApply)
else else
{ {
// cause the service to stop creating processes. // cause the service to stop creating processes.
sendDaemonCommand("", false); m_IpcClient.sendCommand("", false);
} }
if ((appConfig().processMode() == Desktop) && !firstRun && appConfig().autoConnect()) if ((appConfig().processMode() == Desktop) && !firstRun && appConfig().autoConnect())
{ {
startSynergy(); startSynergy();
} }
m_pElevateCheckBox->setEnabled(appConfig().processMode() == Service);
} }
void MainWindow::refreshStartButton() void MainWindow::refreshStartButton()
@ -221,6 +228,10 @@ void MainWindow::loadSettings()
m_pLineEditConfigFile->setText(settings().value("configFile", QDir::homePath() + "/" + synergyConfigName).toString()); m_pLineEditConfigFile->setText(settings().value("configFile", QDir::homePath() + "/" + synergyConfigName).toString());
m_pGroupClient->setChecked(settings().value("groupClientChecked", true).toBool()); m_pGroupClient->setChecked(settings().value("groupClientChecked", true).toBool());
m_pLineEditHostname->setText(settings().value("serverHostname").toString()); m_pLineEditHostname->setText(settings().value("serverHostname").toString());
m_SuppressElevateWarning = true;
m_pElevateCheckBox->setChecked(settings().value("elevateChecked", false).toBool());
m_SuppressElevateWarning = false;
} }
void MainWindow::initConnections() void MainWindow::initConnections()
@ -452,7 +463,7 @@ void MainWindow::startSynergy()
if (serviceMode) if (serviceMode)
{ {
QString command(app + " " + args.join(" ")); QString command(app + " " + args.join(" "));
sendDaemonCommand(command, true); m_IpcClient.sendCommand(command, m_ElevateProcess);
} }
} }
@ -702,14 +713,29 @@ void MainWindow::on_m_pButtonConfigureServer_clicked()
dlg.exec(); dlg.exec();
} }
void MainWindow::sendDaemonCommand(const QString& command, bool showErrors)
{
std::string s = command.toStdString();
const char* data = s.c_str();
m_IpcClient.write(kIpcCommand, strlen(data), data);
}
void MainWindow::on_m_pActionWizard_triggered() void MainWindow::on_m_pActionWizard_triggered()
{ {
m_SetupWizard->show(); m_SetupWizard->show();
} }
void MainWindow::on_m_pElevateCheckBox_toggled(bool checked)
{
if (checked && !m_SuppressElevateWarning) {
int r = QMessageBox::warning(
this, tr("Elevate Synergy"),
tr("Are you sure you want to elevate Synergy?\n\n"
"This allows Synergy to interact with elevated processes "
"and the UAC dialog, but can cause problems with non-elevated "
"processes. Elevate Synergy only if you really need to."),
QMessageBox::Yes | QMessageBox::No);
if (r != QMessageBox::Yes) {
m_pElevateCheckBox->setChecked(false);
return;
}
}
m_ElevateProcess = checked;
settings().setValue("elevateChecked", checked);
settings().sync();
}

View File

@ -104,6 +104,7 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase
void on_m_pActionAbout_triggered(); void on_m_pActionAbout_triggered();
void on_m_pActionSettings_triggered(); void on_m_pActionSettings_triggered();
void on_m_pActionWizard_triggered(); void on_m_pActionWizard_triggered();
void on_m_pElevateCheckBox_toggled(bool checked);
void synergyFinished(int exitCode, QProcess::ExitStatus); void synergyFinished(int exitCode, QProcess::ExitStatus);
void iconActivated(QSystemTrayIcon::ActivationReason reason); void iconActivated(QSystemTrayIcon::ActivationReason reason);
void startSynergy(); void startSynergy();
@ -132,7 +133,6 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase
bool clientArgs(QStringList& args, QString& app); bool clientArgs(QStringList& args, QString& app);
bool serverArgs(QStringList& args, QString& app); bool serverArgs(QStringList& args, QString& app);
void setStatus(const QString& status); void setStatus(const QString& status);
void sendDaemonCommand(const QString& command, bool showErrors);
void sendIpcMessage(qIpcMessageType type, const char* buffer, bool showErrors); void sendIpcMessage(qIpcMessageType type, const char* buffer, bool showErrors);
void onModeChanged(bool firstRun, bool forceServiceApply); void onModeChanged(bool firstRun, bool forceServiceApply);
@ -149,6 +149,8 @@ class MainWindow : public QMainWindow, public Ui::MainWindowBase
VersionChecker m_versionChecker; VersionChecker m_versionChecker;
SetupWizard* m_SetupWizard; SetupWizard* m_SetupWizard;
IpcClient m_IpcClient; IpcClient m_IpcClient;
bool m_ElevateProcess;
bool m_SuppressElevateWarning;
}; };
#endif #endif

View File

@ -173,10 +173,11 @@ CIpcCommandMessage*
CIpcClientProxy::parseCommand() CIpcClientProxy::parseCommand()
{ {
CString command; CString command;
CProtocolUtil::readf(&m_stream, kIpcMsgCommand + 4, &command); UInt8 elevate;
CProtocolUtil::readf(&m_stream, kIpcMsgCommand + 4, &command, &elevate);
// must be deleted by event handler. // must be deleted by event handler.
return new CIpcCommandMessage(command); return new CIpcCommandMessage(command, elevate != 0);
} }
void void

View File

@ -56,9 +56,10 @@ CIpcLogLineMessage::~CIpcLogLineMessage()
{ {
} }
CIpcCommandMessage::CIpcCommandMessage(const CString& command) : CIpcCommandMessage::CIpcCommandMessage(const CString& command, bool elevate) :
CIpcMessage(kIpcCommand), CIpcMessage(kIpcCommand),
m_command(command) m_command(command),
m_elevate(elevate)
{ {
} }

View File

@ -69,12 +69,16 @@ private:
class CIpcCommandMessage : public CIpcMessage { class CIpcCommandMessage : public CIpcMessage {
public: public:
CIpcCommandMessage(const CString& command); CIpcCommandMessage(const CString& command, bool elevate);
virtual ~CIpcCommandMessage(); virtual ~CIpcCommandMessage();
//! Gets the command. //! Gets the command.
CString command() const { return m_command; } CString command() const { return m_command; }
//! Gets whether or not the process should be elevated on MS Windows.
bool elevate() const { return m_elevate; }
private: private:
CString m_command; CString m_command;
bool m_elevate;
}; };

View File

@ -19,5 +19,5 @@
const char* kIpcMsgHello = "IHEL%1i"; const char* kIpcMsgHello = "IHEL%1i";
const char* kIpcMsgLogLine = "ILOG%s"; const char* kIpcMsgLogLine = "ILOG%s";
const char* kIpcMsgCommand = "ICMD%s"; const char* kIpcMsgCommand = "ICMD%s%1i";
const char* kIpcMsgShutdown = "ISDN"; const char* kIpcMsgShutdown = "ISDN";

View File

@ -33,7 +33,19 @@ enum EIpcClientType {
kIpcClientNode, kIpcClientNode,
}; };
// handshake: node/gui -> daemon
// $1 = type, the client identifies it's self as gui or node (synergyc/s).
extern const char* kIpcMsgHello; extern const char* kIpcMsgHello;
// log line: daemon -> gui
// $1 = aggregate log lines collected from synergys/c or the daemon itself.
extern const char* kIpcMsgLogLine; extern const char* kIpcMsgLogLine;
// command: gui -> daemon
// $1 = command; the command for the daemon to launch, typically the full
// path to synergys/c. $2 = true when process must be elevated on ms windows.
extern const char* kIpcMsgCommand; extern const char* kIpcMsgCommand;
// shutdown: daemon -> node
// the daemon tells synergys/c to shut down gracefully.
extern const char* kIpcMsgShutdown; extern const char* kIpcMsgShutdown;

View File

@ -33,6 +33,8 @@
#include <Tlhelp32.h> #include <Tlhelp32.h>
#include <UserEnv.h> #include <UserEnv.h>
#include <sstream> #include <sstream>
#include <Wtsapi32.h>
#include <Shellapi.h>
enum { enum {
kOutputBufferSize = 4096 kOutputBufferSize = 4096
@ -51,7 +53,8 @@ CMSWindowsRelauncher::CMSWindowsRelauncher(
m_stdOutWrite(NULL), m_stdOutWrite(NULL),
m_stdOutRead(NULL), m_stdOutRead(NULL),
m_ipcServer(ipcServer), m_ipcServer(ipcServer),
m_ipcLogOutputter(ipcLogOutputter) m_ipcLogOutputter(ipcLogOutputter),
m_elevateProcess(false)
{ {
} }
@ -90,7 +93,7 @@ CMSWindowsRelauncher::getSessionId()
} }
BOOL BOOL
CMSWindowsRelauncher::winlogonInSession(DWORD sessionId, PHANDLE process) CMSWindowsRelauncher::isProcessInSession(const char* name, DWORD sessionId, PHANDLE process)
{ {
// first we need to take a snapshot of the running processes // first we need to take a snapshot of the running processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
@ -138,9 +141,8 @@ CMSWindowsRelauncher::winlogonInSession(DWORD sessionId, PHANDLE process)
// store the names so we can record them for debug // store the names so we can record them for debug
nameList.push_back(entry.szExeFile); nameList.push_back(entry.szExeFile);
if (_stricmp(entry.szExeFile, "winlogon.exe") == 0) { if (_stricmp(entry.szExeFile, name) == 0) {
pid = entry.th32ProcessID; pid = entry.th32ProcessID;
break;
} }
} }
} }
@ -167,67 +169,97 @@ CMSWindowsRelauncher::winlogonInSession(DWORD sessionId, PHANDLE process)
nameListJoin.append(", "); nameListJoin.append(", ");
} }
LOG((CLOG_DEBUG "checked processes while looking for winlogon.exe: %s", LOG((CLOG_DEBUG "processes in session %d: %s",
nameListJoin.c_str())); sessionId, nameListJoin.c_str()));
CloseHandle(snapshot); CloseHandle(snapshot);
if (pid) { if (pid) {
// now get the process so we can get the process, with which // now get the process so we can get the process, with which
// we'll use to get the process token. // we'll use to get the process token.
LOG((CLOG_DEBUG "found %s in session %i", name, sessionId));
*process = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid); *process = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid);
return true; return true;
} }
else { else {
LOG((CLOG_DEBUG "could not find winlogon.exe in session %i", sessionId)); LOG((CLOG_DEBUG "could not find %s in session %i", name, sessionId));
return false; return false;
} }
} }
// gets the current user (so we can launch under their session)
HANDLE HANDLE
CMSWindowsRelauncher::getCurrentUserToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security) CMSWindowsRelauncher::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security)
{ {
HANDLE currentToken; HANDLE sourceToken;
HANDLE winlogonProcess;
if (winlogonInSession(sessionId, &winlogonProcess)) { BOOL tokenRet = OpenProcessToken(
process,
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
&sourceToken);
LOG((CLOG_DEBUG "session %i has winlogon.exe", sessionId)); if (!tokenRet) {
LOG((CLOG_ERR "could not open token, process handle: %d (error: %i)", process, GetLastError()));
// get the token, so we can re-launch with this token return NULL;
// -- do we really need all these access bits?
BOOL tokenRet = OpenProcessToken(
winlogonProcess,
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY |
TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY |
TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE,
&currentToken);
if (!tokenRet) {
LOG((CLOG_ERR "could not open token (error: %i)", GetLastError()));
return 0;
}
}
else {
LOG((CLOG_ERR "session %i does not have winlogon.exe "
"which is needed for re-launch", sessionId));
return 0;
} }
HANDLE primaryToken; LOG((CLOG_ERR "got token %i, duplicating", sourceToken));
HANDLE newToken;
BOOL duplicateRet = DuplicateTokenEx( BOOL duplicateRet = DuplicateTokenEx(
currentToken, MAXIMUM_ALLOWED, security, sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
SecurityImpersonation, TokenPrimary, &primaryToken); SecurityImpersonation, TokenPrimary, &newToken);
if (!duplicateRet) { if (!duplicateRet) {
LOG((CLOG_ERR "could not duplicate token %i (error: %i)", LOG((CLOG_ERR "could not duplicate token %i (error: %i)",
currentToken, GetLastError())); sourceToken, GetLastError()));
return NULL;
}
LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
return newToken;
}
// use either an elevated token (winlogon) or the user's session
// token (non-elevated). processes launched with a non-elevated token
// cannot interact with elevated processes.
HANDLE
CMSWindowsRelauncher::getUserToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security)
{
if (m_elevateProcess) {
HANDLE process;
if (isProcessInSession("winlogon.exe", sessionId, &process)) {
return duplicateProcessToken(process, security);
}
else {
LOG((CLOG_ERR "could not find token in session %d", sessionId));
return NULL;
}
}
else {
return getSessionToken(sessionId, security);
}
}
HANDLE
CMSWindowsRelauncher::getSessionToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security)
{
HANDLE sourceToken;
if (!WTSQueryUserToken(sessionId, &sourceToken)) {
LOG((CLOG_ERR "could not get token from session %d (error: %i)", sessionId, GetLastError()));
return 0; return 0;
} }
return primaryToken; HANDLE newToken;
if (!DuplicateTokenEx(
sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
SecurityImpersonation, TokenPrimary, &newToken)) {
LOG((CLOG_ERR "could not duplicate token (error: %i)", GetLastError()));
return 0;
}
LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
return newToken;
} }
void void
@ -319,67 +351,64 @@ CMSWindowsRelauncher::mainLoop(void*)
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
// get the token for the user in active session, which is the HANDLE userToken = getUserToken(sessionId, &sa);
// one receiving input from mouse and keyboard. if (userToken == NULL) {
HANDLE userToken = getCurrentUserToken(sessionId, &sa); // HACK: trigger retry mechanism.
launched = true;
continue;
}
if (userToken != 0) { std::string cmd = command();
LOG((CLOG_DEBUG "got user token to launch new process")); if (cmd == "") {
// this appears on first launch when the user hasn't configured
// anything yet, so don't show it as a warning, only show it as
// debug to devs to let them know why nothing happened.
LOG((CLOG_DEBUG "nothing to launch, no command specified."));
continue;
}
std::string cmd = command(); // in case reusing process info struct
if (cmd == "") { ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
// this appears on first launch when the user hasn't configured
// anything yet, so don't show it as a warning, only show it as
// debug to devs to let them know why nothing happened.
LOG((CLOG_DEBUG "nothing to launch, no command specified."));
continue;
}
// in case reusing process info struct STARTUPINFO si;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = "winsta0\\Default"; // TODO: maybe this should be \winlogon if we have logonui.exe?
si.hStdError = m_stdOutWrite;
si.hStdOutput = m_stdOutWrite;
si.dwFlags |= STARTF_USESTDHANDLES;
STARTUPINFO si; LPVOID environment;
ZeroMemory(&si, sizeof(STARTUPINFO)); BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
si.cb = sizeof(STARTUPINFO); if (!blockRet) {
si.lpDesktop = "winsta0\\default"; LOG((CLOG_ERR "could not create environment block (error: %i)",
si.hStdError = m_stdOutWrite; GetLastError()));
si.hStdOutput = m_stdOutWrite; continue;
si.dwFlags |= STARTF_USESTDHANDLES; }
LPVOID environment; DWORD creationFlags =
BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE); NORMAL_PRIORITY_CLASS |
if (!blockRet) { CREATE_NO_WINDOW |
LOG((CLOG_ERR "could not create environment block (error: %i)", CREATE_UNICODE_ENVIRONMENT;
GetLastError()));
continue;
}
else {
DWORD creationFlags = // re-launch in current active user session
NORMAL_PRIORITY_CLASS | LOG((CLOG_INFO "starting new process"));
CREATE_NO_WINDOW | BOOL createRet = CreateProcessAsUser(
CREATE_UNICODE_ENVIRONMENT; userToken, NULL, LPSTR(cmd.c_str()),
&sa, NULL, TRUE, creationFlags,
environment, NULL, &si, &pi);
// re-launch in current active user session DestroyEnvironmentBlock(environment);
LOG((CLOG_INFO "starting new process")); CloseHandle(userToken);
BOOL createRet = CreateProcessAsUser(
userToken, NULL, LPSTR(cmd.c_str()),
&sa, NULL, TRUE, creationFlags,
environment, NULL, &si, &pi);
DestroyEnvironmentBlock(environment); if (!createRet) {
CloseHandle(userToken); LOG((CLOG_ERR "could not launch (error: %i)", GetLastError()));
continue;
if (!createRet) { }
LOG((CLOG_ERR "could not launch (error: %i)", GetLastError())); else {
continue; LOG((CLOG_DEBUG "launched in session %i (cmd: %s)",
} sessionId, cmd.c_str()));
else { launched = true;
LOG((CLOG_DEBUG "launched in session %i (cmd: %s)",
sessionId, cmd.c_str()));
launched = true;
}
}
} }
} }
@ -406,11 +435,11 @@ CMSWindowsRelauncher::mainLoop(void*)
} }
void void
CMSWindowsRelauncher::command(const std::string& command) CMSWindowsRelauncher::command(const std::string& command, bool elevate)
{ {
LOG((CLOG_INFO "service command updated")); LOG((CLOG_INFO "service command updated"));
LOG((CLOG_DEBUG "new command: %s", command.c_str()));
m_command = command; m_command = command;
m_elevateProcess = elevate;
m_commandChanged = true; m_commandChanged = true;
} }

View File

@ -35,17 +35,19 @@ public:
virtual ~CMSWindowsRelauncher(); virtual ~CMSWindowsRelauncher();
void startAsync(); void startAsync();
std::string command() const; std::string command() const;
void command(const std::string& command); void command(const std::string& command, bool elevate);
void stop(); void stop();
private: private:
void mainLoop(void*); void mainLoop(void*);
BOOL winlogonInSession(DWORD sessionId, PHANDLE process); BOOL isProcessInSession(const char* name, DWORD sessionId, PHANDLE process);
DWORD getSessionId(); DWORD getSessionId();
HANDLE getCurrentUserToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security);
void outputLoop(void*); void outputLoop(void*);
void shutdownProcess(HANDLE handle, DWORD pid, int timeout); void shutdownProcess(HANDLE handle, DWORD pid, int timeout);
void shutdownExistingProcesses(); void shutdownExistingProcesses();
HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
HANDLE getSessionToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security);
HANDLE getUserToken(DWORD sessionId, LPSECURITY_ATTRIBUTES security);
private: private:
CThread* m_thread; CThread* m_thread;
@ -58,4 +60,5 @@ private:
CThread* m_outputThread; CThread* m_outputThread;
CIpcServer& m_ipcServer; CIpcServer& m_ipcServer;
CIpcLogOutputter& m_ipcLogOutputter; CIpcLogOutputter& m_ipcLogOutputter;
bool m_elevateProcess;
}; };

View File

@ -223,10 +223,11 @@ CDaemonApp::mainLoop(bool logToFile)
CGameDeviceInfo gameDevice; CGameDeviceInfo gameDevice;
CScreen dummyScreen(new CMSWindowsScreen(false, true, gameDevice)); CScreen dummyScreen(new CMSWindowsScreen(false, true, gameDevice));
string command = ARCH->setting("Command"); CString command = ARCH->setting("Command");
bool elevate = ARCH->setting("Elevate") == "1";
if (command != "") { if (command != "") {
LOG((CLOG_INFO "using last known command: %s", command.c_str())); LOG((CLOG_INFO "using last known command: %s", command.c_str()));
m_relauncher->command(command); m_relauncher->command(command, elevate);
} }
m_relauncher->startAsync(); m_relauncher->startAsync();
@ -295,7 +296,7 @@ CDaemonApp::handleIpcMessage(const CEvent& e, void*)
case kIpcCommand: { case kIpcCommand: {
CIpcCommandMessage* cm = static_cast<CIpcCommandMessage*>(m); CIpcCommandMessage* cm = static_cast<CIpcCommandMessage*>(m);
CString command = cm->command(); CString command = cm->command();
LOG((CLOG_DEBUG "got new command: %s", command.c_str())); LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str()));
CString debugArg("--debug"); CString debugArg("--debug");
int debugArgPos = command.find(debugArg); int debugArgPos = command.find(debugArg);
@ -319,16 +320,19 @@ CDaemonApp::handleIpcMessage(const CEvent& e, void*)
// store command in system settings. this is used when the daemon // store command in system settings. this is used when the daemon
// next starts. // next starts.
ARCH->setting("Command", command); ARCH->setting("Command", command);
// TODO: it would be nice to store bools/ints...
ARCH->setting("Elevate", CString(cm->elevate() ? "1" : "0"));
} }
catch (XArch& e) { catch (XArch& e) {
LOG((CLOG_ERR "failed to save Command setting, %s", e.what().c_str())); LOG((CLOG_ERR "failed to save settings, %s", e.what().c_str()));
} }
#if SYSAPI_WIN32 #if SYSAPI_WIN32
// tell the relauncher about the new command. this causes the // tell the relauncher about the new command. this causes the
// relauncher to stop the existing command and start the new // relauncher to stop the existing command and start the new
// command. // command.
m_relauncher->command(command); m_relauncher->command(command, cm->elevate());
#endif #endif
break; break;
} }

View File

@ -181,7 +181,7 @@ CIpcTests::connectToServer_handleMessageReceived(const CEvent& e, void*)
void void
CIpcTests::sendMessageToServer_handleClientConnected(const CEvent& e, void*) CIpcTests::sendMessageToServer_handleClientConnected(const CEvent& e, void*)
{ {
CIpcCommandMessage m("test"); CIpcCommandMessage m("test", true);
m_sendMessageToServer_client->send(m); m_sendMessageToServer_client->send(m);
} }