//+----------------------------------------------------------------------- // // Microsoft Windows // // Copyright (c) Microsoft Corporation // // File: ktcontrol.cxx // // Contents: Kerberos Tunneller, service infrastructure // // History: 28-Jun-2001 t-ryanj Created // //------------------------------------------------------------------------ #include "ktdebug.h" #include "ktcontrol.h" #include "ktcore.h" #include "ktmem.h" #include "ktsock.h" #include "kthttp.h" VOID WINAPI KtMainServiceEntry( IN DWORD argc, IN LPTSTR argv[] ); DWORD WINAPI KtControlHandlerEx( DWORD dwControl, DWORD dwEventType, PVOID pvEventData, PVOID pvContext ); BOOL KtStartupInit( VOID ); VOID KtContinueService( VOID ); VOID KtPauseService( VOID ); VOID KtStopService( IN DWORD dwWin32ErrorCode ); BOOL KtBeginStateTransition( IN DWORD dwUltimateState, IN DWORD dwWaitHint ); BOOL KtFinishStateTransition( VOID ); HANDLE KtIocp = NULL; HANDLE KtStateTransitionLock = NULL; LPTSTR KtServiceName = TEXT("kerbtunnel"); SERVICE_TABLE_ENTRY KtServiceTable[] = { { KtServiceName, KtMainServiceEntry }, { NULL, NULL } }; SERVICE_STATUS KtServiceStatus = {0}; SERVICE_STATUS_HANDLE KtServiceStatusHandle = NULL; //+------------------------------------------------------------------------- // // Function: KtStartServiceCtrlDispatcher // // Synopsis: Starts the Service Control Dispatcher. // // Effects: // // Arguments: // // Requires: // // Returns: Success value. // If FALSE, GetLastError() for more info. // // Notes: // // //-------------------------------------------------------------------------- BOOL KtStartServiceCtrlDispatcher( VOID ) { return StartServiceCtrlDispatcher( KtServiceTable ); } //+------------------------------------------------------------------------- // // Function: KtFinishStateTransition // // Synopsis: Closes out a state transition, reporting the new state // to service control. // // Effects: Modifies KtServiceStatus, the global service state. // // Signals KtStateTransitionLock, signifying the completion of // a state transition. // // Arguments: // // Requires: // // Returns: TRUE on success, FALSE on failure. // If FALSE, GetLastError() for details. // // Notes: // // //-------------------------------------------------------------------------- BOOL KtFinishStateTransition( VOID ) { BOOL fSuccess = TRUE; DWORD dwUltimateState; // // First, we determine what state we want to be in at the end of the // transition. // switch( KtServiceStatus.dwCurrentState ) { case SERVICE_START_PENDING: case SERVICE_CONTINUE_PENDING: dwUltimateState = SERVICE_RUNNING; break; case SERVICE_STOP_PENDING: dwUltimateState = SERVICE_STOPPED; break; case SERVICE_PAUSE_PENDING: dwUltimateState = SERVICE_PAUSED; break; default: DebugLog( DEB_WARN, "%s(%d): Unhandled case: 0x%x.\n", __FILE__, __LINE__, KtServiceStatus.dwCurrentState ); SetLastError( ERROR_INVALID_PARAMETER ); DsysAssert( KtServiceStatus.dwCurrentState == SERVICE_START_PENDING || KtServiceStatus.dwCurrentState == SERVICE_CONTINUE_PENDING || KtServiceStatus.dwCurrentState == SERVICE_STOP_PENDING || KtServiceStatus.dwCurrentState == SERVICE_PAUSE_PENDING ); goto Error; } // // Time to construct and set the new state. // // Checkpoint and WaitHint are always 0 for non-pending states, // and we only report errors when punting, which is handled by // a different routine. // KtServiceStatus.dwCheckPoint = KtServiceStatus.dwWaitHint = 0; KtServiceStatus.dwWin32ExitCode = NO_ERROR; KtServiceStatus.dwServiceSpecificExitCode = 0; KtServiceStatus.dwCurrentState = dwUltimateState; if( !SetServiceStatus( KtServiceStatusHandle, &KtServiceStatus ) ) { DebugLog( DEB_ERROR, "%s(%d): Error from SetServiceStatus: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); goto Error; } // // Now we signal the StateTransition event, indicating that it's // alright to begin a new state transition. // if( !SetEvent( KtStateTransitionLock ) ) { DebugLog( DEB_ERROR, "%s(%d): Error from SetEvent: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); goto Error; } DebugLog( DEB_TRACE, "%s(%d): State transition completed.\n", __FILE__, __LINE__ ); Cleanup: return fSuccess; Error: /* TODO: Event log, and maybe something drastic. */ fSuccess = FALSE; goto Cleanup; } //+------------------------------------------------------------------------- // // Function: KtBeginStateTransition // // Synopsis: Enters a state transition, reporting an appropriate pending // state to Service Control and requesting dwWaitHint millisec // to complete the transition. // // Effects: Modifies KtServiceStatus, the global service status. // Waits for KtStateTransitionLock to begin state transition. // // Arguments: dwUltimateState - One of SERVICE_STOPPED, SERVICE_RUNNING, // or SERVICE_PAUSED. // // dwWaitHint - Estimated millisec before next state report. // Service Control may kill the service if it does not // report again within the time specified. (See Notes.) // // Requires: // // Returns: Success value. If FALSE, GetLastError() for details. // // Notes: If 0 is supplied as dwWaitHint, this call will go directly // to the ultimate state rather than a pending state. // //-------------------------------------------------------------------------- BOOL KtBeginStateTransition( IN DWORD dwUltimateState, IN DWORD dwWaitHint ) { BOOL fSuccess = TRUE; DWORD dwPendingState = 0; // // dwUltimateState is our target state, so determine which pending // state to be in in the meantime. // switch( dwUltimateState ) { case SERVICE_STOPPED: dwPendingState = SERVICE_STOP_PENDING; break; case SERVICE_RUNNING: dwPendingState = (KtServiceStatus.dwCurrentState == SERVICE_PAUSED) ? SERVICE_CONTINUE_PENDING : SERVICE_START_PENDING; break; case SERVICE_PAUSED: dwPendingState = SERVICE_PAUSE_PENDING; break; default: DebugLog( DEB_WARN, "%s(%d): Invalid parameter to KtBeginStateTransition: 0x%x.\n", dwUltimateState ); SetLastError(ERROR_INVALID_PARAMETER); DsysAssert( dwUltimateState == SERVICE_STOPPED || dwUltimateState == SERVICE_RUNNING || dwUltimateState == SERVICE_PAUSED ); goto Error; } // // Wait for any pending state transitions to complete before // starting a new one. // WaitForSingleObjectEx( KtStateTransitionLock, INFINITE, FALSE ); DebugLog( DEB_TRACE, "%s(%d): Beginning State Transition.\n", __FILE__, __LINE__ ); // // If dwWaitHint was given as 0, we want to go right ahead // and set the target state, otherwise we set the appropriate // pending state. // if( dwWaitHint == 0 ) { if( !KtFinishStateTransition() ) goto Error; } else { // // We're ready to initialize and set the state. // // Checkpoint is 1 since this will be the first checkpoint // in the new pending state, WaitHint as supplied, and // no errors are reported except when stopping. // KtServiceStatus.dwCheckPoint = 1; KtServiceStatus.dwWaitHint = dwWaitHint; KtServiceStatus.dwWin32ExitCode = NO_ERROR; KtServiceStatus.dwServiceSpecificExitCode = 0; KtServiceStatus.dwCurrentState = dwPendingState; if( !SetServiceStatus( KtServiceStatusHandle, &KtServiceStatus ) ) { DebugLog( DEB_ERROR, "%s(%d): Error from SetServiceStatus: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); goto Error; } } Cleanup: return fSuccess; Error: fSuccess = FALSE; goto Cleanup; } //+------------------------------------------------------------------------- // // Function: KtControlHandlerEx // // Synopsis: Service Control Handler, called by Service Control to // process control events. // // Effects: Posts to the i/o completion port to wake a service thread. // // Arguments: dwControl - The control to handle. // dwEventType - Extended info for certain control events. // pvEventData - Additional info for certain control events. // pvContext - User defined data. (Unused here.) // // Requires: // // Returns: Winerror code, spec. NO_ERROR or ERROR_CALL_NOT_IMPLEMENTED. // // Notes: // //-------------------------------------------------------------------------- DWORD WINAPI KtControlHandlerEx( IN DWORD dwControl, IN DWORD dwEventType, IN PVOID pvEventData, IN PVOID pvContext ) { DWORD dwReturn = NO_ERROR; BOOL fPostControlToServiceThread = FALSE; // // If we're told to change state, initiate an appropriate state // transition. To service an INTERROGATE request we merely report // our current status. No other control events are implemented. // switch( dwControl ) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: DebugLog( DEB_TRACE, "%s(%d): SERVICE_CONTROL_%s recieved.\n", __FILE__, __LINE__, (dwControl==SERVICE_CONTROL_STOP)?"STOP":"SHUTDOWN" ); KtBeginStateTransition( SERVICE_STOPPED, 2000 ); fPostControlToServiceThread = TRUE; break; case SERVICE_CONTROL_PAUSE: DebugLog( DEB_TRACE, "%s(%d): SERVICE_CONTROL_PAUSE recieved.\n", __FILE__, __LINE__ ); KtBeginStateTransition( SERVICE_PAUSED, 2000 ); fPostControlToServiceThread = TRUE; break; case SERVICE_CONTROL_CONTINUE: DebugLog( DEB_TRACE, "%s(%d): SERVICE_CONTROL_CONTINUE recieved.\n", __FILE__, __LINE__ ); KtBeginStateTransition( SERVICE_RUNNING, 2000 ); fPostControlToServiceThread = TRUE; break; case SERVICE_CONTROL_INTERROGATE: DebugLog( DEB_TRACE, "%s(%d): SERVICE_CONTROL_CONTINUE recieved.\n", __FILE__, __LINE__ ); SetServiceStatus( KtServiceStatusHandle, &KtServiceStatus ); dwReturn = NO_ERROR; break; case SERVICE_CONTROL_PARAMCHANGE: case SERVICE_CONTROL_NETBINDADD: case SERVICE_CONTROL_NETBINDREMOVE: case SERVICE_CONTROL_NETBINDENABLE: case SERVICE_CONTROL_NETBINDDISABLE: case SERVICE_CONTROL_DEVICEEVENT: case SERVICE_CONTROL_HARDWAREPROFILECHANGE: case SERVICE_CONTROL_POWEREVENT: case SERVICE_CONTROL_SESSIONCHANGE: DebugLog( DEB_WARN, "%s(%d): Unsupported control recieved.\n", __FILE__, __LINE__ ); dwReturn = ERROR_CALL_NOT_IMPLEMENTED; break; default: DebugLog( DEB_WARN, "%s(%d): Unhandled case: 0x%x.\n", __FILE__, __LINE__, dwControl ); dwReturn = ERROR_CALL_NOT_IMPLEMENTED; DsysAssert( dwControl == SERVICE_CONTROL_STOP || dwControl == SERVICE_CONTROL_SHUTDOWN || dwControl == SERVICE_CONTROL_PAUSE || dwControl == SERVICE_CONTROL_CONTINUE || dwControl == SERVICE_CONTROL_INTERROGATE || dwControl == SERVICE_CONTROL_DEVICEEVENT || dwControl == SERVICE_CONTROL_HARDWAREPROFILECHANGE || dwControl == SERVICE_CONTROL_POWEREVENT || dwControl == SERVICE_CONTROL_PARAMCHANGE || dwControl == SERVICE_CONTROL_NETBINDADD || dwControl == SERVICE_CONTROL_NETBINDREMOVE || dwControl == SERVICE_CONTROL_NETBINDENABLE || dwControl == SERVICE_CONTROL_NETBINDDISABLE || dwControl == SERVICE_CONTROL_DEVICEEVENT || dwControl == SERVICE_CONTROL_HARDWAREPROFILECHANGE || dwControl == SERVICE_CONTROL_POWEREVENT || dwControl == SERVICE_CONTROL_SESSIONCHANGE ); break; } // // If we're changing state, we need to wake up a service thread // to do the actual work, so we post an event to the Completion // port with the key KTCK_SERVICE_CONTROL. Since no data transfer // actually took place, we can pass the control event in as the // "number of bytes transferred" without ambiguity. // if( fPostControlToServiceThread ) { BOOL IocpSuccess; IocpSuccess = PostQueuedCompletionStatus( KtIocp, dwControl, KTCK_SERVICE_CONTROL, NULL ); if( !IocpSuccess ) { DebugLog( DEB_ERROR, "%s(%d): Error posting completion status to I/O completion port: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); dwReturn = GetLastError(); /* TODO: Event log, possibly something drastic. */ } } return dwReturn; } //+------------------------------------------------------------------------- // // Function: KtStartupInit // // Synopsis: Does the startup initialization for the service. // // Effects: // // Arguments: // // Requires: // // Returns: Success value. // If initialization fails, KtServiceStatus.dwWin32ErrorCode is set // to an appropriate error code, and the service stops. // // Notes: // //-------------------------------------------------------------------------- BOOL KtStartupInit( VOID ) { BOOL fRet = TRUE; // // Initialize the Service Status structure. // KtServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; KtServiceStatus.dwCurrentState = SERVICE_START_PENDING; KtServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; KtServiceStatus.dwWin32ExitCode = NO_ERROR; KtServiceStatus.dwServiceSpecificExitCode = 0; KtServiceStatus.dwCheckPoint = 0; KtServiceStatus.dwWaitHint = 2000; // // First thing we need to do is get a handle on our status so we can // tell Service Control what we're doing. // The SERVICE_STATUS_HANDLE returned by this call need not be closed. // DsysAssert(KtServiceStatusHandle == NULL); KtServiceStatusHandle = RegisterServiceCtrlHandlerEx( KtServiceName, KtControlHandlerEx, NULL ); if( KtServiceStatusHandle == NULL ) { DebugLog( DEB_ERROR, "%s(%d): Error from RegisterServiceCtrlHandlerEx: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); goto Error; } // // Next we create an event (auto reset, initially signalled) that we'll // use to make sure we aren't in multiple state transitions simultaneously. // DsysAssert(KtStateTransitionLock == NULL ); KtStateTransitionLock = CreateEvent( NULL, FALSE, // NOT manual reset TRUE, // initially signalled NULL ); if( KtStateTransitionLock == NULL ) { DebugLog( DEB_ERROR, "%s(%d): Error from CreateEvent: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); goto Error; } // // Now we can signal Service Control that we're starting. // if( !KtBeginStateTransition( SERVICE_RUNNING, 2000 ) ) goto Error; // // Create an unassociated I/O Completion port that we'll use to queue // events to be serviced. // DsysAssert(KtIocp == NULL); KtIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0 ); if( KtIocp == NULL ) { DebugLog( DEB_ERROR, "%s(%d): Error from CreateIoCompletionPort: 0x%x.\n", __FILE__, __LINE__, GetLastError() ); goto Error; } // // Initialize memory routines // if( !KtInitMem() ) goto Error; // // Initialize Winsock // if( !KtInitWinsock() ) goto Error; // // Initialize WinInet // if( !KtInitHttp() ) goto Error; // // Initialize contexts // if( !KtInitContexts() ) goto Error; // // Finish bringing up the service. // KtContinueService(); Cleanup: return fRet; Error: // // We couldn't get the resources we needed to start, so punt the service. // Note that KtStopService cleans up any resources we may have successfully // allocated in this routine. // /* TODO: Event log. */ KtStopService( GetLastError() ); fRet = FALSE; goto Cleanup; } //+------------------------------------------------------------------------- // // Function: KtContinueService // // Synopsis: Continues the service. This is not only the opposite of // pause, but the second half of the initialization necessary // to start the service in the first place. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // //-------------------------------------------------------------------------- VOID KtContinueService( VOID ) { // // To continue the service we start listening for new connections. // if( !KtStartListening() ) goto Error; KtFinishStateTransition(); return; Error: /* TODO: Event log. */ DebugLog( DEB_ERROR, "%s(%d): Unable to bring up service. Shutting down with error code 0x%x.\n", __FILE__, __LINE__, GetLastError() ); KtStopService( GetLastError() ); } //+------------------------------------------------------------------------- // // Function: KtPauseService // // Synopsis: Pauses the service. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // //-------------------------------------------------------------------------- VOID KtPauseService( VOID ) { // // To pause the service, we stop making new connections, but we can // leave any open connections open. // KtStopListening(); KtFinishStateTransition(); } //+------------------------------------------------------------------------- // // Function: KtStopService // // Synopsis: Stops the service. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // //-------------------------------------------------------------------------- VOID KtStopService( DWORD dwWin32ErrorCode ) { // // Release all our resources. // KtStopListening(); KtCleanupContexts(); KtCleanupHttp(); KtCleanupWinsock(); KtCleanupMem(); if( KtStateTransitionLock ) CloseHandle(KtStateTransitionLock); if( KtIocp ) CloseHandle(KtIocp); // // Tell service control that we're done. // KtServiceStatus.dwCurrentState = SERVICE_STOPPED; KtServiceStatus.dwCheckPoint = 0; KtServiceStatus.dwWaitHint = 0; KtServiceStatus.dwWin32ExitCode = dwWin32ErrorCode; KtServiceStatus.dwServiceSpecificExitCode = 0; if( KtServiceStatusHandle ) SetServiceStatus( KtServiceStatusHandle, &KtServiceStatus ); } //+------------------------------------------------------------------------- // // Function: KtServiceControlEvent // // Synopsis: Dispatches control events to the appropriate routine to // do the state change. // // Effects: // // Arguments: dwControl - Code of the control event. // // Requires: // // Returns: // // Notes: // //-------------------------------------------------------------------------- VOID KtServiceControlEvent( DWORD dwControl ) { switch( dwControl ) { case SERVICE_CONTROL_CONTINUE: KtContinueService(); break; case SERVICE_CONTROL_PAUSE: KtPauseService(); break; case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: KtStopService(NO_ERROR); break; default: DebugLog( DEB_WARN, "%s(%d): Unhandled case: 0x%x.", __FILE__, __LINE__, dwControl ); DsysAssert( dwControl == SERVICE_CONTROL_CONTINUE || dwControl == SERVICE_CONTROL_PAUSE || dwControl == SERVICE_CONTROL_STOP || dwControl == SERVICE_CONTROL_SHUTDOWN ); break; } } //+------------------------------------------------------------------------- // // Function: KtMainServiceEntry // // Synopsis: This initializes the service, then acts as the main service // thread. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // //-------------------------------------------------------------------------- VOID WINAPI KtMainServiceEntry( IN DWORD argc, IN LPTSTR argv[] ) { // // Do startup initialization. // if( !KtStartupInit() ) goto Cleanup; // // Do our thing. // KtThreadCore(); Cleanup: ExitProcess( KtServiceStatus.dwWin32ExitCode ); } //+------------------------------------------------------------------------- // // Function: KtIsStopped // // Synopsis: Determines whether the service is still running. // // Effects: // // Arguments: // // Requires: // // Returns: TRUE if the service has stopped, FALSE otherwise. // // Notes: // //-------------------------------------------------------------------------- BOOL KtIsStopped( VOID ) { return (KtServiceStatus.dwCurrentState == SERVICE_STOPPED); }