// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . #include "boinc_win.h" #include "diagnostics.h" #include "error_numbers.h" #include "filesys.h" #include "network.h" #include "prefs.h" #include "str_replace.h" #include "url.h" #include "util.h" #include "win_util.h" #include "client_state.h" #include "log_flags.h" #include "client_msgs.h" #include "http_curl.h" #include "sandbox.h" #include "main.h" #include "cs_proxy.h" #include "net_stats.h" #include "sysmon_win.h" static HANDLE g_hWindowsMonitorSystemPowerThread = NULL; static HWND g_hWndWindowsMonitorSystemPower = NULL; static HANDLE g_hWindowsMonitorSystemProxyThread = NULL; // The following 3 functions are called in a separate thread, // so we can't do anything directly. // Set flags telling the main thread what to do. // // Quit operations static void quit_client() { gstate.requested_exit = true; while (1) { boinc_sleep(1.0); if (gstate.cleanup_completed) break; } } // Suspend client operations static void suspend_client() { gstate.os_requested_suspend = true; gstate.os_requested_suspend_time = dtime(); } // Resume client operations static void resume_client() { gstate.os_requested_suspend = false; } // Process console messages sent by the system static BOOL WINAPI console_control_handler( DWORD dwCtrlType ){ BOOL bReturnStatus = FALSE; BOINCTRACE("***** Console Event Detected *****\n"); switch( dwCtrlType ){ case CTRL_LOGOFF_EVENT: BOINCTRACE("Event: CTRL-LOGOFF Event\n"); if (!gstate.executing_as_daemon) { quit_client(); } bReturnStatus = TRUE; break; case CTRL_C_EVENT: case CTRL_BREAK_EVENT: BOINCTRACE("Event: CTRL-C or CTRL-BREAK Event\n"); quit_client(); bReturnStatus = TRUE; break; case CTRL_CLOSE_EVENT: case CTRL_SHUTDOWN_EVENT: BOINCTRACE("Event: CTRL-CLOSE or CTRL-SHUTDOWN Event\n"); quit_client(); break; } return bReturnStatus; } static void post_sysmon_msg(const char* msg) { if (gstate.have_sysmon_msg) return; safe_strcpy(gstate.sysmon_msg, msg); gstate.have_sysmon_msg = true; } // Trap events on Windows so we can clean ourselves up. // NOTE: this runs in a separate thread. // Be careful accessing global data structures. // static LRESULT CALLBACK WindowsMonitorSystemPowerWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { switch(uMsg) { // If we are not installed as a service, begin the task shutdown process // when we are notified that the system is going down. If we are installed // as a service, wait until the service control manager tells us to shutdown. // // The console handler happens a little to late in the cycle and leads // to BOINC attempting to start new tasks, which fail, when the OS has // shutdown some of the ones that were executing. // case WM_QUERYENDSESSION: if (!gstate.executing_as_daemon) { quit_client(); } return TRUE; break; // On Windows power events are broadcast via the WM_POWERBROADCAST // window message. It has the following parameters: // PBT_APMQUERYSUSPEND // PBT_APMQUERYSUSPENDFAILED // PBT_APMSUSPEND // PBT_APMRESUMECRITICAL // PBT_APMRESUMESUSPEND // PBT_APMBATTERYLOW // PBT_APMPOWERSTATUSCHANGE // PBT_APMOEMEVENT // PBT_APMRESUMEAUTOMATIC case WM_POWERBROADCAST: switch(wParam) { // System is preparing to suspend. This is valid on // Windows versions older than Vista case PBT_APMQUERYSUSPEND: return TRUE; break; // System is resuming from a failed request to suspend // activity. This is only valid on Windows versions // older than Vista case PBT_APMQUERYSUSPENDFAILED: resume_client(); break; // System is critically low on battery power. This is // only valid on Windows versions older than Vista case PBT_APMBATTERYLOW: post_sysmon_msg("Critical battery alarm, Windows is suspending operations"); suspend_client(); break; // System is suspending case PBT_APMSUSPEND: post_sysmon_msg("Windows is suspending operations"); suspend_client(); break; // System is resuming from a normal power event case PBT_APMRESUMESUSPEND: gstate.set_now(); post_sysmon_msg("Windows is resuming operations"); // Check for a proxy working_proxy_info.need_autodetect_proxy_settings = true; resume_client(); break; } break; default: break; } return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } // Create a thread to monitor system events static DWORD WINAPI WindowsMonitorSystemPowerThread( LPVOID ) { WNDCLASS wc; MSG msg; wc.style = CS_GLOBALCLASS; wc.lpfnWndProc = (WNDPROC)WindowsMonitorSystemPowerWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = NULL; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "BOINCWindowsMonitorSystemPower"; if (!RegisterClass(&wc)) { log_message_error("Failed to register the WindowsMonitorSystem window class."); return 1; } g_hWndWindowsMonitorSystemPower = CreateWindow( wc.lpszClassName, "BOINC Monitor System (Power)", WS_OVERLAPPEDWINDOW & ~WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL); if (!g_hWndWindowsMonitorSystemPower) { log_message_error("Failed to create the WindowsMonitorSystem window."); return 0; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } // Detect any proxy configuration settings automatically. static void windows_detect_autoproxy_settings() { if (log_flags.proxy_debug) { post_sysmon_msg("[proxy] automatic proxy check in progress"); } HMODULE hModWinHttp = LoadLibrary("winhttp.dll"); if (!hModWinHttp) { return; } pfnWinHttpOpen pWinHttpOpen = (pfnWinHttpOpen)GetProcAddress(hModWinHttp, "WinHttpOpen"); if (!pWinHttpOpen) { return; } pfnWinHttpCloseHandle pWinHttpCloseHandle = (pfnWinHttpCloseHandle)(GetProcAddress(hModWinHttp, "WinHttpCloseHandle")); if (!pWinHttpCloseHandle) { return; } pfnWinHttpGetProxyForUrl pWinHttpGetProxyForUrl = (pfnWinHttpGetProxyForUrl)(GetProcAddress(hModWinHttp, "WinHttpGetProxyForUrl")); if (!pWinHttpGetProxyForUrl) { return; } HINTERNET hWinHttp = NULL; WINHTTP_AUTOPROXY_OPTIONS autoproxy_options; WINHTTP_PROXY_INFO proxy_info; PARSED_URL purl; std::wstring network_test_url; size_t pos; memset(&autoproxy_options, 0, sizeof(autoproxy_options)); memset(&proxy_info, 0, sizeof(proxy_info)); autoproxy_options.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; autoproxy_options.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; autoproxy_options.fAutoLogonIfChallenged = TRUE; network_test_url = A2W(config.network_test_url).c_str(); hWinHttp = pWinHttpOpen( L"BOINC client", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, NULL ); char msg[1024], buf[1024]; strcpy(msg, "[proxy] "); if (pWinHttpGetProxyForUrl(hWinHttp, network_test_url.c_str(), &autoproxy_options, &proxy_info)) { // Apparently there are some conditions where WinHttpGetProxyForUrl can return // success but where proxy_info.lpszProxy is null. Maybe related to UPNP? // // For the time being check to see if proxy_info.lpszProxy is non-null. // if (proxy_info.lpszProxy) { std::string proxy(W2A(std::wstring(proxy_info.lpszProxy))); std::string new_proxy; if (log_flags.proxy_debug) { strcat(msg, "proxy list: "); strcat(msg, proxy.c_str()); } if (!proxy.empty()) { // Trim string if more than one proxy is defined // proxy list is defined as: // ([=]["://"][":"]) // Find and erase first delimeter type. pos = proxy.find(';'); if (pos != -1 ) { new_proxy = proxy.erase(pos); proxy = new_proxy; } // Find and erase second delimeter type. pos = proxy.find(' '); if (pos != -1 ) { new_proxy = proxy.erase(pos); proxy = new_proxy; } // Parse the remaining url parse_url(proxy.c_str(), purl); // Store the results for future use. if (0 != strcmp(working_proxy_info.autodetect_server_name, purl.host)) { // Reset clients connection error detection path net_status.need_physical_connection = false; working_proxy_info.autodetect_protocol = purl.protocol; safe_strcpy(working_proxy_info.autodetect_server_name, purl.host); working_proxy_info.autodetect_port = purl.port; } if (log_flags.proxy_debug) { sprintf(buf, "proxy detected %s:%d", purl.host, purl.port); strcat(msg, buf); } } } // Clean up if (proxy_info.lpszProxy) GlobalFree(proxy_info.lpszProxy); if (proxy_info.lpszProxyBypass) GlobalFree(proxy_info.lpszProxyBypass); } else { // We can get here if the user is switching from a network that // requires a proxy to one that does not require a proxy. working_proxy_info.autodetect_protocol = 0; strcpy(working_proxy_info.autodetect_server_name, ""); working_proxy_info.autodetect_port = 0; if (log_flags.proxy_debug) { strcat(msg, "no automatic proxy detected"); } } if (hWinHttp) pWinHttpCloseHandle(hWinHttp); FreeLibrary(hModWinHttp); if (log_flags.proxy_debug) { post_sysmon_msg(msg); } } static DWORD WINAPI WindowsMonitorSystemProxyThread( LPVOID ) { // notify the main client thread that detecting proxies is // supported. working_proxy_info.autodetect_proxy_supported = true; while (1) { if (working_proxy_info.need_autodetect_proxy_settings) { working_proxy_info.have_autodetect_proxy_settings = false; windows_detect_autoproxy_settings(); working_proxy_info.need_autodetect_proxy_settings = false; working_proxy_info.have_autodetect_proxy_settings = true; } Sleep(1000); } return 0; } // Setup the client software to monitor various system events int initialize_system_monitor(int /*argc*/, char** /*argv*/) { // Windows: install console controls, the service control manager will send us // the needed events when we are running as a service. // if (!gstate.executing_as_daemon) { if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)console_control_handler, TRUE)){ log_message_error("Failed to register the console control handler."); return ERR_IO; } } // Create a window to receive system power events. g_hWindowsMonitorSystemPowerThread = CreateThread( NULL, 0, WindowsMonitorSystemPowerThread, NULL, 0, NULL); if (!g_hWindowsMonitorSystemPowerThread) { g_hWindowsMonitorSystemPowerThread = NULL; g_hWndWindowsMonitorSystemPower = NULL; } // Create a thread to handle proxy auto-detection. g_hWindowsMonitorSystemProxyThread = CreateThread( NULL, 0, WindowsMonitorSystemProxyThread, NULL, 0, NULL); if (!g_hWindowsMonitorSystemProxyThread) { g_hWindowsMonitorSystemProxyThread = NULL; } return 0; } // Cleanup the system event monitor int cleanup_system_monitor() { if (g_hWindowsMonitorSystemPowerThread) { TerminateThread(g_hWindowsMonitorSystemPowerThread, 0); CloseHandle(g_hWindowsMonitorSystemPowerThread); g_hWindowsMonitorSystemPowerThread = NULL; g_hWndWindowsMonitorSystemPower = NULL; } if (g_hWindowsMonitorSystemProxyThread) { TerminateThread(g_hWindowsMonitorSystemProxyThread, 0); CloseHandle(g_hWindowsMonitorSystemProxyThread); g_hWindowsMonitorSystemProxyThread = NULL; } return 0; } // internal variables for managing the service SERVICE_STATUS ssStatus; // current status of the service SERVICE_STATUS_HANDLE sshStatusHandle; DWORD dwErr = 0; TCHAR szErr[1024]; SERVICE_TABLE_ENTRY service_dispatch_table[] = { { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)BOINCServiceMain }, { NULL, NULL } }; // Inform the service control manager that the service is about to // start. int initialize_service_dispatcher(int /*argc*/, char** /*argv*/) { fprintf(stdout, "\nStartServiceCtrlDispatcher being called.\n"); fprintf(stdout, "This may take several seconds. Please wait.\n"); if (!StartServiceCtrlDispatcher(service_dispatch_table)) { log_message_error("StartServiceCtrlDispatcher failed."); return ERR_IO; } return 0; } // // FUNCTION: BOINCServiceMain // // PURPOSE: To perform actual initialization of the service // // PARAMETERS: // dwArgc - number of command line arguments // lpszArgv - array of command line arguments // // RETURN VALUE: // none // // COMMENTS: // This routine performs the service initialization and then calls // the user defined main() routine to perform majority // of the work. // void WINAPI BOINCServiceMain(DWORD /*dwArgc*/, LPTSTR * /*lpszArgv*/) { // SERVICE_STATUS members that don't change in example // ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; ssStatus.dwControlsAccepted = SERVICE_ACCEPTED_ACTIONS; ssStatus.dwServiceSpecificExitCode = 0; // register our service control handler: // sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), BOINCServiceCtrl); if (!sshStatusHandle) goto cleanup; if (!ReportStatus( SERVICE_RUNNING, // service state ERROR_SUCCESS, // exit code 0)) // wait hint goto cleanup; dwErr = boinc_main_loop(); cleanup: // try to report the stopped status to the service control manager. // if (sshStatusHandle) { (VOID)ReportStatus( SERVICE_STOPPED, dwErr, 0 ); } } // // FUNCTION: BOINCServiceCtrl // // PURPOSE: This function is called by the SCM whenever // ControlService() is called on this service. // // PARAMETERS: // dwCtrlCode - type of control requested // // RETURN VALUE: // none // // COMMENTS: // VOID WINAPI BOINCServiceCtrl(DWORD dwCtrlCode) { // Handle the requested control code. // switch(dwCtrlCode) { // Stop the service. // // SERVICE_STOP_PENDING should be reported before // setting the Stop Event - hServerStopEvent - in // ServiceStop(). This avoids a race condition // which may result in a 1053 - The Service did not respond... // error. case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: ReportStatus(SERVICE_STOP_PENDING, ERROR_SUCCESS, 30000); quit_client(); return; // Pause the service. // case SERVICE_CONTROL_PAUSE: ReportStatus(SERVICE_PAUSE_PENDING, ERROR_SUCCESS, 10000); suspend_client(); ReportStatus(SERVICE_PAUSED, ERROR_SUCCESS, 10000); return; // Continue the service. // case SERVICE_CONTROL_CONTINUE: ReportStatus(SERVICE_CONTINUE_PENDING, ERROR_SUCCESS, 10000); resume_client(); ReportStatus(SERVICE_RUNNING, ERROR_SUCCESS, 10000); return; // Update the service status. // case SERVICE_CONTROL_INTERROGATE: break; // invalid control code // default: break; } ReportStatus(ssStatus.dwCurrentState, ERROR_SUCCESS, 1000); } // // FUNCTION: ReportStatus() // // PURPOSE: Sets the current status of the service and // reports it to the Service Control Manager // // PARAMETERS: // dwCurrentState - the state of the service // dwWin32ExitCode - error code to report // dwWaitHint - worst case estimate to next checkpoint // // RETURN VALUE: // TRUE - success // FALSE - failure // // COMMENTS: // BOOL ReportStatus( DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint ) { static DWORD dwCheckPoint = 1; BOOL fResult = TRUE; if (dwCurrentState == SERVICE_START_PENDING) ssStatus.dwControlsAccepted = 0; else ssStatus.dwControlsAccepted = SERVICE_ACCEPTED_ACTIONS; ssStatus.dwCurrentState = dwCurrentState; ssStatus.dwWin32ExitCode = dwWin32ExitCode; ssStatus.dwWaitHint = dwWaitHint; if ( ( dwCurrentState == SERVICE_RUNNING ) || ( dwCurrentState == SERVICE_STOPPED ) ) { ssStatus.dwCheckPoint = 0; } else { ssStatus.dwCheckPoint = dwCheckPoint++; } // Report the status of the service to the service control manager. // fResult = SetServiceStatus( sshStatusHandle, &ssStatus); if (!fResult) { LogEventErrorMessage(TEXT("SetServiceStatus")); } return fResult; } // // FUNCTION: LogEventErrorMessage(LPTSTR lpszMsg) // // PURPOSE: Allows any thread to log an error message // // PARAMETERS: // lpszMsg - text for message // // RETURN VALUE: // none // // COMMENTS: // VOID LogEventErrorMessage(LPTSTR lpszMsg) { TCHAR szMsg[1024]; HANDLE hEventSource; LPTSTR lpszStrings[2]; dwErr = GetLastError(); // Use event logging to log the error. // hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr); lpszStrings[0] = szMsg; lpszStrings[1] = lpszMsg; if (hEventSource != NULL) { ReportEvent(hEventSource, // handle of event source EVENTLOG_ERROR_TYPE, // event type 0, // event category 1, // event ID NULL, // current user's SID 2, // strings in lpszStrings 0, // no bytes of raw data (LPCSTR*)lpszStrings, // array of error strings NULL // no raw data ); (VOID) DeregisterEventSource(hEventSource); } } // // FUNCTION: LogEventWarningMessage(LPTSTR lpszMsg) // // PURPOSE: Allows any thread to log an warning message // // PARAMETERS: // lpszMsg - text for message // // RETURN VALUE: // none // // COMMENTS: // VOID LogEventWarningMessage(LPTSTR lpszMsg) { HANDLE hEventSource; LPTSTR lpszStrings[2]; // Use event logging to log the error. // hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); lpszStrings[0] = lpszMsg; lpszStrings[1] = '\0'; if (hEventSource != NULL) { ReportEvent(hEventSource, // handle of event source EVENTLOG_WARNING_TYPE,// event type 0, // event category 1, // event ID NULL, // current user's SID 2, // strings in lpszStrings 0, // no bytes of raw data (LPCSTR*)lpszStrings, // array of error strings NULL); // no raw data (VOID) DeregisterEventSource(hEventSource); } } // // FUNCTION: LogEventInfoMessage(LPTSTR lpszMsg) // // PURPOSE: Allows any thread to log an info message // // PARAMETERS: // lpszMsg - text for message // // RETURN VALUE: // none // // COMMENTS: // VOID LogEventInfoMessage(LPTSTR lpszMsg) { HANDLE hEventSource; LPTSTR lpszStrings[2]; // Use event logging to log the error. // hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); lpszStrings[0] = lpszMsg; lpszStrings[1] = '\0'; if (hEventSource != NULL) { ReportEvent(hEventSource, // handle of event source EVENTLOG_INFORMATION_TYPE,// event type 0, // event category 1, // event ID NULL, // current user's SID 2, // strings in lpszStrings 0, // no bytes of raw data (LPCSTR*)lpszStrings, // array of error strings NULL); // no raw data (VOID) DeregisterEventSource(hEventSource); } }