Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

835 lines
21 KiB

/*++
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 <stdio.h>
#endif
//#ifndef UNICODE
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
//#endif
#include <stdarg.h>
#include <windows.h>
#include <winsvc.h>
#include <malloc.h>
#include <tstring.h>
#include <lmerr.h>
#include <lmcons.h>
#include <lmshare.h>
#include <lmsname.h>
#include <lmaccess.h>
#include <lmapibuf.h>
#include <lmmsg.h>
#include <upsfunc.h>
#include <netdebug.h>
//
// 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;
}