| /* |
| * stunnel TLS offloading and load-balancing proxy |
| * Copyright (C) 1998-2015 Michal Trojnara <Michal.Trojnara@mirt.net> |
| * |
| * 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; if not, see <http://www.gnu.org/licenses>. |
| * |
| * Linking stunnel statically or dynamically with other modules is making |
| * a combined work based on stunnel. Thus, the terms and conditions of |
| * the GNU General Public License cover the whole combination. |
| * |
| * In addition, as a special exception, the copyright holder of stunnel |
| * gives you permission to combine stunnel with free software programs or |
| * libraries that are released under the GNU LGPL and with code included |
| * in the standard release of OpenSSL under the OpenSSL License (or |
| * modified versions of such code, with unchanged license). You may copy |
| * and distribute such a system following the terms of the GNU GPL for |
| * stunnel and the licenses of the other code concerned. |
| * |
| * Note that people who make modified versions of stunnel are not obligated |
| * to grant this special exception for their modified versions; it is their |
| * choice whether to do so. The GNU General Public License gives permission |
| * to release a modified version without this exception; this exception |
| * also makes it possible to release a modified version which carries |
| * forward this exception. |
| */ |
| |
| #include "common.h" |
| #include "prototypes.h" |
| #include <commdlg.h> |
| #include <commctrl.h> |
| #ifndef _WIN32_WCE |
| #include <psapi.h> |
| #endif |
| #include "resources.h" |
| |
| #define LOG_LINES 1000 |
| |
| #ifdef _WIN32_WCE |
| #define STUNNEL_PLATFORM "WinCE" |
| #else |
| #ifdef _WIN64 |
| #define STUNNEL_PLATFORM "Win64" |
| #else /* MSDN claims that _WIN32 is always defined */ |
| #define STUNNEL_PLATFORM "Win32" |
| #endif |
| #define SERVICE_NAME TEXT("stunnel") |
| #define SERVICE_DISPLAY_NAME TEXT("Stunnel SSL wrapper") |
| #endif |
| |
| /* mingw-Patches-1825044 is missing in Debian Squeeze */ |
| WINBASEAPI BOOL WINAPI CheckTokenMembership(HANDLE, PSID, PBOOL); |
| |
| /* prototypes */ |
| NOEXPORT BOOL CALLBACK enum_windows(HWND, LPARAM); |
| NOEXPORT void gui_cmdline(); |
| NOEXPORT int initialize_winsock(void); |
| NOEXPORT int gui_loop(); |
| |
| NOEXPORT void CALLBACK timer_proc(HWND, UINT, UINT_PTR, DWORD); |
| NOEXPORT LRESULT CALLBACK window_proc(HWND, UINT, WPARAM, LPARAM); |
| NOEXPORT LRESULT CALLBACK about_proc(HWND, UINT, WPARAM, LPARAM); |
| NOEXPORT LRESULT CALLBACK pass_proc(HWND, UINT, WPARAM, LPARAM); |
| |
| NOEXPORT void save_log(void); |
| NOEXPORT void win_log(LPCTSTR); |
| NOEXPORT int save_text_file(LPTSTR, char *); |
| NOEXPORT void update_logs(void); |
| NOEXPORT LPTSTR log_txt(void); |
| |
| NOEXPORT void daemon_thread(void *); |
| |
| NOEXPORT void valid_config(void); |
| NOEXPORT void invalid_config(void); |
| NOEXPORT void update_peer_menu(void); |
| NOEXPORT void tray_update(const int); |
| NOEXPORT void tray_delete(void); |
| NOEXPORT void error_box(LPCTSTR); |
| NOEXPORT void edit_config(HWND); |
| NOEXPORT BOOL is_admin(void); |
| |
| /* NT Service related function */ |
| #ifndef _WIN32_WCE |
| NOEXPORT int service_initialize(void); |
| NOEXPORT int service_install(void); |
| NOEXPORT int service_uninstall(void); |
| NOEXPORT int service_start(void); |
| NOEXPORT int service_stop(void); |
| NOEXPORT int service_user(DWORD); |
| NOEXPORT void WINAPI service_main(DWORD, LPTSTR *); |
| NOEXPORT void WINAPI control_handler(DWORD); |
| #endif /* !defined(_WIN32_WCE) */ |
| |
| /* other functions */ |
| NOEXPORT LPTSTR get_params(); |
| |
| /* global variables */ |
| static struct LIST { |
| struct LIST *next; |
| size_t len; |
| TCHAR txt[1]; /* single character for trailing '\0' */ |
| } *head=NULL, *tail=NULL; |
| |
| static unsigned number_of_sections=0; |
| |
| static HINSTANCE ghInst; |
| static HWND edit_handle=NULL; |
| static HMENU tray_menu_handle=NULL; |
| #ifndef _WIN32_WCE |
| static HMENU main_menu_handle=NULL; |
| #endif |
| static HWND hwnd=NULL; /* main window handle */ |
| #ifdef _WIN32_WCE |
| static HWND command_bar_handle; /* command bar handle */ |
| #endif |
| /* win32_name is needed for any error_box(), message_box(), |
| * and the initial main window title */ |
| static TCHAR *win32_name=TEXT("stunnel ") TEXT(STUNNEL_VERSION) |
| TEXT(" on ") TEXT(STUNNEL_PLATFORM) TEXT(" (not configured)"); |
| |
| #ifndef _WIN32_WCE |
| static SERVICE_STATUS serviceStatus; |
| static SERVICE_STATUS_HANDLE serviceStatusHandle=0; |
| #define SERVICE_CONTROL_USER 128 |
| #endif |
| |
| static BOOL visible=FALSE; |
| static HANDLE main_initialized=NULL; /* global initialization performed */ |
| static HANDLE config_ready=NULL; /* reload without a valid configuration */ |
| static LONG new_logs=0; |
| |
| static UI_DATA *ui_data=NULL; |
| |
| static struct { |
| char *config_file; |
| unsigned service:1, install:1, uninstall:1, start:1, stop:1, |
| quiet:1, exit:1, reload:1, reopen:1; |
| } cmdline; |
| |
| /**************************************** initialization */ |
| |
| int WINAPI WinMain(HINSTANCE this_instance, HINSTANCE prev_instance, |
| #ifdef _WIN32_WCE |
| LPWSTR lpCmdLine, |
| #else |
| LPSTR lpCmdLine, |
| #endif |
| int nCmdShow) { |
| TCHAR stunnel_exe_path[MAX_PATH]; |
| LPTSTR c; |
| #ifndef _WIN32_WCE |
| LPTSTR errmsg; |
| #endif |
| |
| (void)prev_instance; /* skip warning about unused parameter */ |
| (void)lpCmdLine; /* skip warning about unused parameter */ |
| (void)nCmdShow; /* skip warning about unused parameter */ |
| |
| tls_init(); /* initialize thread-local storage */ |
| ghInst=this_instance; |
| gui_cmdline(); /* setup global cmdline structure */ |
| GetModuleFileName(0, stunnel_exe_path, MAX_PATH); |
| |
| #ifndef _WIN32_WCE |
| /* find previous instances of the same executable */ |
| if(!cmdline.service && !cmdline.install && !cmdline.uninstall && |
| !cmdline.reload && !cmdline.reopen && |
| !cmdline.start && !cmdline.stop) { |
| EnumWindows(enum_windows, (LPARAM)stunnel_exe_path); |
| if(cmdline.exit) |
| return 0; /* in case EnumWindows didn't find a previous instance */ |
| } |
| #endif |
| |
| /* set current working directory and engine path */ |
| c=_tcsrchr(stunnel_exe_path, TEXT('\\')); /* last backslash */ |
| if(c) /* found */ |
| c[1]=TEXT('\0'); /* truncate program name */ |
| #ifndef _WIN32_WCE |
| if(!SetCurrentDirectory(stunnel_exe_path)) { |
| errmsg=str_tprintf(TEXT("Cannot set directory to %s"), |
| stunnel_exe_path); |
| message_box(errmsg, MB_ICONERROR); |
| str_free(errmsg); |
| return 1; |
| } |
| #endif |
| _tputenv(str_tprintf(TEXT("OPENSSL_ENGINES=%s"), stunnel_exe_path)); |
| |
| if(initialize_winsock()) |
| return 1; |
| |
| #ifndef _WIN32_WCE |
| if(cmdline.service) /* "-service" must be processed before "-install" */ |
| return service_initialize(); |
| if(cmdline.install) |
| return service_install(); |
| if(cmdline.uninstall) |
| return service_uninstall(); |
| if(cmdline.start) |
| return service_start(); |
| if(cmdline.stop) |
| return service_stop(); |
| if(cmdline.reload) |
| return service_user(SIGNAL_RELOAD_CONFIG); |
| if(cmdline.reopen) |
| return service_user(SIGNAL_REOPEN_LOG); |
| #endif |
| return gui_loop(); |
| } |
| |
| #ifndef _WIN32_WCE |
| |
| NOEXPORT BOOL CALLBACK enum_windows(HWND other_window_handle, LPARAM lParam) { |
| DWORD pid; |
| HINSTANCE hInstance; |
| HANDLE process_handle; |
| TCHAR window_exe_path[MAX_PATH]; |
| LPTSTR stunnel_exe_path=(LPTSTR)lParam; |
| |
| if(!other_window_handle) |
| return TRUE; |
| hInstance=(HINSTANCE)GetWindowLong(other_window_handle, GWL_HINSTANCE); |
| GetWindowThreadProcessId(other_window_handle, &pid); |
| process_handle=OpenProcess(SYNCHRONIZE| /* WaitForSingleObject() */ |
| PROCESS_TERMINATE| /* TerminateProcess() */ |
| PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, /* GetModuleFileNameEx() */ |
| FALSE, pid); |
| if(!process_handle) |
| return TRUE; |
| if(!GetModuleFileNameEx(process_handle, |
| hInstance, window_exe_path, MAX_PATH)) { |
| CloseHandle(process_handle); |
| return TRUE; |
| } |
| if(_tcscmp(stunnel_exe_path, window_exe_path)) { |
| CloseHandle(process_handle); |
| return TRUE; |
| } |
| if(cmdline.exit) { |
| PostMessage(other_window_handle, WM_COMMAND, IDM_EXIT, 0); |
| if(WaitForSingleObject(process_handle, 3000)==WAIT_TIMEOUT) { |
| TerminateProcess(process_handle, 0); |
| WaitForSingleObject(process_handle, 3000); |
| } |
| } else { |
| ShowWindow(other_window_handle, SW_SHOWNORMAL); /* show window */ |
| SetForegroundWindow(other_window_handle); /* bring on top */ |
| } |
| CloseHandle(process_handle); |
| exit(0); |
| return FALSE; /* should never be executed */ |
| } |
| |
| #endif |
| |
| NOEXPORT void gui_cmdline() { |
| char *line, *c, *opt; |
| |
| line=tstr2str(get_params()); |
| memset(&cmdline, 0, sizeof cmdline); |
| c=line; |
| while(*c && (*c=='/' || *c=='-')) { |
| opt=c; |
| while(*c && !isspace(*c)) /* skip non-whitespaces */ |
| c++; |
| while(*c && isspace(*c)) /* replace whitespaces with '\0' */ |
| *c++='\0'; |
| if(!strcasecmp(opt+1, "install")) |
| cmdline.install=1; |
| else if(!strcasecmp(opt+1, "uninstall")) |
| cmdline.uninstall=1; |
| else if(!strcasecmp(opt+1, "reload")) |
| cmdline.reload=1; |
| else if(!strcasecmp(opt+1, "reopen")) |
| cmdline.reopen=1; |
| else if(!strcasecmp(opt+1, "start")) |
| cmdline.start=1; |
| else if(!strcasecmp(opt+1, "stop")) |
| cmdline.stop=1; |
| else if(!strcasecmp(opt+1, "service")) |
| cmdline.service=1; |
| else if(!strcasecmp(opt+1, "quiet")) |
| cmdline.quiet=1; |
| else if(!strcasecmp(opt+1, "exit")) |
| cmdline.exit=1; |
| else { /* an option to be processed in options.c */ |
| c=opt; |
| break; |
| } |
| } |
| if(*c=='\"') { /* the option is within double quotes */ |
| c++; |
| opt=c; |
| while(*c && *c!='\"') /* find the closing double quote */ |
| c++; |
| *c='\0'; |
| } else /* the option is simply the rest of the line */ |
| opt=c; |
| cmdline.config_file=*opt ? str_dup(opt) : NULL; |
| str_free(line); |
| } |
| |
| /* try to load winsock2 resolver functions from a specified dll name */ |
| NOEXPORT int initialize_winsock() { |
| static struct WSAData wsa_state; |
| |
| if(WSAStartup(MAKEWORD( 2, 2 ), &wsa_state)) { |
| message_box(TEXT("Failed to initialize winsock"), MB_ICONERROR); |
| return 1; /* error */ |
| } |
| resolver_init(); |
| return 0; /* IPv4 detected -> OK */ |
| } |
| |
| /**************************************** GUI thread */ |
| |
| NOEXPORT int gui_loop() { |
| #ifdef _WIN32_WCE |
| WNDCLASS wc; |
| #else |
| WNDCLASSEX wc; |
| #endif |
| MSG msg; |
| LPTSTR classname=TEXT("stunnel_main_window_class"); |
| |
| /* register the class */ |
| #ifndef _WIN32_WCE |
| wc.cbSize=sizeof wc; |
| #endif |
| wc.style=CS_VREDRAW|CS_HREDRAW; |
| wc.lpfnWndProc=window_proc; |
| wc.cbClsExtra=wc.cbWndExtra=0; |
| wc.hInstance=ghInst; |
| wc.hIcon=LoadIcon(ghInst, MAKEINTRESOURCE(IDI_STUNNEL_MAIN)); |
| wc.hCursor=LoadCursor(NULL, IDC_ARROW); |
| wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); |
| wc.lpszMenuName=NULL; |
| wc.lpszClassName=classname; |
| #ifdef _WIN32_WCE |
| RegisterClass(&wc); |
| #else |
| /* load 16x16 icon */ |
| wc.hIconSm=LoadImage(ghInst, MAKEINTRESOURCE(IDI_STUNNEL_MAIN), IMAGE_ICON, |
| GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); |
| RegisterClassEx(&wc); |
| #endif |
| |
| /* create main window */ |
| #ifdef _WIN32_WCE |
| hwnd=CreateWindow(classname, win32_name, 0, |
| CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, |
| NULL, NULL, ghInst, NULL); |
| #else |
| main_menu_handle=LoadMenu(ghInst, MAKEINTRESOURCE(IDM_MAINMENU)); |
| if(main_menu_handle && cmdline.service) { |
| /* block unsafe operations in the service mode */ |
| EnableMenuItem(main_menu_handle, IDM_EDIT_CONFIG, MF_GRAYED); |
| EnableMenuItem(main_menu_handle, IDM_SAVE_LOG, MF_GRAYED); |
| } |
| hwnd=CreateWindow(classname, win32_name, WS_TILEDWINDOW, |
| CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, |
| NULL, main_menu_handle, ghInst, NULL); |
| #endif |
| |
| /* auto-reset, non-signaled events */ |
| main_initialized=CreateEvent(NULL, FALSE, FALSE, NULL); |
| config_ready=CreateEvent(NULL, FALSE, FALSE, NULL); |
| /* hwnd needs to be initialized before _beginthread() */ |
| _beginthread(daemon_thread, DEFAULT_STACK_SIZE, NULL); |
| WaitForSingleObject(main_initialized, INFINITE); |
| /* logging subsystem is now available */ |
| |
| /* setup periodic event to trigger update_logs() */ |
| SetTimer(NULL, 0, 1000, timer_proc); /* run callback once per second */ |
| |
| s_log(LOG_DEBUG, "GUI message loop initialized"); |
| for(;;) |
| switch(GetMessage(&msg, NULL, 0, 0)) { |
| case -1: |
| ioerror("GetMessage"); |
| return 0; |
| case 0: |
| s_log(LOG_DEBUG, "GUI message loop terminated"); |
| return (int)msg.wParam; |
| default: |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| } |
| |
| NOEXPORT void CALLBACK timer_proc(HWND hwnd, UINT msg, UINT_PTR id, DWORD t) { |
| (void)hwnd; /* skip warning about unused parameter */ |
| (void)msg; /* skip warning about unused parameter */ |
| (void)id; /* skip warning about unused parameter */ |
| (void)t; /* skip warning about unused parameter */ |
| if(visible) |
| update_logs(); |
| tray_update(num_clients); /* needed when explorer.exe (re)starts */ |
| } |
| |
| NOEXPORT LRESULT CALLBACK window_proc(HWND main_window_handle, |
| UINT message, WPARAM wParam, LPARAM lParam) { |
| POINT pt; |
| RECT rect; |
| PAINTSTRUCT ps; |
| SERVICE_OPTIONS *section; |
| unsigned section_number; |
| LPTSTR txt; |
| |
| #if 0 |
| switch(message) { |
| case WM_CTLCOLORSTATIC: |
| case WM_TIMER: |
| case WM_LOG: |
| break; |
| default: |
| s_log(LOG_DEBUG, "Window message: 0x%x(0x%hx,0x%lx)", |
| message, wParam, lParam); |
| } |
| #endif |
| switch(message) { |
| case WM_CREATE: |
| #ifdef _WIN32_WCE |
| /* create command bar */ |
| command_bar_handle=CommandBar_Create(ghInst, main_window_handle, 1); |
| if(!command_bar_handle) |
| error_box(TEXT("CommandBar_Create")); |
| if(!CommandBar_InsertMenubar(command_bar_handle, ghInst, IDM_MAINMENU, 0)) |
| error_box(TEXT("CommandBar_InsertMenubar")); |
| if(!CommandBar_AddAdornments(command_bar_handle, 0, 0)) |
| error_box(TEXT("CommandBar_AddAdornments")); |
| #endif |
| |
| /* create child edit window */ |
| edit_handle=CreateWindowEx(WS_EX_STATICEDGE, TEXT("EDIT"), NULL, |
| WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL|ES_MULTILINE|ES_READONLY, |
| 0, 0, 0, 0, main_window_handle, (HMENU)IDE_EDIT, ghInst, NULL); |
| #ifndef _WIN32_WCE |
| SendMessage(edit_handle, WM_SETFONT, |
| (WPARAM)CreateFont(-12, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, |
| DEFAULT_CHARSET, OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS, |
| PROOF_QUALITY, DEFAULT_PITCH, TEXT("Courier")), |
| MAKELPARAM(FALSE, 0)); /* no need to redraw right now */ |
| #endif |
| /* NOTE: there's no return statement here -> proceeding with resize */ |
| |
| case WM_SIZE: |
| GetClientRect(main_window_handle, &rect); |
| #ifdef _WIN32_WCE |
| MoveWindow(edit_handle, 0, CommandBar_Height(command_bar_handle), |
| rect.right, rect.bottom-CommandBar_Height(command_bar_handle), |
| TRUE); |
| SendMessage(command_bar_handle, TB_AUTOSIZE, 0L, 0L); |
| CommandBar_AlignAdornments(command_bar_handle); |
| #else |
| MoveWindow(edit_handle, 0, 0, rect.right, rect.bottom, TRUE); |
| #endif |
| UpdateWindow(edit_handle); |
| /* CommandBar_Show(command_bar_handle, TRUE); */ |
| return 0; |
| |
| case WM_SETFOCUS: |
| SetFocus(edit_handle); |
| return 0; |
| |
| case WM_PAINT: |
| BeginPaint(hwnd, &ps); |
| EndPaint(hwnd, &ps); |
| break; |
| |
| case WM_CLOSE: |
| ShowWindow(main_window_handle, SW_HIDE); |
| return 0; |
| |
| #ifdef WM_SHOWWINDOW |
| case WM_SHOWWINDOW: |
| visible=(BOOL)wParam; |
| #else /* this works for Pierre Delaage, but not for me... */ |
| case WM_WINDOWPOSCHANGED: |
| visible=IsWindowVisible(main_window_handle); |
| #endif |
| if(tray_menu_handle) |
| CheckMenuItem(tray_menu_handle, IDM_SHOW_LOG, |
| visible ? MF_CHECKED : MF_UNCHECKED); |
| if(visible) |
| update_logs(); |
| #ifdef WM_SHOWWINDOW |
| return 0; |
| #else |
| break; /* proceed to DefWindowProc() */ |
| #endif |
| |
| case WM_DESTROY: |
| #ifdef _WIN32_WCE |
| CommandBar_Destroy(command_bar_handle); |
| #else |
| if(main_menu_handle) { |
| if(!DestroyMenu(main_menu_handle)) |
| ioerror("DestroyMenu"); |
| main_menu_handle=NULL; |
| } |
| #endif |
| tray_delete(); /* remove the taskbark icon if exists */ |
| PostQuitMessage(0); |
| return 0; |
| |
| case WM_COMMAND: |
| if(wParam>=IDM_PEER_MENU && wParam<IDM_PEER_MENU+number_of_sections) { |
| for(section=service_options.next, section_number=0; |
| section && wParam!=IDM_PEER_MENU+section_number; |
| section=section->next, ++section_number) |
| ; |
| if(!section) |
| return 0; |
| if(save_text_file(section->file, section->chain)) |
| return 0; |
| #ifndef _WIN32_WCE |
| if(main_menu_handle) |
| CheckMenuItem(main_menu_handle, wParam, MF_CHECKED); |
| #endif |
| if(tray_menu_handle) |
| CheckMenuItem(tray_menu_handle, wParam, MF_CHECKED); |
| message_box(section->help, MB_ICONINFORMATION); |
| return 0; |
| } |
| switch(wParam) { |
| case IDM_ABOUT: |
| DialogBox(ghInst, TEXT("AboutBox"), main_window_handle, |
| (DLGPROC)about_proc); |
| break; |
| case IDM_SHOW_LOG: |
| if(visible) { |
| ShowWindow(main_window_handle, SW_HIDE); /* hide window */ |
| } else { |
| ShowWindow(main_window_handle, SW_SHOWNORMAL); /* show window */ |
| SetForegroundWindow(main_window_handle); /* bring on top */ |
| } |
| break; |
| case IDM_CLOSE: |
| ShowWindow(main_window_handle, SW_HIDE); /* hide window */ |
| break; |
| case IDM_EXIT: |
| if(num_clients>=0) /* signal_pipe is active */ |
| signal_post(SIGNAL_TERMINATE); |
| DestroyWindow(main_window_handle); |
| break; |
| case IDM_SAVE_LOG: |
| if(!cmdline.service) /* security */ |
| save_log(); |
| break; |
| case IDM_EDIT_CONFIG: |
| #ifndef _WIN32_WCE |
| if(!cmdline.service) /* security */ |
| edit_config(main_window_handle); |
| #endif |
| break; |
| case IDM_RELOAD_CONFIG: |
| if(num_clients>=0) /* signal_pipe is active */ |
| signal_post(SIGNAL_RELOAD_CONFIG); |
| else |
| SetEvent(config_ready); /* unlock daemon_thread() */ |
| break; |
| case IDM_REOPEN_LOG: |
| signal_post(SIGNAL_REOPEN_LOG); |
| break; |
| case IDM_MANPAGE: |
| #ifndef _WIN32_WCE |
| if(!cmdline.service) /* security */ |
| ShellExecute(main_window_handle, TEXT("open"), |
| TEXT("stunnel.html"), NULL, NULL, SW_SHOWNORMAL); |
| #endif |
| break; |
| case IDM_HOMEPAGE: |
| #ifndef _WIN32_WCE |
| if(!cmdline.service) /* security */ |
| ShellExecute(main_window_handle, TEXT("open"), |
| TEXT("http://www.stunnel.org/"), NULL, NULL, SW_SHOWNORMAL); |
| #endif |
| break; |
| } |
| return 0; |
| |
| case WM_SYSTRAY: /* a taskbar event */ |
| switch(lParam) { |
| #ifdef _WIN32_WCE |
| case WM_LBUTTONDOWN: /* no right mouse button on Windows CE */ |
| GetWindowRect(GetDesktopWindow(), &rect); /* no cursor position */ |
| pt.x=rect.right; |
| pt.y=rect.bottom-25; |
| #else |
| case WM_RBUTTONDOWN: |
| GetCursorPos(&pt); |
| #endif |
| SetForegroundWindow(main_window_handle); |
| if(tray_menu_handle) |
| TrackPopupMenuEx(GetSubMenu(tray_menu_handle, 0), |
| TPM_BOTTOMALIGN, pt.x, pt.y, main_window_handle, NULL); |
| PostMessage(main_window_handle, WM_NULL, 0, 0); |
| break; |
| #ifndef _WIN32_WCE |
| case WM_LBUTTONDBLCLK: /* switch log window visibility */ |
| if(visible) { |
| ShowWindow(main_window_handle, SW_HIDE); /* hide window */ |
| } else { |
| ShowWindow(main_window_handle, SW_SHOWNORMAL); /* show window */ |
| SetForegroundWindow(main_window_handle); /* bring on top */ |
| } |
| break; |
| #endif |
| } |
| return 0; |
| |
| case WM_VALID_CONFIG: |
| valid_config(); |
| return 0; |
| |
| case WM_INVALID_CONFIG: |
| invalid_config(); |
| return 0; |
| |
| case WM_LOG: |
| txt=(LPTSTR)wParam; |
| win_log(txt); |
| str_free(txt); |
| return 0; |
| |
| case WM_NEW_CHAIN: |
| #ifndef _WIN32_WCE |
| if(main_menu_handle) |
| EnableMenuItem(main_menu_handle, IDM_PEER_MENU+wParam, MF_ENABLED); |
| #endif |
| if(tray_menu_handle) |
| EnableMenuItem(tray_menu_handle, IDM_PEER_MENU+wParam, MF_ENABLED); |
| return 0; |
| |
| case WM_CLIENTS: |
| tray_update((int)wParam); |
| return 0; |
| } |
| |
| return DefWindowProc(main_window_handle, message, wParam, lParam); |
| } |
| |
| NOEXPORT LRESULT CALLBACK about_proc(HWND dialog_handle, UINT message, |
| WPARAM wParam, LPARAM lParam) { |
| (void)lParam; /* skip warning about unused parameter */ |
| |
| switch(message) { |
| case WM_INITDIALOG: |
| return TRUE; |
| case WM_COMMAND: |
| switch(wParam) { |
| case IDOK: |
| case IDCANCEL: |
| EndDialog(dialog_handle, TRUE); |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| NOEXPORT LRESULT CALLBACK pass_proc(HWND dialog_handle, UINT message, |
| WPARAM wParam, LPARAM lParam) { |
| LPTSTR titlebar; |
| union { |
| TCHAR txt[PEM_BUFSIZE]; |
| WORD len; |
| } pass_dialog; |
| WORD pass_len; |
| char* pass_txt; |
| LPTSTR key_file_name; |
| |
| switch(message) { |
| case WM_INITDIALOG: |
| /* set the default push button to "Cancel" */ |
| SendMessage(dialog_handle, DM_SETDEFID, (WPARAM)IDCANCEL, (LPARAM)0); |
| |
| key_file_name=str2tstr(ui_data->section->key); |
| titlebar=str_tprintf(TEXT("Private key: %s"), key_file_name); |
| str_free(key_file_name); |
| SetWindowText(dialog_handle, titlebar); |
| str_free(titlebar); |
| return TRUE; |
| |
| case WM_COMMAND: |
| /* set the default push button to "OK" when the user enters text */ |
| if(HIWORD(wParam)==EN_CHANGE && LOWORD(wParam)==IDE_PASSEDIT) |
| SendMessage(dialog_handle, DM_SETDEFID, (WPARAM)IDOK, (LPARAM)0); |
| switch(wParam) { |
| case IDOK: |
| /* get number of characters */ |
| pass_len=(WORD)SendDlgItemMessage(dialog_handle, |
| IDE_PASSEDIT, EM_LINELENGTH, (WPARAM)0, (LPARAM)0); |
| if(!pass_len || pass_len>=PEM_BUFSIZE) { |
| EndDialog(dialog_handle, FALSE); |
| return FALSE; |
| } |
| |
| /* put the number of characters into first word of buffer */ |
| pass_dialog.len=pass_len; |
| |
| /* get the characters */ |
| SendDlgItemMessage(dialog_handle, IDE_PASSEDIT, EM_GETLINE, |
| (WPARAM)0 /* line 0 */, (LPARAM)pass_dialog.txt); |
| pass_dialog.txt[pass_len]='\0'; /* null-terminate the string */ |
| |
| /* convert input password to UTF-8 string (as ui_data->pass) */ |
| pass_txt=tstr2str(pass_dialog.txt); |
| strcpy(ui_data->pass, pass_txt); |
| str_free(pass_txt); |
| |
| EndDialog(dialog_handle, TRUE); |
| return TRUE; |
| |
| case IDCANCEL: |
| EndDialog(dialog_handle, FALSE); |
| return TRUE; |
| } |
| return 0; |
| } |
| return FALSE; |
| |
| UNREFERENCED_PARAMETER(lParam); |
| } |
| |
| int passwd_cb(char *buf, int size, int rwflag, void *userdata) { |
| (void)rwflag; /* skip warning about unused parameter */ |
| |
| ui_data=userdata; |
| if(size<0) /* just in case */ |
| return 0; |
| if(!DialogBox(ghInst, TEXT("PassBox"), hwnd, (DLGPROC)pass_proc)) |
| return 0; /* error */ |
| strncpy(buf, ui_data->pass, (size_t)size); |
| buf[size-1]='\0'; |
| return (int)strlen(buf); |
| } |
| |
| #ifndef OPENSSL_NO_ENGINE |
| int pin_cb(UI *ui, UI_STRING *uis) { |
| ui_data=UI_get0_user_data(ui); /* was: ui_data=UI_get_app_data(ui); */ |
| if(!ui_data) { |
| s_log(LOG_ERR, "INTERNAL ERROR: user data data pointer"); |
| return 0; |
| } |
| if(!DialogBox(ghInst, TEXT("PassBox"), hwnd, (DLGPROC)pass_proc)) |
| return 0; /* error */ |
| UI_set_result(ui, uis, ui_data->pass); |
| return 1; |
| } |
| #endif |
| |
| /**************************************** log handling */ |
| |
| NOEXPORT void save_log() { |
| TCHAR file_name[MAX_PATH]; |
| OPENFILENAME ofn; |
| LPTSTR txt; |
| LPSTR str; |
| |
| ZeroMemory(&ofn, sizeof ofn); |
| file_name[0]='\0'; |
| |
| ofn.lStructSize=sizeof ofn; |
| ofn.hwndOwner=hwnd; |
| ofn.lpstrFilter=TEXT("Log Files (*.log)\0*.log\0All Files (*.*)\0*.*\0\0"); |
| ofn.lpstrFile=file_name; |
| ofn.nMaxFile=MAX_PATH; |
| ofn.lpstrDefExt=TEXT("LOG"); |
| ofn.lpstrInitialDir=TEXT("."); |
| |
| ofn.lpstrTitle=TEXT("Save Log"); |
| ofn.Flags=OFN_EXPLORER|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY| |
| OFN_OVERWRITEPROMPT; |
| if(!GetSaveFileName(&ofn)) |
| return; |
| |
| txt=log_txt(); /* need to convert the result to UTF-8 */ |
| str=tstr2str(txt); |
| str_free(txt); |
| save_text_file(file_name, str); |
| str_free(str); |
| } |
| |
| NOEXPORT int save_text_file(LPTSTR file_name, char *str) { |
| HANDLE file_handle; |
| DWORD ignore; |
| |
| file_handle=CreateFile(file_name, GENERIC_WRITE, 0, NULL, |
| CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); |
| if(file_handle==INVALID_HANDLE_VALUE) { |
| error_box(TEXT("CreateFile")); |
| return 1; |
| } |
| if(!WriteFile(file_handle, str, strlen(str), &ignore, NULL)) { |
| CloseHandle(file_handle); |
| error_box(TEXT("WriteFile")); |
| return 1; |
| } |
| CloseHandle(file_handle); |
| return 0; |
| } |
| |
| NOEXPORT void win_log(LPCTSTR txt) { |
| struct LIST *curr; |
| size_t txt_len; |
| static size_t log_len=0; |
| |
| txt_len=_tcslen(txt); |
| curr=str_alloc(sizeof(struct LIST)+txt_len*sizeof(TCHAR)); |
| curr->len=txt_len; |
| _tcscpy(curr->txt, txt); |
| curr->next=NULL; |
| if(tail) |
| tail->next=curr; |
| tail=curr; |
| if(!head) |
| head=tail; |
| log_len++; |
| while(log_len>LOG_LINES) { |
| curr=head; |
| head=head->next; |
| str_free(curr); |
| log_len--; |
| } |
| new_logs=1; |
| } |
| |
| NOEXPORT void update_logs(void) { |
| LPTSTR txt; |
| |
| if(!InterlockedExchange(&new_logs, 0)) |
| return; |
| txt=log_txt(); |
| SetWindowText(edit_handle, txt); |
| str_free(txt); |
| SendMessage(edit_handle, WM_VSCROLL, (WPARAM)SB_BOTTOM, (LPARAM)0); |
| } |
| |
| NOEXPORT LPTSTR log_txt(void) { |
| LPTSTR buff; |
| unsigned ptr=0, len=0; |
| struct LIST *curr; |
| |
| for(curr=head; curr; curr=curr->next) |
| len+=curr->len+2; /* +2 for trailing '\r\n' */ |
| buff=str_alloc((len+1)*sizeof(TCHAR)); /* +1 for trailing '\0' */ |
| for(curr=head; curr; curr=curr->next) { |
| memcpy(buff+ptr, curr->txt, curr->len*sizeof(TCHAR)); |
| ptr+=curr->len; |
| if(curr->next) { |
| buff[ptr++]=TEXT('\r'); |
| buff[ptr++]=TEXT('\n'); |
| } |
| } |
| buff[ptr]=TEXT('\0'); |
| return buff; |
| } |
| |
| /**************************************** worker thread */ |
| |
| NOEXPORT void daemon_thread(void *arg) { |
| (void)arg; /* skip warning about unused parameter */ |
| |
| tls_alloc(NULL, NULL, "main"); /* new thread-local storage */ |
| main_init(); |
| SetEvent(main_initialized); /* unlock the GUI thread */ |
| /* get a valid configuration */ |
| while(main_configure(cmdline.config_file, NULL)) { |
| cmdline.config_file=NULL; /* don't retry commandline switches */ |
| unbind_ports(); /* in case initialization failed after bind_ports() */ |
| log_flush(LOG_MODE_ERROR); /* otherwise logs are buffered */ |
| PostMessage(hwnd, WM_INVALID_CONFIG, 0, 0); /* display error */ |
| WaitForSingleObject(config_ready, INFINITE); |
| log_close(); /* prevent main_configure() from logging in error mode */ |
| } |
| PostMessage(hwnd, WM_VALID_CONFIG, 0, 0); |
| |
| /* start the main loop */ |
| daemon_loop(); |
| main_cleanup(); |
| _endthread(); /* SIGNAL_TERMINATE received */ |
| } |
| |
| /**************************************** helper functions */ |
| |
| NOEXPORT void invalid_config() { |
| /* update the main window title */ |
| win32_name=TEXT("stunnel ") TEXT(STUNNEL_VERSION) TEXT(" on ") |
| TEXT(STUNNEL_PLATFORM) TEXT(" (invalid configuration file)"); |
| SetWindowText(hwnd, win32_name); |
| |
| /* log window is hidden by default */ |
| ShowWindow(hwnd, SW_SHOWNORMAL); /* show window */ |
| SetForegroundWindow(hwnd); /* bring on top */ |
| |
| tray_update(-1); /* error icon */ |
| update_peer_menu(); /* purge the list of sections */ |
| |
| win_log(TEXT("")); |
| s_log(LOG_ERR, "Server is down"); |
| message_box(TEXT("Stunnel server is down due to an error.\n") |
| TEXT("You need to exit and correct the problem.\n") |
| TEXT("Click OK to see the error log window."), |
| MB_ICONERROR); |
| } |
| |
| NOEXPORT void valid_config() { |
| /* update the main window title */ |
| win32_name=TEXT("stunnel ") TEXT(STUNNEL_VERSION) TEXT(" on ") |
| TEXT(STUNNEL_PLATFORM); |
| SetWindowText(hwnd, win32_name); |
| |
| tray_update(num_clients); /* idle or busy icon (on reload) */ |
| update_peer_menu(); /* one menu item per section */ |
| |
| /* enable IDM_REOPEN_LOG menu if a log file is used, disable otherwise */ |
| #ifndef _WIN32_WCE |
| EnableMenuItem(main_menu_handle, IDM_REOPEN_LOG, |
| (UINT)(global_options.output_file ? MF_ENABLED : MF_GRAYED)); |
| #endif |
| if(tray_menu_handle) |
| EnableMenuItem(tray_menu_handle, IDM_REOPEN_LOG, |
| (UINT)(global_options.output_file ? MF_ENABLED : MF_GRAYED)); |
| } |
| |
| NOEXPORT void update_peer_menu(void) { |
| SERVICE_OPTIONS *section; |
| #ifndef _WIN32_WCE |
| HMENU main_peer_list=NULL; |
| #endif |
| HMENU tray_peer_list=NULL; |
| unsigned section_number; |
| LPTSTR servname; |
| |
| /* purge menu peer lists */ |
| #ifndef _WIN32_WCE |
| if(main_menu_handle) |
| main_peer_list=GetSubMenu(main_menu_handle, 2); /* 3rd submenu */ |
| if(main_peer_list) |
| while(GetMenuItemCount(main_peer_list)) /* purge old menu */ |
| DeleteMenu(main_peer_list, 0, MF_BYPOSITION); |
| #endif |
| if(tray_menu_handle) |
| tray_peer_list=GetSubMenu(GetSubMenu(tray_menu_handle, 0), 2); |
| if(tray_peer_list) |
| while(GetMenuItemCount(tray_peer_list)) /* purge old menu */ |
| DeleteMenu(tray_peer_list, 0, MF_BYPOSITION); |
| |
| /* initialize data structures */ |
| number_of_sections=0; |
| for(section=service_options.next; section; section=section->next) |
| section->section_number=number_of_sections++; |
| |
| section_number=0; |
| for(section=service_options.next; section; section=section->next) { |
| servname=str2tstr(section->servname); |
| |
| /* setup LPTSTR section->file */ |
| section->file=str_tprintf(TEXT("peer-%s.pem"), servname); |
| |
| /* setup section->help */ |
| section->help=str_tprintf( |
| TEXT("Peer certificate chain has been saved.\n") |
| TEXT("Add the following lines to section [%s]:\n") |
| TEXT("\tCAfile = peer-%s.pem\n") |
| TEXT("\tverify = 3\n") |
| TEXT("to enable cryptographic authentication.\n") |
| TEXT("Then reload stunnel configuration file."), |
| servname, servname); |
| |
| str_free(servname); |
| |
| /* setup section->chain */ |
| section->chain=NULL; |
| |
| /* insert new menu item */ |
| #ifndef _WIN32_WCE |
| if(main_peer_list) |
| if(!InsertMenu(main_peer_list, section_number, |
| MF_BYPOSITION|MF_STRING|MF_GRAYED, |
| IDM_PEER_MENU+section_number, section->file)) |
| ioerror("InsertMenu"); |
| #endif |
| if(tray_peer_list) |
| if(!InsertMenu(tray_peer_list, section_number, |
| MF_BYPOSITION|MF_STRING|MF_GRAYED, |
| IDM_PEER_MENU+section_number, section->file)) |
| ioerror("InsertMenu"); |
| |
| ++section_number; |
| } |
| if(hwnd) |
| DrawMenuBar(hwnd); |
| } |
| |
| ICON_IMAGE load_icon_default(ICON_TYPE type) { |
| WORD idi; |
| ICON_IMAGE img; |
| |
| switch(type) { |
| case ICON_ACTIVE: |
| idi=IDI_STUNNEL_ACTIVE; |
| break; |
| case ICON_ERROR: |
| idi=IDI_STUNNEL_ERROR; |
| break; |
| case ICON_IDLE: |
| idi=IDI_STUNNEL_IDLE; |
| break; |
| default: |
| return NULL; |
| } |
| img=LoadImage(ghInst, MAKEINTRESOURCE(idi), IMAGE_ICON, |
| GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); |
| return DuplicateIcon(NULL, img); |
| } |
| |
| ICON_IMAGE load_icon_file(const char *name) { |
| LPTSTR tname; |
| ICON_IMAGE icon; |
| |
| tname=str2tstr((LPSTR)name); |
| #ifndef _WIN32_WCE |
| icon=LoadImage(NULL, tname, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), |
| GetSystemMetrics(SM_CYSMICON), LR_LOADFROMFILE); |
| #else |
| /* TODO: Implement a WCE version of LoadImage() */ |
| /* icon=wceLoadIconFromFile(tname); */ |
| s_log(LOG_ERR, "Loading image from file not implemented on WCE"); |
| icon=NULL; |
| #endif |
| str_free(tname); |
| return icon; |
| } |
| |
| NOEXPORT void tray_update(const int num) { |
| NOTIFYICONDATA nid; |
| static ICON_TYPE previous_icon=ICON_NONE; |
| ICON_TYPE current_icon; |
| LPTSTR tip; |
| |
| if(!global_options.option.taskbar) { /* currently disabled */ |
| tray_delete(); /* remove the taskbark icon if exists */ |
| return; |
| } |
| if(!tray_menu_handle) /* initialize taskbar */ |
| tray_menu_handle=LoadMenu(ghInst, MAKEINTRESOURCE(IDM_TRAYMENU)); |
| if(!tray_menu_handle) { |
| ioerror("LoadMenu"); |
| return; |
| } |
| if(cmdline.service) |
| EnableMenuItem(tray_menu_handle, IDM_EDIT_CONFIG, MF_GRAYED); |
| |
| ZeroMemory(&nid, sizeof nid); |
| nid.cbSize=sizeof nid; |
| nid.uID=1; /* application-defined icon ID */ |
| nid.uFlags=NIF_MESSAGE|NIF_TIP; |
| nid.uCallbackMessage=WM_SYSTRAY; /* notification message */ |
| nid.hWnd=hwnd; /* window to receive notifications */ |
| if(num<0) { |
| tip=str_tprintf(TEXT("Server is down")); |
| current_icon=ICON_ERROR; |
| } else if(num>0) { |
| tip=str_tprintf(TEXT("%d active session(s)"), num); |
| current_icon=ICON_ACTIVE; |
| } else { |
| tip=str_tprintf(TEXT("Server is idle")); |
| current_icon=ICON_IDLE; |
| } |
| _tcsncpy(nid.szTip, tip, 63); |
| nid.szTip[63]=TEXT('\0'); |
| str_free(tip); |
| nid.hIcon=global_options.icon[current_icon]; |
| if(current_icon!=previous_icon) { |
| nid.uFlags|=NIF_ICON; |
| previous_icon=current_icon; |
| } |
| if(Shell_NotifyIcon(NIM_MODIFY, &nid)) /* modify tooltip */ |
| return; /* OK: taskbar icon exists */ |
| /* tooltip update failed - try to create the icon */ |
| nid.uFlags|=NIF_ICON; |
| Shell_NotifyIcon(NIM_ADD, &nid); |
| } |
| |
| NOEXPORT void tray_delete(void) { |
| NOTIFYICONDATA nid; |
| |
| if(tray_menu_handle) { |
| ZeroMemory(&nid, sizeof nid); |
| nid.cbSize=sizeof nid; |
| nid.uID=1; /* application-defined icon ID */ |
| nid.hWnd=hwnd; /* window to receive notifications */ |
| nid.uFlags=NIF_TIP; /* not really sure what to put here, but it works */ |
| Shell_NotifyIcon(NIM_DELETE, &nid); /* this removes the icon */ |
| if(!DestroyMenu(tray_menu_handle)) /* release menu resources */ |
| ioerror("DestroyMenu"); |
| tray_menu_handle=NULL; |
| } |
| } |
| |
| NOEXPORT void error_box(LPCTSTR text) { |
| LPTSTR errmsg, fullmsg; |
| DWORD dw; |
| |
| dw=GetLastError(); |
| FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| (LPTSTR)&errmsg, 0, NULL); |
| fullmsg=str_tprintf(TEXT("%s: error %ld: %s"), text, dw, errmsg); |
| LocalFree(errmsg); |
| message_box(fullmsg, MB_ICONERROR); |
| str_free(fullmsg); |
| } |
| |
| void message_box(LPCTSTR text, const UINT type) { |
| if(cmdline.quiet) |
| return; |
| MessageBox(hwnd, text, win32_name, type); |
| } |
| |
| void ui_new_chain(const unsigned section_number) { |
| PostMessage(hwnd, WM_NEW_CHAIN, section_number, 0); |
| } |
| |
| void ui_new_log(const char *line) { |
| LPTSTR txt; |
| txt=str2tstr(line); |
| str_detach(txt); /* this allocation will be freed in the GUI thread */ |
| PostMessage(hwnd, WM_LOG, (WPARAM)txt, 0); |
| } |
| |
| void ui_config_reloaded(void) { |
| PostMessage(hwnd, WM_VALID_CONFIG, 0, 0); |
| } |
| |
| void ui_clients(const long num) { |
| PostMessage(hwnd, WM_CLIENTS, (WPARAM)num, 0); |
| } |
| |
| /* TODO: port it to WCE */ |
| NOEXPORT void edit_config(HWND main_window_handle) { |
| TCHAR cwd[MAX_PATH]; |
| LPTSTR conf_file, conf_path; |
| |
| conf_file=str2tstr(configuration_file); |
| if(*conf_file==TEXT('\"')) { |
| conf_path=conf_file; |
| } else if(_tcschr(conf_file, TEXT('\\'))) { |
| conf_path=str_tprintf(TEXT("\"%s\""), conf_file); |
| str_free(conf_file); |
| } else { |
| GetCurrentDirectory(MAX_PATH, cwd); |
| conf_path=str_tprintf(TEXT("\"%s\\%s\""), cwd, conf_file); |
| str_free(conf_file); |
| } |
| |
| if(is_admin()) { |
| ShellExecute(main_window_handle, TEXT("open"), |
| TEXT("notepad.exe"), conf_path, |
| NULL, SW_SHOWNORMAL); |
| } else { /* UAC workaround */ |
| ShellExecute(main_window_handle, TEXT("runas"), |
| TEXT("notepad.exe"), conf_path, |
| NULL, SW_SHOWNORMAL); |
| } |
| str_free(conf_path); |
| } |
| |
| NOEXPORT BOOL is_admin(void) { |
| #ifndef _WIN32_WCE |
| SID_IDENTIFIER_AUTHORITY NtAuthority={SECURITY_NT_AUTHORITY}; |
| PSID admin_group; |
| BOOL retval; |
| |
| retval=AllocateAndInitializeSid(&NtAuthority, 2, |
| SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, |
| 0, 0, 0, 0, 0, 0, &admin_group); |
| if(retval) { |
| if(!CheckTokenMembership(NULL, admin_group, &retval)) |
| retval=FALSE; |
| FreeSid(admin_group); |
| } |
| return retval; |
| #else |
| return TRUE; /* always an admin on WCE */ |
| #endif |
| } |
| |
| /**************************************** windows service */ |
| |
| #ifndef _WIN32_WCE |
| |
| NOEXPORT int service_initialize(void) { |
| SERVICE_TABLE_ENTRY serviceTable[]={{0, 0}, {0, 0}}; |
| |
| serviceTable[0].lpServiceName=SERVICE_NAME; |
| serviceTable[0].lpServiceProc=service_main; |
| /* disable taskbar for security */ |
| /* global_options.option.taskbar=0; */ |
| if(!StartServiceCtrlDispatcher(serviceTable)) { |
| error_box(TEXT("StartServiceCtrlDispatcher")); |
| return 1; |
| } |
| return 0; /* NT service started */ |
| } |
| |
| #define DESCR_LEN 256 |
| |
| NOEXPORT int service_install() { |
| SC_HANDLE scm, service; |
| TCHAR stunnel_exe_path[MAX_PATH]; |
| LPTSTR service_path; |
| TCHAR descr_str[DESCR_LEN]; |
| SERVICE_DESCRIPTION descr; |
| |
| scm=OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); |
| if(!scm) { |
| error_box(TEXT("OpenSCManager")); |
| return 1; |
| } |
| GetModuleFileName(0, stunnel_exe_path, MAX_PATH); |
| service_path=str_tprintf(TEXT("\"%s\" -service %s"), |
| stunnel_exe_path, get_params()); |
| service=CreateService(scm, SERVICE_NAME, SERVICE_DISPLAY_NAME, |
| SERVICE_ALL_ACCESS, |
| SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS, |
| SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service_path, |
| NULL, NULL, TEXT("TCPIP\0"), NULL, NULL); |
| if(!service) { |
| error_box(TEXT("CreateService")); |
| str_free(service_path); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| str_free(service_path); |
| if(LoadString(ghInst, IDS_SERVICE_DESC, descr_str, DESCR_LEN)) { |
| descr.lpDescription=descr_str; |
| ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &descr); |
| } |
| message_box(TEXT("Service installed"), MB_ICONINFORMATION); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 0; |
| } |
| |
| NOEXPORT int service_uninstall(void) { |
| SC_HANDLE scm, service; |
| SERVICE_STATUS serviceStatus; |
| |
| scm=OpenSCManager(0, 0, SC_MANAGER_CONNECT); |
| if(!scm) { |
| error_box(TEXT("OpenSCManager")); |
| return 1; |
| } |
| service=OpenService(scm, SERVICE_NAME, SERVICE_QUERY_STATUS|DELETE); |
| if(!service) { |
| error_box(TEXT("OpenService")); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!QueryServiceStatus(service, &serviceStatus)) { |
| error_box(TEXT("QueryServiceStatus")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(serviceStatus.dwCurrentState!=SERVICE_STOPPED) { |
| message_box(TEXT("The service is still running"), MB_ICONERROR); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!DeleteService(service)) { |
| error_box(TEXT("DeleteService")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| message_box(TEXT("Service uninstalled"), MB_ICONINFORMATION); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 0; |
| } |
| |
| NOEXPORT int service_start(void) { |
| SC_HANDLE scm, service; |
| SERVICE_STATUS serviceStatus; |
| |
| scm=OpenSCManager(0, 0, SC_MANAGER_CONNECT); |
| if(!scm) { |
| error_box(TEXT("OpenSCManager")); |
| return 1; |
| } |
| service=OpenService(scm, SERVICE_NAME, SERVICE_QUERY_STATUS|SERVICE_START); |
| if(!service) { |
| error_box(TEXT("OpenService")); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!StartService(service, 0, NULL)) { |
| error_box(TEXT("StartService")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| do { |
| Sleep(1000); |
| if(!QueryServiceStatus(service, &serviceStatus)) { |
| error_box(TEXT("QueryServiceStatus")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| } while(serviceStatus.dwCurrentState==SERVICE_START_PENDING); |
| if(serviceStatus.dwCurrentState!=SERVICE_RUNNING) { |
| message_box(TEXT("Failed to start service"), MB_ICONERROR); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| message_box(TEXT("Service started"), MB_ICONINFORMATION); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 0; |
| } |
| |
| NOEXPORT int service_stop(void) { |
| SC_HANDLE scm, service; |
| SERVICE_STATUS serviceStatus; |
| |
| scm=OpenSCManager(0, 0, SC_MANAGER_CONNECT); |
| if(!scm) { |
| error_box(TEXT("OpenSCManager")); |
| return 1; |
| } |
| service=OpenService(scm, SERVICE_NAME, SERVICE_QUERY_STATUS|SERVICE_STOP); |
| if(!service) { |
| error_box(TEXT("OpenService")); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!QueryServiceStatus(service, &serviceStatus)) { |
| error_box(TEXT("QueryServiceStatus")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(serviceStatus.dwCurrentState==SERVICE_STOPPED) { |
| message_box(TEXT("The service is already stopped"), MB_ICONERROR); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!ControlService(service, SERVICE_CONTROL_STOP, &serviceStatus)) { |
| error_box(TEXT("ControlService")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| do { |
| Sleep(1000); |
| if(!QueryServiceStatus(service, &serviceStatus)) { |
| error_box(TEXT("QueryServiceStatus")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| } while(serviceStatus.dwCurrentState!=SERVICE_STOPPED); |
| message_box(TEXT("Service stopped"), MB_ICONINFORMATION); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 0; |
| } |
| |
| NOEXPORT int service_user(DWORD sig) { |
| SC_HANDLE scm, service; |
| SERVICE_STATUS serviceStatus; |
| |
| scm=OpenSCManager(0, 0, SC_MANAGER_CONNECT); |
| if(!scm) { |
| error_box(TEXT("OpenSCManager")); |
| return 1; |
| } |
| service=OpenService(scm, SERVICE_NAME, |
| SERVICE_QUERY_STATUS|SERVICE_USER_DEFINED_CONTROL); |
| if(!service) { |
| error_box(TEXT("OpenService")); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!QueryServiceStatus(service, &serviceStatus)) { |
| error_box(TEXT("QueryServiceStatus")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(serviceStatus.dwCurrentState==SERVICE_STOPPED) { |
| message_box(TEXT("The service is stopped"), MB_ICONERROR); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| if(!ControlService(service, SERVICE_CONTROL_USER+sig, &serviceStatus)) { |
| error_box(TEXT("ControlService")); |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 1; |
| } |
| switch(sig) { |
| case SIGNAL_RELOAD_CONFIG: |
| message_box(TEXT("Service configuration reloaded"), |
| MB_ICONINFORMATION); |
| break; |
| case SIGNAL_REOPEN_LOG: |
| message_box(TEXT("Service log file reopened"), |
| MB_ICONINFORMATION); |
| break; |
| default: |
| message_box(TEXT("Undefined operation requested"), |
| MB_ICONINFORMATION); |
| } |
| CloseServiceHandle(service); |
| CloseServiceHandle(scm); |
| return 0; |
| } |
| |
| NOEXPORT void WINAPI service_main(DWORD argc, LPTSTR* argv) { |
| (void)argc; /* skip warning about unused parameter */ |
| (void)argv; /* skip warning about unused parameter */ |
| |
| tls_alloc(NULL, NULL, "service"); /* new thread-local storage */ |
| |
| /* initialise service status */ |
| serviceStatus.dwServiceType=SERVICE_WIN32; |
| serviceStatus.dwCurrentState=SERVICE_STOPPED; |
| serviceStatus.dwControlsAccepted=0; |
| serviceStatus.dwWin32ExitCode=NO_ERROR; |
| serviceStatus.dwServiceSpecificExitCode=NO_ERROR; |
| serviceStatus.dwCheckPoint=0; |
| serviceStatus.dwWaitHint=0; |
| |
| serviceStatusHandle= |
| RegisterServiceCtrlHandler(SERVICE_NAME, control_handler); |
| |
| if(serviceStatusHandle) { |
| /* service is starting */ |
| serviceStatus.dwCurrentState=SERVICE_START_PENDING; |
| SetServiceStatus(serviceStatusHandle, &serviceStatus); |
| |
| /* running */ |
| serviceStatus.dwControlsAccepted|= |
| (SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN); |
| serviceStatus.dwCurrentState=SERVICE_RUNNING; |
| SetServiceStatus(serviceStatusHandle, &serviceStatus); |
| |
| gui_loop(); |
| |
| /* service was stopped */ |
| serviceStatus.dwCurrentState=SERVICE_STOP_PENDING; |
| SetServiceStatus(serviceStatusHandle, &serviceStatus); |
| |
| /* service is now stopped */ |
| serviceStatus.dwControlsAccepted&= |
| (DWORD)~(SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN); |
| serviceStatus.dwCurrentState=SERVICE_STOPPED; |
| SetServiceStatus(serviceStatusHandle, &serviceStatus); |
| } |
| } |
| |
| NOEXPORT void WINAPI control_handler(DWORD controlCode) { |
| switch(controlCode) { |
| case SERVICE_CONTROL_INTERROGATE: |
| break; |
| case SERVICE_CONTROL_SHUTDOWN: |
| case SERVICE_CONTROL_STOP: |
| serviceStatus.dwCurrentState=SERVICE_STOP_PENDING; |
| SetServiceStatus(serviceStatusHandle, &serviceStatus); |
| PostMessage(hwnd, WM_COMMAND, IDM_EXIT, 0); |
| return; |
| case SERVICE_CONTROL_PAUSE: |
| break; |
| case SERVICE_CONTROL_CONTINUE: |
| break; |
| case SERVICE_CONTROL_USER+SIGNAL_RELOAD_CONFIG: |
| signal_post(SIGNAL_RELOAD_CONFIG); |
| break; |
| case SERVICE_CONTROL_USER+SIGNAL_REOPEN_LOG: |
| signal_post(SIGNAL_REOPEN_LOG); |
| break; |
| default: |
| if(controlCode >= 128 && controlCode <= 255) |
| break; /* user defined control code */ |
| else |
| break; /* unrecognised control code */ |
| } |
| SetServiceStatus(serviceStatusHandle, &serviceStatus); |
| } |
| |
| #endif /* !defined(_WIN32_WCE) */ |
| |
| /**************************************** other functions */ |
| |
| NOEXPORT LPTSTR get_params() { |
| LPTSTR c; |
| TCHAR s; |
| |
| c=GetCommandLine(); |
| if(*c==TEXT('\"')) { |
| s=TEXT('\"'); |
| ++c; |
| } else { |
| s=TEXT(' '); |
| } |
| for(; *c; ++c) |
| if(*c==s) { |
| ++c; |
| break; |
| } |
| while(*c==TEXT(' ')) |
| ++c; |
| return c; |
| } |
| |
| /* end of ui_win_gui.c */ |