diff --git a/as.c b/as.c index eac771b..07b02be 100644 --- a/as.c +++ b/as.c @@ -44,7 +44,7 @@ * Profile name is either (sorted in priority order): * - value of OVPN_ACCESS_SERVER_FRIENDLY_NAME * - value of OVPN_ACCESS_SERVER_PROFILE - * - URL + * - specified default_name * * @param profile profile content * @param default_name default name for profile if it doesn't contain name @@ -88,9 +88,10 @@ ExtractProfileName(const WCHAR *profile, const WCHAR *default_name, WCHAR *out_n out_name[out_name_length - 1] = L'\0'; /* sanitize profile name */ + const char *reserved = "<>:\"/\\|?*;"; /* remap these and ascii 1 to 31 */ while (*out_name) { wchar_t c = *out_name; - if (c == L'<' || c == L'>' || c == L':' || c == L'\"' || c == L'/' || c == L'\\' || c == L'|' || c == L'?' || c == L'*') + if (c < 32 || strchr(reserved, c)) *out_name = L'_'; ++out_name; } @@ -111,6 +112,7 @@ struct UrlComponents { int port; WCHAR host[URL_LEN]; + WCHAR path[URL_LEN]; bool https; }; @@ -124,27 +126,33 @@ ParseUrl(const WCHAR *url, struct UrlComponents* comps) { ZeroMemory(comps, sizeof(struct UrlComponents)); - int domain_off = 0; comps->port = 443; comps->https = true; if (wcsbegins(url, L"http://")) { - domain_off = 7; + url += 7; } else if (wcsbegins(url, L"https://")) { - domain_off = 8; + url +=8; } - WCHAR* strport = wcsstr(url + domain_off, L":"); + WCHAR *strport = wcsstr(url, L":"); + WCHAR *pathptr = wcsstr(url, L"/"); if (strport) { - WCHAR* end; - wcsncpy(comps->host, url + domain_off, strport - url - domain_off); - comps->port = wcstol(strport + 1, &end, 10); + wcsncpy_s(comps->host, URL_LEN, url, strport - url); + comps->port = wcstol(strport + 1, NULL, 10); } - else { - wcscpy(comps->host, url + domain_off); + else if (pathptr) + { + wcsncpy_s(comps->host, URL_LEN, url, pathptr - url); + } + else + { + wcsncpy_s(comps->host, URL_LEN, url, _TRUNCATE); } - if (comps->host[wcslen(comps->host) - 1] == L'/') - comps->host[wcslen(comps->host) - 1] = L'\0'; + if (pathptr) + { + wcsncpy_s(comps->path, URL_LEN, pathptr + 1, _TRUNCATE); + } } /** @@ -270,19 +278,38 @@ CRDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) } /** - * Download profile from AS and save it to a special-named temp file + * Construct the REST URL for AS profile * - * @param hWnd handle of window which initiated download * @param host AS hostname, entered by user, might contain protocol and port + * @param autologin should autologin profile be used + * @param comps Pointer to UrlComponents. Value assigned by this function. + */ +static void +GetASUrl(const WCHAR *host, bool autologin, struct UrlComponents *comps) +{ + + ParseUrl(host, comps); + + swprintf(comps->path, URL_LEN, L"/rest/%ls?tls-cryptv2=1&action=import", autologin ? L"GetAutologin" : L"GetUserlogin"); + comps->path[URL_LEN - 1] = L'\0'; +} + +/** + * Download profile from a generic URL and save it to a temp file + * + * @param hWnd handle of window which initiated download + * @param comps pointer to struct UrlComponents describing the URL * @param username UTF-8 encoded username used for HTTP basic auth * @param password UTF-8 encoded password used for HTTP basic auth - * @param autologin should autologin profile be used * @param out_path full path to where profile is downloaded. Value assigned by this function. * @param out_path_size number of elements in out_path arrray + * + * Filename in out_path is parsed from tags in received data + * with the url hostname as a fallback. */ -BOOL -DownloadProfile(HANDLE hWnd, const WCHAR *host, const char *username, const char *password_orig, - BOOL autologin, WCHAR *out_path, size_t out_path_size) +static BOOL +DownloadProfile(HANDLE hWnd, const struct UrlComponents *comps, const char *username, + const char *password_orig, WCHAR *out_path, size_t out_path_size) { HANDLE hInternet = NULL; HANDLE hConnect = NULL; @@ -290,9 +317,9 @@ DownloadProfile(HANDLE hWnd, const WCHAR *host, const char *username, const char BOOL result = FALSE; char* buf = NULL; + /* need to make copy of password to use it for dynamic response */ char password[USER_PASS_LEN] = { 0 }; - strncpy(password, password_orig, USER_PASS_LEN - 1); - password[USER_PASS_LEN - 1] = '\0'; + strncpy_s(password, _countof(password), password_orig, _TRUNCATE); hInternet = InternetOpenW(L"openvpn-gui/1.0", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (!hInternet) { @@ -300,22 +327,18 @@ DownloadProfile(HANDLE hWnd, const WCHAR *host, const char *username, const char goto done; } - struct UrlComponents comps = { 0 }; - ParseUrl(host, &comps); - /* wait cursor will be automatically reverted later */ SetCursor(LoadCursorW(0, IDC_WAIT)); - hConnect = InternetConnectW(hInternet, comps.host, comps.port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + hConnect = InternetConnectW(hInternet, comps->host, comps->port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); if (!hConnect) { ShowWinInetError(hWnd); goto done; } - WCHAR obj_name[URL_LEN] = { 0 }; - swprintf(obj_name, URL_LEN, L"/rest/%ls?tls-cryptv2=1&action=import", autologin ? L"GetAutologin" : L"GetUserlogin"); - obj_name[URL_LEN - 1] = L'\0'; - hRequest = HttpOpenRequestW(hConnect, NULL, obj_name, NULL, NULL, NULL, comps.https ? INTERNET_FLAG_SECURE : 0, 0); + DWORD req_flags = INTERNET_FLAG_RELOAD; /* load from server, do not use cached data */ + req_flags |= comps->https ? INTERNET_FLAG_SECURE : 0; + hRequest = HttpOpenRequestW(hConnect, NULL, comps->path, NULL, NULL, NULL, req_flags, 0); if (!hRequest) { ShowWinInetError(hWnd); goto done; @@ -423,7 +446,7 @@ again: MessageBoxW(hWnd, L"Failed to convert profile content to wchar", _T(PACKAGE_NAME), MB_OK); goto done; } - ExtractProfileName(wbuf, comps.host, name, MAX_PATH); + ExtractProfileName(wbuf, comps->host, name, MAX_PATH); free(wbuf); /* save profile content into tmp file */ @@ -460,38 +483,53 @@ done: return result; } +typedef enum { + server_as = 1, + server_generic = 2 +} server_type_t; + INT_PTR CALLBACK ImportProfileFromURLDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { WCHAR url[URL_LEN] = {0}; BOOL autologin = FALSE; + server_type_t type; switch (msg) { case WM_INITDIALOG: + type = (server_type_t) lParam; + SetProp(hwndDlg, cfgProp, (HANDLE)lParam); SetStatusWinIcon(hwndDlg, ID_ICO_APP); - /* disable OK button by default - not disabled in resources */ + if (type == server_generic) + { + /* Change window title and hide autologin checkbox */ + SetWindowTextW(hwndDlg, LoadLocalizedString(IDS_MENU_IMPORT_URL)); + ShowWindow(GetDlgItem(hwndDlg, ID_CHK_AUTOLOGIN), SW_HIDE); + } + /* disable OK button until required data is filled in */ EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); break; case WM_COMMAND: + type = (server_type_t) GetProp(hwndDlg, cfgProp); switch (LOWORD(wParam)) { case ID_EDT_AUTH_USER: case ID_EDT_AUTH_PASS: case ID_EDT_URL: if (HIWORD(wParam) == EN_UPDATE) { - /* enable OK button only if url and username are filled */ + /* enable OK button only if url and username (for AS only) are filled */ BOOL enableOK = GetWindowTextLengthW(GetDlgItem(hwndDlg, ID_EDT_URL)) - && GetWindowTextLengthW(GetDlgItem(hwndDlg, ID_EDT_AUTH_USER)); + && (type == server_generic + || GetWindowTextLengthW(GetDlgItem(hwndDlg, ID_EDT_AUTH_USER))); EnableWindow(GetDlgItem(hwndDlg, IDOK), enableOK); } break; case IDOK: - autologin = IsDlgButtonChecked(hwndDlg, ID_CHK_AUTOLOGIN) == BST_CHECKED; GetDlgItemTextW(hwndDlg, ID_EDT_URL, url, _countof(url)); @@ -504,7 +542,18 @@ ImportProfileFromURLDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lPa GetDlgItemTextUtf8(hwndDlg, ID_EDT_AUTH_PASS, &password, &password_len); WCHAR path[MAX_PATH + 1] = { 0 }; - BOOL downloaded = DownloadProfile(hwndDlg, url, username, password, autologin, path, _countof(path)); + struct UrlComponents comps = {0}; + if (type == server_as) + { + + autologin = IsDlgButtonChecked(hwndDlg, ID_CHK_AUTOLOGIN) == BST_CHECKED; + GetASUrl(url, autologin, &comps); + } + else + { + ParseUrl(url, &comps); + } + BOOL downloaded = DownloadProfile(hwndDlg, &comps, username, password, path, _countof(path)); if (username_len != 0) free(username); @@ -540,5 +589,10 @@ ImportProfileFromURLDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lPa void ImportConfigFromAS() { - LocalizedDialogBoxParam(ID_DLG_URL_PROFILE_IMPORT, ImportProfileFromURLDialogFunc, 0); -} \ No newline at end of file + LocalizedDialogBoxParam(ID_DLG_URL_PROFILE_IMPORT, ImportProfileFromURLDialogFunc, (LPARAM) server_as); +} + +void ImportConfigFromURL() +{ + LocalizedDialogBoxParam(ID_DLG_URL_PROFILE_IMPORT, ImportProfileFromURLDialogFunc, (LPARAM) server_generic); +} diff --git a/as.h b/as.h index 4a7439a..d404e47 100644 --- a/as.h +++ b/as.h @@ -19,4 +19,5 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -void ImportConfigFromAS(); \ No newline at end of file +void ImportConfigFromAS(); +void ImportConfigFromURL(); diff --git a/main.c b/main.c index 0ba2dbc..4146032 100644 --- a/main.c +++ b/main.c @@ -559,6 +559,9 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM else if (LOWORD(wParam) == IDM_IMPORT_AS) { ImportConfigFromAS(); } + else if (LOWORD(wParam) == IDM_IMPORT_URL) { + ImportConfigFromURL(); + } else if (LOWORD(wParam) == IDM_SETTINGS) { ShowSettingsDialog(); } diff --git a/openvpn-gui-res.h b/openvpn-gui-res.h index 16b870c..6a47e63 100644 --- a/openvpn-gui-res.h +++ b/openvpn-gui-res.h @@ -183,6 +183,7 @@ #define IDS_MENU_RECONNECT 1025 #define IDS_MENU_IMPORT_AS 1026 #define IDS_MENU_IMPORT_FILE 1027 +#define IDS_MENU_IMPORT_URL 1028 /* LogViewer Dialog */ #define IDS_ERR_START_LOG_VIEWER 1101 diff --git a/res/openvpn-gui-res-en.rc b/res/openvpn-gui-res-en.rc index 2aea42b..92a7e3a 100644 --- a/res/openvpn-gui-res-en.rc +++ b/res/openvpn-gui-res-en.rc @@ -264,14 +264,14 @@ BEGIN END /* URL Profile Import Dialog */ -ID_DLG_URL_PROFILE_IMPORT DIALOG 6, 18, 160, 95 +ID_DLG_URL_PROFILE_IMPORT DIALOG 6, 18, 249, 95 STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER | DS_SETFOREGROUND CAPTION "Import Profile from Access Server" FONT 8, "Microsoft Sans Serif" LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT BEGIN LTEXT "U&RL:", 201, 6, 9, 50, 10 - EDITTEXT ID_EDT_URL, 60, 6, 94, 12, ES_AUTOHSCROLL + EDITTEXT ID_EDT_URL, 60, 6, 183, 12, ES_AUTOHSCROLL LTEXT "&Username:", 202, 6, 26, 50, 10 EDITTEXT ID_EDT_AUTH_USER, 60, 23, 94, 12, ES_AUTOHSCROLL LTEXT "&Password:", ID_LTEXT_PASSWORD, 6, 42, 50, 10 @@ -295,6 +295,7 @@ BEGIN IDS_MENU_SERVICE "OpenVPN Service" IDS_MENU_IMPORT "Import" IDS_MENU_IMPORT_AS "Import from Access Server…" + IDS_MENU_IMPORT_URL "Import from URL…" IDS_MENU_IMPORT_FILE "Import file…" IDS_MENU_SETTINGS "Settings…" IDS_MENU_CLOSE "Exit" diff --git a/tray.c b/tray.c index 00d6009..1143c59 100644 --- a/tray.c +++ b/tray.c @@ -220,6 +220,7 @@ CreatePopupMenus() AppendMenu(hMenu, MF_POPUP, (UINT_PTR) hMenuImport, LoadLocalizedString(IDS_MENU_IMPORT)); AppendMenu(hMenuImport, MF_STRING, IDM_IMPORT_FILE, LoadLocalizedString(IDS_MENU_IMPORT_FILE)); AppendMenu(hMenuImport, MF_STRING, IDM_IMPORT_AS, LoadLocalizedString(IDS_MENU_IMPORT_AS)); + AppendMenu(hMenuImport, MF_STRING, IDM_IMPORT_URL, LoadLocalizedString(IDS_MENU_IMPORT_URL)); AppendMenu(hMenu, MF_STRING ,IDM_SETTINGS, LoadLocalizedString(IDS_MENU_SETTINGS)); AppendMenu(hMenu, MF_STRING ,IDM_CLOSE, LoadLocalizedString(IDS_MENU_CLOSE)); @@ -280,6 +281,7 @@ CreatePopupMenus() AppendMenu(hMenu, MF_POPUP, (UINT_PTR) hMenuImport, LoadLocalizedString(IDS_MENU_IMPORT)); AppendMenu(hMenuImport, MF_STRING, IDM_IMPORT_FILE, LoadLocalizedString(IDS_MENU_IMPORT_FILE)); AppendMenu(hMenuImport, MF_STRING, IDM_IMPORT_AS, LoadLocalizedString(IDS_MENU_IMPORT_AS)); + AppendMenu(hMenuImport, MF_STRING, IDM_IMPORT_URL, LoadLocalizedString(IDS_MENU_IMPORT_URL)); AppendMenu(hMenu, MF_STRING, IDM_SETTINGS, LoadLocalizedString(IDS_MENU_SETTINGS)); AppendMenu(hMenu, MF_STRING, IDM_CLOSE, LoadLocalizedString(IDS_MENU_CLOSE)); diff --git a/tray.h b/tray.h index e236abf..fbd465f 100644 --- a/tray.h +++ b/tray.h @@ -34,6 +34,7 @@ #define IDM_IMPORT 224 #define IDM_IMPORT_FILE 225 #define IDM_IMPORT_AS 226 +#define IDM_IMPORT_URL 227 #define IDM_CONNECTMENU 300 #define IDM_DISCONNECTMENU (1 + IDM_CONNECTMENU)