0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00
openvpn3/openvpn/win/call.hpp
James Yonan b2cd82a5bf copyright: updated to 2017.
Signed-off-by: James Yonan <james@openvpn.net>
2017-03-18 13:11:09 -06:00

158 lines
5.6 KiB
C++

// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012-2017 OpenVPN Technologies, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License Version 3
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program in the COPYING file.
// If not, see <http://www.gnu.org/licenses/>.
// execute a Windows command, capture the output
#ifndef OPENVPN_WIN_CALL_H
#define OPENVPN_WIN_CALL_H
#include <windows.h>
#include <shlobj.h>
#include <cstring>
#include <openvpn/common/uniqueptr.hpp>
#include <openvpn/win/scoped_handle.hpp>
namespace openvpn {
namespace Win {
OPENVPN_EXCEPTION(win_call);
inline std::string call(const std::string& cmd)
{
// split command name from args
std::string name;
std::string args;
const size_t spcidx = cmd.find_first_of(" ");
if (spcidx != std::string::npos)
{
name = cmd.substr(0, spcidx);
if (spcidx+1 < cmd.length())
args = cmd.substr(spcidx+1);
}
else
name = cmd;
#if _WIN32_WINNT >= 0x0600
// get system path (Vista and higher)
wchar_t *syspath_ptr = nullptr;
if (::SHGetKnownFolderPath(FOLDERID_System, 0, nullptr, &syspath_ptr) != S_OK)
throw win_call("cannot get system path using SHGetKnownFolderPath");
unique_ptr_del<wchar_t> syspath(syspath_ptr, [](wchar_t* p) { ::CoTaskMemFree(p); });
# define SYSPATH_FMT_CHAR L"s"
# define SYSPATH_LEN_METH(x) ::wcslen(x)
#else
// get system path (XP and higher)
std::unique_ptr<wchar_t[]> syspath(new wchar_t[MAX_PATH]);
if (::SHGetFolderPathW(nullptr, CSIDL_SYSTEM, nullptr, 0, syspath.get()) != S_OK)
throw win_call("cannot get system path using SHGetFolderPathW");
# define SYSPATH_FMT_CHAR L"s"
# define SYSPATH_LEN_METH(x) ::wcslen(x)
#endif
// build command line
const size_t wcmdlen = SYSPATH_LEN_METH(syspath.get()) + name.length() + args.length() + 64;
std::unique_ptr<wchar_t[]> wcmd(new wchar_t[wcmdlen]);
const char *spc = "";
if (!args.empty())
spc = " ";
::_snwprintf(wcmd.get(), wcmdlen, L"\"%" SYSPATH_FMT_CHAR L"\\%S.exe\"%S%S", syspath.get(), name.c_str(), spc, args.c_str());
wcmd.get()[wcmdlen-1] = 0;
//::wprintf(L"CMD[%d]: %s\n", (int)::wcslen(wcmd.get()), wcmd.get());
# undef SYSPATH_FMT_CHAR
# undef SYSPATH_LEN_METH
// Set the bInheritHandle flag so pipe handles are inherited.
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = nullptr;
// Create a pipe for the child process's STDOUT.
ScopedHANDLE cstdout_r; // child write side
ScopedHANDLE cstdout_w; // parent read side
if (!::CreatePipe(cstdout_r.ref(), cstdout_w.ref(), &saAttr, 0))
throw win_call("cannot create pipe for child stdout");
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!::SetHandleInformation(cstdout_r(), HANDLE_FLAG_INHERIT, 0))
throw win_call("SetHandleInformation failed for child stdout pipe");
// Set up members of the PROCESS_INFORMATION structure.
PROCESS_INFORMATION piProcInfo;
::ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
STARTUPINFOW siStartInfo;
::ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = cstdout_w();
siStartInfo.hStdOutput = cstdout_w();
siStartInfo.hStdInput = nullptr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
if (!::CreateProcessW(nullptr,
wcmd.get(), // command line
nullptr, // process security attributes
nullptr, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
nullptr, // use parent's environment
nullptr, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo)) // receives PROCESS_INFORMATION
throw win_call("cannot create process");
// wrap handles to child process and its primary thread.
ScopedHANDLE process_hand(piProcInfo.hProcess);
ScopedHANDLE thread_hand(piProcInfo.hThread);
// close child's end of stdout/stderr pipe
cstdout_w.close();
// read child's stdout
const size_t outbuf_size = 512;
std::unique_ptr<char[]> outbuf(new char[outbuf_size]);
std::string out;
while (true)
{
DWORD dwRead;
if (!::ReadFile(cstdout_r(), outbuf.get(), outbuf_size, &dwRead, nullptr))
break;
if (dwRead == 0)
break;
out += std::string(outbuf.get(), 0, dwRead);
}
// wait for child to exit
if (::WaitForSingleObject(process_hand(), INFINITE) == WAIT_FAILED)
throw win_call("WaitForSingleObject failed on child process handle");
return out;
}
}
}
#endif