0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00
openvpn3/openvpn/win/call.hpp
Lev Stipakov 980ef1eff8 win/call.hpp: re-encode command output to utf8
Command output can be in any encoding, for example cp866
in Russian edition of Windows and cp850 in English one.

This ensures that output is always utf8 encoded.

Signed-off-by: Lev Stipakov <lev@openvpn.net>
2019-05-06 17:10:12 +03:00

168 lines
5.9 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 Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero 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);
// console codepage, used to decode output
int console_cp = ::GetOEMCP();
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);
}
// decode output using console codepage, convert to utf16
UTF16 utf16output(Win::utf16(out, console_cp));
// re-encode utf16 to utf8
UTF8 utf8output(Win::utf8(utf16output.get()));
out.assign(utf8output.get());
// wait for child to exit
if (::WaitForSingleObject(process_hand(), INFINITE) == WAIT_FAILED)
throw win_call("WaitForSingleObject failed on child process handle");
return out;
}
}
}
#endif