/* * remote checksum server * * sumserve.c main module * * program to supply lists of files and checksums from a remote server. * This program runs remotely, and is queried over a named pipe: a client * connects to us, and gives us a pathname. We then send him one at a time, * the names of all the files in the file tree starting at that path, together * with a checksum for the files. * Useful for comparing file trees that are separated by a slow link. * * outline: * this module: named pipe creation and connects - main loop * * service.c service control manager interface (start/stop) * * scan.c: service code that scans and checksums * * * Geraint Davies, july 92 */ #include #include #include #include "sumserve.h" #include "errlog.h" #include "server.h" #include "list.h" BOOL bNoCompression = FALSE; BOOL bTracing = FALSE; /* * error and activity log */ HLOG hlogErrors; HLOG hlogEvents; /* * we keep one of these on the listConnections for each current * connection. It is created by a call to ss_logon, and should be * removed by a call to ss_logoff when the connection terminates. */ typedef struct _connect { FILETIME ftLogon; char Username[256]; } CONNECT, * PCONNECT; /* * list of current connections - protect by critsecConnects; */ CRITICAL_SECTION critsecConnects; LIST listConnects; PCONNECT ss_logon(HANDLE hpipe); VOID ss_logoff(PCONNECT); VOID ss_sendconnects(HANDLE hpipe); /* forward declarations of procedures ----------------------------- */ BOOL ss_handleclient(LPVOID arg); BOOL ss_readmessage(HANDLE hpipe, LPSTR buffer, int size); void ParseArgs(DWORD dwArgc, LPTSTR *lpszArgv); /* functions ------------------------------------------------------- */ #define trace #ifdef trace static HANDLE hTraceFile = INVALID_HANDLE_VALUE; void Trace_File(LPSTR msg) { DWORD nw; /* number of bytes writtten */ if (!bTracing) return; if (hTraceFile==INVALID_HANDLE_VALUE) hTraceFile = CreateFile( "sumserve.trc" , GENERIC_WRITE , FILE_SHARE_WRITE , NULL , CREATE_ALWAYS , 0 , NULL ); WriteFile(hTraceFile, msg, lstrlen(msg)+1, &nw, NULL); FlushFileBuffers(hTraceFile); } /* Trace_File */ void Trace_Close(void) { if (hTraceFile!=INVALID_HANDLE_VALUE) CloseHandle(hTraceFile); hTraceFile = INVALID_HANDLE_VALUE; } /* Trace_Close */ typedef struct { DWORD dw[5]; } BLOCK; #endif //trace static void Error(PSTR Title) { Log_Write(hlogErrors, "Error %d from %s when creating main pipe", GetLastError(), Title); } HANDLE SS_CreateServerPipe(PSTR pname) { /**************************************** We need security attributes for the pipe to let anyone other than the current user log on to it. ***************************************/ /* Allocate DWORDs for the ACL to get them aligned. Round up to next DWORD above */ DWORD Acl[(sizeof(ACL)+sizeof(ACCESS_ALLOWED_ACE)+3)/4+4]; // +4 by experiment!! SECURITY_DESCRIPTOR sd; PSECURITY_DESCRIPTOR psd = &sd; PSID psid; SID_IDENTIFIER_AUTHORITY SidWorld = SECURITY_WORLD_SID_AUTHORITY; PACL pacl = (PACL)(&(Acl[0])); SECURITY_ATTRIBUTES sa; HANDLE hpipe; if (!AllocateAndInitializeSid( &SidWorld, 1, SECURITY_WORLD_RID , 1, 2, 3, 4, 5, 6, 7 , &psid ) ) { Error("AllocateAndInitializeSid"); return(INVALID_HANDLE_VALUE); } if (!InitializeAcl(pacl, sizeof(Acl), ACL_REVISION)){ Error("InitializeAcl"); return(INVALID_HANDLE_VALUE); } if (!AddAccessAllowedAce(pacl, ACL_REVISION, GENERIC_WRITE|GENERIC_READ, psid)){ Error("AddAccessAllowedAce"); return(INVALID_HANDLE_VALUE); } if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION)){ Error("InitializeSecurityDescriptor"); return(INVALID_HANDLE_VALUE); } if (!SetSecurityDescriptorDacl(psd, TRUE, pacl, FALSE)){ Error("SetSecurityDescriptorDacl"); return(INVALID_HANDLE_VALUE); } sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = psd; sa.bInheritHandle = TRUE; /* We now have a good security descriptor! */ dprintf1(("creating new pipe instance\n")); hpipe = CreateNamedPipe(pname, /* pipe name */ PIPE_ACCESS_DUPLEX, /* both read and write */ PIPE_WAIT|PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, 0, 0, /* dynamic buffer allocation*/ 5000, /* def. timeout 5 seconds */ &sa /* security descriptor */ ); FreeSid(psid); if (hpipe == INVALID_HANDLE_VALUE) { Error("CreateNamedPipe"); return(INVALID_HANDLE_VALUE); } return(hpipe); } /* program main loop * * creates the named pipe, and loops waiting for client connections and * calling ss_handleclient for each connection. only exits when told * to by a client. * * currently permits only one client connection at once. */ VOID MainLoop(DWORD dwArgc, LPTSTR *lpszArgv) { char msg[400]; HANDLE hpipe; DWORD threadid; ParseArgs(dwArgc, lpszArgv); /* * initialise error and activity logs */ hlogErrors = Log_Create(); hlogEvents = Log_Create(); Log_Write(hlogEvents, "Checksum service started"); /* initialise connection list and protective critsec */ InitializeCriticalSection(&critsecConnects); List_Init(); listConnects = List_Create(); if (bTracing){ SYSTEMTIME st; char msg[120]; GetSystemTime(&st); wsprintf(msg, "Sumserve trace, started %hd:%hd on %hd/%hd/%hd (British notation)\n" , st.wHour, st.wMinute, st.wDay, st.wMonth, st.wYear); } /* create the named pipe at the known name NPNAME on this server */ /* build the correct syntax for a named pipe on the local machine, * with the pipe name being NPNAME - thus the full name should be * \\.\pipe\NPNAME */ sprintf(msg, "\\\\.\\pipe\\%s", NPNAME); /* * loop creating instances of the named pipe and connecting to * clients. * * When a client connects, we spawn a thread to handle him, and * we create another instance of the named pipe to service * further clients. * * if we receive a quit message (TRUE return from handleclient) * we exit here so that no new clients will be connected. * the process will exit when all the client requests are * finished. */ for (;;) { hpipe = SS_CreateServerPipe(msg); if (hpipe == INVALID_HANDLE_VALUE) { return; } dprintf1(("Waiting for client to connect to main pipe %x\n", hpipe)); if (ConnectNamedPipe(hpipe, NULL)) { /* we have a client connection */ dprintf1(("Client has connected\n")); /* * create a thread to service all requests */ CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ss_handleclient, (LPVOID) hpipe, 0, &threadid); dprintf1(("created thread %ld for pipe %x\n", threadid, hpipe)); } } #ifdef trace Trace_Close(); #endif /* free up logs */ Log_Delete(hlogErrors); Log_Delete(hlogEvents); List_Destroy(&listConnects); DeleteCriticalSection(&critsecConnects); return; } /* collect arguments: -n means bNoCompression = TRUE, -t means bTracing = TRUE */ void ParseArgs(DWORD dwArgc, LPTSTR *lpszArgv) { DWORD i; PSTR ps; for (i = 1; i < dwArgc; i++) { ps = lpszArgv[i]; /* is this an option ? */ if ((ps[0] == '-') || (ps[0] == '/')) { switch(ps[1]) { case 'n': case 'N': bNoCompression = TRUE; break; #ifdef trace case 't': case 'T': bTracing = TRUE; break; #endif //trace default: Log_Write(hlogErrors, "Bad option(s) ignored"); return; } } else { Log_Write(hlogErrors, "Bad argument(s) ignored"); return; } } } /* ParseArgs */ /* * handle a client connection. This routine is called in a separate thread * to service a given client. * * loop reading messages until the client sends a session exit or * program exit code, or until the pipe connection goes away. * * return TRUE if the server is to exit (indicated by a program exit * command SSREQ_EXIT from the client) */ BOOL ss_handleclient(LPVOID arg) { HANDLE hpipe = (HANDLE) arg; SSREQUEST req; SSNEWREQ newreq; LPSTR p1, p2; PFNAMELIST connects = NULL; BOOL bExitServer = FALSE; LONG lVersion = 0; BOOL bDirty = TRUE; /* cleared on clean exit */ PCONNECT pLogon; try { /* make a logon entry in the connections table*/ pLogon = ss_logon(hpipe); // dprintf1(("Client handler for pipe %x\n", hpipe)); /* loop indefinitely - exit only from within the loop if * the connection goes away or we receive an exit command */ for (; ; ) { /* read a message from the pipe - if false, * connection is dropped. */ if (!ss_readmessage(hpipe, (LPSTR) &newreq, sizeof(newreq))) { break; } if (newreq.lCode<0) { lVersion = newreq.lVersion; dprintf1(("Client for pipe %x is at Version %d\n", hpipe, lVersion)); newreq.lCode = -newreq.lCode; } else { /* juggle the fields to get them right */ memcpy(&req, &newreq, sizeof(req)); /* lCode is already in the right place */ dprintf1(("Version 0 (i.e. down level client) for pipe %x\n", hpipe)); newreq.lVersion = 0; memcpy(&newreq.szPath, &req.szPath, MAX_PATH*sizeof(char)); } if (newreq.lVersion>SS_VERSION) /* WE are down level! */ { ss_sendnewresp( hpipe, SS_VERSION, SSRESP_BADVERS , 0,0, 0,0, NULL); /* Sorry - can't help - clean exit */ Log_Write(hlogErrors, "server is down level! Please upgrade! Client wants %d" , newreq.lVersion); FlushFileBuffers(hpipe); break; } if (newreq.lCode == SSREQ_EXIT) { /* exit the program */ Log_Write(hlogErrors, "Server exit request from %s - Ignored", pLogon->Username); /* clean exit */ FlushFileBuffers(hpipe); /* * now exit the server - * returning bExitServer from this function will * cause MainLoop to exit. This will result in * the service being stopped, and the process exiting. */ bExitServer = TRUE; #ifdef trace Trace_Close(); #endif break; } else if (newreq.lCode == SSREQ_END) { /* clean exit */ dprintf1(("Server end session request for pipe %x\n", hpipe)); FlushFileBuffers(hpipe); break; } else if (newreq.lCode == SSREQ_SCAN || newreq.lCode == SSREQ_QUICKSCAN) { /* please scan the following file or dir, * and return the list of files and * checksums. */ Log_Write(hlogEvents, "%s scan for %s", pLogon->Username, newreq.szPath); #ifdef SECURE /* lower security to the client's level */ if (!ImpersonateNamedPipeClient(hpipe)) { dprintf1(("Client impersonate failed %d\n", GetLastError() )); } #endif if (!ss_scan( hpipe, newreq.szPath, lVersion , (newreq.lCode == SSREQ_SCAN) , 0!=(newreq.lFlags&INCLUDESUBS) ) ) { /* return to our own security token */ RevertToSelf(); dprintf1(("connection lost during scan for pipe %x\n", hpipe)); break; } /* return to our own security token */ RevertToSelf(); } else if (newreq.lCode == SSREQ_UNC) { dprintf1(("connect request for pipe %x\n", hpipe)); /* this packet has two strings in the buffer, first * is the password, second is the server */ p1 = newreq.szPath; p2 = &p1[strlen(p1) + 1]; /* remember to add the connect name to our list * of servers to disconnect from at end of client * session */ connects = ss_handleUNC (hpipe, lVersion, p1, p2 , connects); } else if (newreq.lCode == SSREQ_FILE) { Log_Write(hlogEvents, "%s copy file %s", pLogon->Username, newreq.szPath); ss_sendfile(hpipe, newreq.szPath, lVersion); } else if (newreq.lCode == SSREQ_FILES) { Log_Write(hlogEvents, "%s bulk copy request", pLogon->Username); if (!ss_sendfiles(hpipe, lVersion)) { RevertToSelf(); dprintf1(("Sendfiles completed with error on pipe %x\n", hpipe)); break; } } else if (newreq.lCode == SSREQ_NEXTFILE) { Log_Write(hlogErrors, "file list from %s (pipe %x) request out of sequence! (ignored)", pLogon->Username, hpipe); } else if (newreq.lCode == SSREQ_ERRORLOG) { Log_Send(hpipe, hlogErrors); } else if (newreq.lCode == SSREQ_EVENTLOG) { Log_Send(hpipe, hlogEvents); } else if (newreq.lCode == SSREQ_CONNECTS) { ss_sendconnects(hpipe); } else { /* packet error ? - carry on anyway */ Log_Write(hlogErrors, "error in message from %s code: %d", pLogon->Username, newreq.lCode); } } /* we break out of the loop at end of client session */ /* close this pipe instance */ DisconnectNamedPipe(hpipe); CloseHandle(hpipe); /* clean all connections made for this client */ ss_cleanconnections(connects); /* exit this server thread */ dprintf1(("thread %ld exiting on behalf of pipe %x\n", GetCurrentThreadId(), hpipe)); bDirty = FALSE; } except (EXCEPTION_EXECUTE_HANDLER) { if (bDirty) { Log_Write(hlogErrors, "!!Exception on thread %ld. Exiting on behalf of %s" , GetCurrentThreadId(), pLogon->Username); try { DisconnectNamedPipe(hpipe); CloseHandle(hpipe); } except (EXCEPTION_EXECUTE_HANDLER) { /* Oh dear - let's just go home! */ } } else dprintf1(( "Thread %ld exiting on behalf of pipe %x\n" , GetCurrentThreadId(), hpipe)); } /* note that we have logged off */ ss_logoff(pLogon); return(bExitServer); } /* ss_handle_client */ /* build and send a response packet to the client. Check for network * errors, and retry (unless the pipe is broken) up to 10 times. * * if write succeeds - return TRUE. * if failure - return FALSE to indicate connection is dropped. */ BOOL ss_sendnewresp( HANDLE hpipe , long lVersion , long lCode , ULONG ulSize /* used for Win32 error code for SSRESP_ERRROR */ , ULONG ulSum , DWORD dwLowTime , DWORD dwHighTime , PSTR szFile ) { SSNEWRESP resp; if (lVersion==0) { return ss_sendresponse(hpipe, lCode, ulSize, ulSum, szFile); } resp.lVersion = lVersion; resp.lResponse = LRESPONSE; resp.lCode = lCode; resp.ulSize = ulSize; resp.ulSum = ulSum; resp.ft_lastwrite.dwLowDateTime = dwLowTime; resp.ft_lastwrite.dwHighDateTime = dwHighTime; if (szFile != NULL) { lstrcpy(resp.szFile, szFile); } return(ss_sendblock(hpipe, (PSTR) &resp, sizeof(resp))); } /* ss_sendnewresp */ /* build and send a response packet to the client. Check for network * errors, and retry (unless the pipe is broken) up to 10 times. * * if write succeeds - return TRUE. * if failure - return FALSE to indicate connection is dropped. */ BOOL ss_sendresponse(HANDLE hpipe, long lCode, ULONG ulSize, ULONG ulSum, PSTR szFile) { SSRESPONSE resp; resp.lCode = lCode; resp.ulSize = ulSize; resp.ulSum = ulSum; if (szFile != NULL) { lstrcpy(resp.szFile, szFile); } return(ss_sendblock(hpipe, (PSTR) &resp, sizeof(resp))); } /* * send a block of data or response packet to the named pipe. * * return TRUE if ok, or false if connection dropped */ BOOL ss_sendblock(HANDLE hpipe, PSTR buffer, int length) { int size, count, errorcode; /* loop retrying the send until it goes ok */ for (count = 0; count < 10; count++) { #ifdef trace { char msg[80]; BLOCK * pb; pb = (BLOCK *) buffer; wsprintf( msg, "sendblock on %x: %x %x %x %x %x\n" , hpipe, pb->dw[0], pb->dw[1], pb->dw[2], pb->dw[3], pb->dw[4]); Trace_File(msg); } #endif if (WriteFile(hpipe, buffer, length, (LPDWORD)(&size), NULL)) { /* no error reported - was everything written?*/ if (size != length) { #ifdef trace { char msg[80]; wsprintf(msg, " !!Bad length send for %x \n", hpipe); Trace_File(msg); } #endif /* write was NOT ok - report and retry */ printf("pipe write size differs for pipe %x\n", hpipe); continue; // ??? will this confuse client } else { #ifdef trace { char msg[80]; wsprintf(msg, " good send for %x \n", hpipe); Trace_File(msg); } #endif /* all ok */ return(TRUE); } } #ifdef trace { char msg[80]; wsprintf(msg, " !!Bad send for %x \n", hpipe); Trace_File(msg); } #endif /* an error occurred */ switch( (errorcode = (int)GetLastError())) { case ERROR_NO_DATA: case ERROR_BROKEN_PIPE: /* pipe connection lost - forget it */ dprintf1(("pipe %x broken on write\n", hpipe)); return(FALSE); default: Log_Write(hlogErrors, "write error %d on pipe %x", errorcode, hpipe); break; } } /* retry count reached - abandon this attempt */ Log_Write(hlogErrors, "retry count reached on pipe %s - write error", hpipe); return(FALSE); } /* read a message from a pipe, allowing for network errors * * if error occurs, retry up to 10 times unless error code * indicates that pipe is broken - in which case, give up. * * return TRUE if all ok, or FALSE to mean the connection is broken, * abort this client. */ BOOL ss_readmessage(HANDLE hpipe, LPSTR buffer, int size) { int count; int actualsize; int errorcode; /* retry up to 10 times */ for (count = 0; count < 10; count++ ) { // dprintf1(("waiting for read of pipe %x ...\n", hpipe)); #ifdef trace { char msg[80]; wsprintf(msg, "ReadFile for pipe %x ...", hpipe ); Trace_File(msg); } #endif if (ReadFile(hpipe, buffer, size, (LPDWORD)(&actualsize), NULL)) { #ifdef trace { char msg[80]; BLOCK * pb; pb = (BLOCK *) buffer; wsprintf(msg, " Good ReadFile for %x: %x %x %x %x %x\n" , hpipe, pb->dw[0], pb->dw[1], pb->dw[2], pb->dw[3], pb->dw[4]); Trace_File(msg); } #endif /* everything ok */ // dprintf1((" pipe %x read OK\n", hpipe)); return(TRUE); } #ifdef trace { char msg[80]; wsprintf(msg, "!!Bad ReadFile for %x\n", hpipe ); Trace_File(msg); } #endif /* error occurred - check code */ switch((errorcode = (int)GetLastError())) { case ERROR_BROKEN_PIPE: /* connection broken. no point in retrying */ dprintf1(("pipe %x broken on read\n", hpipe)); return(FALSE); case ERROR_MORE_DATA: /* the message sent is larger than our buffer. * this is an internal error - report it and carryon */ Log_Write(hlogErrors, "error from pipe %x - message too large", hpipe); return(TRUE); default: Log_Write(hlogErrors, "pipe %x read error %d", hpipe, errorcode); break; } } Log_Write(hlogErrors, "retry count reached on pipe %x read error", hpipe); return(FALSE); } /* * note a logon, and return a logon entry that should be removed at * logoff time */ PCONNECT ss_logon(HANDLE hpipe) { PCONNECT pLogon; SYSTEMTIME systime; char msg[256]; EnterCriticalSection(&critsecConnects); pLogon = List_NewLast(listConnects, sizeof(CONNECT)); LeaveCriticalSection(&critsecConnects); GetSystemTime(&systime); SystemTimeToFileTime(&systime, &pLogon->ftLogon); GetNamedPipeHandleState( hpipe, NULL, NULL, NULL, NULL, pLogon->Username, sizeof(pLogon->Username)); /* log the connect event in the main log*/ wsprintf(msg, "%s connected", pLogon->Username); Log_WriteData(hlogEvents, &pLogon->ftLogon, msg); return(pLogon); } /* * remove a current connection from the connections list */ VOID ss_logoff(PCONNECT pLogon) { /* note the logoff event in the main log */ Log_Write(hlogEvents, "%s connection terminated", pLogon->Username); /* remove the entry from the list */ EnterCriticalSection(&critsecConnects); List_Delete(pLogon); LeaveCriticalSection(&critsecConnects); } /* * send the current-connections log * * Current connections are held on a list - we need to build a standard * log from the current list and then send that. */ VOID ss_sendconnects(HANDLE hpipe) { HLOG hlog; PCONNECT pconn; hlog = Log_Create(); EnterCriticalSection(&critsecConnects); List_TRAVERSE(listConnects, pconn) { Log_WriteData(hlog, &pconn->ftLogon, pconn->Username); } LeaveCriticalSection(&critsecConnects); Log_Send(hpipe, hlog); Log_Delete(hlog); }