v08i042: file integrity checker w/checksums
Brandon S. Allbery - comp.sources.misc
allbery at uunet.UU.NET
Fri Sep 15 09:00:18 AEST 1989
Posting-number: Volume 8, Issue 42
Submitted-by: mjr at 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 at 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
More information about the Comp.sources.misc
mailing list