/* * OpenVPN-GUI -- A Windows GUI for OpenVPN. * * Copyright (C) 2004 Mathias Sundman * 2010 Heiko Hund * * 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 * * Parts of this sourcefile is taken from openvpnserv.c from the * OpenVPN source, with approval from the author, James Yonan * . */ #include #include #include #include #include #include #include "config.h" #include "tray.h" #include "main.h" #include "openvpn.h" #include "openvpn_monitor_process.h" #include "openvpn_config.h" #include "openvpn-gui-res.h" #include "options.h" #include "scripts.h" #include "viewlog.h" #include "proxy.h" #include "passphrase.h" #include "localization.h" extern options_t o; /* * Creates a unique exit_event name based on the * config file number. */ static BOOL CreateExitEvent(int config) { _sntprintf_0(o.conn[config].exit_event_name, _T("openvpngui_exit_event_%d"), config); o.conn[config].exit_event = CreateEvent(NULL, TRUE, FALSE, o.conn[config].exit_event_name); if (o.conn[config].exit_event == NULL) { /* error creating exit event */ ShowLocalizedMsg(IDS_ERR_CREATE_EVENT, o.conn[config].exit_event_name); return FALSE; } return TRUE; } /* * Set priority based on the registry or cmd-line value */ int SetProcessPriority(DWORD *priority) { /* set process priority */ *priority = NORMAL_PRIORITY_CLASS; if (!_tcscmp(o.priority_string, _T("IDLE_PRIORITY_CLASS"))) *priority = IDLE_PRIORITY_CLASS; else if (!_tcscmp(o.priority_string, _T("BELOW_NORMAL_PRIORITY_CLASS"))) *priority = BELOW_NORMAL_PRIORITY_CLASS; else if (!_tcscmp(o.priority_string, _T("NORMAL_PRIORITY_CLASS"))) *priority = NORMAL_PRIORITY_CLASS; else if (!_tcscmp(o.priority_string, _T("ABOVE_NORMAL_PRIORITY_CLASS"))) *priority = ABOVE_NORMAL_PRIORITY_CLASS; else if (!_tcscmp(o.priority_string, _T("HIGH_PRIORITY_CLASS"))) *priority = HIGH_PRIORITY_CLASS; else { /* unknown priority */ ShowLocalizedMsg(IDS_ERR_UNKNOWN_PRIORITY, o.priority_string); return (false); } return(true); } static BOOL GetPipeHandles(PHANDLE phInputRead, PHANDLE phInputWrite, PHANDLE phOutputRead, PHANDLE phOutputWrite) { HANDLE hProc = GetCurrentProcess(); HANDLE hOutputReadTmp, hInputWriteTmp; SECURITY_DESCRIPTOR sd; SECURITY_ATTRIBUTES sa; CLEAR(sa); CLEAR(sd); /* Make security attributes for the pipes so they can be inherited */ sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = TRUE; if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { ShowLocalizedMsg(IDS_ERR_INIT_SEC_DESC); return FALSE; } if (!SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE)) { ShowLocalizedMsg(IDS_ERR_SET_SEC_DESC_ACL); return FALSE; } /* Create the stdin pipe with uninheritable write end */ if (!CreatePipe(phInputRead, &hInputWriteTmp, &sa, 0)) { ShowLocalizedMsg(IDS_ERR_CREATE_PIPE_IN_READ); return FALSE; } if (!DuplicateHandle(hProc, hInputWriteTmp, hProc, phInputWrite, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { ShowLocalizedMsg(IDS_ERR_DUP_HANDLE_IN_WRITE); CloseHandle(*phInputRead); return FALSE; } /* Create the stdout pipe with uninheritable read end */ if (!CreatePipe(&hOutputReadTmp, phOutputWrite, &sa, 0)) { ShowLocalizedMsg(IDS_ERR_CREATE_PIPE_OUTPUT); CloseHandle(*phInputRead); CloseHandle(*phInputWrite); return FALSE; } if (!DuplicateHandle(hProc, hOutputReadTmp, hProc, phOutputRead, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { ShowLocalizedMsg(IDS_ERR_DUP_HANDLE_OUT_READ); CloseHandle(*phInputRead); CloseHandle(*phInputWrite); CloseHandle(*phOutputWrite); return FALSE; } return TRUE; } /* * Launch an OpenVPN process */ int StartOpenVPN(int config) { HANDLE hOutputRead = NULL; HANDLE hOutputWrite = NULL; HANDLE hInputRead = NULL; HANDLE hInputWrite = NULL; HANDLE hErrorWrite = NULL; HANDLE hThread; DWORD IDThread; DWORD priority; STARTUPINFO start_info; PROCESS_INFORMATION proc_info; TCHAR command_line[256]; TCHAR proxy_string[100]; CLEAR (start_info); CLEAR (proc_info); /* Warn if "log" or "log-append" option is found in config file */ if ((ConfigFileOptionExist(config, "log ")) || (ConfigFileOptionExist(config, "log-append "))) { if (MessageBox(NULL, LoadLocalizedString(IDS_ERR_OPTION_LOG_IN_CONFIG), _T(PACKAGE_NAME), MB_YESNO | MB_DEFBUTTON2 | MB_ICONWARNING) != IDYES) return(false); } /* Clear connection unique vars */ o.conn[config].failed_psw = 0; CLEAR (o.conn[config].ip); /* Create our exit event */ if (!CreateExitEvent(config)) return(false); /* set process priority */ if (!SetProcessPriority(&priority)) goto failed; /* Check that log append flag has a valid value */ if ((o.append_string[0] != '0') && (o.append_string[0] != '1')) { /* append_log must be 0 or 1 */ ShowLocalizedMsg(IDS_ERR_LOG_APPEND_BOOL, o.append_string); goto failed; } /* construct proxy string to append to command line */ ConstructProxyCmdLine(proxy_string, _tsizeof(proxy_string)); /* construct command line */ _sntprintf_0(command_line, _T("openvpn --service %s 0 --config \"%s\" %s"), o.conn[config].exit_event_name, o.conn[config].config_file, proxy_string); if (!GetPipeHandles(&hInputRead, &hInputWrite, &hOutputRead, &hOutputWrite)) return false; // Create a duplicate of the output write handle for the std error // write handle. This is necessary in case the child application // closes one of its std output handles. if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite, GetCurrentProcess(),&hErrorWrite,0, TRUE,DUPLICATE_SAME_ACCESS)) { /* DuplicateHandle failed. */ ShowLocalizedMsg(IDS_ERR_DUP_HANDLE_ERR_WRITE); goto failed; } /* fill in STARTUPINFO struct */ GetStartupInfo(&start_info); start_info.cb = sizeof(start_info); start_info.dwFlags = STARTF_USESTDHANDLES; start_info.hStdInput = hInputRead; start_info.hStdOutput = hOutputWrite; start_info.hStdError = hErrorWrite; /* Run Pre-connect script */ RunPreconnectScript(config); /* create an OpenVPN process for one config file */ if (!CreateProcess(o.exe_path, command_line, NULL, NULL, TRUE, priority | CREATE_NO_WINDOW, NULL, o.conn[config].config_dir, &start_info, &proc_info)) { /* CreateProcess failed */ ShowLocalizedMsg(IDS_ERR_CREATE_PROCESS, o.exe_path, command_line, o.conn[config].config_dir); goto failed; } /* close unneeded handles */ Sleep (250); /* try to prevent race if we close logfile handle before child process DUPs it */ if(!CloseHandle (proc_info.hThread) || !CloseHandle (hOutputWrite) || !CloseHandle (hInputRead) || !CloseHandle (hErrorWrite)) { /* CloseHandle failed */ ShowLocalizedMsg(IDS_ERR_CLOSE_HANDLE); CloseHandle (o.conn[config].exit_event); return(false); } hOutputWrite = NULL; hInputRead = NULL; hErrorWrite = NULL; /* Save StdIn and StdOut handles in our options struct */ o.conn[config].hStdIn = hInputWrite; o.conn[config].hStdOut = hOutputRead; /* Save Process Handle */ o.conn[config].hProcess=proc_info.hProcess; /* Start Thread to show Status Dialog */ hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadOpenVPNStatus, (int *) config, // pass config nr 0, &IDThread); if (hThread == NULL) { /* CreateThread failed */ ShowLocalizedMsg(IDS_ERR_CREATE_THREAD_STATUS); goto failed; } return(true); failed: if (o.conn[config].exit_event) CloseHandle (o.conn[config].exit_event); if (hOutputWrite) CloseHandle (hOutputWrite); if (hOutputRead) CloseHandle (hOutputRead); if (hInputWrite) CloseHandle (hInputWrite); if (hInputRead) CloseHandle (hInputRead); if (hErrorWrite) CloseHandle (hOutputWrite); return(false); } void StopOpenVPN(int config) { o.conn[config].state = disconnecting; if (o.conn[config].exit_event) { /* Run Disconnect script */ RunDisconnectScript(config, false); EnableWindow(GetDlgItem(o.conn[config].hwndStatus, ID_DISCONNECT), FALSE); EnableWindow(GetDlgItem(o.conn[config].hwndStatus, ID_RESTART), FALSE); SetMenuStatus(config, disconnecting); /* UserInfo: waiting for OpenVPN termination... */ SetDlgItemText(o.conn[config].hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM)); SetEvent(o.conn[config].exit_event); } } void SuspendOpenVPN(int config) { o.conn[config].state = suspending; o.conn[config].restart = true; if (o.conn[config].exit_event) { EnableWindow(GetDlgItem(o.conn[config].hwndStatus, ID_DISCONNECT), FALSE); EnableWindow(GetDlgItem(o.conn[config].hwndStatus, ID_RESTART), FALSE); SetMenuStatus(config, disconnecting); SetDlgItemText(o.conn[config].hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM)); SetEvent(o.conn[config].exit_event); } } void StopAllOpenVPN() { int i; for(i=0; i < o.num_configs; i++) { if(o.conn[i].state != disconnected) StopOpenVPN(i); } /* Wait for all connections to terminate (Max 5 sec) */ for (i=0; i<20; i++, Sleep(250)) if (CountConnState(disconnected) == o.num_configs) break; } BOOL CALLBACK StatusDialogFunc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { static const TCHAR cfgProp[] = _T("config"); HWND hwndLogWindow; RECT rect; CHARFORMAT charformat; UINT config; switch (msg) { case WM_INITDIALOG: /* Set Window Icon "DisConnected" */ SetStatusWinIcon(hwndDlg, ID_ICO_CONNECTING); /* Set config number for this dialog */ SetProp(hwndDlg, cfgProp, (HANDLE) lParam); /* Create LogWindow */ hwndLogWindow = CreateWindowEx (0, RICHEDIT_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | \ ES_SUNKEN | ES_LEFT | ES_MULTILINE | \ ES_READONLY | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 20, 25, 350, 160, // Posision and Size hwndDlg, // Parent window handle (HMENU) ID_EDT_LOG, // hMenu o.hInstance, // hInstance NULL); // WM_CREATE lpParam if (!hwndLogWindow) { /* Create RichEd LogWindow Failed */ ShowLocalizedMsg(IDS_ERR_CREATE_EDIT_LOGWINDOW); return FALSE; } /* Set font and fontsize of the LogWindow */ charformat.cbSize = sizeof(CHARFORMAT); charformat.dwMask = CFM_SIZE | CFM_FACE | CFM_BOLD | CFM_ITALIC | \ CFM_UNDERLINE | CFM_STRIKEOUT | CFM_PROTECTED; charformat.dwEffects = 0; charformat.yHeight = 100; _tcscpy(charformat.szFaceName, _T("MS Sans Serif")); if ((SendMessage(hwndLogWindow, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM) &charformat) && CFM_SIZE) == 0) { /* set size failed */ ShowLocalizedMsg(IDS_ERR_SET_SIZE); } /* Set Size and Posision of controls */ GetClientRect(hwndDlg, &rect); MoveWindow (hwndLogWindow, 20, 25, rect.right - 40, rect.bottom - 70, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_TXT_STATUS), 20, 5, rect.right - 25, 15, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_DISCONNECT), 20, rect.bottom - 30, 90, 23, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_RESTART), 125, rect.bottom - 30, 90, 23, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_HIDE), rect.right - 110, rect.bottom - 30, 90, 23, TRUE); /* Set focus on the LogWindow so it scrolls automatically */ SetFocus(hwndLogWindow); return FALSE; case WM_SIZE: MoveWindow (GetDlgItem(hwndDlg, ID_EDT_LOG), 20, 25, LOWORD (lParam) - 40, HIWORD (lParam) - 70, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_DISCONNECT), 20, HIWORD (lParam) - 30, 90, 23, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_RESTART), 125, HIWORD (lParam) - 30, 90, 23, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_HIDE), LOWORD (lParam) - 110, HIWORD (lParam) - 30, 90, 23, TRUE); MoveWindow (GetDlgItem(hwndDlg, ID_TXT_STATUS), 20, 5, LOWORD (lParam) - 25, 15, TRUE); InvalidateRect(hwndDlg, NULL, TRUE); return TRUE; case WM_COMMAND: config = (UINT) GetProp(hwndDlg, cfgProp); switch (LOWORD(wParam)) { case ID_DISCONNECT: SetFocus(GetDlgItem(o.conn[config].hwndStatus, ID_EDT_LOG)); StopOpenVPN(config); return TRUE; case ID_HIDE: if (o.conn[config].state != disconnected) { ShowWindow(hwndDlg, SW_HIDE); } else { DestroyWindow(hwndDlg); } return TRUE; case ID_RESTART: SetFocus(GetDlgItem(o.conn[config].hwndStatus, ID_EDT_LOG)); o.conn[config].restart = true; StopOpenVPN(config); return TRUE; } break; case WM_SHOWWINDOW: if (wParam == TRUE) { config = (UINT) GetProp(hwndDlg, cfgProp); if (o.conn[config].hwndStatus) SetFocus(GetDlgItem(o.conn[config].hwndStatus, ID_EDT_LOG)); } return FALSE; case WM_CLOSE: config = (UINT) GetProp(hwndDlg, cfgProp); if (o.conn[config].state != disconnected) { ShowWindow(hwndDlg, SW_HIDE); } else { DestroyWindow(hwndDlg); } return TRUE; case WM_NCDESTROY: RemoveProp(hwndDlg, cfgProp); break; case WM_DESTROY: PostQuitMessage(0); break; } return FALSE; } void SetStatusWinIcon(HWND hwndDlg, int IconID) { /* Set Window Icon */ HICON hIcon = LoadLocalizedIcon(IconID); if (hIcon) { SendMessage(hwndDlg, WM_SETICON, (WPARAM) (ICON_SMALL), (LPARAM) (hIcon)); SendMessage(hwndDlg, WM_SETICON, (WPARAM) (ICON_BIG), (LPARAM) (hIcon)); } } int AutoStartConnections() { int i; for (i=0; i < o.num_configs; i++) { if (o.conn[i].auto_connect) StartOpenVPN(i); } return(true); } int VerifyAutoConnections() { int i,j; BOOL match; for (i=0; (o.auto_connect[i] != 0) && (i < MAX_CONFIGS); i++) { match = false; for (j=0; j < MAX_CONFIGS; j++) { if (_tcsicmp(o.conn[j].config_file, o.auto_connect[i]) == 0) { match=true; break; } } if (match == false) { /* autostart config not found */ ShowLocalizedMsg(IDS_ERR_AUTOSTART_CONF, o.auto_connect[i]); return false; } } return true; } BOOL CheckVersion() { HANDLE hOutputRead; HANDLE hOutputWrite; HANDLE hInputRead; HANDLE hInputWrite; BOOL retval = FALSE; STARTUPINFO si; PROCESS_INFORMATION pi; TCHAR cmdline[] = _T("openvpn --version"); char match_version[] = "OpenVPN 2."; TCHAR pwd[MAX_PATH]; char line[1024]; TCHAR *p; CLEAR(si); CLEAR(pi); if (!GetPipeHandles(&hInputRead, &hInputWrite, &hOutputRead, &hOutputWrite)) return FALSE; /* Construct the process' working directory */ _tcsncpy(pwd, o.exe_path, _tsizeof(pwd)); p = _tcsrchr(pwd, _T('\\')); if (p != NULL) *p = _T('\0'); /* Fill in STARTUPINFO struct */ si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = hInputRead; si.hStdOutput = hOutputWrite; si.hStdError = hOutputWrite; /* Start OpenVPN to check version */ if (!CreateProcess(o.exe_path, cmdline, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, pwd, &si, &pi)) { ShowLocalizedMsg(IDS_ERR_CREATE_PROCESS, o.exe_path, cmdline, pwd); } else if (ReadLineFromStdOut(hOutputRead, 0, line)) { #ifdef DEBUG PrintDebug("VersionString: %s", line); #endif CloseHandle(pi.hThread); CloseHandle(pi.hProcess); /* OpenVPN version 2.x */ if (strstr(line, match_version)) retval = TRUE; } if (!CloseHandle(hInputRead) || !CloseHandle(hInputWrite) || !CloseHandle(hOutputRead) || !CloseHandle(hOutputWrite)) ShowLocalizedMsg(IDS_ERR_CLOSE_HANDLE); return retval; } void CheckAndSetTrayIcon() { /* Show green icon if service is running */ if (o.service_state == service_connected) { SetTrayIcon(connected); return; } /* Change tray icon if no more connections is running */ if (CountConnState(connected) != 0) SetTrayIcon(connected); else { if ((CountConnState(connecting) != 0) || (CountConnState(reconnecting) != 0) || (o.service_state == service_connecting)) SetTrayIcon(connecting); else SetTrayIcon(disconnected); } } void ThreadOpenVPNStatus(int config) { TCHAR conn_name[200]; HANDLE hThread; DWORD IDThread; MSG messages; /* Cut of extention from config filename. */ _tcsncpy(conn_name, o.conn[config].config_file, _tsizeof(conn_name)); conn_name[_tcslen(conn_name) - (_tcslen(o.ext_string)+1)]=0; if (o.conn[config].restart) { /* UserInfo: Connecting */ SetDlgItemText(o.conn[config].hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_CONNECTING)); SetStatusWinIcon(o.conn[config].hwndStatus, ID_ICO_CONNECTING); EnableWindow(GetDlgItem(o.conn[config].hwndStatus, ID_DISCONNECT), TRUE); EnableWindow(GetDlgItem(o.conn[config].hwndStatus, ID_RESTART), TRUE); SetFocus(GetDlgItem(o.conn[config].hwndStatus, ID_EDT_LOG)); o.conn[config].restart = false; } else { /* Create and Show Status Dialog */ o.conn[config].hwndStatus = CreateLocalizedDialogParam(ID_DLG_STATUS, StatusDialogFunc, config); if (!o.conn[config].hwndStatus) ExitThread(1); /* UserInfo: Connecting */ SetDlgItemText(o.conn[config].hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_CONNECTING)); SetWindowText(o.conn[config].hwndStatus, LoadLocalizedString(IDS_NFO_CONNECTION_XXX, conn_name)); if (o.silent_connection[0]=='0') ShowWindow(o.conn[config].hwndStatus, SW_SHOW); } /* Start Thread to monitor our OpenVPN process */ hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) WatchOpenVPNProcess, (int *) config, // pass config nr 0, &IDThread); if (hThread == NULL) { /* CreateThread failed */ ShowLocalizedMsg(IDS_ERR_THREAD_READ_STDOUT); ExitThread(0); } /* Run the message loop. It will run until GetMessage() returns 0 */ while (GetMessage (&messages, NULL, 0, 0)) { if(!IsDialogMessage(o.conn[config].hwndStatus, &messages)) { TranslateMessage(&messages); DispatchMessage(&messages); } } ExitThread(0); }