mirror of https://github.com/OpenVPN/openvpn-gui
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
509 lines
14 KiB
509 lines
14 KiB
/* |
|
* OpenVPN-GUI -- A Windows GUI for OpenVPN. |
|
* |
|
* Copyright (C) 2004 Mathias Sundman <mathias@nilings.se> |
|
* 2010 Heiko Hund <heikoh@users.sf.net> |
|
* 2016 Selva Nair <selva.nair@gmail.com> |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* 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 (see the file COPYING included with this |
|
* distribution); if not, write to the Free Software Foundation, Inc., |
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
*/ |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include <config.h> |
|
#endif |
|
|
|
#include <windows.h> |
|
|
|
#include "main.h" |
|
#include "openvpn-gui-res.h" |
|
#include "options.h" |
|
#include "localization.h" |
|
#include "save_pass.h" |
|
#include "misc.h" |
|
|
|
typedef enum |
|
{ |
|
match_false, |
|
match_file, |
|
match_dir |
|
} match_t; |
|
|
|
extern options_t o; |
|
|
|
static match_t |
|
match(const WIN32_FIND_DATA *find, const TCHAR *ext) |
|
{ |
|
size_t ext_len = _tcslen(ext); |
|
int i; |
|
|
|
if (find->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) |
|
{ |
|
return match_dir; |
|
} |
|
|
|
if (ext_len == 0) |
|
{ |
|
return match_file; |
|
} |
|
|
|
i = _tcslen(find->cFileName) - ext_len - 1; |
|
|
|
if (i > 0 && find->cFileName[i] == '.' |
|
&& _tcsicmp(find->cFileName + i + 1, ext) == 0) |
|
{ |
|
return match_file; |
|
} |
|
|
|
return match_false; |
|
} |
|
|
|
static bool |
|
CheckReadAccess(const TCHAR *dir, const TCHAR *file) |
|
{ |
|
TCHAR path[MAX_PATH]; |
|
|
|
_sntprintf_0(path, _T("%ls\\%ls"), dir, file); |
|
|
|
return CheckFileAccess(path, GENERIC_READ); |
|
} |
|
|
|
static int |
|
ConfigAlreadyExists(TCHAR *newconfig) |
|
{ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
if (_tcsicmp(c->config_file, newconfig) == 0) |
|
{ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
static void |
|
AddConfigFileToList(int group, const TCHAR *filename, const TCHAR *config_dir) |
|
{ |
|
connection_t *c = calloc(1, sizeof(connection_t)); |
|
|
|
if (!c) |
|
{ |
|
ErrorExit(1, L"Out of memory in AddConfigFileToList"); |
|
} |
|
|
|
if (o.ctail) |
|
{ |
|
o.ctail->next = c; |
|
o.ctail = c; |
|
} |
|
else |
|
{ |
|
o.chead = o.ctail = c; |
|
} |
|
|
|
c->id = o.num_configs++; |
|
c->group = group; |
|
|
|
_tcsncpy(c->config_file, filename, _countof(c->config_file) - 1); |
|
_tcsncpy(c->config_dir, config_dir, _countof(c->config_dir) - 1); |
|
_tcsncpy(c->config_name, c->config_file, _countof(c->config_name) - 1); |
|
c->config_name[_tcslen(c->config_name) - _tcslen(o.ext_string) - 1] = _T('\0'); |
|
_sntprintf_0(c->log_path, _T("%ls\\%ls.log"), o.log_dir, c->config_name); |
|
|
|
c->manage.sk = INVALID_SOCKET; |
|
c->manage.skaddr.sin_family = AF_INET; |
|
c->manage.skaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
|
c->manage.skaddr.sin_port = htons(o.mgmt_port_offset + c->id); |
|
|
|
if (wcsstr(config_dir, o.config_auto_dir)) |
|
{ |
|
c->flags |= FLAG_DAEMON_PERSISTENT; |
|
_sntprintf_0(c->log_path, _T("%ls\\%ls.log"), o.global_log_dir, c->config_name); |
|
/* set to auto-connect -- this attempts to attach to them on startup */ |
|
if (o.enable_persistent == 2) |
|
{ |
|
c->auto_connect = true; |
|
} |
|
} |
|
|
|
/* Check if connection should be autostarted */ |
|
for (int i = 0; i < o.num_auto_connect; ++i) |
|
{ |
|
if (_tcsicmp(c->config_file, o.auto_connect[i]) == 0 |
|
|| _tcsicmp(c->config_name, o.auto_connect[i]) == 0) |
|
{ |
|
c->auto_connect = true; |
|
break; |
|
} |
|
} |
|
/* check whether passwords are saved */ |
|
if (o.disable_save_passwords) |
|
{ |
|
DisableSavePasswords(c); |
|
} |
|
else |
|
{ |
|
if (IsAuthPassSaved(c->config_name)) |
|
{ |
|
c->flags |= FLAG_SAVE_AUTH_PASS; |
|
} |
|
if (IsKeyPassSaved(c->config_name)) |
|
{ |
|
c->flags |= FLAG_SAVE_KEY_PASS; |
|
} |
|
} |
|
if (o.disable_popup_messages) |
|
{ |
|
DisablePopupMessages(c); |
|
} |
|
} |
|
|
|
#define FLAG_WARN_DUPLICATES (0x1) |
|
#define FLAG_WARN_MAX_CONFIGS (0x2) |
|
#define FLAG_ADD_CONFIG_GROUPS (0x4) |
|
|
|
/* |
|
* Create a new group with the given name as a child of the |
|
* specified parent group id and returns the id of the new group. |
|
* If FLAG_ADD_CONFIG_GROUPS is not enabled, returns the |
|
* parent itself. |
|
*/ |
|
static int |
|
NewConfigGroup(const wchar_t *name, int parent, int flags) |
|
{ |
|
if (!(flags & FLAG_ADD_CONFIG_GROUPS)) |
|
{ |
|
return parent; |
|
} |
|
|
|
if (!o.groups || o.num_groups == o.max_groups) |
|
{ |
|
o.max_groups += 10; |
|
void *tmp = realloc(o.groups, sizeof(*o.groups)*o.max_groups); |
|
if (!tmp) |
|
{ |
|
o.max_groups -= 10; |
|
ErrorExit(1, L"Out of memory while grouping configs"); |
|
} |
|
o.groups = tmp; |
|
} |
|
|
|
config_group_t *cg = &o.groups[o.num_groups]; |
|
memset(cg, 0, sizeof(*cg)); |
|
|
|
_sntprintf_0(cg->name, L"%ls", name); |
|
cg->id = o.num_groups++; |
|
cg->parent = parent; |
|
cg->active = false; /* activated later if not empty */ |
|
|
|
return cg->id; |
|
} |
|
|
|
/* |
|
* All groups that link at least one config to the root are |
|
* enabled. Dangling entries with no terminal configs will stay |
|
* disabled and are not displayed in the menu tree. |
|
* Also groups with single configs are squashed if the group |
|
* and config names match --- this improves the display. |
|
*/ |
|
static void |
|
ActivateConfigGroups(void) |
|
{ |
|
/* the root group is always active */ |
|
o.groups[0].active = true; |
|
|
|
/* children is a counter re-used for activation, menu indexing etc. -- reset before use */ |
|
for (int i = 0; i < o.num_groups; i++) |
|
{ |
|
o.groups[i].children = 0; |
|
} |
|
|
|
/* count children of each group -- this includes groups |
|
* and configs which have it as parent |
|
*/ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
CONFIG_GROUP(c)->children++; |
|
} |
|
|
|
for (int i = 1; i < o.num_groups; i++) |
|
{ |
|
config_group_t *this = &o.groups[i]; |
|
config_group_t *parent = PARENT_GROUP(this); |
|
if (parent) /* should be true as i = 0 is omitted */ |
|
{ |
|
parent->children++; |
|
} |
|
|
|
/* unless activated below the group stays inactive */ |
|
this->active = false; |
|
} |
|
|
|
/* Squash single config directories with name matching the config |
|
* one depth up. This is done so that automatically imported configs |
|
* which are added as a single config per directory are handled |
|
* as if its in the parent directory. This encourages the |
|
* practice of keeping each config and its dependencies (certs, |
|
* script etc.) in a separate directory, without making the menu structure |
|
* too deeply nested. |
|
*/ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
config_group_t *cg = CONFIG_GROUP(c); |
|
|
|
/* if not root and has only this config as child -- squash it */ |
|
if (PARENT_GROUP(cg) && cg->children == 1 |
|
&& !wcscmp(cg->name, c->config_name)) |
|
{ |
|
cg->children--; |
|
c->group = cg->parent; |
|
} |
|
} |
|
|
|
/* activate all groups that connect a config to the root */ |
|
for (connection_t *c = o.chead; c; c = c->next) |
|
{ |
|
config_group_t *cg = CONFIG_GROUP(c); |
|
|
|
while (cg) |
|
{ |
|
cg->active = true; |
|
cg = PARENT_GROUP(cg); |
|
} |
|
} |
|
} |
|
|
|
/* Scan for configs in config_dir recursing down up to recurse_depth. |
|
* Input: config_dir -- root of the directory to scan from |
|
* group -- the group into which add the configs to |
|
* flags -- enable warnings, use directory based |
|
* grouping of configs etc. |
|
* Currently configs in a directory are grouped together and group is |
|
* the id of the current group in the global group array |o.groups| |
|
* This may be recursively called until depth becomes 1 and each time |
|
* the group is changed to that of the directory being recursed into. |
|
*/ |
|
static void |
|
BuildFileList0(const TCHAR *config_dir, int recurse_depth, int group, int flags) |
|
{ |
|
WIN32_FIND_DATA find_obj; |
|
HANDLE find_handle; |
|
TCHAR find_string[MAX_PATH]; |
|
TCHAR subdir_name[MAX_PATH]; |
|
|
|
_sntprintf_0(find_string, _T("%ls\\*"), config_dir); |
|
find_handle = FindFirstFile(find_string, &find_obj); |
|
if (find_handle == INVALID_HANDLE_VALUE) |
|
{ |
|
return; |
|
} |
|
|
|
/* Loop over each config file in config dir */ |
|
do |
|
{ |
|
match_t match_type = match(&find_obj, o.ext_string); |
|
if (match_type == match_file) |
|
{ |
|
if (ConfigAlreadyExists(find_obj.cFileName)) |
|
{ |
|
if (flags & FLAG_WARN_DUPLICATES) |
|
{ |
|
ShowLocalizedMsg(IDS_ERR_CONFIG_EXIST, find_obj.cFileName); |
|
} |
|
continue; |
|
} |
|
|
|
if (CheckReadAccess(config_dir, find_obj.cFileName)) |
|
{ |
|
AddConfigFileToList(group, find_obj.cFileName, config_dir); |
|
} |
|
} |
|
} while (FindNextFile(find_handle, &find_obj)); |
|
|
|
FindClose(find_handle); |
|
|
|
/* optionally loop over each subdir */ |
|
if (recurse_depth < 1) |
|
{ |
|
return; |
|
} |
|
|
|
find_handle = FindFirstFile(find_string, &find_obj); |
|
if (find_handle == INVALID_HANDLE_VALUE) |
|
{ |
|
return; |
|
} |
|
|
|
do |
|
{ |
|
match_t match_type = match(&find_obj, o.ext_string); |
|
if (match_type == match_dir) |
|
{ |
|
if (wcscmp(find_obj.cFileName, _T(".")) |
|
&& wcscmp(find_obj.cFileName, _T(".."))) |
|
{ |
|
/* recurse into subdirectory */ |
|
_sntprintf_0(subdir_name, _T("%ls\\%ls"), config_dir, find_obj.cFileName); |
|
int sub_group = NewConfigGroup(find_obj.cFileName, group, flags); |
|
|
|
BuildFileList0(subdir_name, recurse_depth - 1, sub_group, flags); |
|
} |
|
} |
|
} while (FindNextFile(find_handle, &find_obj)); |
|
|
|
FindClose(find_handle); |
|
} |
|
|
|
/* |
|
* Open a path and get its file information structure. |
|
* Returns true on success, false on error. |
|
*/ |
|
static bool |
|
GetFileInfo(const wchar_t *path, BY_HANDLE_FILE_INFORMATION *info) |
|
{ |
|
bool ret = false; |
|
|
|
/* FILE_FLAG_BACKUP_SEMANTICS required to open directories */ |
|
HANDLE fd = CreateFileW(path, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, |
|
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
|
|
|
if (fd == INVALID_HANDLE_VALUE) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"GetFileInfo: Error opening path <%ls> (status = %lu)", path, GetLastError()); |
|
return ret; |
|
} |
|
|
|
ret = GetFileInformationByHandle(fd, info); |
|
if (!ret) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"GetFileInfo: Error accessing file information for path <%ls> (status = %lu)", path, GetLastError()); |
|
} |
|
else |
|
{ |
|
PrintDebug(L"path = <%ls> volumeid = %lu file index = (%lu,%lu)", path, info->dwVolumeSerialNumber, info->nFileIndexLow, info->nFileIndexHigh); |
|
} |
|
CloseHandle(fd); |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* Compare two paths by checking whether they point to the |
|
* same object in the file system. Returns true if the paths |
|
* are same, false otherwise. |
|
* If the two paths are identical strings return true early. |
|
* If any of the paths do not exist, are not accessible or |
|
* fail to provide file information, we return false. |
|
*/ |
|
static bool |
|
IsSamePath(const wchar_t *path1, const wchar_t *path2) |
|
{ |
|
BOOL ret = false; |
|
BY_HANDLE_FILE_INFORMATION info1, info2; |
|
|
|
if (_wcsicmp(path1, path2) == 0) |
|
{ |
|
return true; |
|
} |
|
|
|
if (GetFileInfo(path1, &info1) && GetFileInfo(path2, &info2)) |
|
{ |
|
ret = (info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber |
|
&& info1.nFileIndexLow == info2.nFileIndexLow |
|
&& info1.nFileIndexHigh == info2.nFileIndexHigh); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
void |
|
BuildFileList() |
|
{ |
|
static bool issue_warnings = true; |
|
int recurse_depth = 20; /* maximum number of levels below config_dir to recurse into */ |
|
int flags = 0; |
|
static int root_gp, system_gp, persistent_gp; |
|
|
|
if (o.silent_connection) |
|
{ |
|
issue_warnings = false; |
|
} |
|
|
|
/* |
|
* If first time or no entries in the connection list reset groups and rescan |
|
* to make a new list. Else we keep all current configs and |
|
* rescan to add any new one's found. |
|
*/ |
|
if (!o.num_configs) |
|
{ |
|
o.num_groups = 0; |
|
flags |= FLAG_ADD_CONFIG_GROUPS; |
|
root_gp = NewConfigGroup(L"ROOT", -1, flags); /* -1 indicates no parent */ |
|
persistent_gp = NewConfigGroup(L"Persistent Profiles", root_gp, flags); |
|
system_gp = NewConfigGroup(L"System Profiles", root_gp, flags); |
|
} |
|
/* else these parent groups use their saved values */ |
|
|
|
if (&o.groups[persistent_gp] != PERSISTENT_ROOT_GROUP) |
|
{ |
|
MsgToEventLog(EVENTLOG_ERROR_TYPE, L"%hs:%d Logic error", __func__, __LINE__); |
|
return; |
|
} |
|
|
|
if (issue_warnings) |
|
{ |
|
flags |= FLAG_WARN_DUPLICATES | FLAG_WARN_MAX_CONFIGS; |
|
} |
|
|
|
BuildFileList0(o.config_dir, recurse_depth, root_gp, flags); |
|
|
|
if (!IsSamePath(o.global_config_dir, o.config_dir)) |
|
{ |
|
BuildFileList0(o.global_config_dir, recurse_depth, system_gp, flags); |
|
} |
|
|
|
if (o.service_state == service_connected |
|
&& o.enable_persistent) |
|
{ |
|
if (!IsSamePath(o.config_auto_dir, o.config_dir)) |
|
{ |
|
BuildFileList0(o.config_auto_dir, recurse_depth, persistent_gp, flags); |
|
} |
|
} |
|
|
|
if (o.num_configs == 0 && issue_warnings) |
|
{ |
|
ShowLocalizedMsg(IDS_NFO_NO_CONFIGS, o.config_dir, o.global_config_dir); |
|
} |
|
|
|
ActivateConfigGroups(); |
|
|
|
issue_warnings = false; |
|
} |
|
|
|
void |
|
FreeConfigList(options_t *o) |
|
{ |
|
connection_t *next = NULL; |
|
for (connection_t *c = o->chead; c; c = next) |
|
{ |
|
next = c->next; |
|
free(c); |
|
} |
|
free(o->groups); |
|
o->groups = NULL; |
|
o->num_configs = 0; |
|
o->num_groups = 0; |
|
}
|
|
|