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