/*++ Copyright (c) 1992-1996 Microsoft Corporation Module Name: tftpd.c Author: Sam Patton (sampa) 08-Apr-1992 History: MohsinA, 02-Dec-96. - Winsock2 asynchronous version with synchronisation events, graceful termination, event logging, debugging info. Credits: Anirudh Sahni - Info on SC. AdamBa, 11-Jun-97 - add security --*/ #include "tftpd.h" char USAGE[] = " ======================================================================== \n" "Abstract: \n" " This implements an RFC 783 tftp daemon. \n" " It listens on port 69 for requests \n" " and spawns a thread to process each request. \n" " \n" "TFTPD USAGE and Installation: \n" " \n" " md d:/tftpd (the StartDirectory). \n" " copy //MohsinA_p90/test/tftpd.exe . \n" " sc create tftpd binPath= d:/tftpd/tftpd.exe (give full path). \n" " sc query tftpd (check if installed). \n" " \n" "Start: \n" " sc start tftpd -f (creates a log file). \n" "or sc start tftpd \n" "or net start tftpd \n" "or sc start tftpd [-dStartDirectory] [-e] [-f] \n" " Options: -e use event log. \n" " -f log to file. \n" " -dStartDirectory \n" "Info: \n" " sc interrogate tftpd (logs will be updated). \n" " sc query tftpd Check whether running. \n" "Stop: \n" " sc stop tftpd \n" " net stop tftpd \n" " \n" "Variables that control what files can be read/written and by whom: \n" " StartDirectory - only files there will be accessible. \n" " LogFile is created here. \n" " ValidClients - Clients matching this ip address can read files. \n" " eg. you can set it to \"157.55.8?.*\" \n" " ValidMasters - clients matching this can write and read files. \n" " eg. you can set it to \"\" and no one can write. \n" " ValidReadFiles - only matching files will be served out, eg. \"r*.t?t\"\n" " ValidWriteFiles- only matching files will be accepted, eg. \"w*.txt\" \n" " \n" "Client: \n" " tftp [-i] servername {get|put} src_file dest_file \n" " -i from binary mode, else ascii mode is used. \n" " \n" " ======================================================================== \n" ; #define NUM_INITIAL_THREADS 1 #define MAX_THREAD_COUNT 25 #define RCV_WAIT_INTERVAL_MSEC 5 #define RECEIVE_HEAP_THREASHOLD1 10 #define RECEIVE_HEAP_THREASHOLD2 3 #define RECEIVE_NUM_FREE 2 #define MAX_SOCKET_INIT_ATTEMPT 10 #define LOOPBACK 0x100007f #define SE_SOCKET_REOPEN 0x1 #define MAX_LOCK_TRIES 125 SERVICE_STATUS TftpdServiceStatus; SERVICE_STATUS_HANDLE TftpdServiceStatusHandle; char TftpdServiceName[] = TEXT("Tftpd"); HANDLE MasterThreadHandle; DWORD MasterThreadId; WSADATA WsaData; FILE * LogFile = NULL; BOOL LoggingEvent = FALSE; BOOL LoggingFile = FALSE; HANDLE eMasterTerminate = NULL; // Event to stop main thread. HANDLE eSock = NULL; // Event to start WSARecvFrom DWORD TotalThreadCount=0; // total worker threads DWORD AvailableThreads=0; // number of idle worker threads TFTP_GLOBALS Globals; HANDLE MasterSocketEvent; // set when data available on TFTPD socket HANDLE MasterSocketWait; // handle to RtlRegisterWait for above event LIST_ENTRY ReceiveList; CRITICAL_SECTION ReceiveLock; LIST_ENTRY SocketList; CRITICAL_SECTION SocketLock; HANDLE ReceiveHeap=0; HANDLE ReaperWait; DWORD ReceiveHeapSize=0; DWORD ActiveReceive=0; OVERLAPPED AddrChangeOverlapped; HANDLE AddrChangeEvent=NULL; HANDLE AddrChangeWaitHandle=NULL; HANDLE AddrChangeHandle=NULL; const char BuildString[] = __FILE__ " built " __DATE__ " " __TIME__ "\n"; struct TFTPD_STAT tftpd_stat; // See sdk/inc/winsvc.h SERVICE_TABLE_ENTRY TftpdDispatchTable[] = { { TftpdServiceName, // TEXT "Tftpd" TftpdStart // main function. }, { NULL, NULL } }; #if defined(REMOTE_BOOT_SECURITY) BOOL TftpdInitSecurityArray( VOID ); VOID TftpdUninitSecurityArray( VOID ); #endif //defined(REMOTE_BOOT_SECURITY) // ======================================================================== void __cdecl main( int argc, char * argv[] ) { int ok; DbgPrint( "main: %s" "Starting service \"%s\".\n", BuildString, TftpdDispatchTable[0].lpServiceName ); if( argc > 1 && !strcmp( argv[1], "-?" ) ){ printf( USAGE ); printf( " TFTPD_DEFAULT_DIR is %s\n", TFTPD_DEFAULT_DIR ); printf( " TFTPD_LOGFILE is %s\n\n", TFTPD_LOGFILE ); printf( "Registry key names, all strings: HKEY_LOCAL_MACHINE %s\n", TFTPD_REGKEY ); printf( " o StartDirectory keyname \"%s\"\n", TFTPD_REGKEY_DIR ); printf( "These keys are shell patterns with * and ? (see examples above):\n"); printf( " o ValidClients keyname \"%s\"\n", TFTPD_REGKEY_CLIENTS ); printf( " o ValidMasters keyname \"%s\"\n", TFTPD_REGKEY_MASTERS ); printf( " o Readable files keyname \"%s\"\n", TFTPD_REGKEY_READABLE ); printf( " o writable files keyname \"%s\"\n", TFTPD_REGKEY_WRITEABLE); exit( -1 ); } ok = StartServiceCtrlDispatcher(TftpdDispatchTable); if( ok ){ DbgPrint("tftpd StartServiceCtrlDispatcher ok.\n" ); }else{ DbgPrint("tftpd StartServiceCtrlDispatcher error=%d\n", GetLastError() ); } ExitProcess(0); } // ======================================================================== // Main function called by service controller. DWORD TftpdStart( IN DWORD argc, IN LPTSTR argv[] ) /*++ Routine Description: This sits waiting on the tftpd well-known port (69) until it gets a request Then it spawns a thread to either TftpdHandleRead or TftpdHandleWrite which processes the request. Arguments: argc: argv: [-dStartDirectory] option. Return Value: None Error? --*/ { int Status; struct sockaddr_in TftpdAddress; int err, ok, i; DWORD WaitStatus; // // Initialize all the status fields so that subsequent calls to // SetServiceStatus need to only update fields that changed. // TftpdServiceStatus.dwServiceType = SERVICE_WIN32; TftpdServiceStatus.dwCurrentState = SERVICE_START_PENDING; TftpdServiceStatus.dwControlsAccepted = 0; TftpdServiceStatus.dwCheckPoint = 1; TftpdServiceStatus.dwWaitHint = 20000; // 20 seconds SET_SERVICE_EXITCODE( NO_ERROR, TftpdServiceStatus.dwWin32ExitCode, TftpdServiceStatus.dwServiceSpecificExitCode ); // // Initialize server to receive service requests by registering the // control handler. // TftpdServiceStatusHandle = RegisterServiceCtrlHandler( TftpdServiceName, TftpdControlHandler ); if ( TftpdServiceStatusHandle == 0 ) { DbgPrint("TftpdStart: RegisterServiceCtrlHandler failed: %lx\n", GetLastError()); goto failed; } ok = SetServiceStatus( TftpdServiceStatusHandle, &TftpdServiceStatus); if( !ok ){ DbgPrint("TftpdStart: SetServiceStatus failed=%d\n", GetLastError() ); goto failed; } // // Create the main synchronisation events. // eMasterTerminate = CreateEvent( NULL, // LPSECURITY_ATTRIBUTES. FALSE, // bManualReset, FALSE, // bInitialState NULL // LPCTSTR pointer to event-object name ); eSock = CreateEvent( NULL, FALSE, FALSE, NULL ); if( ! eMasterTerminate || ! eSock ){ DbgPrint("TftpdStart: CreateEvent failed.\n"); goto failed; } // // Start Winsock // err = WSAStartup( 0x0101, &WsaData ); if (err == SOCKET_ERROR ) { DbgPrint("TftpdStart: WSAStartup failed, Error=%d.\n", WSAGetLastError() ); goto failed; } DbgPrint("TftpdStart: winsock: %x/%x, %s\n %s\n", WsaData.wVersion, WsaData.wHighVersion, WsaData.szDescription, WsaData.szSystemStatus ); #if defined(REMOTE_BOOT_SECURITY) // // Initialize security-related info. // if (!TftpdInitSecurityArray()) { DbgPrint("TftpdStart: cannot initialize security array\n"); goto failed; } #endif //defined(REMOTE_BOOT_SECURITY) // // Startup worked - announce that we're all done. // TftpdServiceStatus.dwCurrentState = SERVICE_RUNNING; TftpdServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN; TftpdServiceStatus.dwCheckPoint = 0; TftpdServiceStatus.dwWaitHint = 0; ok = SetServiceStatus( TftpdServiceStatusHandle, &TftpdServiceStatus ); if( !ok ){ DbgPrint("TftpdStart: SetServiceStatus failed=%d\n", GetLastError() ); goto failed; } // // Clear the stats. // memset( &tftpd_stat, '\0', sizeof( struct TFTPD_STAT ) ); time( &tftpd_stat.started_at ); // // Process Command line arguments/options. // #ifdef DBG DbgPrint("TftpdStart: argc=%d\n", argc); for( ok = 0; ok < (int) argc; ok++ ){ DbgPrint("\targv[%d]=%s.\n", ok, argv[ok] ); } #endif while( --argc > 0 && argv[argc][0] == '-' ){ switch( argv[argc][1] ){ case 'd' : // ie. -ddirectory. strcpy( StartDirectory, &argv[argc][2] ); DbgPrint("TftpdStart: StartDirectory=%s\n", StartDirectory ); break; case 'e' : LoggingEvent = TRUE; break; case 'f' : LoggingFile = TRUE; break; default: DbgPrint("TftpdStart: invalid option %s\n", argv[0] ); break; } } ReadRegistryValues(); Set_StartDirectory(); // // Start in the dir from where we want to export files. // err = _chdir( StartDirectory ); if( err == -1 ){ DbgPrint( "TftpdStart: _chdir(%s) failed, errno=%d\n", StartDirectory, errno ); err = _mkdir(StartDirectory); if (err == ERROR_SUCCESS) { err = _chdir( StartDirectory ); } if (err != ERROR_SUCCESS) { goto failed; } } // // Open Logfile only after chdir. // if( LoggingFile ){ LogFile = fopen( TFTPD_LOGFILE, "a+" ); if( LogFile == NULL ){ LoggingFile = FALSE; DbgPrint( "Cannot open TFTPD_LOGFILE=%s\n", TFTPD_LOGFILE ); } } // // Only the risque and successful come here. // DbgPrint("TftpdStart in %s at %s.\n", StartDirectory, ctime( &tftpd_stat.started_at ) ); // Initialize Thread Pool TftpdInitializeThreadPool(); TftpdInitializeReceiveHeap(); WaitStatus=WaitForSingleObject(eMasterTerminate,INFINITE); if (WaitStatus != WAIT_OBJECT_0) { DbgPrint("Wait for termination error %d",GetLastError()); } // Thread pool thread still active, but oh well ... exiting process. return(0); // // Failures jump here. // failed: TftpdServiceExit(ERROR_GEN_FAILURE); exit(1); return(0); } HANDLE RegisterSocket(SOCKET Sock, HANDLE Event, DWORD Flag) /*++ Routine Description: Schedule calling of TftpdReceive when data received on socket Sock Arguments: Return Value: Handle to registered function, NULL on failure --*/ { NTSTATUS status; HANDLE MasterSocketWait; DbgPrint("RegisterSocket: Socket %d, Event %d\n",Sock,Event); if (WSAEventSelect(Sock,Event,FD_READ|FD_WRITE) != 0) { DbgPrint("EventSelect failed %d",GetLastError()); return 0; } if (Flag & REG_NEW_SOCKET) { status = RtlRegisterWait(&MasterSocketWait, Event, TftpdNewReceive, (PVOID)Sock, INFINITE, 0); } else { status = RtlRegisterWait(&MasterSocketWait, Event, TftpdContinueReceive, (PVOID)Sock, INFINITE, 0); } if (!NT_SUCCESS(status)) { DbgPrint("Failed to register wait %d",status); } return MasterSocketWait; } DWORD TftpdInitializeThreadPool() /*++ Routine Description: Create initial pool of thread Arguments: Return Value: Exit status 0 == success 1 == failure --*/ { DWORD i; HANDLE ThreadHandle; DWORD ThreadId; NTSTATUS status; PMIB_IPADDRTABLE IpAddrTable; InitializeCriticalSection(&Globals.Lock); InitializeCriticalSection(&SocketLock); InitializeListHead(&Globals.WorkList); InitializeListHead(&SocketList); if (GetIpTable(&IpAddrTable) == ERROR_SUCCESS) { for (i=0; i < IpAddrTable->dwNumEntries; i++) { if ( (IpAddrTable->table[i].dwAddr != 0) && (IpAddrTable->table[i].dwAddr != LOOPBACK)) { AddSocket(IpAddrTable->table[i].dwAddr); } } free(IpAddrTable); } status=RtlCreateTimerQueue(&Globals.TimerQueueHandle); if (status != ERROR_SUCCESS) { return status; } status=RtlCreateTimer(Globals.TimerQueueHandle, &ReaperWait, TftpdReaper, (PVOID)NULL, REAPER_INTERVAL_SEC*1000, REAPER_INTERVAL_SEC*1000, 0); if (!NT_SUCCESS(status)) { DbgPrint("Failed to Arm Timer %d",status); } AddrChangeEvent=CreateEvent(NULL,FALSE,FALSE,NULL); if (!AddrChangeEvent) { return status; } status = RtlRegisterWait( &AddrChangeWaitHandle, AddrChangeEvent, InterfaceChange, NULL, INFINITE, 0 ); if (status != ERROR_SUCCESS) { return status; } memset(&AddrChangeOverlapped,0,sizeof(OVERLAPPED)); AddrChangeOverlapped.hEvent=AddrChangeEvent; status = NotifyAddrChange(&AddrChangeHandle,&AddrChangeOverlapped); if (status != ERROR_SUCCESS && status != ERROR_IO_PENDING) { return status; } return ERROR_SUCCESS; } VOID TftpdInitializeReceiveHeap() { DWORD size; InitializeListHead(&ReceiveList); InitializeCriticalSection(&ReceiveLock); size = 5 * (sizeof(TFTP_REQUEST)); ReceiveHeap = HeapCreate(0, size ,0); ASSERT(ReceiveHeap); } /* Called on the reaper interval. Cleanup extra heap entries */ VOID TftpdCleanHeap() { DWORD i=0; PLIST_ENTRY pEntry; PTFTP_REQUEST Request=NULL; DWORD NumToFree=0; EnterCriticalSection(&ReceiveLock); if (ReceiveHeapSize - ActiveReceive > RECEIVE_HEAP_THREASHOLD1) { NumToFree = ((ReceiveHeapSize - ActiveReceive) / 2); } else if ((ReceiveHeapSize - ActiveReceive > RECEIVE_HEAP_THREASHOLD2)) { NumToFree = RECEIVE_NUM_FREE; } while(i < NumToFree) { pEntry=RemoveHeadList(&ReceiveList); Request=CONTAINING_RECORD(pEntry,TFTP_REQUEST,RequestLinkage); CloseHandle(Request->RcvEvent); HeapFree(ReceiveHeap,0,Request); i++; ReceiveHeapSize--; } LeaveCriticalSection(&ReceiveLock); } DWORD HandleRequest(SOCKET Sock, IPAddr MyAddr) { unsigned short Opcode; struct sockaddr_in PeerAddress; int PeerAddressLength; PTFTP_REQUEST Request; char RequestPacket[MAX_TFTP_DATAGRAM + 1]; int Status; struct sockaddr_in TftpdAddress; HANDLE ThreadHandle; DWORD ThreadId; LPTHREAD_START_ROUTINE ThreadRoutine=NULL; WSABUF RecvBuf[1]; WSAOVERLAPPED Overlap; DWORD dwNumberBytes = 0; DWORD dwFlags = 0; int err, ok; HANDLE handle_list[2]; DWORD handle_ready; PLIST_ENTRY pEntry; DWORD size; DWORD IterCount=0; SocketEntry *SE; IterCount=0; while(1) { // loop while data available on this socket { int ret; DWORD DataAvail; ret=ioctlsocket(Sock, FIONREAD, &DataAvail); if (ret != 0) { DbgPrint("ioctlsocket failed %d",WSAGetLastError()); return 0; } if (DataAvail == 0) { return 0; } } err = 0; handle_ready = 0; memset( &Overlap, 0, sizeof(WSAOVERLAPPED)); EnterCriticalSection(&ReceiveLock); if (!IsListEmpty(&ReceiveList)) { pEntry=RemoveHeadList(&ReceiveList); Request=CONTAINING_RECORD(pEntry,TFTP_REQUEST,RequestLinkage); ResetEvent(Request->RcvEvent); Overlap.hEvent = Request->RcvEvent; } else { size = sizeof(TFTP_REQUEST); Request = HeapAlloc(ReceiveHeap, HEAP_ZERO_MEMORY, size); if (Request == NULL) { DWORD bytesReceived = 0, flags = 0; // Failed to allocate REQUEST structure, perform no-op winsock recv // so as to re-enable its event-signalling mechanism. if (WSARecvFrom(Sock, NULL, 0, &bytesReceived, &flags, NULL, NULL, NULL, NULL) == SOCKET_ERROR) DbgPrint("HandleRequest: Failed to allocate REQUEST structure, " "and failed to re-enable socket event.\n"); LeaveCriticalSection(&ReceiveLock); return 0; } ReceiveHeapSize++; Request->RcvEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (Request->RcvEvent == NULL) { DWORD bytesReceived = 0, flags = 0; HeapFree(ReceiveHeap, 0, Request); ReceiveHeapSize--; // Failed to create event, perform no-op winsock recv // so as to re-enable its event-signalling mechanism. if (WSARecvFrom(Sock, NULL, 0, &bytesReceived, &flags, NULL, NULL, NULL, NULL) == SOCKET_ERROR) DbgPrint("HandleRequest: Failed to allocate REQUEST structure, " "and failed to re-enable socket event.\n"); LeaveCriticalSection(&ReceiveLock); return 0; } Overlap.hEvent = Request->RcvEvent; } LeaveCriticalSection(&ReceiveLock); ActiveReceive++; // Zero the request packet, to streamline option negotiation code. memset( Request->Packet1, 0, MAX_TFTP_DATAGRAM + 1); RecvBuf[0].buf = (char*)&Request->Packet1; RecvBuf[0].len = MAX_TFTP_DATAGRAM + 1; PeerAddressLength = sizeof(PeerAddress); Request->MyAddr=MyAddr; Status = WSARecvFrom( Sock, RecvBuf, 1, &Request->DataSize, &dwFlags, (struct sockaddr *) &Request->ForeignAddress, &PeerAddressLength, &Overlap, NULL ); DbgPrint("Received from Peer Addr: %x Port %d\n",Request->ForeignAddress.sin_addr.s_addr, ntohs(Request->ForeignAddress.sin_port)); if( Status ){ err = WSAGetLastError(); DbgPrint("err %d\n",err); if( err == WSA_IO_PENDING){ DbgPrint("Operation pending\n"); handle_list[0] = eMasterTerminate; handle_list[1] = Overlap.hEvent; handle_ready = WaitForMultipleObjects( 2, handle_list, FALSE, INFINITE ); if( handle_ready == WAIT_FAILED ){ DbgPrint("TftpdMasterThread: WAIT_FAILED\n"); goto failed; } if (handle_ready == WAIT_TIMEOUT) { DbgPrint("Wait timed out Socket %d\n",Sock); goto failed; } if( handle_ready == WAIT_OBJECT_0 ){ goto terminated; } else { ok = WSAGetOverlappedResult( Sock, // SOCKET s, &Overlap, // LPWSAOVERLAPPED lpOverlapped, &Request->DataSize, // LPDWORD lpcbTransfer, FALSE, // BOOL fWait, &dwFlags // LPDWORD lpdwFlags ); if( ! ok ){ DbgPrint("WSAGetOverlappedResult failed=%d", WSAGetLastError() ); goto cleanup; } } }else{ DbgPrint("TftpdMasterThread: WSARecvFrom failed=%d.\n", err ); goto failed; } } IterCount++; if( WaitForSingleObject( eMasterTerminate, 0 ) == WAIT_OBJECT_0 ){ goto terminated; } Status = Request->DataSize; // winsock1 <= winsock2. if( Status >= 2 ){ // // Process the packet // if (MyAddr != 0) { // New request ThreadRoutine=NULL; Opcode = *((unsigned short *) &Request->Packet1[0]); Opcode = htons(Opcode); switch(Opcode) { case TFTPD_RRQ: case TFTPD_WRQ: #if defined(REMOTE_BOOT_SECURITY) case TFTPD_LOGIN: case TFTPD_KEY: #endif // defined(REMOTE_BOOT_SECURITY) if ( Opcode == TFTPD_RRQ ) { tftpd_stat.req_read++; ThreadRoutine = TftpdHandleRead; } else if ( Opcode == TFTPD_WRQ ) { tftpd_stat.req_write++; ThreadRoutine = TftpdHandleWrite; } #if defined(REMOTE_BOOT_SECURITY) else if ( Opcode == TFTPD_LOGIN ) { tftpd_stat.req_login++; ThreadRoutine = TftpdHandleLogin; } else { tftpd_stat.req_key++; ThreadRoutine = TftpdHandleKey; } #endif //defined(REMOTE_BOOT_SECURITY) Request->TftpdPort = Sock; DbgPrint("Posting work"); if (ThreadRoutine) { (*ThreadRoutine)(Request); } break; case TFTPD_ERROR: break; case TFTPD_ACK: case TFTPD_DATA: case TFTPD_OACK: default: DbgPrint("TftpdMasterThread: invalid TFTPD_(%d).\n", Opcode ); tftpd_stat.req_error++; TftpdErrorPacket( (struct sockaddr *) &PeerAddress, RequestPacket, Sock, TFTPD_ERROR_ILLEGAL_OPERATION, NULL); } } else { // Continue serving existing request Request->TftpdPort=Sock; TftpdResumeProcessing(Request); } } cleanup: EnterCriticalSection(&ReceiveLock); pEntry=&Request->RequestLinkage; InsertHeadList(&ReceiveList,pEntry); InterlockedIncrement(&AvailableThreads); ActiveReceive--; LeaveCriticalSection(&ReceiveLock); } terminated: DbgPrint("TftpdReceive: exiting\n"); failed: EnterCriticalSection(&ReceiveLock); pEntry=&Request->RequestLinkage; InsertHeadList(&ReceiveList,pEntry); InterlockedIncrement(&AvailableThreads); ActiveReceive--; LeaveCriticalSection(&ReceiveLock); return (0); } // ======================================================================== DWORD TftpdNewReceive( PVOID Argument, BYTE Flags ) /*++ Routine Description: This handles all incoming requests and dispatches them to appropriate worker threads. Arguments: Argument - not used Return Value: Exit status 0 == success 1 == failure, stop service and exit if severe error. --*/ { IPAddr MyAddr=0; DWORD IterCount=0; BOOL LockHeld=FALSE; SocketEntry *SE; DbgPrint("TftpdReceive: entered. Socket %d\n", (DWORD)((DWORD_PTR)Argument)); LockHeld=TryEnterCriticalSection(&SocketLock); while(IterCount < MAX_LOCK_TRIES && !LockHeld) { //Potential for deadlock on interface change if this socket is being deleted. // Break the deadlock by not waiting forever Sleep(200); LockHeld=TryEnterCriticalSection(&SocketLock); IterCount++; } if (!LockHeld) { DbgPrint("TftpdReceived: SocketLock held. Dropping packet"); return 0; } if (LookupSocketEntryBySock((SOCKET)Argument,&SE) == ERROR_SUCCESS) { MyAddr=SE->IPAddress; DbgPrint("TftpdReceive: Found Addr in SocketList %x\n",MyAddr); } LeaveCriticalSection(&SocketLock); HandleRequest((SOCKET)Argument,MyAddr); return(0); } DWORD TftpdContinueReceive( PVOID Argument, BYTE Flags ) /*++ Routine Description: This handles all incoming requests and dispatches them to appropriate worker threads. Arguments: Argument - not used Return Value: Exit status 0 == success 1 == failure, stop service and exit if severe error. --*/ { DbgPrint("TftpdReceive: entered. Socket %d\n", (DWORD)((DWORD_PTR)Argument)); HandleRequest((SOCKET)Argument,0); return(0); } // ======================================================================== VOID TftpdControlHandler( DWORD Opcode) /*++ Routine Description: This does a write with the appropriate conversions for netascii or octet modes. Arguments: WriteFd - file to read from Buffer - Buffer to read into BufferSize - size of buffer WriteMode - O_TEXT or O_BINARY O_TEXT means the netascii conversions must be done O_BINARY means octet mode Return Value: BytesWritten Error? --*/ { int ok; time_t current_time; time( ¤t_time ); TftpdServiceStatus.dwCheckPoint++; DbgPrint("TftpdControlHandler(%d) dwCheckPoint=%d, at %s.\n", Opcode, TftpdServiceStatus.dwCheckPoint, ctime( ¤t_time ) ); switch (Opcode) { case SERVICE_CONTROL_PAUSE: // Actually can we pause listening? DbgPrint("TftpdControlHandler: got SERVICE_CONTROL_PAUSE\n"); SuspendThread(MasterThreadHandle); TftpdServiceStatus.dwCurrentState = SERVICE_PAUSED; break; case SERVICE_CONTROL_CONTINUE: DbgPrint("TftpdControlHandler: got SERVICE_CONTROL_CONTINUE\n"); ResumeThread(MasterThreadHandle); TftpdServiceStatus.dwCurrentState = SERVICE_RUNNING; break; case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: DbgPrint("TftpdControlHandler: got SERVICE_CONTROL_STOP/SHUTDOWN\n"); // TerminateThread(MasterThreadHandle, 0); TftpdServiceExit(NO_ERROR); goto done; break; case SERVICE_CONTROL_INTERROGATE: DbgPrint("TftpdControlHandler: got SERVICE_CONTROL_INTERROGATE\n"); DbgPrint( " req_read=%d, req_write=%d, req_error=%d", tftpd_stat.req_read, tftpd_stat.req_write, tftpd_stat.req_error); #if defined(REMOTE_BOOT_SECURITY) DbgPrint("req_login=%d, req_key=%d.\n", tftpd_stat.req_login, tftpd_stat.req_key ); #endif //defined(REMOTE_BOOT_SECURITY) break; default: DbgPrint("TftpdControlHandler: got SERVICE_CONTROL_(%d)?\n", Opcode ); break; } /** Send a status response **/ ok = SetServiceStatus( TftpdServiceStatusHandle, &TftpdServiceStatus ); if( !ok ){ DbgPrint("TftpdControlHandler: SetServiceStatus failed=%d\n", GetLastError() ); } done: return; } // ======================================================================== // // Announce that we're going down, clean up, stop master thread. // VOID TftpdServiceExit( IN ULONG Error ) { int ok; DbgPrint("TftpdServiceExit(%d)\n", Error ); // ==================== // stage one, stop pending, signal master thread. // ==================== TftpdServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; ok = SetServiceStatus( TftpdServiceStatusHandle, &TftpdServiceStatus ); if( !ok ){ DbgPrint("TftpdServiceExit: SetServiceStatus failed=%d\n", GetLastError() ); } SetEvent( eMasterTerminate ); // ==================== // stage two, stop. // ==================== TftpdServiceStatus.dwCurrentState = SERVICE_STOPPED; TftpdServiceStatus.dwCheckPoint = 0; TftpdServiceStatus.dwWaitHint = 0; SET_SERVICE_EXITCODE( Error, TftpdServiceStatus.dwWin32ExitCode, TftpdServiceStatus.dwServiceSpecificExitCode ); ok = SetServiceStatus( TftpdServiceStatusHandle, &TftpdServiceStatus ); if( !ok ){ DbgPrint("TftpdServiceExit: SetServiceStatus failed=%d\n", GetLastError() ); } // ==================== // stage three, cleanup. // ==================== if( eSock ){ CloseHandle( eSock ); eSock = NULL; } if( eMasterTerminate ){ CloseHandle( eMasterTerminate ); eMasterTerminate = NULL; } if( LogFile ){ fclose( LogFile ); LogFile = NULL; } #if defined(REMOTE_BOOT_SECURITY) TftpdUninitSecurityArray(); #endif //defined(REMOTE_BOOT_SECURITY) } /*++ Remove Entry from list, and free it. SocketLock must be held by caller. --*/ VOID DeleteSocketEntry(SocketEntry *SE) { DbgPrint("Deregister wait %x",SE->WaitHandle); RtlDeregisterWaitEx(SE->WaitHandle,INVALID_HANDLE_VALUE); closesocket(SE->Sock); WSACloseEvent(SE->WaitEvent); if (SE->Linkage.Flink == SE->Linkage.Blink) { // single entry case RemoveHeadList(&SocketList); } else { SE->Linkage.Blink->Flink=SE->Linkage.Flink; SE->Linkage.Flink->Blink=SE->Linkage.Blink; } DbgPrint("removing socket to %x",SE->IPAddress); free(SE); } DWORD GetIpTable(PMIB_IPADDRTABLE *AddrTable) { HRESULT hr; DWORD IpAddrTableSize=0; PMIB_IPADDRTABLE IpAddrTable=NULL, TmpAddrTable=NULL; DWORD ReturnValue=STATUS_NO_MEMORY; *AddrTable=NULL; hr=GetIpAddrTable(NULL, &IpAddrTableSize, FALSE); if (hr != ERROR_SUCCESS && hr != ERROR_INSUFFICIENT_BUFFER) { DbgPrint("GetIpAddrTable failed with %x",hr); goto ret; } IpAddrTable=(PMIB_IPADDRTABLE)malloc(IpAddrTableSize); if (IpAddrTable == NULL) { goto ret; } while (1) { hr=GetIpAddrTable(IpAddrTable, &IpAddrTableSize, FALSE); if (hr == ERROR_SUCCESS) { break; } if (hr == ERROR_INSUFFICIENT_BUFFER) { TmpAddrTable=realloc(IpAddrTable,IpAddrTableSize); if (TmpAddrTable == NULL) { free(IpAddrTable); goto ret; } else { IpAddrTable = TmpAddrTable; } continue; } DbgPrint("GetIpAddrTable failed with %x",hr); goto ret; } ReturnValue=ERROR_SUCCESS; *AddrTable=IpAddrTable; ret: return ReturnValue; } VOID inet_ntoa_copy(struct in_addr IP, BYTE* IPString) { BYTE *Tmp; Tmp=inet_ntoa(IP); if (Tmp) { strcpy(IPString,Tmp); } } /*++ Addr : IP Addr to bind socket to. Return: NULL of failure. Pointer to SocketEntry that is added to SocketList on success. SocketLock must be held by caller. --*/ SocketEntry* AddSocket(IPAddr Addr) { struct sockaddr_in TftpdAddress; SOCKET Sock; HANDLE SocketEvent; SocketEntry *SE; struct servent * serventry; int Count=0; DWORD ErrStatus; do { Sock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (Sock != INVALID_SOCKET) { memset(&TftpdAddress, 0, sizeof(struct sockaddr_in)); serventry = getservbyname("tftp", "udp"); if (serventry == NULL) { DbgPrint("TftpdStart: getservbyname cannot find tftp port.\n"); continue; } TftpdAddress.sin_family = AF_INET; TftpdAddress.sin_port = serventry->s_port; TftpdAddress.sin_addr.s_addr = Addr; if (bind(Sock, (struct sockaddr *)&TftpdAddress, sizeof(struct sockaddr_in))) { DWORD err=GetLastError(); DbgPrint("Bind failed %d\n",err); return NULL; } else { break; } } ErrStatus=WSAGetLastError(); DbgPrint("WSASocket failed with %d\n",ErrStatus); Sleep(750); Count++; } while (Count < MAX_SOCKET_INIT_ATTEMPT); if (Sock == INVALID_SOCKET) { DbgPrint("Failed to create socket for addr %x\n",Addr); return NULL; } SE=(SocketEntry*)malloc(sizeof(SocketEntry)); if (!SE) { goto out; } else { BYTE TmpAddr[20]; memset(SE,0,sizeof(SocketEntry)); SE->Sock=Sock; SE->IPAddress=Addr; inet_ntoa_copy(*((struct in_addr*)&Addr),TmpAddr); SocketEvent=CreateEvent(NULL,FALSE,FALSE,NULL); if (SocketEvent == NULL) { goto out; } SE->WaitEvent=SocketEvent; SE->WaitHandle=RegisterSocket(Sock,SocketEvent,REG_NEW_SOCKET); if (SE->WaitHandle == 0) { goto out; } InsertHeadList(&SocketList,&SE->Linkage); DbgPrint("Adding socket: %d addr: %s\n",Sock,TmpAddr); } return SE; out: closesocket(Sock); if (SocketEvent) { CloseHandle(SocketEvent); } if (SE) { free(SE); } return NULL; } /*++ Walk through SocketList, deleting unreferenced entries, cleaning reference on referenced entries. SocketList lock must be held by caller. --*/ DWORD CleanSocketList() { PLIST_ENTRY pEntry, pNextEntry; SocketEntry *SE; BOOL DeletedEntry=FALSE; pEntry = SocketList.Flink; while ( pEntry != &SocketList) { SE = CONTAINING_RECORD(pEntry, SocketEntry, Linkage); pNextEntry=pEntry->Flink; if (!SE->Referenced) { DeleteSocketEntry(SE); DeletedEntry=TRUE; } else { SE->Referenced = FALSE; } pEntry=pNextEntry; } return DeletedEntry; } /*++ SocketLock must be held by caller. Returns: S_OK if found. SE contains pointer. S_FALSE if failed. SE contains NULL. --*/ DWORD LookupInterfaceEntry(IPAddr Addr,SocketEntry **SE) { PLIST_ENTRY pEntry; SocketEntry *LocalSE; *SE=NULL; for ( pEntry = SocketList.Flink; pEntry != &SocketList; pEntry = pEntry->Flink) { LocalSE = CONTAINING_RECORD(pEntry, SocketEntry, Linkage); if (LocalSE->IPAddress == Addr) { *SE=LocalSE; return TRUE; } } return FALSE; } /*++ SocketLock must be held by caller. Returns: S_OK if found. SE contains pointer. S_FALSE if failed. SE contains NULL. --*/ DWORD LookupSocketEntryBySock(SOCKET Sock, SocketEntry **SE) { PLIST_ENTRY pEntry; SocketEntry *LocalSE; *SE=NULL; for ( pEntry = SocketList.Flink; pEntry != &SocketList; pEntry = pEntry->Flink) { LocalSE = CONTAINING_RECORD(pEntry, SocketEntry, Linkage); if (LocalSE->Sock == Sock) { *SE=LocalSE; break; } } if (*SE) { return ERROR_SUCCESS; } else { return ERROR_INVALID_PARAMETER; } } /*++ Handle interface change event signalled by PA. --*/ VOID NTAPI InterfaceChange(PVOID Context, BOOLEAN Flag) { PMIB_IPADDRTABLE IpAddrTable; DWORD i; DWORD EntryCount=0; PLIST_ENTRY pEntry,pNextEntry; SocketEntry *SE,*NewSE; int Count=0; DWORD ErrStatus; DWORD Added=FALSE, Removed=FALSE; IPAddr IPAddress; DbgPrint("InterfaceChange\n"); EnterCriticalSection(&SocketLock); if (GetIpTable(&IpAddrTable) == ERROR_SUCCESS) { for (i=0; i < IpAddrTable->dwNumEntries; i++) { if ( (IpAddrTable->table[i].dwAddr != 0) && (IpAddrTable->table[i].dwAddr != LOOPBACK)) { if (LookupInterfaceEntry(IpAddrTable->table[i].dwAddr,&SE)) { SE->Referenced=TRUE; } else { Added=TRUE; SE=AddSocket(IpAddrTable->table[i].dwAddr); if (SE) { SE->Referenced=TRUE; } } DbgPrint("Referenced Socket %x\n",IpAddrTable->table[i].dwAddr); } } Removed=CleanSocketList(); free(IpAddrTable); } if (!Added && !Removed) { // Got a notify, and didn't detect any differences. Could be a race condition, and // addr got removed, and readded before we could process the notify // Reopen all sockets to handle this case pEntry=SocketList.Flink; while (pEntry != &SocketList) { SE = CONTAINING_RECORD(pEntry, SocketEntry, Linkage); pNextEntry=pEntry->Flink; if (!(SE->Flags & SE_SOCKET_REOPEN)) { IPAddress=SE->IPAddress; DeleteSocketEntry(SE); NewSE=AddSocket(IPAddress); if (NewSE) { NewSE->Flags |= SE_SOCKET_REOPEN; } } pEntry=pNextEntry; } } ErrStatus = NotifyAddrChange(&AddrChangeHandle,&AddrChangeOverlapped); if (ErrStatus != ERROR_SUCCESS && ErrStatus != ERROR_IO_PENDING) { DbgPrint("NotifyAddrChange failed %d",ErrStatus); } LeaveCriticalSection(&SocketLock); } // ======================================================================== // EOF.