/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2011-2025 Heiko Hund <heiko.hund@sophos.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* 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; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "service.h"
#include "validate.h"
LPCWSTR service_instance = L"";
static wchar_t win_sys_path[MAX_PATH];
static DWORD
GetRegString(HKEY key, LPCWSTR value, LPWSTR data, DWORD size, LPCWSTR default_value)
{
LONG status = RegGetValue(key, NULL, value, RRF_RT_REG_SZ,
NULL, (LPBYTE) data, &size);
if (status == ERROR_FILE_NOT_FOUND && default_value)
{
size_t len = size/sizeof(data[0]);
if (swprintf(data, len, default_value))
{
status = ERROR_SUCCESS;
}
}
if (status != ERROR_SUCCESS)
{
SetLastError(status);
return MsgToEventLog(M_SYSERR, L"Error querying registry value: HKLM\\SOFTWARE\\" _L(PACKAGE_NAME) L"%ls\\%ls", service_instance, value);
}
return ERROR_SUCCESS;
}
DWORD
GetOpenvpnSettings(settings_t *s)
{
WCHAR reg_path[256];
WCHAR priority[64];
WCHAR append[2];
DWORD error;
HKEY key;
WCHAR install_path[MAX_PATH];
WCHAR default_value[MAX_PATH];
swprintf(reg_path, _countof(reg_path), L"SOFTWARE\\" _L(PACKAGE_NAME) L"%ls", service_instance);
LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &key);
if (status != ERROR_SUCCESS)
{
SetLastError(status);
return MsgToEventLog(M_SYSERR, L"Could not open Registry key HKLM\\%ls not found", reg_path);
}
/* The default value of REG_KEY is the install path */
status = GetRegString(key, NULL, install_path, sizeof(install_path), NULL);
if (status != ERROR_SUCCESS)
{
error = status;
goto out;
}
swprintf(default_value, _countof(default_value), L"%ls\\bin\\openvpn.exe",
install_path);
error = GetRegString(key, L"exe_path", s->exe_path, sizeof(s->exe_path), default_value);
if (error != ERROR_SUCCESS)
{
goto out;
}
swprintf(default_value, _countof(default_value), L"%ls\\config", install_path);
error = GetRegString(key, L"config_dir", s->config_dir, sizeof(s->config_dir),
default_value);
if (error != ERROR_SUCCESS)
{
goto out;
}
swprintf(default_value, _countof(default_value), L"%ls\\bin", install_path);
error = GetRegString(key, L"bin_dir", s->bin_dir, sizeof(s->bin_dir),
default_value);
if (error != ERROR_SUCCESS)
{
goto out;
}
error = GetRegString(key, L"config_ext", s->ext_string, sizeof(s->ext_string),
L".ovpn");
if (error != ERROR_SUCCESS)
{
goto out;
}
swprintf(default_value, _countof(default_value), L"%ls\\log", install_path);
error = GetRegString(key, L"log_dir", s->log_dir, sizeof(s->log_dir), default_value);
if (error != ERROR_SUCCESS)
{
goto out;
}
error = GetRegString(key, L"priority", priority, sizeof(priority),
L"NORMAL_PRIORITY_CLASS");
if (error != ERROR_SUCCESS)
{
goto out;
}
error = GetRegString(key, L"log_append", append, sizeof(append), L"0");
if (error != ERROR_SUCCESS)
{
goto out;
}
/* read if present, else use default */
error = GetRegString(key, L"ovpn_admin_group", s->ovpn_admin_group,
sizeof(s->ovpn_admin_group), OVPN_ADMIN_GROUP);
if (error != ERROR_SUCCESS)
{
goto out;
}
error = GetRegString(key, L"ovpn_service_user", s->ovpn_service_user,
sizeof(s->ovpn_service_user), OVPN_SERVICE_USER);
if (error != ERROR_SUCCESS)
{
goto out;
}
/* set process priority */
if (!_wcsicmp(priority, L"IDLE_PRIORITY_CLASS"))
{
s->priority = IDLE_PRIORITY_CLASS;
}
else if (!_wcsicmp(priority, L"BELOW_NORMAL_PRIORITY_CLASS"))
{
s->priority = BELOW_NORMAL_PRIORITY_CLASS;
}
else if (!_wcsicmp(priority, L"NORMAL_PRIORITY_CLASS"))
{
s->priority = NORMAL_PRIORITY_CLASS;
}
else if (!_wcsicmp(priority, L"ABOVE_NORMAL_PRIORITY_CLASS"))
{
s->priority = ABOVE_NORMAL_PRIORITY_CLASS;
}
else if (!_wcsicmp(priority, L"HIGH_PRIORITY_CLASS"))
{
s->priority = HIGH_PRIORITY_CLASS;
}
else
{
SetLastError(ERROR_INVALID_DATA);
error = MsgToEventLog(M_SYSERR, L"Unknown priority name: %ls", priority);
goto out;
}
/* set log file append/truncate flag */
if (append[0] == L'0')
{
s->append = FALSE;
}
else if (append[0] == L'1')
{
s->append = TRUE;
}
else
{
SetLastError(ERROR_INVALID_DATA);
error = MsgToEventLog(M_ERR, L"Log file append flag (given as '%ls') must be '0' or '1'", append);
goto out;
}
out:
RegCloseKey(key);
return error;
}
LPCWSTR
GetLastErrorText(void)
{
DWORD error;
static WCHAR buf[256];
DWORD len;
LPWSTR tmp = NULL;
error = GetLastError();
len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL, error, LANG_NEUTRAL, tmp, 0, NULL);
if (len == 0 || (long) _countof(buf) < (long) len + 14)
{
buf[0] = L'\0';
}
else
{
tmp[wcslen(tmp) - 2] = L'\0'; /* remove CR/LF characters */
swprintf(buf, _countof(buf), L"%ls (0x%x)", tmp, error);
}
if (tmp)
{
LocalFree(tmp);
}
return buf;
}
DWORD
MsgToEventLog(DWORD flags, LPCWSTR format, ...)
{
HANDLE hEventSource;
WCHAR msg[2][256];
DWORD error = 0;
LPCWSTR err_msg = L"";
va_list arglist;
if (flags & MSG_FLAGS_SYS_CODE)
{
error = GetLastError();
err_msg = GetLastErrorText();
}
hEventSource = RegisterEventSource(NULL, APPNAME);
if (hEventSource != NULL)
{
swprintf(msg[0], _countof(msg[0]),
L"%ls%ls%ls: %ls", APPNAME, service_instance,
(flags & MSG_FLAGS_ERROR) ? L" error" : L"", err_msg);
va_start(arglist, format);
vswprintf(msg[1], _countof(msg[1]), format, arglist);
va_end(arglist);
const WCHAR *mesg[] = { msg[0], msg[1] };
ReportEvent(hEventSource, flags & MSG_FLAGS_ERROR ?
EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE,
0, 0, NULL, 2, 0, mesg, NULL);
DeregisterEventSource(hEventSource);
}
return error;
}
wchar_t *
utf8to16_size(const char *utf8, int size)
{
int n = MultiByteToWideChar(CP_UTF8, 0, utf8, size, NULL, 0);
wchar_t *utf16 = malloc(n * sizeof(wchar_t));
if (!utf16)
{
return NULL;
}
MultiByteToWideChar(CP_UTF8, 0, utf8, size, utf16, n);
return utf16;
}
const wchar_t *
get_win_sys_path(void)
{
const wchar_t *default_sys_path = L"C:\\Windows\\system32";
if (!GetSystemDirectoryW(win_sys_path, _countof(win_sys_path)))
{
wcscpy_s(win_sys_path, _countof(win_sys_path), default_sys_path);
win_sys_path[_countof(win_sys_path) - 1] = L'\0';
}
return win_sys_path;
}