/*++ Copyright (c) 1991-1992 Microsoft Corporation Module Name: upsfunc.c Abstract: Contains function to alert users when server is going down as a result of UPS running out of juice Note: It is assumed that the UpsNotifyUsers is called by one thread only ie. It is not re-entrant Contents: UpsNotifyUsers (BitsSet) (UpspOpenService) (UpspCloseService) (UpspControlService) (UpspNotifyAllUsers) Author: Richard L Firth (rfirth) 09-Apr-1992 Revision History: --*/ #ifdef UNIT_TEST #include #endif //#ifndef UNICODE #include #include #include //#endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include // // manifests & types // #define UPSP_ACCESS_RIGHTS ( SERVICE_STOP \ | SERVICE_PAUSE_CONTINUE \ | SERVICE_QUERY_STATUS ) #define MAX_WAIT_PERIOD 0x10000L // 10 seconds - tune? #define MAX_HANG_COUNT 100 // ditto #define MAX_UPS_MESSAGE_LENGTH 1024 #ifdef UNIT_TEST #ifdef UNICODE #define PERCENT_S "%ws" #else #define PERCENT_S "%s" #endif #define DEBUG_PRINT(x) printf x #else #define DEBUG_PRINT(x) #endif typedef struct { LPTSTR UserName; DWORD Language; } USER_LANGUAGE_INFO, *LPUSER_LANGUAGE_INFO; // // prototypes // DBGSTATIC DWORD BitsSet( IN DWORD Dword ); DBGSTATIC SC_HANDLE UpspOpenService( IN LPTSTR ServiceName ); DBGSTATIC VOID UpspCloseService( IN SC_HANDLE Handle ); DBGSTATIC NET_API_STATUS UpspControlService( IN SC_HANDLE Handle, IN DWORD Control ); DBGSTATIC NET_API_STATUS UpspNotifyAllUsers( IN DWORD MessageId, IN HANDLE MessageHandle, IN va_list *Arguments OPTIONAL ); // // functions // NET_API_STATUS UpsNotifyUsers( IN DWORD MessageId, IN HANDLE MessageHandle, IN DWORD ActionFlags, IN ... ) /*++ Routine Description: Sends a notification message to all users using this server and optionally pauses the server service or stops the workstation and server services Now also continues server; Messages sent dependent on flag Assumes: only 1 action + send message per call - cannot stop + continue + send message in same call Arguments: MessageId - which message to send MessageHandle - resource handle to message file ActionFlags - what action(s) to take ... - optional insertion args for the message Return Value: NET_API_STATUS Success - NERR_Success Failure - ERROR_INVALID_PARAMETER Action not one of predefined values ERROR_SERVICE_DOES_NOT_EXIST Tried to pause (server) or stop (server or wksta) service which isn't started --*/ { NET_API_STATUS status; SC_HANDLE hScWksta = NULL; SC_HANDLE hScServer = NULL; va_list argList; BOOL sendMessage = ActionFlags & UPS_ACTION_SEND_MESSAGE; // // can only have one action, plus send message // ActionFlags &= ~UPS_ACTION_SEND_MESSAGE; if (BitsSet(ActionFlags) > 1 || ActionFlags & ~UPS_ACTION_FLAGS_ALLOWED) { return ERROR_INVALID_PARAMETER; } // // open handle to the server service // hScServer = UpspOpenService(SERVICE_SERVER); // // this would most likely be ERROR_SERVICE_DOES_NOT_EXIST // if (!hScServer) { return (NET_API_STATUS)GetLastError(); } // // perform specific action - stop, pause or continue // if (ActionFlags & (UPS_ACTION_PAUSE_SERVER | UPS_ACTION_CONTINUE_SERVER)) { DWORD serviceAction = ActionFlags & UPS_ACTION_PAUSE_SERVER ? SERVICE_CONTROL_PAUSE : SERVICE_CONTROL_CONTINUE; status = UpspControlService(hScServer, serviceAction); } else if (ActionFlags & UPS_ACTION_STOP_SERVER) { // // stopping the server - we have to stop the workstation service too // hScWksta = UpspOpenService(SERVICE_WORKSTATION); // // NB. If can't stop the services then have to take ownership of ACL // for SC and make sure we have enough priv. to stop services // // // NB2. I am assuming here that server is dependent on wksta. This may // be an invalid assumption - should I enumerate the dependent services // and stop in reverse order? // status = UpspControlService(hScServer, SERVICE_CONTROL_STOP); // // we won't be too upset if the workstation service can't be stopped // (on NT, it doesn't have to be started before the server can start) // UpspControlService(hScWksta, SERVICE_CONTROL_STOP); UpspCloseService(hScWksta); } // // close the handle to the server service object // UpspCloseService(hScServer); if (sendMessage) { va_start(argList, ActionFlags); status = UpspNotifyAllUsers(MessageId, MessageHandle, &argList); va_end(argList); } return status; } DBGSTATIC DWORD BitsSet( IN DWORD Dword ) /*++ Routine Description: Count number of bits set in argument Arguments: Dword - find number of bits set in this Return Value: DWORD number of bits set --*/ { DWORD i, n; for (n = 0, i = 1; i; i <<= 1) { n += (Dword & i) ? 1 : 0; Dword &= ~i; if (!Dword) { return n; } } return n; } DBGSTATIC SC_HANDLE hScManager = NULL; DBGSTATIC DWORD ObjectsOpened = 0; // unprotected reference counter DBGSTATIC SC_HANDLE UpspOpenService( IN LPTSTR ServiceName ) /*++ Routine Description: Opens a handle to the required service object. As a side-effect also opens a handle to SC Manager object Arguments: ServiceName - name of service to open handle to Return Value: SC_HANDLE NULL - couldn't open handle to SC Manager or requested service !NULL - handle opened to requested service --*/ { SC_HANDLE handle = NULL; DEBUG_PRINT(("UpspOpenService: " PERCENT_S ": ", ServiceName)); // // Note: if ERROR_ACCESS_DENIED from either open then we have to take // ownership of the ACL and do the following: // // 1. add ACE at start of list to give LocalSystem privilege to pause // and stop processes. The ACE must go at the start because there // may be an overriding ACE earlier in the list which would take // precedence // 2. re-open the service object (& possibly sc manager object) // 3. pause or stop the service // 4. remove ACE. Unless we do this, the new ACL will be written to the // registry. // // LocalSystem *SHOULD* have rights to stop and pause services. Currently // it can start services, but Rita is going to change that. A problem may // arise if an admin somehow removes LocalSystem's privilege to pause and // stop services // if (!hScManager) { hScManager = OpenSCManager(NULL, NULL, GENERIC_READ); } if (hScManager) { handle = OpenService(hScManager, ServiceName, UPSP_ACCESS_RIGHTS); if (handle) { ++ObjectsOpened; DEBUG_PRINT(("OK\n")); } else { DEBUG_PRINT(("FAILED (service object)\n")); } } else { DEBUG_PRINT(("FAILED (SC manager object)\n")); } return handle; } DBGSTATIC VOID UpspCloseService( IN SC_HANDLE Handle ) /*++ Routine Description: Closes handle to service. If no more service handles open then closes handle to SC Manager Arguments: Handle - to close Return Value: None --*/ { DEBUG_PRINT(("UpspCloseService (service object)\n")); CloseServiceHandle(Handle); if (!--ObjectsOpened) { DEBUG_PRINT(("UpspCloseService (SC manager object)\n")); CloseServiceHandle(hScManager); hScManager = NULL; } } DBGSTATIC NET_API_STATUS UpspControlService( IN SC_HANDLE Handle, IN DWORD Control ) /*++ Routine Description: Sends a control to a service and waits until the requested action has occurred or until the service has died or gone into limbo Arguments: Handle - to service to control Control - what control to apply Return Value: NET_API_STATUS Success - NERR_Success The service identified by Handle has been successfully stopped or paused, depending on Control Failure - ERROR_ACCESS_DENIED Don't expect this - change the code somewhere if this happens ERROR_DEPENDENT_SERVICES_RUNNING Again, need to modify things if this returned ERROR_SERVICE_REQUEST_TIMEOUT We hijack this error code when we detect that the service process has snuffed it ERROR_INVALID_SERVICE_CONTROL ERROR_INSUFFICIENT_BUFFER not expected return code from registry? --*/ { BOOL ok; SERVICE_STATUS serviceStatus; DWORD desiredState; DWORD lastCheckPoint; DWORD hungService; DEBUG_PRINT(("UpspControlService: " PERCENT_S " service\n", Control == SERVICE_CONTROL_PAUSE ? "PAUSING" : "STOPPING" )); ok = ControlService(Handle, Control, &serviceStatus); if (!ok) { DEBUG_PRINT(("UpspControlService failed - ControlService returned %d\n", GetLastError())); return (NET_API_STATUS)GetLastError(); } if (serviceStatus.dwCurrentState == SERVICE_STOP_PENDING || serviceStatus.dwCurrentState == SERVICE_PAUSE_PENDING) { switch (Control) { case SERVICE_CONTROL_PAUSE: desiredState = SERVICE_PAUSED; break; case SERVICE_CONTROL_STOP: desiredState = SERVICE_STOPPED; break; } // // record the current check point. The service should "periodically // increment" this if it is still alive // lastCheckPoint = serviceStatus.dwCheckPoint; hungService = 0; while (serviceStatus.dwCurrentState != desiredState) { ok = QueryServiceStatus(Handle, &serviceStatus); if (!ok) { DEBUG_PRINT(("UpspControlService failed - QueryServiceStatus returned %d\n", GetLastError())); return (NET_API_STATUS)GetLastError(); } if (serviceStatus.dwCurrentState != desiredState) { if (lastCheckPoint == serviceStatus.dwCheckPoint) { ++hungService; if (hungService > MAX_HANG_COUNT) { // // as above: according to the Service Control // Specification Rev 1.2, this error code is usually // returned from the service control manager if a // timeout occurs on service start // DEBUG_PRINT(("UpspControlService failed - service is HUNG\n")); return ERROR_SERVICE_REQUEST_TIMEOUT; } } else { hungService = 0; } lastCheckPoint = serviceStatus.dwCheckPoint; // // wait for the process. Try to avoid errant wait values by // imposing a maximum // Sleep(serviceStatus.dwWaitHint > MAX_WAIT_PERIOD ? MAX_WAIT_PERIOD : serviceStatus.dwWaitHint ); } } } #if DBG if (serviceStatus.dwCurrentState != SERVICE_STOPPED && serviceStatus.dwCurrentState != SERVICE_PAUSED) { NetpKdPrint(("UpsNotifyUsers: service object not in expected state\n")); } #endif DEBUG_PRINT(("UpspControlService - returning SUCCESS\n")); return NERR_Success; } DBGSTATIC NET_API_STATUS UpspNotifyAllUsers( IN DWORD MessageId, IN HANDLE MessageHandle, IN va_list *Arguments OPTIONAL ) /*++ Routine Description: Sends a message to all users logged onto this server. Assumes that the server service (& hence server process) is started Arguments: MessageId - which message to send MessageHandle - handle to resource Arguments - insertion args for message Return Value: NET_API_STATUS Success - NERR_Success Failure - --*/ { NET_API_STATUS status; NET_API_STATUS enumStatus; LPSESSION_INFO_10 enumBuf = NULL; LPSESSION_INFO_10 enumPtr; DWORD entriesRead; DWORD entriesLeft; LPUSER_LANGUAGE_INFO names = NULL; DWORD resumeHandle = 0; DWORD namesOnList = 0; DWORD i; BOOL found; LPTSTR nameBuf; LPUSER_INFO_11 infoBuf = NULL; DWORD language; DWORD previousLanguage = (DWORD)(-1); TCHAR thisComputer[MAX_COMPUTERNAME_LENGTH+1]; DWORD nameLen; #ifndef UNICODE UNICODE_STRING unicodeString; OEM_STRING ansiString; NTSTATUS ntstatus; #endif TCHAR messageBuffer[MAX_UPS_MESSAGE_LENGTH+1]; nameLen = sizeof(thisComputer); GetComputerName(thisComputer, &nameLen); DEBUG_PRINT(("UpspNotifyAllUsers: this computer = " PERCENT_S "\n", thisComputer)); // // Always send message to the local computer first. // language = previousLanguage = 0; (VOID)FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_MAX_WIDTH_MASK, MessageHandle, MessageId, language, messageBuffer, sizeof(messageBuffer), Arguments ); (VOID)NetMessageBufferSend( NULL, thisComputer, thisComputer, (LPBYTE)messageBuffer, STRSIZE(messageBuffer) ); // // NB. Prove that all information comes back from one call to SessionEnum // and considerably simplify this routine // do { enumStatus = NetSessionEnum(NULL, // ServerName NULL, // ClientName NULL, // UserName 10, // Level (LPBYTE*)&enumBuf, (DWORD)(-1),// everything, please &entriesRead, &entriesLeft, &resumeHandle ); DEBUG_PRINT(("UpspNotifyAllUsers: NetSessionEnum returns : %u\n" " enumBuf = %x\n" " entriesRead = %u\n" " entriesLeft = %u\n" " resumeHandle = %x\n", enumStatus, enumBuf, entriesLeft, entriesRead, resumeHandle )); if ((enumStatus != NERR_Success && enumStatus != ERROR_MORE_DATA) || entriesRead == 0) { #if DBG if (enumStatus != NERR_Success && enumStatus != ERROR_MORE_DATA) { NetpKdPrint(("UpsNotifyUsers: NetSessionEnum(level 10) returns %u\n", enumStatus)); } #endif status = enumStatus; goto exitRoutine; } // // allocate a list of pointers big enough for all names we will send // the message to. Since we asked for all connections to all users on // this server, we will allocate more space than we need if users have // more than 1 connection // if (!names) { names = (LPUSER_LANGUAGE_INFO)calloc(entriesLeft, sizeof(USER_LANGUAGE_INFO)); if (!names) { status = ERROR_NOT_ENOUGH_MEMORY; goto exitRoutine; } DEBUG_PRINT(("UpspNotifyAllUsers: allocated names @ %x\n", names)); } for (enumPtr = enumBuf; entriesRead; ++enumPtr, --entriesRead) { for (i = 0, found = FALSE; i < namesOnList; ++i) { if (!STRICMP(names[i].UserName, enumPtr->sesi10_username)) { found = TRUE; DEBUG_PRINT(("UpspNotifyAllUsers: already have names " PERCENT_S " on list\n", enumPtr->sesi10_username )); break; } } if (!found) { // // NB. We don't have to do this if we can prove that we never // have to go through the outer loop more than once // nameBuf = (LPTSTR)malloc(STRSIZE(enumPtr->sesi10_username)); if (!nameBuf) { status = ERROR_NOT_ENOUGH_MEMORY; goto exitRoutine; } DEBUG_PRINT(("UpspNotifyAllUsers: allocated name buffer @ %x\n", nameBuf)); STRCPY(nameBuf, enumPtr->sesi10_username); names[namesOnList].UserName = nameBuf; #ifndef UNICODE NetpInitOemString(&ansiString, nameBuf); ntstatus = RtlOemStringToUnicodeString(&unicodeString, &ansiString, TRUE); #if DBG NetpAssert(NT_SUCCESS(ntstatus)); #endif #endif status = NetUserGetInfo(NULL, #ifndef UNICODE unicodeString.Buffer, #else nameBuf, #endif 11, (LPBYTE*)&infoBuf ); if (status == NERR_Success) { language = infoBuf->usri11_country_code; NetApiBufferFree(infoBuf); } else { #ifndef UNICODE #define _USER_NAME_ unicodeString.Buffer #else #define _USER_NAME_ nameBuf #endif DEBUG_PRINT(("UpspNotifyAllUsers: NetUserGetInfo(" PERCENT_S ") failed with %u\n", _USER_NAME_, status )); #undef _USER_NAME_ language = 0; } names[namesOnList].Language = language; ++namesOnList; #ifndef UNICODE RtlFreeUnicodeString(&unicodeString); #endif } else { language = names[i].Language; } if (language != previousLanguage) { // // format the message and return it here in an allocated buffer // BUGBUG - FormatMessage don't allocate for us yet. Use our // own buffer // (VOID)FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_MAX_WIDTH_MASK, MessageHandle, MessageId, language, messageBuffer, sizeof(messageBuffer), Arguments ); previousLanguage = language; } #ifdef UNIT_TEST DEBUG_PRINT(("UpspNotifyAllUsers: sending message '" PERCENT_S "' to " PERCENT_S "\n", messageBuffer, enumPtr->sesi10_cname )); #else status = NetMessageBufferSend(NULL, enumPtr->sesi10_cname, thisComputer, (LPBYTE)messageBuffer, STRSIZE(messageBuffer) ); #endif } } while ( enumStatus == ERROR_MORE_DATA ); status = enumStatus; // // here in case of error and normal termination. Free up any resources // still held // exitRoutine: if (names) { for (i = 0; i < namesOnList; ++i) { DEBUG_PRINT(("UpspNotifyAllUsers: freeing %x\n", names[i].UserName)); free(names[i].UserName); } DEBUG_PRINT(("UpspNotifyAllUsers: freeing %x\n", names)); free(names); } if (enumBuf) { DEBUG_PRINT(("UpspNotifyAllUsers: freeing %x\n", enumBuf)); NetApiBufferFree(enumBuf); } DEBUG_PRINT(("UpspNotifyAllUsers: returning %d\n", status)); return status; }