home *** CD-ROM | disk | FTP | other *** search
- From decwrl!wuarchive!gem.mps.ohio-state.edu!ginosko!uunet!allbery Fri Sep 29 09:55:57 PDT 1989
- Article 1084 of comp.sources.misc:
- Path: decwrl!wuarchive!gem.mps.ohio-state.edu!ginosko!uunet!allbery
- From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
- Newsgroups: comp.sources.misc
- Subject: v08i042: file integrity checker w/checksums
- Message-ID: <67570@uunet.UU.NET>
- Date: 14 Sep 89 23:00:18 GMT
- Sender: allbery@uunet.UU.NET
- Reply-To: mjr@welchlab.welch.jhu.edu (Marcus J. Ranum)
- Lines: 626
- Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
-
- 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
-
-
-