/* * OpenVPN-GUI -- A Windows GUI for OpenVPN. * * Copyright (C) 2004 Mathias Sundman * 2010 Heiko Hund * 2016 Selva Nair * * 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 #endif #include #include "main.h" #include "openvpn-gui-res.h" #include "options.h" #include "localization.h" #include "save_pass.h" #include "misc.h" #include "passphrase.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) { int i; for (i = 0; i < o.num_configs; ++i) { if (_tcsicmp(o.conn[i].config_file, newconfig) == 0) return true; } return false; } static void AddConfigFileToList(int config, const TCHAR *filename, const TCHAR *config_dir) { connection_t *c = &o.conn[config]; int i; memset(c, 0, sizeof(*c)); _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 + config); #ifndef DISABLE_CHANGE_PASSWORD if (CheckKeyFileWriteAccess (c)) c->flags |= FLAG_ALLOW_CHANGE_PASSPHRASE; #endif 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 (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 (int i = 0; i < o.num_configs; i++) { CONFIG_GROUP(&o.conn[i])->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 (int i = 0; i < o.num_configs; i++) { config_group_t *cg = CONFIG_GROUP(&o.conn[i]); /* if not root and has only this config as child -- squash it */ if (PARENT_GROUP(cg) && cg->children == 1 && !wcscmp(cg->name, o.conn[i].config_name)) { cg->children--; o.conn[i].group = cg->parent; } } /* activate all groups that connect a config to the root */ for (int i = 0; i < o.num_configs; i++) { config_group_t *cg = CONFIG_GROUP(&o.conn[i]); 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 { if (!o.conn || o.num_configs == o.max_configs) { o.max_configs += 50; void *tmp = realloc(o.conn, sizeof(*o.conn)*o.max_configs); if (!tmp) { o.max_configs -= 50; FindClose(find_handle); ErrorExit(1, L"Out of memory while scanning configs"); } o.conn = tmp; } 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(o.num_configs, find_obj.cFileName, config_dir); o.conn[o.num_configs++].group = group; } } } 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; int root0 = 0; int max_configs = (1<<16) - o.mgmt_port_offset; if (o.silent_connection) issue_warnings = false; /* * If no connections are active reset num_configs 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_groups || CountConnState(disconnected) == o.num_configs) { o.num_configs = 0; o.num_groups = 0; flags |= FLAG_ADD_CONFIG_GROUPS; root0 = NewConfigGroup(L"ROOT", -1, flags); /* -1 indicates no parent */ } else root0 = 0; if (issue_warnings) { flags |= FLAG_WARN_DUPLICATES | FLAG_WARN_MAX_CONFIGS; } BuildFileList0 (o.config_dir, recurse_depth, root0, flags); int root1 = NewConfigGroup(L"System Profiles", root0, flags); if (!IsSamePath(o.global_config_dir, o.config_dir)) { BuildFileList0 (o.global_config_dir, recurse_depth, root1, flags); } if (o.service_state == service_connected && o.enable_persistent) { root1 = NewConfigGroup(L"Persistent Profiles", root0, flags); if (!IsSamePath(o.config_auto_dir, o.config_dir)) { BuildFileList0 (o.config_auto_dir, recurse_depth, root1, flags); } } if (o.num_configs == 0 && issue_warnings) ShowLocalizedMsg(IDS_NFO_NO_CONFIGS, o.config_dir, o.global_config_dir); /* More than max_configs are ignored in the menu listing */ if (o.num_configs > max_configs) { if (issue_warnings) ShowLocalizedMsg(IDS_ERR_MANY_CONFIGS, max_configs); o.num_configs = max_configs; /* management-port cant handle more -- ignore the rest */ } /* if adding groups, activate non-empty ones */ if (flags &FLAG_ADD_CONFIG_GROUPS) { ActivateConfigGroups(); } issue_warnings = false; }