/* * remote checksum server * * scan.c file scanning and checksum module * * server creates a named pipe and waits for connections. a client connects, * and sends request packets to the server. One such request packet is * the SSREQ_SCAN request: we are given a pathname, and we are to checksum * every file below that point in the directory tree. We pass each * filename and checksum back individually in a separate response packet, * and finally a response packet saying that there are no more files. * * We sort everything into case-insensitive alphabetic order. In a given * directory, we pass out a sorted list of the files before we process * the subdirectories. * * Geraint, July 92 */ #include #include #include #include #include "sumserve.h" #include "errlog.h" #include "server.h" /* module-internal type defns ---------------------------------------------*/ /* sorted list of file names in current dir is in a chained list of these */ typedef struct fnamelist { char szFile[MAX_PATH]; struct fnamelist * next; } FNAMELIST; /* and PFNAMELIST already declared in server.h */ /* forward declaration of functions ---------------------------------------*/ PFNAMELIST ss_addtolist(PFNAMELIST head, PSTR name); BOOL ss_processfile(HANDLE hpipe, long lVersion, LPSTR pAbsName, LPSTR pRelName , BOOL bChecksum); BOOL ss_processdir( HANDLE hpipe, long lVersion, LPSTR pAbsName, LPSTR pRelName , BOOL bChecksum, BOOL fDeep); /*--- externally called functions ----------------------------------------*/ /* ss_scan * * called from ss_handleclient on receipt of a SCAN request. scan the * directory passed in, and pass the files found back to the named pipe * one at a time. filenames returned should be relative to the * starting point (pRoot) and not absolute. * * returns TRUE if all ok; FALSE if an error occured and the connection * is lost. */ BOOL ss_scan(HANDLE hpipe, LPSTR pRoot, LONG lVersion, BOOL bChecksum, BOOL fDeep) { DWORD dwAttrib; LPSTR file; char buffer[MAX_PATH]; /* check whether this is a directory or a file */ dwAttrib = GetFileAttributes(pRoot); if (dwAttrib == -1) { /* file does not exist or is not visible */ if (GetLastError() == ERROR_INVALID_PASSWORD) { dprintf1(("password error\n")); Log_Write(hlogErrors, "password error on %s", pRoot); if (!ss_sendnewresp( hpipe, lVersion, SSRESP_BADPASS , 0, 0, 0, 0, NULL)) { return(FALSE); } } else { dprintf1(("file access error %d\n", GetLastError())); Log_Write(hlogErrors, "file error %d for %s", GetLastError(), pRoot); if (!ss_sendnewresp( hpipe, lVersion, SSRESP_ERROR , GetLastError(), 0, 0, 0, pRoot)) { return(FALSE); } if (!ss_sendnewresp( hpipe, lVersion, SSRESP_END , 0, 0, 0, 0, NULL)) { return(FALSE); } } return TRUE; } if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) { /* it is a directory - read all entries and * then process the entries */ /* * create a "." directory and scan that */ if (!ss_sendnewresp( hpipe, lVersion, SSRESP_DIR , 0 , 0, 0, 0, ".")) { return(FALSE); } if (!ss_processdir(hpipe, lVersion, pRoot, ".", bChecksum, fDeep) ) { return(FALSE); } } else { /* pRoot is a file. we should just return the * checksum and name, and then end. * * note that we should send a filename relative * to pRoot for this file. Since pRoot is this file, * it is not clear what we should send as the file name. * in this case we split off the last component of the * file name and return that */ if ( (file = strrchr(pRoot, '\\')) == NULL) { /* there are no slashes in pRoot - so * there is only one component: use that */ file = pRoot; } else { /* we found a / - skip it so we point at the * final elem */ file++; } /* * make a copy of the filename, prepended with .\ so that * it matches the normal format */ lstrcpy(buffer, ".\\"); lstrcat(buffer, file); if (!ss_processfile(hpipe, lVersion, pRoot, buffer, bChecksum) ) { return(FALSE); } } return(ss_sendnewresp( hpipe, lVersion, SSRESP_END , 0, 0, 0, 0, NULL)); } /* ss_scan */ /* module-internal functions --------------------------------------------*/ /* read all entries in a directory, and create a sorted list of files * in that directory, and a sorted list of subdirs. * * for each file found, call ss_process_file to checksum and report on * the file. * for each subdir, report the name of the new dir and then * recursively call this function to scan it. * * We have two names for the dir- the absolute name (which we use to * scan it) and the name relative to the pRoot starting point - which * pass on to the client * * return TRUE if all ok, or FALSE if the connection has been lost */ BOOL ss_processdir( HANDLE hpipe, long lVersion, LPSTR pAbsName, /* absolute name of dir (to open) */ LPSTR pRelName, /* relative name of dir (to report) */ BOOL bChecksum, /* TRUE iff checksums are wanted */ BOOL fDeep /* TRUE iff subdirs to be included */ ) { PFNAMELIST pfiles = NULL; PFNAMELIST pdirs = NULL; PFNAMELIST pnext; HANDLE hFind; WIN32_FIND_DATA finddata; BOOL bMore; char szNewAbs[MAX_PATH], szNewRel[MAX_PATH]; /* initiate a search of the directory - append * *.* to the directory name */ lstrcpy(szNewAbs, pAbsName); lstrcat(szNewAbs, "\\*.*"); hFind = FindFirstFile(szNewAbs, &finddata); if (hFind == INVALID_HANDLE_VALUE) { bMore = FALSE; } else { bMore = TRUE; } /* loop reading all entries in the directory */ while (bMore) { /* was it a directory or a file ? */ if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { /* ignore . and .. */ if ((strcmp(finddata.cFileName, ".") != 0) && (strcmp(finddata.cFileName, "..") != 0)) { /* insert in sorted list of dir names */ pdirs = ss_addtolist(pdirs, finddata.cFileName); } } else { /* insert in sorted list of file names */ pfiles = ss_addtolist(pfiles, finddata.cFileName); } /* get next entry in directory if there are any */ bMore = FindNextFile(hFind, &finddata); } FindClose(hFind); /* we have now built the sorted lists. * go through the file list first and process each entry */ for (pnext = pfiles; pnext != NULL; ) { /* build a new abs and relative name for this file */ lstrcpy(szNewAbs, pAbsName); lstrcat(szNewAbs, "\\"); lstrcat(szNewAbs, pnext->szFile); lstrcpy(szNewRel, pRelName); lstrcat(szNewRel, "\\"); lstrcat(szNewRel, pnext->szFile); /* checksum the file and send response */ if (!ss_processfile(hpipe, lVersion, szNewAbs, szNewRel, bChecksum)) { return(FALSE); } /* free up the list entry */ pfiles = pnext->next; LocalUnlock(LocalHandle( (PSTR) pnext)); LocalFree(LocalHandle( (PSTR) pnext)); pnext = pfiles; } if (!fDeep) return TRUE; /* loop through the subdirs and recursively scan those */ for (pnext = pdirs; pnext != NULL; ) { /* build a new abs and relative name for this dir */ lstrcpy(szNewAbs, pAbsName); lstrcat(szNewAbs, "\\"); lstrcat(szNewAbs, pnext->szFile); lstrcpy(szNewRel, pRelName); lstrcat(szNewRel, "\\"); lstrcat(szNewRel, pnext->szFile); /* send the name of the new dir to the client */ if (!ss_sendnewresp( hpipe, lVersion, SSRESP_DIR , 0, 0, 0, 0, szNewRel)) { return(FALSE); } if (!ss_processdir(hpipe, lVersion, szNewAbs, szNewRel, bChecksum, TRUE) ) { return(FALSE); } /* free up the list entry */ pdirs = pnext->next; LocalUnlock(LocalHandle( (PSTR) pnext)); LocalFree(LocalHandle( (PSTR) pnext)); pnext = pdirs; } return(TRUE); } /* ss_processdir */ /* checksum a file and send the response to the client. * * return FALSE if the connection failed, TRUE otherwise */ BOOL ss_processfile( HANDLE hpipe, long lVersion, LPSTR pAbsName, /* absolute name of file (to open) */ LPSTR pRelName, /* relative name (to report) */ BOOL bChecksum ) { HANDLE hfile; /* file handle from CreateFile() */ DWORD sum, size; FILETIME ft; hfile = CreateFile(pAbsName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hfile == INVALID_HANDLE_VALUE) { /* We can't read the file, but we can still probably report some properties because FindFirst / FindNext must have found it */ WIN32_FIND_DATA finddata; HANDLE hFind; hFind = FindFirstFile(pAbsName, &finddata); if (hFind!=INVALID_HANDLE_VALUE){ FindClose(hFind); Log_Write(hlogErrors, "Cannot read file %s", pAbsName); /* report that we could not read the file */ return(ss_sendnewresp( hpipe, lVersion, SSRESP_CANTOPEN , finddata.nFileSizeLow, 0 , finddata.ftLastWriteTime.dwLowDateTime , finddata.ftLastWriteTime.dwHighDateTime , pRelName)); } else { /* report that this file is cracked */ Log_Write(hlogErrors, "Cannot find file %s", pAbsName); return(ss_sendnewresp( hpipe, lVersion, SSRESP_ERROR , GetLastError(), 0, 0, 0, pRelName)); } } else { size = GetFileSize(hfile, NULL); if (!GetFileTime(hfile, NULL, NULL, &ft)) { ft.dwLowDateTime = 0; ft.dwHighDateTime = 0; } CloseHandle(hfile); if (bChecksum) { LONG err; sum = checksum_file(pAbsName, &err); if (err!=0) { return(ss_sendnewresp( hpipe, lVersion, SSRESP_ERROR , GetLastError(), 0, 0, 0, pRelName)); } } else sum = 0; /* no checksum wanted */ return (ss_sendnewresp( hpipe, lVersion, SSRESP_FILE , size, sum , ft.dwLowDateTime, ft.dwHighDateTime , pRelName)); } }/* ss_processfile */ /* add a file or directory into a sorted single-linked list. alloc memory for * the new element from LocalAlloc * * we sort using utils_CompPath for a case-insensitive canonical sort, * but to match what goes on in the client world, we actually lower-case * everything first! * * return the new head of the list; */ PFNAMELIST ss_addtolist(PFNAMELIST head, PSTR name) { PFNAMELIST pnew, prev, pnext; /* alloc and fill a new entry */ pnew = LocalLock(LocalAlloc(LHND, sizeof (FNAMELIST))); lstrcpy(pnew->szFile, name); /* always lower-case the names, or the comparison (utils_comppath) * will fail. even if we don't do the compare this time, this name * will be what we are compared against next time round... */ AnsiLowerBuff(pnew->szFile, strlen(pnew->szFile)); /* is the list empty ? */ if (head == NULL) { /* yes, so return new head */ return(pnew); } /* find place in list */ prev = NULL; pnext = head; while ((pnext) && (utils_CompPath(pnext->szFile, pnew->szFile) <= 0)) { prev = pnext; pnext = pnext->next; } /* place found: we come between *prev and *pnext */ pnew->next = pnext; if (prev == NULL) { /* we are new head of list */ return(pnew); } else { prev->next = pnew; /* head of list still the same */ return(head); } } /* UNC handling * * client can pass us a SSREQ_UNC: this contains both a password and a server * name (in the form \\server\share). We make a connection to it here and * remember the connection so that we can remove it (in ss_cleanconnections) * when the client session terminates. * * We are passed the head of a FNAMELIST in which we should store the connect * name for later cleanup. We return the new head of this list. * * the client will send this request if a unc-style named scan fails * with the SSRESP_BADPASS error. */ PFNAMELIST ss_handleUNC( HANDLE hpipe, long lVersion , LPSTR password, LPSTR server, PFNAMELIST connects) { NETRESOURCE resource; int errorcode; resource.lpRemoteName = server; resource.lpLocalName = NULL; resource.dwType = RESOURCETYPE_DISK; resource.lpProvider = NULL; errorcode = (int)WNetAddConnection2(&resource, password, NULL, 0); if (errorcode == NO_ERROR) { /* remember the connection name */ connects = ss_addtolist(connects, server); /* report success */ ss_sendnewresp( hpipe, lVersion, SSRESP_END , 0, 0, 0, 0, NULL); } else { Log_Write(hlogErrors, "Connect error %d for server %s", GetLastError(), server); dprintf1(("connect error %d for server %s\n", GetLastError(), server)); /* report error */ ss_sendnewresp( hpipe, lVersion, SSRESP_ERROR , 0, 0, 0, 0, NULL); } return(connects); } /* ss_handleUNC */ /* disconnect from all the sessions that this client asked us to make */ void ss_cleanconnections(PFNAMELIST connects) { PFNAMELIST server, next; for (server = connects; server != NULL; ) { WNetCancelConnection2(server->szFile, 0, 0); /* free block of memory */ next = server->next; LocalUnlock(LocalHandle( (PSTR) server)); LocalFree(LocalHandle( (PSTR) server)); server = next; } connects = NULL; } /* ss_cleanconnections */