allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (09/15/89)
Posting-number: Volume 8, Issue 42 Submitted-by: mjr@welchlab.welch.jhu.edu (Marcus J. Ranum) Archive-name: filescan #!/bin/sh # This is a shell archive. # Run the following text with /bin/sh to create: # README # Makefile # filescan.c # in_cksum.c # filescan.8 # This archive created: Wed Sep 13 16:06:18 1989 echo shar: extracting README '(995 characters)' sed 's/^XX//' << \SHAR_EOF > README XX XX This is a fairly simple little program I whipped up after I read XXthe CERT messages about versions of telnet(1) that snagged passwords. It XXreads a list of files and checks them against stored information to see XXif the files have been monkeyed with. XX XX I don't think this software is a panacea for trojan horses and XXsuch forms of attack, but I *do* think it's a step, and I hope its flaws XXtrigger more in-depth approaches to these problems. Whether it becomes a XXuseful security tool, I've already realized it may save me a lot of work XXmaking sure that modified files are properly carried forward across XXoperating system revisions :-) XX XX Currently the code is pretty UNIX specific, though any machine XXwith dbm or an equivalent, a stat(2) or equivalent system call, and a XXdirectory lister like find(1) could use it. There may be some berklisms XXin the code, but I assure you there are no NULL derefs or any of that XXcrap. XX XX Anyhow, do with this what you will. Hopefully it may help XXsomeone. XX XX--mjr(); SHAR_EOF if test 995 -ne "`wc -c README`" then echo shar: error transmitting README '(should have been 995 characters)' fi echo shar: extracting Makefile '(802 characters)' sed 's/^XX//' << \SHAR_EOF > Makefile XX# XX# Makefile for filescan file summer and scanner. XX# Copyright (C), Marcus J. Ranum, 1989. All rights reserved XX# This code may be freely distributed as long as this header XX# remains intact. No warranties are expressed or implied as XX# to the suitability of this software for any purpose at all. XX# XX# $Header: Makefile,v 1.1 89/09/13 14:21:31 mjr Rel $ XX# XX XXINSDIR=/usr/etc XXMANDIR=/usr/man/man8 XX XX#define DBM if you dont have NDBM. XXDBM= -DNDBM XX#DBM= -DDBM XX XXCFLAGS= -O $(DBM) XXLDFLAGS= -s XX XXLIBS= -ldbm XX XXfilescan: filescan.o in_cksum.o XX cc $(LDFLAGS) -o filescan filescan.o in_cksum.o $(LIBS) XX XXclean: XX rm -f core *.o filescan XX XXlint: XX lint $(DBM) filescan.c in_cksum.c XX XXinstall: filescan filescan.8 XX cp filescan $(INSDIR) XX cp filescan.8 $(MANDIR) XX chmod 644 $(MANDIR)/filescan.8 XX XXfilescan.o: Makefile filescan.c SHAR_EOF if test 802 -ne "`wc -c Makefile`" then echo shar: error transmitting Makefile '(should have been 802 characters)' fi echo shar: extracting filescan.c '(6800 characters)' sed 's/^XX//' << \SHAR_EOF > filescan.c XX#include <sys/types.h> XX#include <sys/stat.h> XX#include <sys/file.h> XX#ifdef DBM XX#include <dbm.h> XX#endif XX#ifdef NDBM XX#include <ndbm.h> XX#endif XX#include <stdio.h> XX XX/* XX filescan file summer and scanner. XX Copyright (C), Marcus J. Ranum, 1989. All rights reserved XX This code may be freely distributed as long as this header XX remains intact. No warranties are expressed or implied as XX to the suitability of this software for any purpose at all. XX*/ XX XX/* XX * $Log: filescan.c,v $ XX * Revision 1.1 89/09/13 14:26:27 mjr XX * Initial revision XX * XX * XX*/ XX XX#ifndef lint XXstatic char *rcsid[] = "$Header: filescan.c,v 1.1 89/09/13 14:26:27 mjr Rel $"; XX#endif XX XX XXextern datum fetch(); XXextern char *rindex(); XXextern char *getpass(); XXextern char *sprintf(); XX XXstatic void warn(); XX XXint docheckin = 0; XXFILE *inf = { stdin }; XXFILE *outf = { stdout }; XXint updflg = 0; XXint sumflg = 0; XX#ifdef NDBM XXDBM *dbf; /* icky global, I know */ XXextern DBM *dbm_open(); XX#endif XX XXstruct secrec { XX struct stat s; /* stat info */ XX int sum; /* actual sum */ XX char sumf; /* checksum is present */ XX}; XX XX XXmain(ac,av) XXint ac; XXchar **av; XX{ XX#ifdef DBM XX int doinit = 0; XX#endif XX char buf[BUFSIZ]; XX char *dbfile = NULL; XX int aflg = 0; XX int wflg = 0; XX int errcnt = 0; XX int warcnt = 0; XX XX while(*++av) { XX if(**av == '-') { XX switch(*(*av + 1)) { XX case 'a': /* append to output */ XX aflg++; XX break; XX XX case 's': /* perform checksum on store */ XX sumflg++; XX break; XX XX case 'u': /* update changes in database */ XX updflg++; XX break; XX XX case 'w': /* exit status is # of warnings */ XX wflg++; XX break; XX XX case 'c': /* perform check in */ XX docheckin++; XX break; XX XX#ifdef DBM XX case 'C': /* initialize and truncate database */ XX doinit++; XX break; XX#endif XX XX case 'o': /* output from check */ XX if((outf = fopen(*++av,aflg?"a":"w")) == NULL) { XX perror(*av); XX exit(1); XX } XX break; XX XX case 'i': /* input to check or store */ XX if((inf = fopen(*++av,"r")) == NULL) { XX perror(*av); XX exit(1); XX } XX break; XX XX case 'd': /* database name */ XX dbfile = *++av; XX break; XX XX default: XX exit(usage()); XX } XX } XX XX } XX XX if(dbfile == NULL) { XX (void)fprintf(stderr,"can't initialize without datbase file name\n"); XX exit(usage()); XX } XX XX XX#ifdef DBM XX /* create new database files, since DBM is not smart enough to */ XX if(doinit) { XX int wfd; XX XX (void)sprintf(buf,"%s.dir",dbfile); XX if((wfd = open(buf,O_RDWR|O_TRUNC|O_CREAT,0600)) < 0) { XX (void)fprintf(stderr,"cannot create "); XX perror(buf); XX exit(1); XX } XX (void)close(wfd); XX (void)sprintf(buf,"%s.pag",dbfile); XX if((wfd = open(buf,O_RDWR|O_TRUNC|O_CREAT,0600)) < 0) { XX (void)fprintf(stderr,"cannot create "); XX perror(buf); XX exit(1); XX } XX (void)close(wfd); XX } XX#endif XX XX XX#ifdef DBM XX /* open data files. DBM is global, so what the hey */ XX if (dbminit(dbfile) < 0) { XX (void)fprintf(stderr,"cannot open database %s\n",dbfile); XX exit(1); XX } XX#endif XX XX#ifdef NDBM XX if((dbf = dbm_open(dbfile,O_RDWR|O_CREAT,0600)) == NULL) { XX (void)fprintf(stderr,"cannot open database %s\n",dbfile); XX exit(1); XX } XX#endif XX XX XX /* main loop. read input and either store it or check it */ XX while(fgets(buf,BUFSIZ,inf) != NULL) { XX char *p; XX XX /* drop the newline */ XX if((p = rindex(buf,'\n')) != NULL) XX *p = '\0'; XX XX if(docheckin) XX errcnt += scan_store(buf,0); XX else XX errcnt += scan_check(buf,&warcnt); XX } XX XX /* exit with different values depending on request */ XX#ifdef DBM XX (void)dbmclose(); XX#endif XX#ifdef NDBM XX (void)dbm_close(dbf); XX#endif XX exit(wflg ? warcnt : errcnt); XX} XX XXscan_store(fil,spec) XXchar *fil; XXchar spec; /* override - make sure checksum is done for update */ XX{ XX struct secrec sbuf; XX datum key; XX datum content; XX XX if(stat(fil,&sbuf.s)) { XX warn("cannot stat",fil); XX return(1); XX } XX XX if(sumflg || spec) { XX sbuf.sum = sumit(fil); XX sbuf.sumf = 1; XX } else XX sbuf.sumf = 0; XX XX key.dsize = strlen(fil); XX key.dptr = fil; XX content.dsize = sizeof(sbuf); XX content.dptr = (char *)&sbuf; XX XX#ifdef DBM XX if(store(key, content)) { XX warn("cannot store",fil); XX return(1); XX } XX#endif XX#ifdef NDBM XX if(dbm_store(dbf,key, content,DBM_REPLACE)) { XX warn("cannot store",fil); XX return(1); XX } XX#endif XX return(0); XX} XX XXscan_check(fil,warnings) XXchar *fil; XXint *warnings; XX{ XX struct secrec sptr; XX struct secrec sbuf; XX datum key; XX datum content; XX int state = 0; XX XX if(stat(fil,&sbuf.s)) { XX warn("cannot stat",fil); XX *warnings++; XX return(1); XX } XX XX key.dptr = fil; XX key.dsize = strlen(fil); XX XX#ifdef DBM XX content = fetch(key); XX#endif XX#ifdef NDBM XX content = dbm_fetch(dbf,key); XX#endif XX XX /* i suppose that not being in the database is an error, */ XX /* not a security violation, in as many words */ XX if (content.dptr == 0) { XX warn("no entry in database",fil); XX XX /* update changes */ XX if(updflg) { XX /* a checksum will be done only if sumflg is set */ XX (void)scan_store(fil,0); XX } XX return(1); XX } XX XX (void)bcopy(content.dptr,(char *)&sptr,sizeof(sptr)); XX XX /* check what we deem important */ XX if(sptr.sumf != 0) { XX sbuf.sum = sumit(fil); XX if(sptr.sum != sbuf.sum) { XX warn("checksum does not match",fil); XX state++; XX } XX } XX if(sptr.s.st_size != sbuf.s.st_size) { XX warn("file size has changed",fil); XX state++; XX } XX if(sptr.s.st_uid != sbuf.s.st_uid) { XX warn("owner uid has changed",fil); XX state++; XX } XX if(sptr.s.st_uid != sbuf.s.st_uid) { XX warn("owner gid has changed",fil); XX state++; XX } XX if(sptr.s.st_mode != sbuf.s.st_mode) { XX warn("permissions have changed",fil); XX state++; XX } XX if(sptr.s.st_mtime != sbuf.s.st_mtime) { XX warn("modification time has changed",fil); XX state++; XX } XX if(sptr.s.st_ctime != sbuf.s.st_ctime) { XX warn("creation time has changed",fil); XX state++; XX } XX XX /* update changes */ XX if(updflg && state != 0) XX /* checksum will be done if sumflg or the file flag is set */ XX (void)scan_store(fil,sptr.sumf); XX XX return(state); XX} XX XXusage() XX{ XX (void)fprintf(stderr,"usage:\n"); XX (void)fprintf(stderr,"filescan -d database [-a (append to log)] [-s (perform checksums)]\n"); XX#ifdef NDBM XX (void)fprintf(stderr,"\t[-w (exit with warnings)] [-c (load database)]\n"); XX#endif XX#ifdef DBM XX (void)fprintf(stderr,"\t[-w (exit with warnings)] [-c (load database)] [-C (create database)]\n"); XX#endif XX (void)fprintf(stderr,"\t[-i filename (read list from file)] [-o filename (log file)]\n"); XX (void)fprintf(stderr,"\t[-u (update any changes found)]\n"); XX return(1); XX} XX XXstatic void XXwarn(s1,s2) XXchar *s1; XXchar *s2; XX{ XX extern int errno; XX extern char *sys_errlist[]; XX XX if(errno) XX (void)fprintf(outf,"%s:%s(%s)\n",s2,s1,sys_errlist[errno]); XX else XX (void)fprintf(outf,"%s:%s\n",s2,s1); XX} XX XX XXsumit(fil) XXchar *fil; XX{ XX int sum = 0; XX int fd; XX int cnt; XX char buf[BUFSIZ]; XX XX if((fd = open(fil,O_RDONLY)) < 0) { XX warn("cannot read for sum",fil); XX } else { XX while((cnt = read(fd,buf,BUFSIZ)) > 0) XX sum += in_cksum((u_short *)buf,cnt); XX (void)close(fd); XX } XX return(sum); XX} SHAR_EOF if test 6800 -ne "`wc -c filescan.c`" then echo shar: error transmitting filescan.c '(should have been 6800 characters)' fi echo shar: extracting in_cksum.c '(830 characters)' sed 's/^XX//' << \SHAR_EOF > in_cksum.c XX#include <sys/types.h> XX XX/* XX * Internet Protocol checksum routine, stolen from ping.c XX */ XX XXin_cksum(addr, len) XX u_short *addr; XX int len; XX{ XX register int nleft = len; XX register u_short *w = addr; XX register u_short answer; XX register int sum = 0; XX XX /* XX * Our algorithm is simple, using a 32 bit accumulator (sum), XX * we add sequential 16 bit words to it, and at the end, fold XX * back all the carry bits from the top 16 bits into the lower XX * 16 bits. XX */ XX while( nleft > 1 ) { XX sum += *w++; XX nleft -= 2; XX } XX XX /* mop up an odd byte, if necessary */ XX if( nleft == 1 ) XX sum += *(u_char *)w; XX XX /* XX * add back carry outs from top 16 bits to low 16 bits XX */ XX sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ XX sum += (sum >> 16); /* add carry */ XX answer = ~sum; /* truncate to 16 bits */ XX return (answer); XX} SHAR_EOF if test 830 -ne "`wc -c in_cksum.c`" then echo shar: error transmitting in_cksum.c '(should have been 830 characters)' fi echo shar: extracting filescan.8 '(3949 characters)' sed 's/^XX//' << \SHAR_EOF > filescan.8 XX.\" $Header: filescan.8,v 1.1 89/09/13 15:48:34 mjr Exp $ XX.TH FILESCAN 1 "13 September 1989" XX.SH NAME XXfilescan \- primitive trojan horse detector/permissions checker XX.SH SYNOPSIS XX.B filescan XX.RB " \-d database " XX.RB [ " \-a " ] XX.RB [ " \-C " ] XX.RB [ " \-c " ] XX.RB [ " \-s " ] XX.RB [ " \-u " ] XX.RB [ " \-w " ] XX.RB [ " \-i input" ] XX.RB [ " \-o output" ] XX.SH DESCRIPTION XX.LP XX.B filescan XXreads a list of file names from its standard input or file, and checks XXthe list for permissions changes, modification, change of size, etc. XXThe intent is to make it \fIsomewhat\fR harder to insert a trojan horse into XXa system. Information about the files is stored in a XX.B dbm(3) XXhash table, for quick lookup. Warnings about interesting findings can XXbe either appended to a log file, mailed to systems administrators, and XXso on. XX.LP XX.B filescan XXis not going to make it impossible for someone to insert a trojan into XXa system, by any means. Running a complete checksum on all the files XXin the database can chew up a lot of CPU time, yet resorting to simply XXchecking file sizes, permissions and modification times is not 100% XXreliable, either. An additional weakness of such a system is the XXdatabase itself. In this implementation, there is no protection for XXthe database, though encrypting the hash table's directory would XXmake it hard to modify. Obviously, a wrongdoer could flat-out remove XXthe database - but then they could reformat the disks, too. XX.B filescan XXshould be somewhat effective against basic mischef. XX.SH OPTIONS XX.TP XX.B \-C XXIf the database system is based on XX.B dbm(3) XXrather than XX.B ndbm(3) XXthis option will create the database files, or will truncate existing XXones. XX.TP XX.B \-c XXIndicates that the list being read should be XXentered into the database. Presumably, this option will be run once, XXwhen the database is initialized. XX.TP XX.B \-s XXIndicates that a checksum should be performed on all files that are XXbeing stored or updated in the database. When a file is stored, a XXflag is stored with it, indicating that the file is to be summed, XXand it is automatically checked when the database is scanned. XX.TP XX.B \-u XXIndicates that the list being read should be checked for update against the XXcontents of the database. If any changes are detected, warnings are XXissued, and the changes are updated in the database. If this option is XXnot selected (the default is no update), the warning will be repeated XXevery time the file is checked. XX.TP XX.B \-w XXIndicates that the exit status of the program should be the total XXnumber of warnings and errors. The default is the number of errors XX(a file not existing at all when it should is an error, rather than XXjust a warning). XX.TP XX.B "\-i filename" XXIndicates that XX.B filescan XXshould read its file list from the named file. Only one file can be XXnamed in this manner. XX.TP XX.B "\-o filename" XXIndicates that XX.B filescan XXshould send its warning messages to the named logfile. Only one file can be XXnamed in this manner. XX.TP XX.B \-a XXIndicates that the logfile should be opened for append mode, rather than XXtruncated. XX.SH EXAMPLE XX.LP XXInitializing a sample database: XX.br XXfind /usr/local/bin -type f -print | filescan -d sample -c -s XX.LP XXScanning and updating the database: XX.br XXfind /usr/local/bin -type f -print | filescan -d sample XX.fi XX.ad XX.SH "SEE ALSO" XX.BR sum (1) XX.BR find (1) XX.SH BUGS XX.LP XXThe limited options and failure to ensure security of the data base XXcould be considered a bug. Ideally, there should be an option whereby XXa different checksum could be used, or some kind of keying scheme XXshould be built into the checksum. (possibly, the program should XXread an optional checksum along with the file name?) XX.LP XXThis program should not give a false sense of security. XX.SH WARNING XXAvoid having /dev in the list of files, since the ttys change permission XXand ownership a lot. Also avoid having your database check on itself XXin update mode, or you will always get warnings. XX.SH AUTHOR XXMarcus J. Ranum - mjr@welch.jhu.edu SHAR_EOF if test 3949 -ne "`wc -c filescan.8`" then echo shar: error transmitting filescan.8 '(should have been 3949 characters)' fi # End of shell archive exit 0