|
|
/*
* 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 <windows.h>
#include <stdio.h>
#include <string.h>
#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); }
|