summaryrefslogtreecommitdiffstats
path: root/private/net/svcdlls/at/server
diff options
context:
space:
mode:
Diffstat (limited to 'private/net/svcdlls/at/server')
-rw-r--r--private/net/svcdlls/at/server/at.h384
-rw-r--r--private/net/svcdlls/at/server/atapi.c636
-rw-r--r--private/net/svcdlls/at/server/atdebug.c295
-rw-r--r--private/net/svcdlls/at/server/atenv.c176
-rw-r--r--private/net/svcdlls/at/server/atmain.c1060
-rw-r--r--private/net/svcdlls/at/server/atreg.c603
-rw-r--r--private/net/svcdlls/at/server/atsec.c246
-rw-r--r--private/net/svcdlls/at/server/attime.c355
-rw-r--r--private/net/svcdlls/at/server/makefile6
-rw-r--r--private/net/svcdlls/at/server/run.c242
-rw-r--r--private/net/svcdlls/at/server/sources38
11 files changed, 4041 insertions, 0 deletions
diff --git a/private/net/svcdlls/at/server/at.h b/private/net/svcdlls/at/server/at.h
new file mode 100644
index 000000000..fb49cf7a2
--- /dev/null
+++ b/private/net/svcdlls/at/server/at.h
@@ -0,0 +1,384 @@
+/*++
+
+Copyright (c) 1987-92 Microsoft Corporation
+
+Module Name:
+
+ at.h
+
+Abstract:
+
+ Local include file for the SCHEDULE service.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+#include <nt.h> // NT definitions
+#include <ntrtl.h> // NT runtime library definitions
+#include <nturtl.h>
+
+#include <netevent.h>
+#include <windef.h> // Win32 type definitions
+#include <winbase.h> // Win32 base API prototypes
+#include <winsvc.h> // Win32 service control APIs
+#include <winreg.h> // HKEY
+
+#include <lmcons.h> // LAN Manager common definitions
+#include <lmerr.h> // LAN Manager network error definitions
+#include <lmsname.h> // LAN Manager service names
+#include <lmapibuf.h> // NetApiBufferFree
+
+#include <netlib.h> // LAN Man utility routines
+#include <netlibnt.h> // NetpNtStatusToApiStatus
+#include <netdebug.h> // NetpDbgPrint
+#include <tstring.h> // Transitional string functions
+#include <icanon.h> // I_Net canonicalize functions
+#include <align.h> // ROUND_UP_COUNT macro
+
+#include <services.h> // LMSVCS_GLOBAL_DATA
+#include <apperr.h> // APE_AT_ID_NOT_FOUND
+
+#include <rpc.h> // DataTypes and runtime APIs
+#include <rpcutil.h> // Prototypes for MIDL user functions
+#include <atsvc.h> // Generated by the MIDL complier
+
+
+#if DBG
+#define AT_DEBUG
+#endif // DBG
+
+
+//
+// atmain.c will #include this file with ATDATA_ALLOCATE defined.
+// That will cause each of these variables to be allocated.
+//
+#ifdef ATDATA_ALLOCATE
+#define EXTERN
+#define INIT( _x) = _x
+#else
+#define EXTERN extern
+#define INIT( _x)
+#endif
+
+//
+// Server side data structures.
+//
+
+typedef struct _AT_RECORD {
+ LIST_ENTRY RuntimeList; // queue this on a doubly linked list
+ LIST_ENTRY JobIdList; // queue this on a doubly linked list
+ LARGE_INTEGER Runtime; // next time to run (secs from 01.Jan.1970)
+ DWORD JobId; // unique job id
+ PWCHAR Name; // name of this task in registry
+ WORD CommandSize; // in bytes, including NULL terminator
+ WORD NameSize; // not really needed, but fill in to DWORD
+
+ DWORD JobTime; // time of day to run, in miliseconds from midnight
+ DWORD DaysOfMonth; // bitmask for days of month to run
+ UCHAR DaysOfWeek; // bitmask for days of week to run
+ UCHAR Flags; // see lmat.h
+ UCHAR JobDay; // index of WeekDay or MonthDay when to run
+#ifdef AT_DEBUG
+ UCHAR Debug; // for debugging purposes
+#endif // AT_DEBUG
+ PWCHAR Command; // command & data to execute
+} AT_RECORD, *PAT_RECORD, *LPAT_RECORD;
+
+typedef struct _AT_SCHEDULE {
+ DWORD JobTime; // time of day to run, in seconds from midnight
+ DWORD DaysOfMonth; // bitmask for days of month to run
+ UCHAR DaysOfWeek; // bitmask for days of week to run
+ UCHAR Flags; // see lmat.h
+ WORD Reserved; // padding, since registry pads them as well
+} AT_SCHEDULE, *PAT_SCHEDULE, *LPAT_SCHEDULE;
+
+
+typedef struct _AT_TIME {
+ LARGE_INTEGER LargeInteger; // time since Jan.01,1970 (in NT_TICK-s)
+ DWORD TickCount; // time since most recent boot (in WINDOWS_TICK-s)
+ DWORD CurrentTime; // time in 24h day, (in WINDOWS_TICK-s)
+ WORD CurrentYear;
+ WORD CurrentMonth; // January=1, February=2, ...
+ WORD CurrentDayOfWeek; // Monday=0, Tuesday=1, ...
+ WORD CurrentDay; // first=1, second=2, ...
+} AT_TIME, *PAT_TIME, *LPAT_TIME;
+
+
+
+//
+// Functions exported by atmain.c
+//
+VOID AtReportEvent(
+ IN WORD EventType,
+ IN DWORD MessageId,
+ IN WORD StringsCount,
+ IN LPWSTR * StringArray,
+ IN DWORD RawDataBufferLength OPTIONAL,
+ IN LPVOID RawDataBuffer
+ );
+
+
+//
+// Functions exported by atenv.c
+//
+VOID AtSetEnvironment( LPSTARTUPINFO pStartupInfo);
+
+//
+// Functions exported by attime.c
+//
+VOID AtCalculateRuntime(
+ IN OUT PAT_RECORD pRecord,
+ IN PAT_TIME pTime
+ );
+VOID AtTimeGetCurrents( IN OUT PAT_TIME pTime);
+VOID AtTimeGet( OUT PAT_TIME pTime);
+
+
+//
+// Functions exported by atreg.c
+//
+DWORD AtCreateKey( PAT_RECORD pRecord);
+BOOL AtDeleteKey( PAT_RECORD pRecord);
+VOID AtInsertRecord(
+ PAT_RECORD pNewRecord,
+ DWORD QueueMask
+ );
+NET_API_STATUS AtMakeDataFromRegistry( IN PAT_TIME pTime);
+BOOL AtPermitServerOperators( VOID);
+VOID AtRemoveRecord(
+ PAT_RECORD pNewRecord,
+ DWORD QueueMask
+ );
+BOOL AtSystemInteractive( VOID);
+
+
+//
+// Functions exported by atsec.c
+//
+NET_API_STATUS
+AtCheckSecurity(
+ ACCESS_MASK DesiredAccess
+ );
+NET_API_STATUS
+AtCreateSecurityObject(
+ VOID
+ );
+NET_API_STATUS
+AtDeleteSecurityObject(
+ VOID
+ );
+
+#ifdef NOT_YET
+//
+// Is job enabled or disabled.
+//
+// This flag is not supported yet - i.e. code to disable (and then re-enable)
+// jobs is missing.
+//
+#define JOB_IS_ENABLED 0x40 // set if enabled
+#endif // NOT_YET
+
+
+
+//
+// Bitmask used with AtGlobalTasks. They describe outstanding tasks to be
+// done by the main schedule service thread once it wakes up.
+//
+
+#define AT_SERVICE_SHUTDOWN 0x0004
+
+
+//
+// Bitmask describing on what global queues we should operate on.
+//
+
+#define RUNTIME_QUEUE 0x0001
+#define JOBID_QUEUE 0x0002
+#define BOTH_QUEUES (RUNTIME_QUEUE | JOBID_QUEUE)
+
+
+//
+// For a /NEXT type of program, JOB_CLEAR_WEEKDAY flag determines whether
+// to clear WEEKLY or MONTHLY schedule.
+//
+#define JOB_CLEAR_WEEKDAY 0x80 // set if WEEKLY schedule
+
+
+#define JOB_INVALID_DAY 0xFF
+
+
+
+#define MAXIMUM_COMMAND_LENGTH (MAX_PATH - 1) // == 259, cmd.exe uses this
+
+#define AT_REGISTRY_PATH L"System\\CurrentControlSet\\Services\\Schedule"
+#define WINDOWS_REGISTRY_PATH L"System\\CurrentControlSet\\Control\\Windows"
+#define WINDOWS_VALUE_NAME L"NoInteractiveServices"
+#define INTERACTIVE_DESKTOP L"WinSta0\\Default"
+#define LSA_REGISTRY_PATH L"System\\CurrentControlSet\\Control\\Lsa"
+#define LSA_SUBMIT_CONTROL L"SubmitControl"
+#define LSA_SERVER_OPERATORS 0x00000001 // can they submit jobs, etc.
+
+#define MAXIMUM_JOB_TIME (24 * 60 * 60 * 1000 - 1)
+#define DAYS_OF_WEEK 0x7F // 7 bits for 7 days
+#define DAYS_OF_MONTH 0x7FFFFFFF // 31 bits for 31 days
+
+#define AT_SCHEDULE_NAME L"Schedule"
+#define AT_COMMAND_NAME L"Command"
+
+//
+// WINDOWS_TICK is one milisecond, i.e. 1e-3 seconds
+// NT_TICK is hundred nanoseconds, i.e. 1e-7 seconds
+//
+#define NT_TICKS_IN_WINDOWS_TICK 10000L
+#define ONE_MINUTE_IN_NT_TICKS (60000L * NT_TICKS_IN_WINDOWS_TICK)
+#define MAXIMUM_FINITE_SLEEP_TIME ((DWORD)-2) // in windows ticks
+#define MAX_BUSY_TIMEOUT (300*1000L) // 5 MIN in WINDOWS_TICK-s
+#define MAX_LAZY_TIMEOUT (3600*1000L); // 60 MIN in WINDOWS_TICK-s
+
+#define AT_WAIT_HINT_TIME 5000L
+
+
+//
+// The following are used only when we create new keys via NetrJobAdd.
+// A user can edit registry directly and define key names larger than this.
+//
+#define AT_KEY_NAME_MAX_LEN 8
+#define AT_KEY_NAME_SIZE ((AT_KEY_NAME_MAX_LEN + 1) * sizeof( WCHAR))
+
+
+
+//
+// BUGBUG Need to define ERROR_SERVICE_PAUSED in one of common include
+// BUGBUG files. This error means that request is denied because service
+// BUGBUG is paused. For now, set it to 65535.
+//
+#define ERROR_SERVICE_PAUSED 0xFFFF
+
+
+
+//
+// Object specific access masks
+//
+
+#define AT_JOB_ADD 0x0001
+#define AT_JOB_DEL 0x0002
+#define AT_JOB_ENUM 0x0004
+#define AT_JOB_GET_INFO 0x0008
+
+
+//
+// Defines used to indicate how far we managed to initialize the Schedule
+// service before an error is encountered and the extent of clean up needed
+//
+
+#define AT_EVENT_CREATED 0x00000001
+#define AT_QUEUES_CREATED 0x00000002
+#define AT_RPC_SERVER_STARTED 0x00000004
+#define AT_SECURITY_OBJECT_CREATED 0x00000008
+
+
+//-------------------------------------------------------------------//
+// //
+// Global variables //
+// //
+//-------------------------------------------------------------------//
+
+
+EXTERN CRITICAL_SECTION AtGlobalCriticalSection;
+EXTERN CRITICAL_SECTION AtGlobalProtectLogFile;
+EXTERN DWORD AtGlobalTasks;
+EXTERN SERVICE_STATUS AtGlobalServiceStatus;
+EXTERN SERVICE_STATUS_HANDLE AtGlobalServiceStatusHandle;
+EXTERN DWORD AtGlobalJobId;
+EXTERN LIST_ENTRY AtGlobalRuntimeListHead;
+EXTERN LIST_ENTRY AtGlobalJobIdListHead;
+EXTERN HANDLE AtGlobalEvent;
+EXTERN HANDLE AtGlobalLogFile;
+EXTERN DWORD AtGlobalSeed;
+EXTERN AT_TIME AtGlobalSleepTime;
+EXTERN AT_TIME AtGlobalGetupTime;
+EXTERN HANDLE AtGlobalEvent;
+EXTERN DWORD AtGlobalSeed;
+EXTERN HKEY AtGlobalKey;
+EXTERN BOOL AtGlobalHaveWindowsKey;
+EXTERN BOOL AtGlobalPermitServerOperators;
+EXTERN HKEY AtGlobalWindowsKey;
+EXTERN STARTUPINFO AtGlobalStartupInfo;
+EXTERN CHAR AtGlobalDebugBuffer[ 1024]; // arbitrary
+
+
+#ifdef AT_DEBUG
+
+EXTERN DWORD AtGlobalDebug;
+
+////////////////////////////////////////////////////////////////////////
+//
+// Debug Definititions
+//
+////////////////////////////////////////////////////////////////////////
+
+#define AT_DEBUG_MAIN 0x00000001
+#define AT_DEBUG_UTIL 0x00000002
+#define AT_DEBUG_CRITICAL 0x00000004
+
+//
+// Control bits.
+//
+
+#define AT_TIMESTAMP 0x10000000 // TimeStamp each output line
+
+#define IF_DEBUG(Function) \
+ if (AtGlobalDebug & AT_ ## Function)
+
+VOID AtDebugCreate( VOID);
+VOID AtDebugDelete( VOID);
+
+VOID AtAssertFailed(
+ IN PVOID FailedAssertion,
+ IN PVOID FileName,
+ IN ULONG LineNumber,
+ IN PCHAR Message OPTIONAL
+ );
+
+#define AtAssert( Predicate) \
+ { \
+ if (!(Predicate)) \
+ AtAssertFailed( #Predicate, __FILE__, __LINE__, NULL ); \
+ }
+
+VOID AtLogRoutine(
+ IN DWORD DebugFlag,
+ IN LPSTR Format, // PRINTF()-STYLE FORMAT STRING.
+ ... // OTHER ARGUMENTS ARE POSSIBLE.
+ );
+VOID AtLogRuntimeList( IN PCHAR Comment);
+VOID AtLogTimeout( IN DWORD timeout);
+
+#define AtLog( _x_) AtLogRoutine _x_
+
+#else // AT_DEBUG
+
+#define AtAssert( condition)
+
+#define AtLog( _x_)
+#define AtLogRuntimeList( _x_)
+#define AtLogTimeout( _x_)
+
+#endif // AT_DEBUG
+
+
+
+//
+// SCHEDULE_EVENTLOG_NAME is the name of registry key (under EventLog service)
+// used to interpret events for schedule service.
+//
+
+#define SCHEDULE_EVENTLOG_NAME TEXT( "Schedule")
diff --git a/private/net/svcdlls/at/server/atapi.c b/private/net/svcdlls/at/server/atapi.c
new file mode 100644
index 000000000..0217221b2
--- /dev/null
+++ b/private/net/svcdlls/at/server/atapi.c
@@ -0,0 +1,636 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ atapi.c
+
+Abstract:
+
+ This module contains the worker routines for all APIs implemented
+ in the Schedule service.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+#include "at.h"
+
+
+NET_API_STATUS NET_API_FUNCTION
+NetrJobAdd(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN LPAT_INFO pAtInfo,
+ OUT LPDWORD pJobId
+ )
+/*++
+
+Routine Description:
+
+ This function is the NetJobAdd entry point in the Schedule service.
+ Given the info about a new job, its creates a new job and returns the
+ id of the new job.
+
+Arguments:
+
+ ServerName - IGNORED
+ pAtInfo - pointer to information about the job to be added
+ pJobId - pointer to id of the newly added job
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NET_API_STATUS status;
+ PAT_RECORD pRecord;
+ DWORD CommandLength;
+ DWORD CommandSize;
+ AT_TIME time;
+
+
+ UNREFERENCED_PARAMETER( ServerName);
+
+ status = AtCheckSecurity( AT_JOB_ADD);
+ if ( status != NERR_Success) {
+ return( ERROR_ACCESS_DENIED);
+ }
+
+ //
+ // Is it safe to calculate string length below?
+ // Should RPC supply string length parameter?
+ // Note that wcslen() returns length of UNICODE string in WCHAR-s.
+ // Thus storage needed for Command is (CommandLength+1)*sizeof(WCHAR)
+ //
+ CommandLength = wcslen( pAtInfo->Command);
+
+ if ( ( CommandLength > MAXIMUM_COMMAND_LENGTH) ||
+ ( pAtInfo->JobTime > MAXIMUM_JOB_TIME) ||
+ ( pAtInfo->DaysOfWeek & ~DAYS_OF_WEEK) != 0 ||
+ ( pAtInfo->DaysOfMonth & ~DAYS_OF_MONTH) != 0 ||
+ ( pAtInfo->Flags & ~JOB_INPUT_FLAGS) != 0 ) {
+ return( ERROR_INVALID_PARAMETER);
+ }
+
+ CommandSize = ( CommandLength + 1) * sizeof( WCHAR);
+
+ pRecord = (PAT_RECORD)LocalAlloc(
+ LMEM_FIXED,
+ sizeof( AT_RECORD) + AT_KEY_NAME_SIZE + CommandSize
+ );
+ if ( pRecord == NULL) {
+ return( ERROR_NOT_ENOUGH_MEMORY);
+ }
+
+ pRecord->CommandSize = (WORD)CommandSize;
+ pRecord->NameSize = AT_KEY_NAME_SIZE; // max possible
+
+ pRecord->JobTime = pAtInfo->JobTime;
+ pRecord->JobDay = JOB_INVALID_DAY; // the default
+#ifdef AT_DEBUG
+ pRecord->Debug = 0;
+#endif // AT_DEBUG
+
+ pRecord->Name = (PWCHAR)( (PBYTE)pRecord + sizeof( AT_RECORD));
+ memset( pRecord->Name, 0, AT_KEY_NAME_SIZE);
+
+ pRecord->Command = (PWCHAR)( (PBYTE)&pRecord->Name[0] + AT_KEY_NAME_SIZE);
+ memcpy( pRecord->Command, pAtInfo->Command, CommandSize);
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+
+ AtLog(( AT_DEBUG_MAIN, "++JobAdd: Command=%ws\n", pRecord->Command));
+
+ AtTimeGet( &time); // needed in what follows
+
+ if ( pAtInfo->Flags & JOB_ADD_CURRENT_DATE) {
+ pAtInfo->Flags &= ~JOB_ADD_CURRENT_DATE;
+ pAtInfo->DaysOfMonth |= 1 << ( time.CurrentDay - 1);
+ }
+
+ pRecord->Flags = pAtInfo->Flags;
+ pRecord->DaysOfMonth = pAtInfo->DaysOfMonth;
+ pRecord->DaysOfWeek = pAtInfo->DaysOfWeek;
+
+ //
+ // BUGBUG Should we have a more stringent test that makes sure
+ // BUGBUG that service state is SERVICE_RUNNING ?
+ //
+ if ( AtGlobalServiceStatus.dwCurrentState == SERVICE_PAUSED) {
+ //
+ // BUGBUG Error ERROR_SERVICE_PAUSED is not defined properly for now.
+ //
+ status = ERROR_SERVICE_PAUSED;
+ goto error_exit;
+ }
+ ASSERT( AtGlobalServiceStatus.dwCurrentState == SERVICE_RUNNING);
+
+
+ *pJobId = pRecord->JobId = AtGlobalJobId++;
+
+ status = AtCreateKey( pRecord);
+ if ( status != NERR_Success) {
+ LocalFree( pRecord);
+ goto error_exit;
+ }
+
+ AtCalculateRuntime( pRecord, &time);
+ AtInsertRecord( pRecord, BOTH_QUEUES);
+
+error_exit:
+ AtLog(( AT_DEBUG_MAIN, "--JobAdd: Command=%ws status=%d JobId=%d\n",
+ pRecord->Command, status, pRecord->JobId));
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+ SetEvent( AtGlobalEvent); // to calculate new timeout
+ return( status);
+
+} // NetrJobAdd
+
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+NetrJobDel(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN DWORD MinJobId,
+ IN DWORD MaxJobId
+ )
+/*++
+
+Routine Description:
+
+ This function is the NetJobDel entry point in the Schedule service.
+ Given the minimum and maximum job id, this routines deletes all jobs
+ whose job id is greater than or equal to the minimum job id and
+ less than or equal to the maximum job id.
+
+Arguments:
+
+ ServerName - IGNORED
+ MinJobId - minimum job id
+ MaxJobId - maximum job id
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NET_API_STATUS status;
+ PLIST_ENTRY pListEntry;
+ PAT_RECORD pRecord;
+ BOOL JobDeleted;
+
+ UNREFERENCED_PARAMETER(ServerName);
+
+ status = AtCheckSecurity( AT_JOB_DEL);
+ if ( status != NERR_Success) {
+ return( status);
+ }
+
+ if ( MinJobId > MaxJobId) {
+ return( ERROR_INVALID_PARAMETER);
+ }
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+
+ AtLog(( AT_DEBUG_MAIN, "++JobDel: MinJobId=%d MaxJobId=%d\n",
+ MinJobId, MaxJobId));
+
+ for ( JobDeleted = FALSE, pListEntry = AtGlobalJobIdListHead.Flink;
+ pListEntry != &AtGlobalJobIdListHead;
+ NOTHING) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ JobIdList
+ );
+
+ if ( pRecord->JobId > MaxJobId) {
+ break; // JobId is too larger, we are done
+ }
+
+ pListEntry = pListEntry->Flink; // actual iteration statement
+
+ if ( pRecord->JobId < MinJobId) {
+ continue; // JobId is too small, look further
+ }
+
+ JobDeleted = TRUE;
+
+ AtDeleteKey( pRecord);
+ AtRemoveRecord( pRecord, BOTH_QUEUES);
+ (VOID)LocalFree( pRecord);
+ }
+
+ status = JobDeleted == TRUE ? NERR_Success : APE_AT_ID_NOT_FOUND;
+
+ AtLog(( AT_DEBUG_MAIN, "--JobDel: MinJobId=%d MaxJobId=%d status=%d\n",
+ MinJobId, MaxJobId, status));
+
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+ SetEvent( AtGlobalEvent); // to calculate new timeout
+ return( status);
+
+} // NetrJobDelete
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+NetrJobEnum(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN OUT LPAT_ENUM_CONTAINER pEnumContainer,
+ IN DWORD PreferredMaximumLength,
+ OUT LPDWORD TotalEntries,
+ IN OUT LPDWORD ResumeHandle OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ This function is the NetJobEnum entry point in the Schedule service.
+ It returns information about jobs starting with a job id given by
+ resume handle, or if resume handle is absent, starting with a job
+ with the lowest job id.
+
+Arguments:
+
+ ServerName - IGNORED
+
+ pEnumContainer - pointer to enumeration container which contains the
+ array of job information structures and size of that array.
+
+ PreferedMaximumLength - Supplies the number of bytes of information
+ to return in the buffer. If this value is MAXULONG, all available
+ information will be returned.
+
+ TotalEntries - Returns the total number of entries available. This value
+ is only valid if the return code is NERR_Success or ERROR_MORE_DATA.
+
+ ResumeHandle - Supplies a handle to resume the enumeration from where it
+ left off the last time through. Returns the resume handle if return
+ code is ERROR_MORE_DATA.
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NET_API_STATUS status;
+ DWORD JobId; // resume job id
+ DWORD JobCount;
+ DWORD BufferSize;
+ PLIST_ENTRY pListEntry;
+ PAT_RECORD pRecord;
+ LPBYTE Buffer;
+ LPAT_ENUM pAtEnum;
+ LPBYTE StringBuffer;
+ DWORD EntriesRead;
+ BOOL success;
+ AT_TIME time;
+
+ UNREFERENCED_PARAMETER(ServerName);
+
+ status = AtCheckSecurity( AT_JOB_ENUM);
+ if ( status != NERR_Success) {
+ return( ERROR_ACCESS_DENIED);
+ }
+
+ JobId = (ARGUMENT_PRESENT( ResumeHandle)) ? *ResumeHandle : 0;
+ Buffer = NULL;
+ EntriesRead = 0;
+
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+
+ AtLog(( AT_DEBUG_MAIN, "++JobEnum: JobId=%d\n", JobId));
+
+ AtTimeGet( &time); // needed in what follows
+
+ for ( JobCount = 0, BufferSize = 0,
+ pListEntry = AtGlobalJobIdListHead.Blink;
+ pListEntry != &AtGlobalJobIdListHead;
+ JobCount++, BufferSize += pRecord->CommandSize,
+ pListEntry = pListEntry->Blink) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ JobIdList
+ );
+
+ if ( pRecord->JobId < JobId) {
+ break; // reached first stale record
+ }
+ }
+
+
+ *TotalEntries = JobCount;
+
+ if ( JobCount == 0) {
+ goto error_exit;
+ }
+
+ if ( PreferredMaximumLength == -1) {
+ //
+ // If the caller has not specified a size, calculate a size
+ // that will hold the entire enumeration.
+ //
+ BufferSize += JobCount * sizeof( AT_ENUM);
+
+ } else {
+ BufferSize = PreferredMaximumLength;
+ }
+
+ Buffer = (LPBYTE)MIDL_user_allocate( BufferSize);
+ if ( Buffer == NULL) {
+ status = ERROR_NOT_ENOUGH_MEMORY;
+ goto error_exit;
+ }
+
+ //
+ // When we arrive here "pListEntry" points either to the head of the
+ // list (if we enumerate from the beginning) or to the first stale
+ // record.
+ //
+ for ( pListEntry = pListEntry->Flink, pAtEnum = (PAT_ENUM)Buffer,
+ StringBuffer = Buffer + BufferSize;
+ pListEntry != &AtGlobalJobIdListHead;
+ pListEntry = pListEntry->Flink, pAtEnum++,
+ EntriesRead++) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ JobIdList
+ );
+
+ if ( StringBuffer <= (LPBYTE)pAtEnum + sizeof( AT_ENUM)) {
+ status = ERROR_MORE_DATA;
+ break; // the buffer is full
+ }
+
+ pAtEnum->JobId = pRecord->JobId;
+ pAtEnum->JobTime = pRecord->JobTime;
+ pAtEnum->DaysOfMonth = pRecord->DaysOfMonth;
+ pAtEnum->DaysOfWeek = pRecord->DaysOfWeek;
+ pAtEnum->Flags = pRecord->Flags;
+
+ if ( time.CurrentTime < pRecord->JobTime) {
+ pAtEnum->Flags |= JOB_RUNS_TODAY;
+ }
+
+ success = NetpCopyStringToBuffer(
+ pRecord->Command,
+ pRecord->CommandSize / sizeof( WCHAR) - 1,
+ (LPBYTE)(pAtEnum+1),
+ (LPWSTR *)&StringBuffer,
+ &pAtEnum->Command
+ );
+
+ if ( success == FALSE) {
+ status = ERROR_MORE_DATA;
+ KdPrint(( "[Job] NetrJobEnum: Not enough room\n"));
+ break;
+ }
+ }
+
+ if ( status == ERROR_MORE_DATA) {
+ JobId = pRecord->JobId; // JobId of first one we have not read
+ } else {
+ JobId = 0; // we have read everything, reset resume handle
+ }
+
+error_exit:
+
+ AtLog(( AT_DEBUG_MAIN, "--JobEnum: JobId=%d\n", JobId));
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+ SetEvent( AtGlobalEvent); // to calculate new timeout
+
+ pEnumContainer->EntriesRead = EntriesRead;
+
+ if ( EntriesRead == 0 && Buffer != NULL) {
+
+ MIDL_user_free( Buffer);
+ Buffer = NULL;
+
+ }
+
+ pEnumContainer->Buffer = (LPAT_ENUM)Buffer;
+
+ if ( ARGUMENT_PRESENT( ResumeHandle)) {
+ *ResumeHandle = JobId;
+ }
+
+ return( status);
+
+} // NetrJobEnum
+
+
+
+NET_API_STATUS NET_API_FUNCTION
+NetrJobGetInfo(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN DWORD JobId,
+ OUT LPAT_INFO * ppAtInfo
+ )
+/*++
+
+Routine Description:
+
+ This function is the NetJobGetInfo entry point in the Schedule service.
+ It returns information about a job corresponding to the supplied job id.
+
+Arguments:
+
+ ServerName - IGNORED
+ JobId - job id of a job we are interested in
+ ppAtInfo - pointer to pointer to data about the job in question
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NET_API_STATUS status;
+ PAT_RECORD pRecord;
+ PLIST_ENTRY pListEntry;
+ LPAT_INFO pAtInfo;
+ AT_TIME time;
+
+ UNREFERENCED_PARAMETER(ServerName);
+
+#ifdef AT_DEBUG
+ if ( JobId == 70503010) {
+ DbgUserBreakPoint();
+ }
+#endif // AT_DEBUG
+
+ status = AtCheckSecurity( AT_JOB_GET_INFO);
+ if ( status != NERR_Success) {
+ return( ERROR_ACCESS_DENIED);
+ }
+
+ pAtInfo = NULL;
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+ AtLog(( AT_DEBUG_MAIN, "++JobGetInfo: JobId=%d\n", JobId));
+
+ AtTimeGet( &time); // needed in what follows
+
+ for ( pListEntry = AtGlobalJobIdListHead.Flink;
+ pListEntry != &AtGlobalJobIdListHead;
+ pListEntry = pListEntry->Flink) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ JobIdList
+ );
+
+ if ( pRecord->JobId == JobId) {
+ break;
+ }
+ }
+
+ if ( pListEntry == &AtGlobalJobIdListHead) {
+ status = APE_AT_ID_NOT_FOUND;
+ goto error_exit;
+ }
+
+ pAtInfo = (PAT_INFO)MIDL_user_allocate(
+ sizeof( AT_INFO) + pRecord->CommandSize
+ );
+ if ( pAtInfo == NULL) {
+ status = ERROR_NOT_ENOUGH_MEMORY;
+ goto error_exit;
+ }
+
+ pAtInfo->JobTime = pRecord->JobTime;
+ pAtInfo->DaysOfMonth = pRecord->DaysOfMonth;
+ pAtInfo->DaysOfWeek = pRecord->DaysOfWeek;
+ pAtInfo->Flags = pRecord->Flags;
+
+ if ( time.CurrentTime < pRecord->JobTime) {
+ pAtInfo->Flags |= JOB_RUNS_TODAY;
+ }
+
+ pAtInfo->Command = (LPWSTR)( pAtInfo + 1);
+
+ memcpy( pAtInfo->Command, pRecord->Command, pRecord->CommandSize);
+
+error_exit:
+
+ AtLog(( AT_DEBUG_MAIN, "--JobGetInfo: JobId=%d\n", JobId));
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+ SetEvent( AtGlobalEvent); // to calculate new timeout
+
+ *ppAtInfo = pAtInfo;
+
+ return( status);
+
+} // NetrJobGetInfo
+
+
+#ifdef NOT_YET
+
+
+NET_API_STATUS NET_API_FUNCTION
+NetrJobControl(
+ IN LPCWSTR ServerName OPTIONAL,
+ IN OUT LPJOB_CONTROL_INFO ControlInfo,
+ IN DWORD Opcode
+ )
+/*++
+
+Routine Description:
+
+ This function is the NetJobControl entry point in the Schedule service.
+
+Arguments:
+
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+Comments:
+
+ BUGBUG JobControl contains old stuff - need to clean it up & make it work.
+
+--*/
+{
+ NET_API_STATUS status;
+
+ UNREFERENCED_PARAMETER(ServerName);
+
+ status = AtCheckSecurity( AT_JOB_CONTROL);
+ if ( status != NERR_Success) {
+ return( ERROR_ACCESS_DENIED);
+ }
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+
+ switch ( opcode) {
+ case ENABLE:
+ case DISABLE:
+ break;
+ default:
+ status = ERROR_INVALID_REQUEST;
+ goto error_exit;
+ }
+
+ pJob = FindExactJob( pDelRequest);
+ if ( pJob == NULL) {
+ status = ERROR_JOB_NOT_FOUND;
+ goto error_exit;
+ }
+
+ if ( opcode == pJob->State) { // case of stupid request
+ status = STATUS_SUCCESS;
+ goto error_exit;
+ }
+
+ pJob->State = opcode;
+ DeleteJob( pJob);
+
+ switch( opcode) {
+ case ENABLED:
+ // Evaluate the next run time and queue the job near the front
+ // of the list where all enabled jobs reside.
+ break;
+ case DISABLED:
+ // Optionally set next run time to infinity, then queue the job
+ // near the back of the list where all the disabled jobs reside.
+ break;
+ }
+
+ status = STATUS_SUCCESS;
+
+
+ // Queue a job delete request to a registry queue.
+
+
+error_exit:
+
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+ SetEvent( AtGlobalEvent); // to calculate new timeout
+ return( status);
+
+} // NetrJobControl
+
+#endif // NOT_YET
+
+
diff --git a/private/net/svcdlls/at/server/atdebug.c b/private/net/svcdlls/at/server/atdebug.c
new file mode 100644
index 000000000..e3d88fd6a
--- /dev/null
+++ b/private/net/svcdlls/at/server/atdebug.c
@@ -0,0 +1,295 @@
+/*++
+
+Copyright (c) 1991-1993 Microsoft Corporation
+
+Module Name:
+
+ atdebug.c
+
+Abstract:
+
+ Debugging module.
+
+Author:
+
+ Vladimir Z. Vulovic 27 - July - 1993
+
+Environment:
+
+ User mode
+
+Revision History :
+
+--*/
+
+#include "at.h"
+#include <stdio.h> // vsprintf
+
+#ifdef AT_DEBUG
+
+#define ATSVC_LOG_FILE L"%SystemRoot%\\atsvc.log"
+
+
+VOID AtDebugDelete( VOID)
+{
+ DeleteCriticalSection( &AtGlobalProtectLogFile);
+}
+
+
+VOID AtDebugCreate( VOID)
+{
+ WCHAR Buffer[ MAX_PATH];
+ DWORD Length;
+
+ AtGlobalLogFile = INVALID_HANDLE_VALUE;
+ AtGlobalDebug = (DWORD)(-1); // max debug by default
+ InitializeCriticalSection( &AtGlobalProtectLogFile );
+
+ //
+ // Length returned by ExpandEnvironmentalStrings includes terminating
+ // NULL byte.
+ //
+ Length = ExpandEnvironmentStrings( ATSVC_LOG_FILE, Buffer, sizeof( Buffer));
+ if ( Length == 0) {
+ AtLog(( AT_DEBUG_CRITICAL, "Error=%d", GetLastError()));
+ return;
+ }
+ if ( Length > sizeof( Buffer) || Length != wcslen(Buffer) + 1) {
+ AtLog(( AT_DEBUG_CRITICAL, "Buffer=%x, Length = %d", Buffer, Length));
+ return;
+ }
+
+ AtGlobalLogFile = CreateFileW( Buffer,
+ GENERIC_WRITE,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL );
+
+ if ( AtGlobalLogFile == INVALID_HANDLE_VALUE ) {
+ AtLog(( AT_DEBUG_CRITICAL, "Cannot open %ws", Buffer ));
+ return;
+ }
+
+ //
+ // Position the log file at the end
+ //
+ (VOID) SetFilePointer( AtGlobalLogFile,
+ 0,
+ NULL,
+ FILE_END );
+
+}
+
+
+VOID AtLogRoutine(
+ IN DWORD DebugFlag,
+ IN LPSTR Format,
+ ...
+ )
+
+{
+ va_list arglist;
+ ULONG length;
+ DWORD BytesWritten;
+
+ //
+ // If we aren't debugging this functionality, just return.
+ //
+ if ( DebugFlag != 0 && (AtGlobalDebug & DebugFlag) == 0 ) {
+ return;
+ }
+
+ //
+ // vsprintf isn't multithreaded + we don't want to intermingle output
+ // from different threads. Therefore we can use just a single output
+ // debug buffer.
+ //
+
+ EnterCriticalSection( &AtGlobalProtectLogFile );
+ length = 0;
+
+ //
+ // Handle the beginning of a new line.
+ //
+ //
+
+ //
+ // Put the timestamp at the begining of the line.
+ //
+ IF_DEBUG( TIMESTAMP ) {
+ SYSTEMTIME SystemTime;
+ GetLocalTime( &SystemTime );
+ length += (ULONG) sprintf( &AtGlobalDebugBuffer[length],
+ "%02u/%02u %02u:%02u:%02u ",
+ SystemTime.wMonth,
+ SystemTime.wDay,
+ SystemTime.wHour,
+ SystemTime.wMinute,
+ SystemTime.wSecond );
+ }
+
+ //
+ // Indicate the type of message on the line
+ //
+ {
+ char *Text;
+
+ switch (DebugFlag) {
+ case AT_DEBUG_MAIN:
+ Text = "MAIN";
+ break;
+ case AT_DEBUG_UTIL:
+ Text = "UTIL";
+ break;
+ default:
+ Text = NULL;
+ break;
+ }
+ if ( Text != NULL ) {
+ length += (ULONG) sprintf( &AtGlobalDebugBuffer[length], "[%s] ", Text );
+ }
+ }
+
+ //
+ // Put a the information requested by the caller onto the line
+ //
+
+ va_start( arglist, Format);
+
+ length += (ULONG) vsprintf( &AtGlobalDebugBuffer[length], Format, arglist);
+
+ va_end(arglist);
+
+ AtAssert(length <= sizeof(AtGlobalDebugBuffer));
+
+
+ //
+ // If the log file isn't open, just output to the debug terminal.
+ //
+
+ if ( AtGlobalLogFile == INVALID_HANDLE_VALUE ) {
+ (void) DbgPrint( (PCH) AtGlobalDebugBuffer);
+
+ //
+ // Write the debug info to the log file.
+ //
+
+ } else {
+ if ( !WriteFile( AtGlobalLogFile,
+ AtGlobalDebugBuffer,
+ length,
+ &BytesWritten,
+ NULL ) ) {
+ (void) DbgPrint( (PCH) AtGlobalDebugBuffer);
+ }
+
+ }
+
+ LeaveCriticalSection( &AtGlobalProtectLogFile );
+}
+
+VOID AtAssertFailed(
+ IN PVOID FailedAssertion,
+ IN PVOID FileName,
+ IN ULONG LineNumber,
+ IN PCHAR Message OPTIONAL
+ )
+/*++
+
+ Have my own version of RtlAssert so debug versions of netlogon really assert on
+ free builds.
+
+--*/
+{
+ char Response[ 2 ];
+
+ for ( ; ; ) {
+ DbgPrint( "\n*** Assertion failed: %s%s\n*** Source File: %s, line %ld\n\n",
+ Message ? Message : "",
+ FailedAssertion,
+ FileName,
+ LineNumber
+ );
+
+ DbgPrompt( "Break, Ignore, terminate Process, Sleep 30 seconds, or terminate Thread (bipst)? ",
+ Response, sizeof( Response));
+ switch ( toupper(Response[0])) {
+ case 'B':
+ DbgBreakPoint();
+ break;
+ case 'I':
+ return;
+ break;
+ case 'P':
+ NtTerminateProcess( NtCurrentProcess(), STATUS_UNSUCCESSFUL );
+ break;
+ case 'S':
+ Sleep( 30000L);
+ break;
+ case 'T':
+ NtTerminateThread( NtCurrentThread(), STATUS_UNSUCCESSFUL );
+ break;
+ }
+ }
+
+ DbgBreakPoint();
+ NtTerminateProcess( NtCurrentProcess(), STATUS_UNSUCCESSFUL );
+}
+
+
+VOID AtLogRuntimeList( IN PCHAR Comment)
+{
+ PLIST_ENTRY pListEntry;
+ PAT_RECORD pRecord;
+ TIME_FIELDS TimeFields;
+
+ AtLog(( AT_DEBUG_MAIN, "%s\n", Comment));
+
+ for ( pListEntry = AtGlobalRuntimeListHead.Flink;
+ pListEntry != &AtGlobalRuntimeListHead;
+ pListEntry = pListEntry->Flink) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ RuntimeList
+ );
+ RtlTimeToTimeFields( &pRecord->Runtime, &TimeFields);
+ AtLog(( AT_DEBUG_MAIN,
+ "LogRecord: JobId=%d Command=%ws Runtime=%02u/%02u %02u:%02u:%02u\n",
+ pRecord->JobId,
+ pRecord->Command,
+ TimeFields.Month,
+ TimeFields.Day,
+ TimeFields.Hour,
+ TimeFields.Minute,
+ TimeFields.Second
+ ));
+ }
+}
+
+VOID AtLogTimeout( IN DWORD timeout)
+{
+ int Second, Minute, Hour, Day;
+ Second = timeout / 1000;
+ Minute = Second / 60;
+ Second -= Minute * 60;
+ Hour = Minute / 60;
+ Minute -= Hour * 60;
+ Day = Hour / 24;
+ Hour -= Day * 24;
+ AtLog(( AT_DEBUG_MAIN,
+ "Sleep for: 00/%02u %02u:%02u:%02u\n",
+ Day,
+ Hour,
+ Minute,
+ Second
+ ));
+}
+
+#endif // AT_DEBUG
+
+
+
diff --git a/private/net/svcdlls/at/server/atenv.c b/private/net/svcdlls/at/server/atenv.c
new file mode 100644
index 000000000..c82980279
--- /dev/null
+++ b/private/net/svcdlls/at/server/atenv.c
@@ -0,0 +1,176 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ atenv.c
+
+Abstract:
+
+ Initializes all system and some user environment variables. Code is
+ borrowed from "shell\library\regenv.c" & "user\winlogon\usrenv.c".
+
+ Unlike shell & user code here we do not attempt to set env variables
+ related to user profile. It is harder to obtain user profile for
+ Schedule service than for the interactively logged on user. And, amount
+ of work is too big one month away from Daytona ship date.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 23 - June - 1994
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+ 23-Jun-1994 vladimv
+ Created
+
+--*/
+
+#include "at.h"
+
+#define COMPUTERNAME_VARIABLE TEXT("COMPUTERNAME")
+#define USERNAME_VARIABLE TEXT("USERNAME")
+#define USERDOMAIN_VARIABLE TEXT("USERDOMAIN")
+#define OS_VARIABLE TEXT("OS")
+#define PROCESSOR_VARIABLE TEXT("PROCESSOR_ARCHITECTURE")
+#define PROCESSOR_LEVEL_VARIABLE TEXT("PROCESSOR_LEVEL")
+
+DBGSTATIC BOOL
+GetUserNameAndDomain(
+ OUT LPTSTR * UserName,
+ OUT LPTSTR * UserDomain
+ )
+{
+ HANDLE hToken;
+ DWORD cbTokenBuffer = 0;
+ PTOKEN_USER pUserToken;
+ LPTSTR lpUserName = NULL;
+ LPTSTR lpUserDomain = NULL;
+ DWORD cbAccountName = 0;
+ DWORD cbUserDomain = 0;
+ SID_NAME_USE SidNameUse;
+ BOOL bRet = FALSE;
+
+ if (!OpenProcessToken(GetCurrentProcess(),
+ TOKEN_QUERY,
+ &hToken) ){
+ return(FALSE);
+ }
+
+ //
+ // Get space needed for token information
+ //
+ if (!GetTokenInformation(hToken,
+ TokenUser,
+ NULL,
+ 0,
+ &cbTokenBuffer) ) {
+
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ CloseHandle(hToken);
+ return(FALSE);
+ }
+ }
+
+ //
+ // Get the actual token information
+ //
+ pUserToken = (PTOKEN_USER)LocalAlloc(LPTR, cbTokenBuffer*sizeof(WCHAR));
+ if (pUserToken == NULL) {
+ CloseHandle(hToken);
+ return(FALSE);
+ }
+
+ if (!GetTokenInformation(hToken,
+ TokenUser,
+ pUserToken,
+ cbTokenBuffer,
+ &cbTokenBuffer) ) {
+ goto Error;
+ }
+
+ //
+ // Get the space needed for the User name and the Domain name
+ //
+ if (!LookupAccountSid(NULL,
+ pUserToken->User.Sid,
+ NULL, &cbAccountName,
+ NULL, &cbUserDomain,
+ &SidNameUse
+ ) ) {
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ goto Error;
+ }
+ }
+ lpUserName = (LPTSTR)LocalAlloc(LPTR, cbAccountName*sizeof(WCHAR));
+ if (!lpUserName) {
+ goto Error;
+ }
+
+ lpUserDomain = (LPTSTR)LocalAlloc(LPTR, cbUserDomain*sizeof(WCHAR));
+ if (!lpUserDomain) {
+ LocalFree(lpUserName);
+ goto Error;
+ }
+
+ //
+ // Now get the user name and domain name
+ //
+ if (!LookupAccountSid(NULL,
+ pUserToken->User.Sid,
+ lpUserName, &cbAccountName,
+ lpUserDomain, &cbUserDomain,
+ &SidNameUse
+ ) ) {
+
+ LocalFree(lpUserName);
+ LocalFree(lpUserDomain);
+ goto Error;
+ }
+
+ *UserName = lpUserName;
+ *UserDomain = lpUserDomain;
+ bRet = TRUE;
+
+Error:
+ LocalFree(pUserToken);
+ CloseHandle(hToken);
+
+ return(bRet);
+}
+
+
+
+VOID AtSetEnvironment( LPSTARTUPINFO pStartupInfo)
+/*++
+ Get startup info & set the environment for us & our children.
+--*/
+{
+ LPTSTR UserName = NULL;
+ LPTSTR UserDomain = NULL;
+ TCHAR szComputerName[MAX_COMPUTERNAME_LENGTH+1];
+ DWORD dwComputerNameSize = MAX_COMPUTERNAME_LENGTH+1;
+
+ GetStartupInfo( pStartupInfo);
+ pStartupInfo->lpTitle = NULL;
+
+ //
+ // Changes to the initial environment made in user\winlogon\usrenv.c
+ // should be reflected below. In particular, when processor types
+ // are added in usrenv.c, they also need to be added here.
+ //
+
+ if (GetComputerName (szComputerName, &dwComputerNameSize)) {
+ SetEnvironmentVariable(COMPUTERNAME_VARIABLE, (LPTSTR) szComputerName);
+ }
+ GetUserNameAndDomain(&UserName, &UserDomain);
+ SetEnvironmentVariable( USERNAME_VARIABLE, UserName);
+ SetEnvironmentVariable( USERDOMAIN_VARIABLE, UserDomain);
+ LocalFree( UserName);
+ LocalFree( UserDomain);
+}
diff --git a/private/net/svcdlls/at/server/atmain.c b/private/net/svcdlls/at/server/atmain.c
new file mode 100644
index 000000000..5f326a12e
--- /dev/null
+++ b/private/net/svcdlls/at/server/atmain.c
@@ -0,0 +1,1060 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ atmain.c
+
+Abstract:
+
+ This is the main routine for the NT LAN Manager Schedule service.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+#define ATDATA_ALLOCATE
+#include "at.h"
+#undef ATDATA_ALLOCATE
+#include <atnames.h> // AT_INTERFACE_NAME
+#include <winuser.h> // for winuserp.h
+#include <wingdi.h>
+#include <..\..\..\..\windows\inc\winuserp.h> // for STARTF_DESKTOPINHERIT
+
+
+typedef enum _AT_ERROR_CONDITION {
+ AtErrorRegisterControlHandler = 0,
+ AtErrorCreateEvent,
+ AtErrorNotifyServiceController,
+ AtErrorStartRpcServer,
+ AtErrorCreateSecurityObject,
+ AtErrorMakeDataFromRegistry
+} AT_ERROR_CONDITION, *PAT_ERROR_CONDITION;
+
+
+
+
+DBGSTATIC NET_API_STATUS AtUpdateStatus( VOID)
+/*++
+
+Routine Description:
+
+ This function updates the Schedule service status with the Service
+ Controller.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NET_API_STATUS status = NERR_Success;
+
+ if (AtGlobalServiceStatusHandle == (SERVICE_STATUS_HANDLE) NULL) {
+ AtLog(( AT_DEBUG_MAIN, "Cannot call SetServiceStatus, no status handle.\n"));
+ return( ERROR_INVALID_HANDLE);
+ }
+
+ if ( ! SetServiceStatus( AtGlobalServiceStatusHandle, &AtGlobalServiceStatus)) {
+ status = GetLastError();
+ AtLog(( AT_DEBUG_MAIN, "SetServiceStatus error %lu\n", status));
+ }
+
+ return( status);
+}
+
+
+
+DBGSTATIC VOID AtShutdown(
+ IN NET_API_STATUS ErrorCode,
+ IN DWORD initState
+ )
+/*++
+
+Routine Description:
+
+ This function shuts down the Schedule service.
+
+Arguments:
+
+ ErrorCode - Supplies the error code of the failure
+
+ initState - Supplies a flag to indicate how far we got with initializing
+ the Schedule service before an error occured, thus the amount of
+ clean up needed.
+
+Return Value:
+
+ None.
+
+Note:
+
+ BUGBUG There is a possible race condition between main thread shutting
+ BUGBUG down & some of the rpc-api thread trying to complete their work.
+
+--*/
+{
+ //
+ // Service stop still pending. Update checkpoint counter and the
+ // status with the Service Controller.
+ //
+ (AtGlobalServiceStatus.dwCheckPoint)++;
+ (VOID) AtUpdateStatus();
+
+
+ if ( initState & AT_RPC_SERVER_STARTED) {
+ //
+ // Stop the RPC server
+ //
+ NetpStopRpcServer( atsvc_ServerIfHandle);
+ }
+
+ if ( initState & AT_SECURITY_OBJECT_CREATED) {
+ //
+ // Destroy schedule security object
+ //
+ AtDeleteSecurityObject();
+ }
+
+ //
+ // Service stop still pending. Update checkpoint counter and the
+ // status with the Service Controller.
+ //
+ (AtGlobalServiceStatus.dwCheckPoint)++;
+ (VOID) AtUpdateStatus();
+
+ if ( initState & AT_EVENT_CREATED) {
+ //
+ // Close handle to the event. Note that we do not need to signal
+ // the event. We assume AtShutdown() will be called from the main
+ // schedule service thread and only when it is safe - i.e. when
+ // main service thread is outside its main loop. This avoids some
+ // ugly competition between main thread & shutdown.
+ //
+ CloseHandle( AtGlobalEvent);
+ }
+
+ //
+ // We are done with cleaning up. Tell Service Controller that we are
+ // stopped.
+ //
+ AtGlobalServiceStatus.dwCurrentState = SERVICE_STOPPED;
+ AtGlobalServiceStatus.dwControlsAccepted = 0;
+
+
+ SET_SERVICE_EXITCODE(
+ ErrorCode,
+ AtGlobalServiceStatus.dwWin32ExitCode,
+ AtGlobalServiceStatus.dwServiceSpecificExitCode
+ );
+
+ AtGlobalServiceStatus.dwCheckPoint = 0;
+ AtGlobalServiceStatus.dwWaitHint = 0;
+
+ (VOID) AtUpdateStatus();
+}
+
+
+
+DBGSTATIC VOID AtHandleError(
+ IN AT_ERROR_CONDITION failingCondition,
+ IN NET_API_STATUS status,
+ IN DWORD initState
+ )
+/*++
+
+Routine Description:
+
+ This function handles a Schedule service error condition. If the error
+ condition is fatal, it shuts down the Schedule service.
+
+Arguments:
+
+ failingCondition - Supplies a value which indicates what the failure is.
+
+ status - Supplies the status code for the failure.
+
+ initState - Supplies a flag to indicate how far we got with initializing
+ the Schedule service before an error occured, thus the amount of
+ clean up needed.
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+#ifdef AT_DEBUG
+
+ PCHAR string;
+
+ switch (failingCondition) {
+
+ case AtErrorRegisterControlHandler:
+ string = "Cannot register control handler";
+ break;
+
+ case AtErrorCreateEvent:
+ string = "Cannot create event";
+ break;
+
+ case AtErrorNotifyServiceController:
+ string = "SetServiceStatus error";
+ break;
+
+ case AtErrorStartRpcServer:
+ string = "Cannot start RPC server";
+ break;
+
+ case AtErrorCreateSecurityObject:
+ string = "Error in creating security object";
+ break;
+
+ case AtErrorMakeDataFromRegistry:
+ string = "Severe error while initializing from registry";
+ break;
+
+ default:
+ string = "Unknown error condition %lu\n";
+ NetpAssert(FALSE);
+ }
+
+ NetpKdPrint((
+ "[Job] %s " FORMAT_API_STATUS "\n",
+ string,
+ status
+ ));
+#endif // AT_DEBUG
+
+ AtShutdown( status, initState);
+}
+
+
+VOID AtControlHandler( IN DWORD Opcode)
+/*++
+
+Routine Description:
+
+ This is the service control handler of the Schedule service.
+
+Arguments:
+
+ Opcode - Supplies a value which specifies the action for the Schedule
+ service to perform.
+
+Return Value:
+
+ None.
+
+Comments:
+
+ Note that AtGlobalCriticalSection is used to protect changes both to
+ AtGlobalTasks & AtGlobalServiceStatus.dwCurrentState.
+
+--*/
+{
+ AtLog(( AT_DEBUG_MAIN, "In Control Handler\n"));
+
+ switch (Opcode) {
+
+ case SERVICE_CONTROL_PAUSE:
+
+ // Take critical section and set status so that no new requests
+ // can be submitted. Current requests will still be honored.
+ // Then release critical seciton.
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+ AtGlobalServiceStatus.dwCurrentState = SERVICE_PAUSED;
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+
+ break;
+
+ case SERVICE_CONTROL_CONTINUE:
+
+ // Take critical section and set status so that new requests
+ // can be submitted. Then release critical seciton.
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+ AtGlobalServiceStatus.dwCurrentState = SERVICE_RUNNING;
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+
+ break;
+
+ case SERVICE_CONTROL_STOP:
+
+ if (AtGlobalServiceStatus.dwCurrentState != SERVICE_STOP_PENDING) {
+
+ AtLog(( AT_DEBUG_MAIN, "Stopping schedule service...\n"));
+
+ AtGlobalServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
+ AtGlobalServiceStatus.dwCheckPoint = 1;
+ AtGlobalServiceStatus.dwWaitHint = AT_WAIT_HINT_TIME;
+
+ //
+ // Send the status response.
+ //
+ (VOID) AtUpdateStatus();
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+ AtGlobalTasks |= AT_SERVICE_SHUTDOWN;
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+
+ if (! SetEvent( AtGlobalEvent)) {
+ AtLog(( AT_DEBUG_CRITICAL, "Error setting the event 0x%x\n", GetLastError()));
+ AtAssert( FALSE);
+ }
+
+ return;
+ }
+ break;
+
+ case SERVICE_CONTROL_INTERROGATE:
+ break;
+
+ default:
+ AtLog(( AT_DEBUG_CRITICAL, "Unknown schedule service opcode 0x%x\n", Opcode));
+ break;
+ }
+
+ //
+ // Send the status response.
+ //
+ (VOID) AtUpdateStatus();
+}
+
+
+
+DBGSTATIC NET_API_STATUS AtInitialize(
+ IN PAT_TIME pTime,
+ OUT LPDWORD pInitState
+ )
+/*++
+
+Routine Description:
+
+ This function initializes the Schedule service.
+
+Arguments:
+
+ pInitState - Returns a flag to indicate how far we got with initializing
+ the Schedule service before an error occured.
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NET_API_STATUS status;
+
+
+ //
+ // Initialize all the status fields so that subsequent calls to
+ // SetServiceStatus need to only update fields that changed.
+ //
+
+ AtGlobalServiceStatus.dwServiceType = SERVICE_WIN32;
+ AtGlobalServiceStatus.dwCurrentState = SERVICE_START_PENDING;
+ AtGlobalServiceStatus.dwControlsAccepted = 0;
+ AtGlobalServiceStatus.dwCheckPoint = 1;
+ AtGlobalServiceStatus.dwWaitHint = AT_WAIT_HINT_TIME;
+
+ SET_SERVICE_EXITCODE(
+ NO_ERROR,
+ AtGlobalServiceStatus.dwWin32ExitCode,
+ AtGlobalServiceStatus.dwServiceSpecificExitCode
+ );
+
+ //
+ // Initialize schedule service to receive service requests by registering
+ // the control handler.
+ //
+ if ((AtGlobalServiceStatusHandle = RegisterServiceCtrlHandler(
+ SERVICE_SCHEDULE,
+ AtControlHandler
+ )) == (SERVICE_STATUS_HANDLE) NULL) {
+
+ status = GetLastError();
+ AtHandleError( AtErrorRegisterControlHandler, status, *pInitState);
+ return( status);
+ }
+
+ //
+ // Read the registry path & build queue of things to do.
+ //
+ status = AtMakeDataFromRegistry( pTime);
+ if ( status != NERR_Success) {
+ AtHandleError( AtErrorMakeDataFromRegistry, status, *pInitState);
+ return( status);
+ }
+ (*pInitState) |= AT_QUEUES_CREATED;
+
+ AtGlobalPermitServerOperators = AtPermitServerOperators();
+
+ //
+ // Create the only event used by schedule service.
+ //
+ if ((AtGlobalEvent =
+ CreateEvent(
+ NULL, // Event attributes
+ FALSE, // Event will be automaticallly reset
+ FALSE,
+ NULL // Initial state not signalled
+ )) == NULL) {
+
+ status = GetLastError();
+ AtHandleError( AtErrorCreateEvent, status, *pInitState);
+ return( status);
+ }
+ (*pInitState) |= AT_EVENT_CREATED;
+
+
+ //
+ // Notify the Service Controller for the first time that we are alive
+ // and we are start pending
+ //
+ if ((status = AtUpdateStatus()) != NERR_Success) {
+
+ AtHandleError( AtErrorNotifyServiceController, status, *pInitState);
+ return( status);
+ }
+
+ AtSetEnvironment( &AtGlobalStartupInfo);
+
+ //
+ // Create Schedule service security object
+ //
+ if ((status = AtCreateSecurityObject()) != NERR_Success) {
+
+ AtHandleError( AtErrorCreateSecurityObject, status, *pInitState);
+ return( status);
+ }
+ (*pInitState) |= AT_SECURITY_OBJECT_CREATED;
+
+
+ //
+ // Initialize the schedule service to receive RPC requests.
+ // Note that interface name is defined in atsvc.idl file.
+ //
+ if ((status = NetpStartRpcServer(
+ AT_INTERFACE_NAME,
+ atsvc_ServerIfHandle
+ )) != NERR_Success) {
+
+ AtHandleError( AtErrorStartRpcServer, status, *pInitState);
+ return( status);
+ }
+ (*pInitState) |= AT_RPC_SERVER_STARTED;
+
+
+ //
+ // We are done with starting the Schedule service. Tell Service
+ // Controller our new status.
+ //
+
+ AtGlobalServiceStatus.dwCurrentState = SERVICE_RUNNING;
+ AtGlobalServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
+ SERVICE_ACCEPT_PAUSE_CONTINUE;
+ AtGlobalServiceStatus.dwCheckPoint = 0;
+ AtGlobalServiceStatus.dwWaitHint = 0;
+
+ if ((status = AtUpdateStatus()) != NERR_Success) {
+
+ AtHandleError( AtErrorNotifyServiceController, status, *pInitState);
+ return( status);
+ }
+
+ AtLog(( AT_DEBUG_MAIN, "Successful Initialization\n"));
+
+ return( NERR_Success);
+}
+
+
+
+DBGSTATIC DWORD AtCalculateTimeout(
+ LARGE_INTEGER Runtime,
+ LARGE_INTEGER TimeLargeInteger
+ )
+/*++
+
+Routine Description:
+
+ Given the current time and the next time to run, this routine returns
+ the number of miliseconds to sleep before the next runttime.
+
+Arguments:
+
+ Runtime - next time to run a job
+ TimeLargeInteger - current time
+
+Return Value:
+
+ Number of miliseconds to sleep before the next runtime.
+
+--*/
+{
+ DWORD Remainder;
+
+ Runtime.QuadPart = Runtime.QuadPart - TimeLargeInteger.QuadPart;
+ Runtime = RtlExtendedLargeIntegerDivide(
+ Runtime,
+ NT_TICKS_IN_WINDOWS_TICK,
+ &Remainder
+ );
+ if ( Runtime.HighPart != 0 || Runtime.LowPart > MAX_BUSY_TIMEOUT) {
+ return( MAX_BUSY_TIMEOUT);
+ } else {
+ return( Runtime.LowPart);
+ }
+}
+
+
+DBGSTATIC BOOL AtBatOrCmd( IN OUT PWCHAR Command)
+/*++
+
+ Returns true if we have .BAT or .CMD file.
+
+--*/
+{
+ WCHAR NameBuffer[ MAX_PATH];
+ PWCHAR Temp;
+ DWORD Length;
+ DWORD Index;
+ PWCHAR BatOrCmd[] = { L".bat", L".cmd"};
+
+ for ( Temp = Command; *Temp != 0; Temp++) {
+ if ( *Temp == L' ' || *Temp == L'\t') {
+ *Temp = 0;
+ break;
+ }
+ }
+
+ for ( Index = 0; Index < sizeof(BatOrCmd)/sizeof(BatOrCmd[0]); Index++) {
+ Temp = NULL;
+ Length = SearchPathW(
+ NULL,
+ Command,
+ BatOrCmd[ Index],
+ sizeof( NameBuffer) / sizeof( WCHAR),
+ NameBuffer,
+ &Temp
+ );
+ if ( Length == 0 || Length >= MAX_PATH || Temp == NULL) {
+ continue;
+ }
+#ifdef AT_DEBUG
+ Temp = wcschr( Temp, L'.');
+ if ( Temp == NULL || _wcsicmp( Temp, BatOrCmd[ Index])) {
+ AtLog(( AT_DEBUG_CRITICAL, "BatOrCmd: Command=%ws Extension=%ws\n",
+ Command, BatOrCmd[ Index]));
+ }
+#endif
+ return( TRUE);
+ }
+ return( FALSE);
+}
+
+
+VOID AtReportEvent(
+ IN WORD EventType,
+ IN DWORD MessageId,
+ IN WORD StringCount,
+ IN LPWSTR * StringArray,
+ IN DWORD RawDataBufferLength OPTIONAL,
+ IN LPVOID RawDataBuffer
+ )
+/*++
+
+Routine Description:
+
+ Writes an error message and ascii string to the error log. Also,
+ writes the data in the data buf if there are any.
+
+Arguments:
+
+ EventType - warning, error, ...
+
+ MessageId - Message ID
+
+ StringCount - number of strings to use in the string array
+
+ StringArray - array of insertion strings
+
+ RawDataBufferLength - size of data to be printed from the auxiliary data
+ buffer. May be zero, in which case the actual value depends on
+ "RawDataBuffer". It is 0 if "RawDataBuffer" is NULL, else it is
+ "sizeof( wchar) * wcslen( RawDataBuffer)".
+
+ RawDataBuffer - data buffer containing secondary error code & some
+ other useful info. Must be NULL terminated string or NULL if
+ "RawDataBufferLength" is zero.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ HANDLE logHandle;
+
+ logHandle = RegisterEventSource( NULL, SCHEDULE_EVENTLOG_NAME);
+
+ // If the event log cannot be opened, just return.
+
+ if ( logHandle == NULL) {
+#ifdef AT_DEBUG
+ AtLog(( AT_DEBUG_CRITICAL, "ReportEvent: RegisterEventSource() failed with error %d",
+ GetLastError()));
+#endif
+ return;
+ }
+
+ //
+ // Use default for RawDataBufferLength if caller requested us so.
+ //
+ if ( RawDataBufferLength == 0 && RawDataBuffer != NULL) {
+ RawDataBufferLength = sizeof( TCHAR) * wcslen( (LPWSTR)RawDataBuffer);
+ }
+
+ if ( !ReportEvent(
+ logHandle,
+ EventType,
+ 0, // event category
+ MessageId, // event id
+ NULL, // user SID. We're local system - uninteresting
+ StringCount, // number of strings
+ RawDataBufferLength, // raw data size
+ StringArray, // string array
+ RawDataBuffer // raw data buffer
+ )) {
+#ifdef AT_DEBUG
+ AtLog(( AT_DEBUG_CRITICAL, "ReportEvent: ReportEvent() failed with error %d",
+ GetLastError()));
+#endif // NOT_YET
+ }
+
+ DeregisterEventSource( logHandle);
+}
+
+
+
+DBGSTATIC VOID AtCreateProcess( PAT_RECORD pRecord)
+/*++
+
+Routine Description:
+
+ Creates a process for a given record.
+
+Arguments:
+
+ pRecord - pointer to AT_RECORD
+
+--*/
+#define CMD_EXE L"cmd /c "
+{
+ PROCESS_INFORMATION ProcessInformation;
+ BOOL success;
+ BOOL JobInteractive;
+ LPWSTR StringArray[ 2];
+ WCHAR ErrorCodeString[ 25];
+ WCHAR Command[ sizeof( CMD_EXE) + MAXIMUM_COMMAND_LENGTH];
+
+ wcscpy( Command, pRecord->Command);
+
+ if ( AtBatOrCmd( Command)) {
+ wcscpy( Command, CMD_EXE);
+ wcscat( Command, pRecord->Command);
+ } else {
+ wcscpy( Command, pRecord->Command);
+ }
+
+ if ( pRecord->Flags & JOB_NONINTERACTIVE) {
+ JobInteractive = FALSE;
+ } else {
+ //
+ // Job is to be executed interactively only if the global system flag
+ // allows that.
+ //
+ JobInteractive = AtSystemInteractive();
+ if ( !JobInteractive) {
+ StringArray[ 0] = pRecord->Command;
+ AtReportEvent( EVENTLOG_WARNING_TYPE, EVENT_COMMAND_NOT_INTERACTIVE,
+ 1, StringArray, 0, NULL);
+ }
+ }
+
+ //
+ // If the process is to be interactive, set the appropriate flags.
+ //
+ if ( JobInteractive) {
+ AtGlobalStartupInfo.dwFlags |= STARTF_DESKTOPINHERIT;
+ AtGlobalStartupInfo.lpDesktop = INTERACTIVE_DESKTOP;
+ }
+ else {
+ AtGlobalStartupInfo.dwFlags &= (~STARTF_DESKTOPINHERIT);
+ AtGlobalStartupInfo.lpDesktop = NULL;
+ }
+
+ success = CreateProcess(
+ NULL, // image name is imbedded in the command line
+ Command, // command line
+ NULL, // pSecAttrProcess
+ NULL, // pSecAttrThread
+ FALSE, // this process will not inherit our handles
+ CREATE_NEW_CONSOLE | CREATE_SEPARATE_WOW_VDM,
+// CREATE_NO_WINDOW, // the only choice that works
+ NULL, // pEnvironment
+ NULL, // pCurrentDirectory
+ &AtGlobalStartupInfo,
+ &ProcessInformation
+ );
+
+ if ( success == FALSE) {
+
+ DWORD winError;
+
+ winError = GetLastError();
+
+ AtLog(( AT_DEBUG_CRITICAL, "CreateProcess( %ws) fails with winError = %d\n",
+ pRecord->Command, winError));
+
+ pRecord->Flags |= JOB_EXEC_ERROR;
+
+ StringArray[ 0] = pRecord->Command;
+ wcscpy( ErrorCodeString, L"%%"); // tell event viewer to expand this error
+ ultow( winError, ErrorCodeString + 2, 10);
+ StringArray[ 1] = ErrorCodeString;
+ AtReportEvent( EVENTLOG_ERROR_TYPE, EVENT_COMMAND_START_FAILED,
+ 2, StringArray, 0, NULL);
+
+ } else {
+ AtLog(( AT_DEBUG_MAIN,
+ "CreateProcess( %ws) succeeded\t"
+ "ProcessInformation=%x %x %x %x JobId=%x JobDay=%x Runtime=%x:%x\n",
+ pRecord->Command,
+ ProcessInformation.hProcess,
+ ProcessInformation.hThread,
+ ProcessInformation.dwProcessId,
+ ProcessInformation.dwThreadId,
+ pRecord->JobId,
+ pRecord->JobDay,
+ pRecord->Runtime.HighPart,
+ pRecord->Runtime.LowPart
+ ));
+ CloseHandle( ProcessInformation.hThread);
+ CloseHandle( ProcessInformation.hProcess);
+ pRecord->Flags &= ~JOB_EXEC_ERROR;
+ }
+
+ ASSERT( pRecord->Debug++ < 20);
+
+ if ( pRecord->JobDay != JOB_INVALID_DAY) {
+
+ ASSERT( (pRecord->Flags & JOB_RUN_PERIODICALLY) == 0 &&
+ ( pRecord->DaysOfWeek != 0 || pRecord->DaysOfMonth != 0));
+
+ if ( pRecord->Flags & JOB_CLEAR_WEEKDAY) {
+ pRecord->DaysOfWeek &= ~( 1<< pRecord->JobDay);
+ } else {
+ pRecord->DaysOfMonth &= ~( 1<< ( pRecord->JobDay - 1));
+ }
+ pRecord->JobDay = JOB_INVALID_DAY;
+
+ } else {
+
+ ASSERT( (pRecord->Flags & JOB_RUN_PERIODICALLY) != 0 ||
+ ( pRecord->DaysOfWeek == 0 && pRecord->DaysOfMonth == 0));
+
+ }
+}
+
+
+
+DBGSTATIC VOID AtUpdateRecords( IN PAT_TIME pTime)
+/*++
+
+Routine Description:
+
+ Reshufles runtime queue to reflect the time change.
+
+Arguments:
+
+ pTime - pointer to the new system time
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PLIST_ENTRY pListEntry;
+ PAT_RECORD pRecord;
+
+ AtLog(( AT_DEBUG_MAIN, "Update runtime queue.\n"));
+
+ //
+ // If we want to simulate OS/2 behavior, here we can insert a block
+ // which runs all records whose Runtimes are earlier than the new
+ // Jan.01,70 system time.
+ //
+
+ if ( IsListEmpty( &AtGlobalJobIdListHead) == TRUE) {
+ return; // do not bother waking up the master if queue is empty
+ }
+
+ for ( pListEntry = AtGlobalJobIdListHead.Flink;
+ pListEntry != &AtGlobalJobIdListHead;
+ pListEntry = pListEntry->Flink) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ JobIdList
+ );
+
+ AtCalculateRuntime( pRecord, pTime);
+ AtRemoveRecord( pRecord, RUNTIME_QUEUE);
+ AtInsertRecord( pRecord, RUNTIME_QUEUE);
+ }
+}
+
+
+
+DBGSTATIC BOOL AtTimeChanged(
+ IN PAT_TIME pGetupTime,
+ IN OUT PAT_TIME pSleepTime
+ )
+/*++
+
+Routine Description:
+
+ Detects if there was a change in system time since we went to sleep.
+ If there was a sustem time it revises SleepTime to match GetupTime.
+
+Arguments:
+
+ pGetupTime - points to AT_TIME structure for the Getup moment
+ pSleepTime - points to AT_TIME structure for the Sleep moment
+
+
+Return Value:
+
+ TRUE - if there was a time change
+ FALSE - otherwise
+
+--*/
+
+{
+ //
+ // DateDelta says how long we slept in units that CAN be adjusted
+ // by user.
+ //
+ LARGE_INTEGER DateDelta;
+ //
+ // BootDelta says how long we slept in units that CANNOT be adjusted
+ // by user.
+ //
+ LARGE_INTEGER BootDelta;
+#ifdef AT_DEBUG
+ PCHAR Format;
+#endif
+
+ DateDelta.QuadPart = pGetupTime->LargeInteger.QuadPart -
+ pSleepTime->LargeInteger.QuadPart;
+ BootDelta = RtlEnlargedUnsignedMultiply(
+ pGetupTime->TickCount - pSleepTime->TickCount,
+ NT_TICKS_IN_WINDOWS_TICK
+ );
+
+ if ( DateDelta.QuadPart < 0) {
+#ifdef AT_DEBUG
+ Format = "TimeChanged: negative DateDelta: Getup=%x:%x|%x Sleep=%x:%x|%x\n";
+#endif
+ goto time_changed;
+ }
+
+ if ( DateDelta.QuadPart >= BootDelta.QuadPart ) {
+ DateDelta.QuadPart = DateDelta.QuadPart - BootDelta.QuadPart;
+ } else {
+ DateDelta.QuadPart = BootDelta.QuadPart - DateDelta.QuadPart;
+ }
+
+ if ( DateDelta.HighPart != 0 ||
+ DateDelta.LowPart > ONE_MINUTE_IN_NT_TICKS) {
+#ifdef AT_DEBUG
+ Format = "TimeChanged: large DateDelta: Getup=%x:%x!%x Sleep=%x:%x!%x\n";
+#endif
+ goto time_changed;
+ }
+ return( FALSE);
+
+time_changed:
+
+ AtLog(( AT_DEBUG_MAIN, Format,
+ pGetupTime->LargeInteger.HighPart, pGetupTime->LargeInteger.LowPart, pGetupTime->TickCount,
+ pSleepTime->LargeInteger.HighPart, pSleepTime->LargeInteger.LowPart, pSleepTime->TickCount));
+ pSleepTime->LargeInteger.QuadPart = pGetupTime->LargeInteger.QuadPart - BootDelta.QuadPart;
+ AtTimeGetCurrents( pSleepTime);
+ AtLog(( AT_DEBUG_MAIN, "TimeChanged: revised Sleep=%x:%x|%x\n",
+ pSleepTime->LargeInteger.HighPart, pSleepTime->LargeInteger.LowPart, pSleepTime->TickCount));
+ return( TRUE);
+}
+
+
+VOID SCHEDULE_main(
+ DWORD argc,
+ LPWSTR * argv
+ )
+/*++
+
+Routine Description:
+
+ This is the main routine of the Schedule Service which registers
+ itself as an RPC server and notifies the Service Controller of the
+ Schedule service control entry point.
+
+ After the Schedule Service is started, this thread is used to spawn
+ processes corresponding to records waiting in the queue.
+
+Arguments:
+
+ argc - Supplies the number of strings specified in argv
+
+ argv - Supplies string arguments that are specified in the
+ StartService API call.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ DWORD initState = 0;
+
+ UNREFERENCED_PARAMETER( argc);
+ UNREFERENCED_PARAMETER( argv);
+
+#ifdef AT_DEBUG
+ AtDebugCreate();
+#endif
+
+#ifdef NOT_YET
+ if ( !AllocConsole()) {
+ AT_DEBUG_PRINT(
+ AT_DEBUG_MAIN,(
+ "[Job] AllocConsole() returns WinError = %d\n",
+ GetLastError()
+ ));
+ }
+#endif // NOT_YET
+
+ //
+ // Perhaps one should use something that builds in current day & time
+ // to be even more random.
+ //
+ AtGlobalSeed = GetTickCount();
+
+ InitializeCriticalSection( &AtGlobalCriticalSection);
+
+ //
+ // Initialize the scheduler.
+ //
+ AtTimeGet( &AtGlobalGetupTime); // initialize AtGlobalGetupTime
+ if ( AtInitialize( &AtGlobalGetupTime, &initState) != NERR_Success) {
+ return;
+ }
+
+ //
+ // We enter and leave loop while holding the critical section.
+ //
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+
+ for ( ; ; ) {
+
+ DWORD timeout; // in milliseconds
+ PAT_RECORD pRecord;
+
+ if ( AtGlobalTasks & AT_SERVICE_SHUTDOWN) {
+ AtLog(( AT_DEBUG_MAIN, "Service shutdown.\n"));
+ break;
+ }
+
+ AtLogRuntimeList( "Before executions");
+
+ timeout = MAX_LAZY_TIMEOUT;
+
+ while ( IsListEmpty( &AtGlobalRuntimeListHead) == FALSE) {
+
+ pRecord = CONTAINING_RECORD(
+ AtGlobalRuntimeListHead.Flink,
+ AT_RECORD,
+ RuntimeList
+ );
+
+ if (pRecord->Runtime.QuadPart > AtGlobalGetupTime.LargeInteger.QuadPart) {
+ timeout = AtCalculateTimeout( pRecord->Runtime, AtGlobalGetupTime.LargeInteger);
+ break; // nothing to do for now
+ }
+
+ AtCreateProcess( pRecord);
+
+ if ( pRecord->DaysOfWeek == 0 && pRecord->DaysOfMonth == 0) {
+ AtDeleteKey( pRecord);
+ AtRemoveRecord( pRecord, BOTH_QUEUES);
+ (VOID)LocalFree( pRecord);
+ } else {
+ AtCalculateRuntime( pRecord, &AtGlobalGetupTime);
+ AtRemoveRecord( pRecord, RUNTIME_QUEUE);
+ AtInsertRecord( pRecord, RUNTIME_QUEUE);
+ }
+ }
+
+ AtGlobalSleepTime = AtGlobalGetupTime;
+ AtLogRuntimeList( "After executions");
+
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+
+ AtLogTimeout( timeout);
+ (VOID)WaitForSingleObject( AtGlobalEvent, timeout);
+
+ EnterCriticalSection( &AtGlobalCriticalSection);
+
+ AtTimeGet( &AtGlobalGetupTime);
+ if ( AtTimeChanged( &AtGlobalGetupTime, &AtGlobalSleepTime) == TRUE) {
+ //
+ // SleepTime has been revised and is now expressed in GetupTime
+ // units. Reorder the queue with respect to revised SleepTime
+ // and run whatever needs to be run.
+ // Above has the same effect as if a time change occured
+ // JUST BEFORE the last time main thread ordered the runtime
+ // queue and went to sleep.
+ //
+ AtUpdateRecords( &AtGlobalSleepTime);
+ }
+ }
+
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+
+ AtShutdown( NERR_Success, initState);
+#ifdef AT_DEBUG
+ AtDebugDelete();
+#endif
+}
diff --git a/private/net/svcdlls/at/server/atreg.c b/private/net/svcdlls/at/server/atreg.c
new file mode 100644
index 000000000..4e04a61f3
--- /dev/null
+++ b/private/net/svcdlls/at/server/atreg.c
@@ -0,0 +1,603 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ atreg.c
+
+Abstract:
+
+ Routines that deal with the registry & queues.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Environment:
+
+ User Mode - Win32
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+#include "at.h"
+
+
+DWORD AtCreateKey( PAT_RECORD pRecord)
+/*++
+
+Routine Description:
+
+ This routine first loops trying to find an unused name in AT part
+ of the registry. Then it saves the data from the in memory record
+ into the new registry key. Finally, it saves the name of the
+ registry key in the in memory record.
+
+Arguments:
+
+ pRecord - pointer to record for which we need to create registry key
+
+Return Value:
+
+ TRUE if success
+ FALSE otherwise
+
+Note:
+
+
+--*/
+{
+ DWORD random;
+ DWORD status;
+ HKEY Key;
+ DWORD disposition;
+ DWORD Length;
+ PWCHAR Name;
+ WCHAR Buffer[ AT_KEY_NAME_MAX_LEN + 1];
+ AT_SCHEDULE Schedule;
+
+
+ for ( ; ;) {
+
+ random = RtlRandom( &AtGlobalSeed);
+
+ Name = &Buffer[ AT_KEY_NAME_MAX_LEN];
+ *Name = 0; // null terminate the string
+
+ while ( random != 0) {
+
+ *--Name = L"0123456789ABCDEF"[ random & 0xF];
+ random >>= 4;
+ }
+
+ status = RegCreateKeyEx(
+ AtGlobalKey,
+ Name,
+ 0,
+ NULL,
+ REG_OPTION_NON_VOLATILE,
+ KEY_READ | KEY_WRITE,
+ NULL,
+ &Key,
+ &disposition
+ );
+ if (status != ERROR_SUCCESS){
+ KdPrint((
+ "[Job] Cannot create Name = %ws, status = %d\n",
+ Name,
+ status
+ ));
+ return( status );
+ }
+ if ( disposition == REG_CREATED_NEW_KEY) {
+ break;
+ }
+
+ KdPrint((
+ "[Job] Key name collision Name = %ws\n",
+ Name
+ ));
+ }
+
+ wcscpy( pRecord->Name, Name);
+ Schedule.JobTime = pRecord->JobTime;
+ Schedule.DaysOfMonth = pRecord->DaysOfMonth;
+ Schedule.DaysOfWeek = pRecord->DaysOfWeek;
+ Schedule.Flags = pRecord->Flags;
+ Schedule.Reserved = 0;
+
+ status = RegSetValueEx(
+ Key,
+ AT_SCHEDULE_NAME,
+ 0,
+ REG_BINARY,
+ (LPBYTE)&Schedule,
+ sizeof( Schedule)
+ );
+ if ( status != STATUS_SUCCESS) {
+ return( status );
+ }
+
+ status = RegSetValueEx(
+ Key,
+ AT_COMMAND_NAME,
+ 0,
+ REG_SZ,
+ (LPBYTE)&pRecord->Command[0],
+ pRecord->CommandSize
+ );
+ if ( status != STATUS_SUCCESS) {
+ return( status );
+ }
+
+#ifdef AT_DEBUG
+ {
+ WCHAR Command[ MAXIMUM_COMMAND_LENGTH + 1];
+ DWORD type;
+
+ memset( Command, 0, sizeof( Command));
+ Length = sizeof( Command);
+ status = RegQueryValueEx(
+ Key,
+ AT_COMMAND_NAME,
+ NULL,
+ &type,
+ (LPBYTE)Command,
+ &Length
+ );
+ if ( status != ERROR_SUCCESS ||
+ type != REG_SZ ||
+ wcscmp( pRecord->Command, Command) != 0 ||
+ (DWORD)pRecord->CommandSize != Length ) {
+ ASSERT( FALSE);
+ KdPrint((
+ "[Job] Registry bug: status=%d, type=%x, "
+ " pRecord->Command=%ws, Command=%ws, "
+ " pRecord->CommandSize=%d, Length=%d\n",
+ status,
+ type,
+ pRecord->Command,
+ Command,
+ pRecord->CommandSize,
+ Length
+ ));
+ }
+ }
+#endif // AT_DEBUG
+
+ (VOID)RegCloseKey( Key);
+
+ return( NERR_Success );
+}
+
+
+
+BOOL AtDeleteKey( PAT_RECORD pRecord)
+/*++
+
+Routine Description:
+
+ Given an in memory record this routine deletes the registry key
+ with corresponding name.
+
+Arguments:
+
+ pRecord - pointer to record for which we need to delete registry key
+
+Return Value:
+
+ TRUE if success
+ FALSE otherwise
+
+--*/
+{
+ DWORD status;
+
+ status = RegDeleteKey(
+ AtGlobalKey,
+ pRecord->Name
+ );
+ if (status != ERROR_SUCCESS) {
+ KdPrint((
+ "[Job] Cannot delete pRecord->Name = %ws, status = %d\n",
+ pRecord->Name,
+ status
+ ));
+ return( FALSE);
+ }
+ return( TRUE);
+}
+
+
+
+VOID AtInsertRecord(
+ PAT_RECORD pNewRecord,
+ DWORD QueueMask
+ )
+/*++
+
+Routine Description:
+
+ Depending on the value of a QueueMask argument, this function does
+ one or both of the following:
+
+ - inserts record in a doubly linked list sorted by Runtime
+ - inserts record in a doubly linked list sorted by JobId
+
+ Since newly added jobs have ever increasing JobId-s, JobId queue
+ insertion is done by inserting job at the end of the JobId queue.
+
+
+Arguments:
+
+ pNewRecord - pointer to record to be inserted
+ QueueMask - mask of queues where this record should be inserted to.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PAT_RECORD pRecord;
+ PLIST_ENTRY pListEntry;
+
+ if ( QueueMask & RUNTIME_QUEUE) {
+
+ for ( pListEntry = AtGlobalRuntimeListHead.Flink;
+ pListEntry != &AtGlobalRuntimeListHead;
+ pListEntry = pListEntry->Flink) {
+
+ pRecord = CONTAINING_RECORD(
+ pListEntry,
+ AT_RECORD,
+ RuntimeList
+ );
+
+ if ( pRecord->Runtime.QuadPart >= pNewRecord->Runtime.QuadPart) {
+ InsertTailList( &pRecord->RuntimeList, &pNewRecord->RuntimeList);
+ break;
+ }
+ }
+
+ if ( pListEntry == &AtGlobalRuntimeListHead) {
+ InsertTailList( &AtGlobalRuntimeListHead, &pNewRecord->RuntimeList);
+ }
+ }
+
+ if ( QueueMask & JOBID_QUEUE) {
+ InsertTailList( &AtGlobalJobIdListHead, &pNewRecord->JobIdList);
+ }
+
+}
+
+
+
+NET_API_STATUS AtMakeDataFromRegistry( IN PAT_TIME pTime)
+{
+ NET_API_STATUS status;
+ HKEY Key;
+ DWORD index;
+ WCHAR NameBuffer[ 20]; // some arbitrary value
+ FILETIME lastWriteTime;
+ PAT_RECORD pRecord;
+ WCHAR Command[ MAXIMUM_COMMAND_LENGTH + 1];
+ AT_SCHEDULE Schedule;
+ DWORD Length;
+ DWORD type;
+ DWORD NameSize;
+ DWORD CommandSize;
+
+ InitializeListHead( &AtGlobalRuntimeListHead);
+ InitializeListHead( &AtGlobalJobIdListHead);
+
+
+ // First open the AT service registry tree.
+
+ status = RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ AT_REGISTRY_PATH,
+ 0,
+ KEY_READ,
+ &AtGlobalKey
+ );
+ if (status != ERROR_SUCCESS){
+ KdPrint(("[Job] Cannot open AtGlobalKey, status = %d\n", status));
+ return( status);
+ }
+
+ for ( index = 0; ; index++) {
+
+ //
+ // Regedit can sometimes display other keys in addition to keys
+ // found here. Also, it often fails to display last character in
+ // the Command and after a refresh it may not display some of the
+ // spurious keys.
+ //
+ Length = sizeof( NameBuffer);
+ status = RegEnumKeyEx(
+ AtGlobalKey,
+ index,
+ NameBuffer, // lpName
+ &Length, // lpcbName
+ 0, // lpReserved
+ NULL, // lpClass
+ NULL, // lpcbClass
+ &lastWriteTime
+ );
+ if ( status != ERROR_SUCCESS) {
+ if ( status == ERROR_NO_MORE_ITEMS) {
+ status = ERROR_SUCCESS; // no more items to enumerate
+ }
+ break; // the only exit point from this loop
+ }
+ //
+ // Length returned is the number of characters in a UNICODE string
+ // representing the key name (not counting the terminating NULL
+ // character which is also supplied).
+ //
+ NameSize = ( Length + 1) * sizeof( WCHAR);
+
+ status = RegOpenKeyEx(
+ AtGlobalKey,
+ NameBuffer,
+ 0L,
+ KEY_READ,
+ &Key
+ );
+ if ( status != ERROR_SUCCESS) {
+ KdPrint((
+ "[Job] RegOpenKeyEx( %ws) returns %d\n",
+ NameBuffer,
+ status
+ ));
+ (VOID)RegDeleteKey( AtGlobalKey, NameBuffer);
+ continue;
+ }
+
+ Length = sizeof( Schedule);
+ status = RegQueryValueEx(
+ Key,
+ AT_SCHEDULE_NAME,
+ NULL,
+ &type,
+ (LPBYTE)&Schedule,
+ &Length
+ );
+ if ( status != ERROR_SUCCESS) {
+ KdPrint((
+ "[Job] RegQueryValueEx( AT_SCHEDULE_NAME, %ws) returns %d\n",
+ NameBuffer,
+ status
+ ));
+ (VOID)RegCloseKey( Key);
+ (VOID)RegDeleteKey( AtGlobalKey, NameBuffer);
+ continue;
+ }
+ if ( type != REG_BINARY ||
+ Length != sizeof( AT_SCHEDULE) ||
+ (Schedule.DaysOfWeek & ~DAYS_OF_WEEK) != 0 ||
+ (Schedule.DaysOfMonth & ~DAYS_OF_MONTH) != 0 ||
+ Schedule.JobTime >= MAXIMUM_JOB_TIME ) {
+ KdPrint((
+ "[Job] RegQueryValueEx( %ws) returns invalid schedule info\n"
+ ));
+ (VOID)RegCloseKey( Key);
+ (VOID)RegDeleteKey( AtGlobalKey, NameBuffer);
+ continue;
+ }
+
+ Length = sizeof( Command);
+ status = RegQueryValueEx(
+ Key,
+ AT_COMMAND_NAME,
+ NULL,
+ &type,
+ (LPBYTE)Command,
+ &Length
+ );
+ if ( status != ERROR_SUCCESS) {
+ KdPrint((
+ "[Job] RegQueryValueEx( AT_COMMAND_NAME, %ws) returns %d\n",
+ NameBuffer,
+ status
+ ));
+ (VOID)RegCloseKey( Key);
+ (VOID)RegDeleteKey( AtGlobalKey, NameBuffer);
+ continue;
+ }
+ if ( type != REG_SZ) {
+ KdPrint((
+ "[Job] RegQueryValueEx( %ws): Command is not of REG_SZ type\n"
+ ));
+ (VOID)RegCloseKey( Key);
+ (VOID)RegDeleteKey( AtGlobalKey, NameBuffer);
+ continue;
+ }
+ //
+ // Above call should already return the actual length of the null
+ // terminated string. However, because of bugs in reg apis it may
+ // return larger length than the actual length. Thus this:
+ //
+ Length = wcslen( Command);
+ CommandSize = (Length + 1) * sizeof( WCHAR);
+
+ pRecord = (PAT_RECORD)LocalAlloc(
+ LMEM_FIXED,
+ sizeof( AT_RECORD) + NameSize + CommandSize
+ );
+ if ( pRecord == NULL) {
+ KdPrint((
+ "[Job] LocalAlloc fails\n"
+ ));
+ (VOID)RegCloseKey( Key);
+ (VOID)RegDeleteKey( AtGlobalKey, NameBuffer);
+ continue;
+ }
+
+ pRecord->JobId = AtGlobalJobId++;
+
+ pRecord->JobTime = Schedule.JobTime;
+ pRecord->DaysOfMonth = Schedule.DaysOfMonth;
+ pRecord->DaysOfWeek = Schedule.DaysOfWeek;
+ pRecord->Flags = Schedule.Flags;
+#ifdef AT_DEBUG
+ pRecord->Debug = 0;
+#endif // AT_DEBUG
+ pRecord->CommandSize = (WORD)CommandSize;
+ pRecord->NameSize = (WORD)NameSize;
+
+ pRecord->JobDay = JOB_INVALID_DAY;
+
+ pRecord->Name = (PWCHAR)( pRecord + 1);
+ memcpy( pRecord->Name, NameBuffer, NameSize);
+
+ pRecord->Command = (PWCHAR)( (LPBYTE)&pRecord->Name[0] + NameSize);
+ memcpy( pRecord->Command, Command, CommandSize);
+
+ AtCalculateRuntime( pRecord, pTime);
+
+ AtInsertRecord( pRecord, BOTH_QUEUES);
+
+ (VOID)RegCloseKey( Key);
+ }
+ return( status);
+}
+
+
+
+BOOL AtPermitServerOperators( VOID)
+/*++
+ We permit server operators to manage schedule service only if the key
+ exists the proper flag is set. In all other case we do not permit server
+ operators to manage schedule service.
+--*/
+{
+ DWORD SubmitControl;
+ DWORD status;
+ DWORD type;
+ DWORD Length;
+ HKEY LsaKey;
+
+ status = RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ LSA_REGISTRY_PATH,
+ 0L,
+ KEY_READ,
+ &LsaKey
+ );
+ if ( status != ERROR_SUCCESS) {
+ AtLog(( AT_DEBUG_CRITICAL, "RegOpenKeyEx( LsaKey) returns %d\n", status));
+ return( FALSE);
+ }
+
+ Length = sizeof( SubmitControl);
+ status = RegQueryValueEx(
+ LsaKey,
+ LSA_SUBMIT_CONTROL,
+ NULL,
+ &type,
+ (LPBYTE)&SubmitControl,
+ &Length
+ );
+ (VOID)RegCloseKey( LsaKey);
+
+ if ( status != ERROR_SUCCESS || type != REG_DWORD
+ || Length != sizeof( SubmitControl)) {
+ AtLog(( AT_DEBUG_MAIN, "LsaKey query: status=%d type=0x%x Length=%d value=0x%x\n",
+ status, type, Length, SubmitControl));
+ return( FALSE);
+ }
+
+ return( (SubmitControl & LSA_SERVER_OPERATORS) != 0);
+}
+
+
+
+VOID AtRemoveRecord(
+ PAT_RECORD pRecord,
+ DWORD QueueMask
+ )
+/*++
+
+Routine Description:
+
+ Depending on the value of a QueueMask argument, this function does
+ one or both of the following:
+
+ - removes record from a doubly linked list sorted by Runtime
+ - removes record from a doubly linked list sorted by JobId
+
+Arguments:
+
+ pRecord - pointer to record to be removed
+ QueueMask - mask of queues where this record should be removed from
+
+Return Value:
+
+ None.
+
+Note:
+
+ This routine should probably be a macro.
+
+--*/
+{
+ if ( QueueMask & RUNTIME_QUEUE) {
+ RemoveEntryList( &pRecord->RuntimeList);
+ }
+
+ if ( QueueMask & JOBID_QUEUE) {
+ RemoveEntryList( &pRecord->JobIdList);
+ }
+}
+
+
+
+BOOL AtSystemInteractive( VOID)
+{
+ DWORD NoInteractiveServices;
+ DWORD status;
+ DWORD type;
+ DWORD Length;
+
+ if ( AtGlobalHaveWindowsKey == FALSE) {
+ status = RegOpenKeyEx(
+ HKEY_LOCAL_MACHINE,
+ WINDOWS_REGISTRY_PATH,
+ 0L,
+ KEY_READ,
+ &AtGlobalWindowsKey
+ );
+ if ( status != ERROR_SUCCESS) {
+ AtLog(( AT_DEBUG_CRITICAL, "RegOpenKeyEx( WindowsKey) returns %d\n", status));
+ return( TRUE);
+ }
+ AtGlobalHaveWindowsKey = TRUE;
+ }
+
+ Length = sizeof( NoInteractiveServices);
+ status = RegQueryValueEx(
+ AtGlobalWindowsKey,
+ WINDOWS_VALUE_NAME,
+ NULL,
+ &type,
+ (LPBYTE)&NoInteractiveServices,
+ &Length
+ );
+ if ( status == ERROR_SUCCESS && type == REG_DWORD
+ && Length == sizeof( NoInteractiveServices)) {
+ return( NoInteractiveServices == 0);
+ }
+
+ AtLog(( AT_DEBUG_MAIN, "WindowsKey query: status=%d type=0x%x Length=%d value=0x%x\n",
+ status, type, Length, NoInteractiveServices));
+ return( TRUE);
+}
+
+
diff --git a/private/net/svcdlls/at/server/atsec.c b/private/net/svcdlls/at/server/atsec.c
new file mode 100644
index 000000000..5b62a14b3
--- /dev/null
+++ b/private/net/svcdlls/at/server/atsec.c
@@ -0,0 +1,246 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ atsec.c
+
+Abstract:
+
+ This module contains the schedule service support routines
+ which create security objects and enforce security _access checking.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+#include "at.h"
+#include <secobj.h> // ACE_DATA
+
+
+
+
+
+#define AT_JOB_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \
+ AT_JOB_ADD | \
+ AT_JOB_DEL | \
+ AT_JOB_ENUM | \
+ AT_JOB_GET_INFO )
+
+//
+// Security descriptors of schedule service to control user accesses
+// to the schedule service configuration information.
+//
+PSECURITY_DESCRIPTOR AtGlobalSecurityDescriptor;
+
+//
+// Structure that describes the mapping of Generic access rights to
+// object specific access rights for the schedule service security object.
+//
+GENERIC_MAPPING AtGlobalInformationMapping = {
+
+ STANDARD_RIGHTS_READ | // Generic read
+ AT_JOB_ENUM |
+ AT_JOB_GET_INFO,
+ STANDARD_RIGHTS_WRITE | // Generic write
+ AT_JOB_ADD |
+ AT_JOB_DEL,
+ STANDARD_RIGHTS_EXECUTE, // Generic execute
+ AT_JOB_ALL_ACCESS // Generic all
+ };
+
+
+NET_API_STATUS
+AtCheckSecurity(
+ ACCESS_MASK DesiredAccess
+ )
+/*++
+
+Routine Description:
+
+ This routine checks if an rpc caller is allowed to perform a given
+ operation on AT service. Currently, members of groups LocalAdmin
+ and LocalBackupOperators are allowed to do all operations and everybody
+ else is not allowed to do anything.
+
+ This routine calls NtAccessCheck() and not NtAccessCheckAndAuditAlarm()
+ because:
+
+ - we do not need the audit part anyway
+
+ - NtAccessCheckAndAuditAlarm() cannot be issued if a service is running
+ in an account which is NOT a LocalSystem account
+
+ The second reason prohibits a use of NetpAccessCheckAndAudit() netlib
+ function.
+
+Arguments:
+
+ ACCESS_MASK - Desired Access
+
+Return Value:
+
+ NET_API_STATUS - NERR_Success or reason for failure.
+
+--*/
+{
+ NTSTATUS NtStatus;
+ NET_API_STATUS Status;
+ HANDLE ClientToken;
+ LPWSTR StringArray[ 2];
+ WCHAR ErrorCodeString[ 25];
+
+ if ( ( Status = RpcImpersonateClient(NULL)) != NERR_Success) {
+ AtLog(( AT_DEBUG_CRITICAL, "RpcImpersonateClient() returns WinError = %d\n", Status));
+ return( Status);
+ }
+
+ NtStatus = NtOpenThreadToken(
+ NtCurrentThread(),
+ TOKEN_QUERY,
+ (BOOLEAN)TRUE,
+ &ClientToken
+ );
+
+ if ( NtStatus != STATUS_SUCCESS) {
+ AtLog(( AT_DEBUG_CRITICAL, "NtOpenThreadToken() returns NtStatus = 0x%x\n", NtStatus));
+ } else {
+
+ PRIVILEGE_SET PrivilegeSet;
+ DWORD PrivilegeSetLength;
+ ACCESS_MASK GrantedAccess;
+ NTSTATUS AccessStatus;
+
+ PrivilegeSetLength = sizeof( PrivilegeSet);
+
+ // NtAccessCheck() returns STATUS_SUCCESS if parameters
+ // are correct. Whether or not access is allowed is
+ // governed by the returned value of AccessStatus.
+
+ NtStatus = NtAccessCheck(
+ AtGlobalSecurityDescriptor, // SecurityDescriptor
+ ClientToken, // ClientToken
+ DesiredAccess, // DesiredAccess
+ &AtGlobalInformationMapping, // GenericMapping
+ &PrivilegeSet,
+ &PrivilegeSetLength,
+ &GrantedAccess, // GrantedAccess
+ &AccessStatus // AccessStatus
+ );
+ if ( NtStatus != STATUS_SUCCESS) {
+ AtLog(( AT_DEBUG_CRITICAL, "NtAccessCheck() returns NtStatus = 0x%x\n", NtStatus));
+ } else {
+ NtStatus = AccessStatus;
+ }
+ (VOID)NtClose( ClientToken);
+ }
+
+ if ( ( Status = RpcRevertToSelf()) != NERR_Success) {
+ StringArray[ 0] = L"RpcRevertToSelf";
+ wcscpy( ErrorCodeString, L"%%"); // tell event viewer to expand this error
+ ultow( Status, ErrorCodeString + 2, 10);
+ StringArray[ 1] = ErrorCodeString;
+ AtReportEvent( EVENTLOG_ERROR_TYPE, EVENT_CALL_TO_FUNCTION_FAILED,
+ 2, StringArray, 0, NULL);
+ EnterCriticalSection( &AtGlobalCriticalSection);
+ AtGlobalTasks |= AT_SERVICE_SHUTDOWN;
+ LeaveCriticalSection( &AtGlobalCriticalSection);
+ SetEvent( AtGlobalEvent);
+ return( Status);
+ }
+
+ return( NetpNtStatusToApiStatus( NtStatus));
+}
+
+
+NET_API_STATUS
+AtCreateSecurityObject(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This function creates the scheduler user-mode configuration
+ information object which is represented by a security descriptors.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ NET_API_STATUS code
+
+--*/
+{
+ NTSTATUS status;
+
+ //
+ // Order matters! These ACEs are inserted into the DACL in the
+ // following order. Security access is granted or denied based on
+ // the order of the ACEs in the DACL.
+ //
+ // In win3.1 both LocalGroupAdmins and LocalGroupSystemOps were
+ // allowed to perform all Schedule Service operations. In win3.5
+ // LocalGroupSystemOps may be disallowed (this is the default case).
+ //
+
+ ACE_DATA aceData[] = {
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &AliasAdminsSid},
+ {ACCESS_ALLOWED_ACE_TYPE, 0, 0, GENERIC_ALL, &AliasSystemOpsSid}
+ };
+
+
+ status = NetpCreateSecurityObject(
+ aceData, // pAceData
+ AtGlobalPermitServerOperators ? 2 : 1, // countAceData
+ NULL, // OwnerSid
+ NULL, // PrimaryGroupSid
+ &AtGlobalInformationMapping, // GenericToSpecificMapping
+ &AtGlobalSecurityDescriptor // ppNewDescriptor
+ );
+
+ if ( ! NT_SUCCESS (status)) {
+ AtLog(( AT_DEBUG_CRITICAL, "Failure to create security object\n"));
+ return NetpNtStatusToApiStatus( status);
+ }
+
+ return NERR_Success;
+}
+
+
+
+NET_API_STATUS
+AtDeleteSecurityObject(
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ This function destroys the schedule service user-mode configuration
+ information object which is represented by a security descriptors.
+
+Arguments:
+
+ None.
+
+Return Value:
+
+ NET_API_STATUS code
+
+--*/
+{
+ return( NetpDeleteSecurityObject( &AtGlobalSecurityDescriptor));
+}
+
+
diff --git a/private/net/svcdlls/at/server/attime.c b/private/net/svcdlls/at/server/attime.c
new file mode 100644
index 000000000..c9004ab38
--- /dev/null
+++ b/private/net/svcdlls/at/server/attime.c
@@ -0,0 +1,355 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ attime.c
+
+Abstract:
+
+ This module contains routines for calculating times & runtimes.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+
+#include "at.h"
+
+#define DAYS_IN_WEEK 7
+#define SECONDS_PER_DAY 60L*60L*24L
+
+
+DBGSTATIC INT DaysInMonth(
+ INT month,
+ INT year
+ )
+/*++
+
+Routine Description:
+
+ Returns number of days in a given month - the only exceptional case is
+ a leap year February.
+
+Arguments:
+
+ month - integer index, must be between 0 and 11
+ year - integer index, any value allowed
+
+Return Value:
+
+ Number of days in a particular month.
+
+--*/
+{
+ // JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
+ static int DaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+ return( month == 1 && ( (year%4 == 0 && year%100 != 0) || year%400 == 0)
+ ? 29 : DaysInMonth[ month]);
+}
+
+
+
+VOID AtCalculateRuntime(
+ IN OUT PAT_RECORD pRecord,
+ IN PAT_TIME pTime
+ )
+/*++
+
+Routine Description:
+
+ Fills in the next runtime for a given record.
+
+Arguments:
+
+ pRecord pointer to AT schedule record
+ pTime pointer to AT time structure
+
+Return Value:
+
+ None.
+
+--*/
+{
+ DWORD BestDelta; // number of days before the best runtime day
+ DWORD Delta; // number of days before a possible runtime day
+
+ DWORD BestJobDay; // the JobDay when to run
+ DWORD JobDay; // index of day in week or day in month
+ DWORD CurrentDay; // index of the current day (week or month)
+
+ DWORD DayMask; // mask for days in week or days in month
+ BOOL Weekday; // TRUE if next runtime is for a week day
+
+ DWORD CurrentTime;// day time in miliseconds for the current day
+ DWORD JobTime; // day time in miliseconds for this job
+
+ LARGE_INTEGER Runtime;
+
+#ifdef NOT_YET
+ if ( (pRecord->Flags & JOB_IS_ENABLED) == 0) {
+ //
+ // This job is disabled, so this will have the effect of putting
+ // it at the end of the queue.
+ //
+ pRecord->Runtime.LowPart = MAXULONG;
+ pRecord->Runtime.HighPart = MAXLONG;
+ return;
+ }
+#endif // NOT_YET
+
+
+ CurrentTime = pTime->CurrentTime;
+ JobTime = pRecord->JobTime;
+
+
+ if ( (pRecord->DaysOfWeek == 0) && (pRecord->DaysOfMonth == 0)) {
+
+ BestDelta = (CurrentTime > JobTime) ? 1 : 0; // case of single runtime
+
+ } else {
+
+ BestDelta = MAXULONG; // initialize to an invalid value
+
+ if ( (DayMask = pRecord->DaysOfWeek) != 0) {
+
+ CurrentDay = pTime->CurrentDayOfWeek; // current day of week
+ JobDay = 0; // running day of week, starting from Monday
+
+ // Because of a bug caused by wrong compiler optimization
+ // (compiler obtained in mid January 93), the test for DayMask
+ // equal to 0 and loop increments have to burried within a body
+ // of the for loop. (see bug #7414). An alternative was to
+ // disable global optimization for this routine.
+
+ for ( ; ;) {
+
+ if ( (DayMask & 1) != 0) {
+
+ if ( CurrentDay > JobDay ||
+ CurrentDay == JobDay && CurrentTime >= JobTime) {
+
+ Delta = DAYS_IN_WEEK - CurrentDay + JobDay;
+
+ } else {
+
+ Delta = JobDay - CurrentDay;
+
+ }
+
+ if ( Delta < BestDelta) {
+ BestDelta = Delta;
+ BestJobDay = JobDay;
+ Weekday = TRUE;
+ }
+ }
+
+ DayMask >>= 1;
+
+ if ( DayMask == 0) {
+ break;
+ }
+
+ JobDay++;
+ }
+ }
+
+ if ( (DayMask = pRecord->DaysOfMonth) != 0) {
+
+ DWORD ThisMonthLength;
+ DWORD NextMonthLength;
+
+ ThisMonthLength = DaysInMonth(
+ pTime->CurrentMonth - 1,
+ pTime->CurrentYear
+ );
+ NextMonthLength = DaysInMonth(
+ (pTime->CurrentMonth) % 12,
+ pTime->CurrentYear
+ );
+
+ CurrentDay = pTime->CurrentDay; // current day of month
+ JobDay = 1; // running day of month, starting from 1-st day
+
+ for ( ; ;) {
+
+ if ( (DayMask & 1) != 0) {
+
+ if ( CurrentDay > JobDay ||
+ CurrentDay == JobDay && CurrentTime >= JobTime) {
+
+ //
+ // Must look in the next two months.
+ //
+
+ if ( NextMonthLength >= JobDay) {
+
+ Delta = ThisMonthLength - CurrentDay + JobDay;
+
+ } else {
+
+ Delta = ThisMonthLength - CurrentDay
+ + NextMonthLength + JobDay;
+
+ }
+
+ } else {
+
+ //
+ // Must look in the current month and the next month.
+ //
+
+ if ( ThisMonthLength >= JobDay) {
+
+ Delta = JobDay - CurrentDay;
+
+ } else {
+
+ Delta = ThisMonthLength - CurrentDay + JobDay;
+
+ }
+ }
+
+ if ( Delta < BestDelta) {
+ BestDelta = Delta;
+ BestJobDay = JobDay;
+ Weekday = FALSE;
+ }
+ }
+
+ DayMask >>= 1;
+
+ if ( DayMask == 0) {
+ break;
+ }
+
+ JobDay++;
+ }
+ }
+
+ //
+ // Make sure we found a valid solution.
+ //
+ ASSERT( BestDelta != MAXULONG && BestJobDay != JOB_INVALID_DAY);
+
+ //
+ // If this is a NEXT type of job, remember the day to clear when
+ // we create the process for this job.
+ //
+ if ( (pRecord->Flags & JOB_RUN_PERIODICALLY) == 0) {
+ if ( Weekday == TRUE) {
+ pRecord->Flags |= JOB_CLEAR_WEEKDAY;
+ } else {
+ pRecord->Flags &= ~JOB_CLEAR_WEEKDAY;
+ }
+ pRecord->JobDay = (UCHAR)BestJobDay;
+ }
+ }
+
+ //
+ // The first operation requires large integer when BestDelta > 49.
+ //
+ Runtime.QuadPart = UInt32x32To64(BestDelta, 24L * 3600L * 1000L);
+ // count of miliseconds in a day
+ Runtime.QuadPart += JobTime;
+ Runtime.QuadPart -= CurrentTime;
+ Runtime.QuadPart = Runtime.QuadPart * NT_TICKS_IN_WINDOWS_TICK;
+ pRecord->Runtime.QuadPart = pTime->LargeInteger.QuadPart +
+ Runtime.QuadPart;
+
+// AtLog(( AT_DEBUG_MAIN, "CalculateRuntime: JobId=%d Command=%ws Runtime=%x:%x\n",
+// pRecord->JobId, pRecord->Command, pRecord->Runtime.HighPart, pRecord->Runtime.LowPart));
+ //
+ // Make sure we return a runtime that is after the current time!
+ //
+ ASSERT( pRecord->Runtime.QuadPart > pTime->LargeInteger.QuadPart);
+}
+
+VOID AtTimeGetCurrents( IN OUT PAT_TIME pTime)
+/*++
+
+Routine Description:
+
+ LargeInteger & TickCount fields in input data structure
+ are known at input. Here we just fill in Current fields
+ using the input value of LargeInteger field.
+
+Arguments:
+
+ pTime - points to AT_TIME structure.
+
+ Fields in SYSTEMTIME Structure:
+WORD wYear - the current year.
+WORD wMonth - the current month with January equal to 1.
+WORD wDayOfWeek - the current day of the week where 0=Sunday, 1=Monday...
+WORD wDay - the current day of the month.
+WORD wHour - the current hour.
+WORD wMinute - the current minute within the hour.
+WORD wSecond - the current second within the minute.
+WORD wMilliseconds - the current millisecond within the second.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ TIME_FIELDS TimeFields;
+
+ RtlTimeToTimeFields( &pTime->LargeInteger, &TimeFields);
+
+ pTime->CurrentYear = TimeFields.Year;
+ pTime->CurrentMonth = TimeFields.Month;
+ //
+ // Note that Windows structure has Sunday=0, Monday=1, ...
+ // while AT command uses: Monday=0, Tuesday=1, ... Here
+ // we convert from windows into AT command DayOfWeek units.
+ //
+ if ( TimeFields.Weekday == 0) {
+ pTime->CurrentDayOfWeek = 6; // Sunday
+ } else {
+ pTime->CurrentDayOfWeek = (WORD)(TimeFields.Weekday - 1);
+ }
+ pTime->CurrentDay = TimeFields.Day;
+ pTime->CurrentTime = (DWORD)TimeFields.Milliseconds +
+ 1000 * ( TimeFields.Second +
+ 60 * ( TimeFields.Minute + 60 * TimeFields.Hour));
+}
+
+
+VOID AtTimeGet( OUT PAT_TIME pTime)
+/*++
+
+Routine Description:
+
+ Returns all time information needed by AT service.
+
+Arguments:
+
+ pTime - points to AT_TIME structure
+
+Return Value:
+
+ None.
+
+--*/
+{
+ LARGE_INTEGER UniversalTime;
+
+ NtQuerySystemTime( &UniversalTime);
+ pTime->TickCount = GetTickCount();
+ RtlSystemTimeToLocalTime( &UniversalTime, &pTime->LargeInteger);
+ AtTimeGetCurrents( pTime);
+}
+
+
+
diff --git a/private/net/svcdlls/at/server/makefile b/private/net/svcdlls/at/server/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/net/svcdlls/at/server/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/net/svcdlls/at/server/run.c b/private/net/svcdlls/at/server/run.c
new file mode 100644
index 000000000..3386485bb
--- /dev/null
+++ b/private/net/svcdlls/at/server/run.c
@@ -0,0 +1,242 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ run.c
+
+Abstract:
+
+ Example of a test code for command execution.
+
+Author:
+
+ Vladimir Z. Vulovic (vladimv) 06 - November - 1992
+
+Revision History:
+
+ 06-Nov-1992 vladimv
+ Created
+
+--*/
+
+#include "at.h"
+#include <stdio.h> // printf
+
+DBGSTATIC WCHAR **
+ArgvToUnicode(
+ IN int argc,
+ IN CHAR ** charArgv
+ );
+DBGSTATIC WCHAR *
+AsciizToUnicode(
+ IN CHAR * Asciiz
+ );
+DBGSTATIC BOOL
+FillCommand(
+ IN int argc,
+ IN WCHAR ** argv
+ );
+
+#define MAX_COMMAND_LEN 128
+
+WCHAR Command[ MAX_COMMAND_LEN + 1];
+
+
+VOID _CRTAPI1
+main(
+ int argc,
+ CHAR ** charArgv
+ )
+{
+ PROCESS_INFORMATION ProcessInformation;
+ BOOL success;
+ WCHAR ** argv;
+ STARTUPINFO StartupInfo;
+
+
+
+ if ( ( argv = ArgvToUnicode( argc, charArgv)) == NULL) {
+ printf( "Failed to map input strings to unicode.\n");
+ exit( -1);
+ }
+
+ if ( FillCommand( argc, argv) == FALSE) {
+ exit( -1);
+ }
+
+ GetStartupInfo( &StartupInfo);
+
+// StartupInfo.lpTitle = "just a test";
+ StartupInfo.lpTitle = NULL;
+
+ success = CreateProcess(
+ NULL, // image name is imbedded in the command line
+ Command, // command line
+ NULL, // pSecAttrProcess
+ NULL, // pSecAttrThread
+ FALSE, // this process will not inherit our handles
+ DETACHED_PROCESS, // instead of DETACHED_PROCESS
+ NULL, // pEnvironment
+ NULL, // pCurrentDirectory
+ &StartupInfo, // instead of NULL
+ &ProcessInformation
+ );
+
+ if ( success == FALSE) {
+
+ DWORD winError;
+
+ winError = GetLastError();
+
+ printf(
+ "CreateProcess( %ws) fails with winError = %d\n",
+ Command,
+ winError
+ );
+
+ } else {
+
+ printf(
+ "CreateProcess( %ws) succeeds\n"
+ " ProcessInformation = %x %x %x %x\n",
+ Command,
+ ProcessInformation.hProcess,
+ ProcessInformation.hThread,
+ ProcessInformation.dwProcessId,
+ ProcessInformation.dwThreadId
+ );
+ }
+}
+
+
+DBGSTATIC WCHAR **
+ArgvToUnicode(
+ IN int argc,
+ IN CHAR ** charArgv
+ )
+/*++
+ No attempt is made to clean up if we fail half way along. Cleanup
+ will be done at the exit process time.
+--*/
+{
+ WCHAR ** argv;
+ int index;
+
+ argv = (WCHAR **)LocalAlloc(
+ LMEM_FIXED,
+ argc * sizeof( WCHAR *)
+ );
+ if ( argv == NULL) {
+ return( NULL);
+ }
+
+ for ( index = 0; index < argc; index++ ) {
+
+ if ( ( argv[ index] = AsciizToUnicode( charArgv[ index])) == NULL) {
+ return( NULL);
+ }
+ }
+
+ return( argv);
+}
+
+
+
+DBGSTATIC WCHAR *
+AsciizToUnicode(
+ IN CHAR * Asciiz
+ )
+/*++
+ No attempt is made to clean up if we fail half way along. Cleanup
+ will be done at the exit process time.
+--*/
+{
+ UNICODE_STRING UnicodeString;
+ BOOL success;
+
+ RtlInitUnicodeString(
+ &UnicodeString,
+ NULL
+ );
+
+ success = RtlCreateUnicodeStringFromAsciiz(
+ &UnicodeString,
+ Asciiz
+ );
+
+ if ( success != TRUE) {
+ printf( "Failed to make unicode string out of %s\n", Asciiz);
+ return( NULL);
+ }
+
+ return( UnicodeString.Buffer);
+}
+
+
+#define DEFAULT_CMD_EXE L"cmd"
+#define SLASH_C_APPEND L" /c "
+#define SLASH_C_APPEND_LENGTH ( sizeof( SLASH_C_APPEND) / sizeof( WCHAR) - 1)
+DBGSTATIC BOOL
+FillCommand(
+ IN int argc,
+ IN WCHAR ** argv
+ )
+{
+ WCHAR * recdatap; // ptr used to build atr_command
+ DWORD recdata_len; // len of arg to put in atr_command
+ int i;
+ WCHAR cmdexe[ MAX_PATH + SLASH_C_APPEND_LENGTH];
+ DWORD length;
+
+ //
+ // This is really bogus behavior, but "length" returned is count of
+ // BYTES, not count of UNICODE characters, and it does not include the
+ // terminating NULL UNICODE character.
+ //
+ length = GetEnvironmentVariable( L"ComSpec", cmdexe, MAX_PATH);
+ if ( length == 0) {
+ //
+ // We arrive here if somebody undefined ComSpec environment
+ // variable. Then, DEFAULT_CMD_EXE helps provided there is
+ // no bogus file with cmd.exe name on the search path before
+ // the real cmd.exe (e.g. a bogus file in the current directory).
+ //
+ wcscpy( cmdexe, DEFAULT_CMD_EXE);
+ }
+ wcscat( cmdexe, SLASH_C_APPEND);
+
+ wcscpy( Command, cmdexe);
+ recdatap = Command + wcslen( Command);
+ recdata_len = 0;
+
+ for ( i = 1; i < argc; i++) {
+
+ DWORD temp;
+
+ temp = wcslen( argv[i]) + 1;
+
+ recdata_len += temp;
+
+ if ( recdata_len > MAX_COMMAND_LEN) {
+ printf( "Command too long\n");
+ return( FALSE);
+ }
+
+ wcscpy( recdatap, argv[i]);
+ recdatap += temp;
+
+ // To construct lpszCommandLine argument to CreateProcess call
+ // we replace nuls with spaces.
+
+ *(recdatap - 1) = ' ';
+ }
+
+ // Reset space back to null on last argument in string.
+
+ *(recdatap - 1) = '\0';
+
+ return( TRUE);
+}
+
diff --git a/private/net/svcdlls/at/server/sources b/private/net/svcdlls/at/server/sources
new file mode 100644
index 000000000..ffeeb36c0
--- /dev/null
+++ b/private/net/svcdlls/at/server/sources
@@ -0,0 +1,38 @@
+MAJORCOMP=net
+MINORCOMP=atserver
+
+TARGETPATH=obj
+TARGETNAME=atsvc
+TARGETTYPE=LIBRARY
+
+TARGETLIBS= \
+ $(BASEDIR)\Public\Sdk\Lib\*\netlib.lib \
+ $(BASEDIR)\public\sdk\lib\*\kernel32.lib \
+ $(BASEDIR)\public\sdk\lib\*\advapi32.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcndr.lib \
+ $(BASEDIR)\public\sdk\lib\*\rpcrt4.lib
+
+INCLUDES=..;..\..\..\inc;..\..\..\..\inc;..\..\..\api
+
+!IFNDEF DISABLE_NET_UNICODE
+UNICODE=1
+NET_C_DEFINES=-DUNICODE
+!ENDIF
+
+SOURCES= \
+ atdebug.c \
+ atenv.c \
+ atmain.c \
+ atapi.c \
+ atreg.c \
+ atsec.c \
+ attime.c \
+ atsvc_s.c
+
+C_DEFINES= -DINCL_32= -DNT -DRPC_NO_WINDOWS_H
+
+#386_WARNING_LEVEL=-W4
+
+UMTYPE=console
+UMTEST=run
+UMLIBS=