home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume8 / filescan < prev    next >
Text File  |  1989-09-14  |  16KB  |  633 lines

  1. Newsgroups: comp.sources.misc
  2. subject: v08i042: file integrity checker w/checksums
  3. From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
  4. Reply-To: mjr@welchlab.welch.jhu.edu (Marcus J. Ranum)
  5.  
  6. Posting-number: Volume 8, Issue 42
  7. Submitted-by: mjr@welchlab.welch.jhu.edu (Marcus J. Ranum)
  8. Archive-name: filescan
  9.  
  10. #!/bin/sh
  11. #    This is a shell archive.
  12. #    Run the following text with /bin/sh to create:
  13. #    README
  14. #    Makefile
  15. #    filescan.c
  16. #    in_cksum.c
  17. #    filescan.8
  18. # This archive created: Wed Sep 13 16:06:18 1989
  19. echo shar: extracting README '(995 characters)'
  20. sed 's/^XX//' << \SHAR_EOF > README
  21. XX
  22. XX    This is a fairly simple little program I whipped up after I read
  23. XXthe CERT messages about versions of telnet(1) that snagged passwords. It
  24. XXreads a list of files and checks them against stored information to see
  25. XXif the files have been monkeyed with.
  26. XX
  27. XX    I don't think this software is a panacea for trojan horses and
  28. XXsuch forms of attack, but I *do* think it's a step, and I hope its flaws
  29. XXtrigger more in-depth approaches to these problems. Whether it becomes a
  30. XXuseful security tool, I've already realized it may save me a lot of work
  31. XXmaking sure that modified files are properly carried forward across
  32. XXoperating system revisions :-)
  33. XX
  34. XX    Currently the code is pretty UNIX specific, though any machine
  35. XXwith dbm or an equivalent, a stat(2) or equivalent system call, and a
  36. XXdirectory lister like find(1) could use it. There may be some berklisms
  37. XXin the code, but I assure you there are no NULL derefs or any of that
  38. XXcrap.
  39. XX
  40. XX    Anyhow, do with this what you will. Hopefully it may help
  41. XXsomeone.
  42. XX
  43. XX--mjr();
  44. SHAR_EOF
  45. if test 995 -ne "`wc -c README`"
  46. then
  47. echo shar: error transmitting README '(should have been 995 characters)'
  48. fi
  49. echo shar: extracting Makefile '(802 characters)'
  50. sed 's/^XX//' << \SHAR_EOF > Makefile
  51. XX#
  52. XX#    Makefile for filescan file summer and scanner.
  53. XX#    Copyright (C), Marcus J. Ranum, 1989. All rights reserved
  54. XX#    This code may be freely distributed as long as this header
  55. XX#    remains intact. No warranties are expressed or implied as
  56. XX#    to the suitability of this software for any purpose at all.
  57. XX#
  58. XX#    $Header: Makefile,v 1.1 89/09/13 14:21:31 mjr Rel $
  59. XX#
  60. XX
  61. XXINSDIR=/usr/etc
  62. XXMANDIR=/usr/man/man8
  63. XX
  64. XX#define DBM if you dont have NDBM.
  65. XXDBM= -DNDBM
  66. XX#DBM= -DDBM
  67. XX
  68. XXCFLAGS= -O $(DBM)
  69. XXLDFLAGS= -s
  70. XX
  71. XXLIBS= -ldbm
  72. XX
  73. XXfilescan:    filescan.o in_cksum.o
  74. XX    cc $(LDFLAGS) -o filescan filescan.o in_cksum.o $(LIBS)
  75. XX
  76. XXclean:
  77. XX    rm -f core *.o filescan
  78. XX
  79. XXlint:
  80. XX    lint $(DBM) filescan.c in_cksum.c
  81. XX
  82. XXinstall: filescan filescan.8
  83. XX    cp filescan $(INSDIR)
  84. XX    cp filescan.8 $(MANDIR)
  85. XX    chmod 644 $(MANDIR)/filescan.8
  86. XX
  87. XXfilescan.o:    Makefile filescan.c
  88. SHAR_EOF
  89. if test 802 -ne "`wc -c Makefile`"
  90. then
  91. echo shar: error transmitting Makefile '(should have been 802 characters)'
  92. fi
  93. echo shar: extracting filescan.c '(6800 characters)'
  94. sed 's/^XX//' << \SHAR_EOF > filescan.c
  95. XX#include <sys/types.h>
  96. XX#include <sys/stat.h>
  97. XX#include <sys/file.h>
  98. XX#ifdef    DBM
  99. XX#include <dbm.h>
  100. XX#endif
  101. XX#ifdef    NDBM
  102. XX#include <ndbm.h>
  103. XX#endif
  104. XX#include <stdio.h>
  105. XX
  106. XX/*
  107. XX    filescan file summer and scanner.
  108. XX    Copyright (C), Marcus J. Ranum, 1989. All rights reserved
  109. XX    This code may be freely distributed as long as this header
  110. XX    remains intact. No warranties are expressed or implied as
  111. XX    to the suitability of this software for any purpose at all.
  112. XX*/
  113. XX
  114. XX/*
  115. XX *    $Log:    filescan.c,v $
  116. XX * Revision 1.1  89/09/13  14:26:27  mjr
  117. XX * Initial revision
  118. XX * 
  119. XX * 
  120. XX*/
  121. XX
  122. XX#ifndef    lint
  123. XXstatic    char    *rcsid[] = "$Header: filescan.c,v 1.1 89/09/13 14:26:27 mjr Rel $";
  124. XX#endif
  125. XX
  126. XX
  127. XXextern    datum    fetch();
  128. XXextern    char    *rindex();
  129. XXextern    char    *getpass();
  130. XXextern    char    *sprintf();
  131. XX
  132. XXstatic    void        warn();
  133. XX
  134. XXint    docheckin = 0;
  135. XXFILE    *inf = { stdin };
  136. XXFILE    *outf = { stdout };
  137. XXint    updflg = 0;
  138. XXint    sumflg = 0;
  139. XX#ifdef    NDBM
  140. XXDBM    *dbf;                /* icky global, I know */
  141. XXextern    DBM    *dbm_open();
  142. XX#endif
  143. XX
  144. XXstruct    secrec    {
  145. XX    struct    stat    s;        /* stat info */
  146. XX    int        sum;        /* actual sum */
  147. XX    char        sumf;        /* checksum is present */
  148. XX};
  149. XX
  150. XX
  151. XXmain(ac,av)
  152. XXint    ac;
  153. XXchar    **av;
  154. XX{
  155. XX#ifdef    DBM
  156. XX    int    doinit = 0;
  157. XX#endif
  158. XX    char    buf[BUFSIZ];
  159. XX    char    *dbfile = NULL;
  160. XX    int    aflg = 0;
  161. XX    int    wflg = 0;
  162. XX    int    errcnt = 0;
  163. XX    int    warcnt = 0;
  164. XX
  165. XX    while(*++av) {
  166. XX        if(**av == '-') {
  167. XX            switch(*(*av + 1)) {
  168. XX            case 'a':    /* append to output */
  169. XX                aflg++;
  170. XX                break;
  171. XX
  172. XX            case 's':    /* perform checksum on store */
  173. XX                sumflg++;
  174. XX                break;
  175. XX
  176. XX            case 'u':    /* update changes in database */
  177. XX                updflg++;
  178. XX                break;
  179. XX
  180. XX            case 'w':    /* exit status is # of warnings */
  181. XX                wflg++;
  182. XX                break;
  183. XX
  184. XX            case 'c':    /* perform check in */
  185. XX                docheckin++;
  186. XX                break;
  187. XX    
  188. XX#ifdef    DBM
  189. XX            case 'C':    /* initialize and truncate database */
  190. XX                doinit++;
  191. XX                break;
  192. XX#endif
  193. XX
  194. XX            case 'o':    /* output from check */
  195. XX                if((outf = fopen(*++av,aflg?"a":"w")) == NULL) {
  196. XX                    perror(*av);
  197. XX                    exit(1);
  198. XX                }
  199. XX                break;
  200. XX
  201. XX            case 'i':    /* input to check or store */
  202. XX                if((inf = fopen(*++av,"r")) == NULL) {
  203. XX                    perror(*av);
  204. XX                    exit(1);
  205. XX                }
  206. XX                break;
  207. XX
  208. XX            case 'd':    /* database name */
  209. XX                dbfile = *++av;
  210. XX                break;
  211. XX
  212. XX            default:
  213. XX                 exit(usage());
  214. XX            }
  215. XX        }
  216. XX
  217. XX    }
  218. XX
  219. XX    if(dbfile == NULL) {
  220. XX        (void)fprintf(stderr,"can't initialize without datbase file name\n");
  221. XX        exit(usage());
  222. XX    }
  223. XX
  224. XX
  225. XX#ifdef    DBM
  226. XX    /* create new database files, since DBM is not smart enough to */
  227. XX    if(doinit) {
  228. XX        int    wfd;
  229. XX
  230. XX        (void)sprintf(buf,"%s.dir",dbfile);
  231. XX        if((wfd = open(buf,O_RDWR|O_TRUNC|O_CREAT,0600)) < 0) {
  232. XX            (void)fprintf(stderr,"cannot create ");
  233. XX            perror(buf);
  234. XX            exit(1);
  235. XX        }
  236. XX        (void)close(wfd);
  237. XX        (void)sprintf(buf,"%s.pag",dbfile);
  238. XX        if((wfd = open(buf,O_RDWR|O_TRUNC|O_CREAT,0600)) < 0) {
  239. XX            (void)fprintf(stderr,"cannot create ");
  240. XX            perror(buf);
  241. XX            exit(1);
  242. XX        }
  243. XX        (void)close(wfd);
  244. XX    }
  245. XX#endif
  246. XX
  247. XX
  248. XX#ifdef    DBM
  249. XX    /* open data files. DBM is global, so what the hey */
  250. XX    if (dbminit(dbfile) < 0) {
  251. XX        (void)fprintf(stderr,"cannot open database %s\n",dbfile);
  252. XX        exit(1);
  253. XX    }
  254. XX#endif
  255. XX
  256. XX#ifdef    NDBM
  257. XX    if((dbf = dbm_open(dbfile,O_RDWR|O_CREAT,0600)) == NULL) {
  258. XX        (void)fprintf(stderr,"cannot open database %s\n",dbfile);
  259. XX        exit(1);
  260. XX    }
  261. XX#endif
  262. XX
  263. XX
  264. XX    /* main loop. read input and either store it or check it */
  265. XX    while(fgets(buf,BUFSIZ,inf) != NULL) {
  266. XX        char    *p;
  267. XX
  268. XX        /* drop the newline */
  269. XX        if((p = rindex(buf,'\n')) != NULL)
  270. XX            *p = '\0';
  271. XX
  272. XX        if(docheckin)
  273. XX            errcnt += scan_store(buf,0);
  274. XX        else
  275. XX            errcnt += scan_check(buf,&warcnt);
  276. XX    }
  277. XX
  278. XX    /* exit with different values depending on request */
  279. XX#ifdef    DBM
  280. XX    (void)dbmclose();
  281. XX#endif
  282. XX#ifdef    NDBM
  283. XX    (void)dbm_close(dbf);
  284. XX#endif
  285. XX    exit(wflg ? warcnt : errcnt);
  286. XX}
  287. XX
  288. XXscan_store(fil,spec)
  289. XXchar    *fil;
  290. XXchar    spec;    /* override - make sure checksum is done for update */
  291. XX{
  292. XX    struct    secrec    sbuf;
  293. XX    datum    key;
  294. XX    datum    content;
  295. XX
  296. XX    if(stat(fil,&sbuf.s)) {
  297. XX        warn("cannot stat",fil);
  298. XX        return(1);
  299. XX    }
  300. XX
  301. XX    if(sumflg || spec) {
  302. XX        sbuf.sum = sumit(fil);
  303. XX        sbuf.sumf = 1;
  304. XX    } else
  305. XX        sbuf.sumf = 0;
  306. XX
  307. XX    key.dsize = strlen(fil);
  308. XX    key.dptr = fil;
  309. XX    content.dsize = sizeof(sbuf);
  310. XX    content.dptr = (char *)&sbuf;
  311. XX
  312. XX#ifdef    DBM
  313. XX    if(store(key, content)) {
  314. XX        warn("cannot store",fil);
  315. XX        return(1);
  316. XX    }
  317. XX#endif
  318. XX#ifdef    NDBM
  319. XX    if(dbm_store(dbf,key, content,DBM_REPLACE)) {
  320. XX        warn("cannot store",fil);
  321. XX        return(1);
  322. XX    }
  323. XX#endif
  324. XX    return(0);
  325. XX}
  326. XX
  327. XXscan_check(fil,warnings)
  328. XXchar    *fil;
  329. XXint    *warnings;
  330. XX{
  331. XX    struct    secrec    sptr;
  332. XX    struct    secrec    sbuf;
  333. XX    datum    key;
  334. XX    datum    content;
  335. XX    int    state = 0;
  336. XX
  337. XX    if(stat(fil,&sbuf.s)) {
  338. XX        warn("cannot stat",fil);
  339. XX        *warnings++;
  340. XX        return(1);
  341. XX    }
  342. XX
  343. XX    key.dptr = fil;
  344. XX    key.dsize = strlen(fil);
  345. XX
  346. XX#ifdef    DBM
  347. XX    content = fetch(key);
  348. XX#endif
  349. XX#ifdef    NDBM
  350. XX    content = dbm_fetch(dbf,key);
  351. XX#endif
  352. XX
  353. XX    /* i suppose that not being in the database is an error, */
  354. XX    /* not a security violation, in as many words */
  355. XX    if (content.dptr == 0) {
  356. XX        warn("no entry in database",fil);
  357. XX
  358. XX        /* update changes */
  359. XX        if(updflg) {
  360. XX            /* a checksum will be done only if sumflg is set */
  361. XX            (void)scan_store(fil,0);
  362. XX        }
  363. XX        return(1);
  364. XX    }
  365. XX
  366. XX    (void)bcopy(content.dptr,(char *)&sptr,sizeof(sptr));
  367. XX
  368. XX    /* check what we deem important */
  369. XX    if(sptr.sumf != 0) {
  370. XX        sbuf.sum = sumit(fil);
  371. XX        if(sptr.sum != sbuf.sum) {
  372. XX            warn("checksum does not match",fil);
  373. XX            state++;
  374. XX        }
  375. XX    }
  376. XX    if(sptr.s.st_size != sbuf.s.st_size) {
  377. XX        warn("file size has changed",fil);
  378. XX        state++;
  379. XX    }
  380. XX    if(sptr.s.st_uid != sbuf.s.st_uid) {
  381. XX        warn("owner uid has changed",fil);
  382. XX        state++;
  383. XX    }
  384. XX    if(sptr.s.st_uid != sbuf.s.st_uid) {
  385. XX        warn("owner gid has changed",fil);
  386. XX        state++;
  387. XX    }
  388. XX    if(sptr.s.st_mode != sbuf.s.st_mode) {
  389. XX        warn("permissions have changed",fil);
  390. XX        state++;
  391. XX    }
  392. XX    if(sptr.s.st_mtime != sbuf.s.st_mtime) {
  393. XX        warn("modification time has changed",fil);
  394. XX        state++;
  395. XX    }
  396. XX    if(sptr.s.st_ctime != sbuf.s.st_ctime) {
  397. XX        warn("creation time has changed",fil);
  398. XX        state++;
  399. XX    }
  400. XX
  401. XX    /* update changes */
  402. XX    if(updflg && state != 0)
  403. XX        /* checksum will be done if sumflg or the file flag is set */
  404. XX        (void)scan_store(fil,sptr.sumf);
  405. XX
  406. XX    return(state);
  407. XX}
  408. XX
  409. XXusage()
  410. XX{
  411. XX    (void)fprintf(stderr,"usage:\n");
  412. XX    (void)fprintf(stderr,"filescan -d database [-a (append to log)] [-s (perform checksums)]\n");
  413. XX#ifdef    NDBM
  414. XX    (void)fprintf(stderr,"\t[-w (exit with warnings)] [-c (load database)]\n");
  415. XX#endif
  416. XX#ifdef    DBM
  417. XX    (void)fprintf(stderr,"\t[-w (exit with warnings)] [-c (load database)] [-C (create database)]\n");
  418. XX#endif
  419. XX    (void)fprintf(stderr,"\t[-i filename (read list from file)] [-o filename (log file)]\n");
  420. XX    (void)fprintf(stderr,"\t[-u (update any changes found)]\n");
  421. XX    return(1);
  422. XX}
  423. XX
  424. XXstatic    void
  425. XXwarn(s1,s2)
  426. XXchar    *s1;
  427. XXchar    *s2;
  428. XX{
  429. XX    extern    int    errno;
  430. XX    extern    char    *sys_errlist[];
  431. XX
  432. XX    if(errno) 
  433. XX        (void)fprintf(outf,"%s:%s(%s)\n",s2,s1,sys_errlist[errno]);
  434. XX    else
  435. XX        (void)fprintf(outf,"%s:%s\n",s2,s1);
  436. XX}
  437. XX
  438. XX
  439. XXsumit(fil)
  440. XXchar    *fil;
  441. XX{
  442. XX    int    sum = 0;
  443. XX    int    fd;
  444. XX    int    cnt;
  445. XX    char    buf[BUFSIZ];
  446. XX
  447. XX    if((fd = open(fil,O_RDONLY)) < 0) {
  448. XX        warn("cannot read for sum",fil);
  449. XX    } else {
  450. XX        while((cnt = read(fd,buf,BUFSIZ)) > 0)
  451. XX            sum += in_cksum((u_short *)buf,cnt);
  452. XX        (void)close(fd);
  453. XX    }
  454. XX    return(sum);
  455. XX}
  456. SHAR_EOF
  457. if test 6800 -ne "`wc -c filescan.c`"
  458. then
  459. echo shar: error transmitting filescan.c '(should have been 6800 characters)'
  460. fi
  461. echo shar: extracting in_cksum.c '(830 characters)'
  462. sed 's/^XX//' << \SHAR_EOF > in_cksum.c
  463. XX#include <sys/types.h>
  464. XX
  465. XX/*
  466. XX * Internet Protocol checksum routine, stolen from ping.c
  467. XX */
  468. XX
  469. XXin_cksum(addr, len)
  470. XX    u_short    *addr;
  471. XX    int    len;
  472. XX{
  473. XX    register int nleft = len;
  474. XX    register u_short *w = addr;
  475. XX    register u_short answer;
  476. XX    register int sum = 0;
  477. XX
  478. XX    /*
  479. XX     *  Our algorithm is simple, using a 32 bit accumulator (sum),
  480. XX     *  we add sequential 16 bit words to it, and at the end, fold
  481. XX     *  back all the carry bits from the top 16 bits into the lower
  482. XX     *  16 bits.
  483. XX     */
  484. XX    while( nleft > 1 )  {
  485. XX        sum += *w++;
  486. XX        nleft -= 2;
  487. XX    }
  488. XX
  489. XX    /* mop up an odd byte, if necessary */
  490. XX    if( nleft == 1 )
  491. XX        sum += *(u_char *)w;
  492. XX
  493. XX    /*
  494. XX     * add back carry outs from top 16 bits to low 16 bits
  495. XX     */
  496. XX    sum = (sum >> 16) + (sum & 0xffff);    /* add hi 16 to low 16 */
  497. XX    sum += (sum >> 16);            /* add carry */
  498. XX    answer = ~sum;                /* truncate to 16 bits */
  499. XX    return (answer);
  500. XX}
  501. SHAR_EOF
  502. if test 830 -ne "`wc -c in_cksum.c`"
  503. then
  504. echo shar: error transmitting in_cksum.c '(should have been 830 characters)'
  505. fi
  506. echo shar: extracting filescan.8 '(3949 characters)'
  507. sed 's/^XX//' << \SHAR_EOF > filescan.8
  508. XX.\" $Header: filescan.8,v 1.1 89/09/13 15:48:34 mjr Exp $
  509. XX.TH FILESCAN 1 "13 September 1989"
  510. XX.SH NAME
  511. XXfilescan \- primitive trojan horse detector/permissions checker
  512. XX.SH SYNOPSIS
  513. XX.B filescan
  514. XX.RB " \-d database "
  515. XX.RB [ " \-a " ]
  516. XX.RB [ " \-C " ]
  517. XX.RB [ " \-c " ]
  518. XX.RB [ " \-s " ]
  519. XX.RB [ " \-u " ]
  520. XX.RB [ " \-w " ]
  521. XX.RB [ " \-i input" ]
  522. XX.RB [ " \-o output" ]
  523. XX.SH DESCRIPTION
  524. XX.LP
  525. XX.B filescan
  526. XXreads a list of file names from its standard input or file, and checks
  527. XXthe list for permissions changes, modification, change of size, etc. 
  528. XXThe intent is to make it \fIsomewhat\fR harder to insert a trojan horse into
  529. XXa system. Information about the files is stored in a
  530. XX.B dbm(3)
  531. XXhash table, for quick lookup. Warnings about interesting findings can
  532. XXbe either appended to a log file, mailed to systems administrators, and
  533. XXso on.
  534. XX.LP
  535. XX.B filescan
  536. XXis not going to make it impossible for someone to insert a trojan into
  537. XXa system, by any means. Running a complete checksum on all the files
  538. XXin the database can chew up a lot of CPU time, yet resorting to simply
  539. XXchecking file sizes, permissions and modification times is not 100%
  540. XXreliable, either. An additional weakness of such a system is the 
  541. XXdatabase itself. In this implementation, there is no protection for
  542. XXthe database, though encrypting the hash table's directory would
  543. XXmake it hard to modify. Obviously, a wrongdoer could flat-out remove
  544. XXthe database - but then they could reformat the disks, too.
  545. XX.B filescan
  546. XXshould be somewhat effective against basic mischef.
  547. XX.SH OPTIONS
  548. XX.TP
  549. XX.B \-C
  550. XXIf the database system is based on
  551. XX.B dbm(3)
  552. XXrather than
  553. XX.B ndbm(3)
  554. XXthis option will create the database files, or will truncate existing
  555. XXones.
  556. XX.TP
  557. XX.B \-c
  558. XXIndicates that the list being read should be 
  559. XXentered into the database. Presumably, this option will be run once,
  560. XXwhen the database is initialized.
  561. XX.TP
  562. XX.B \-s
  563. XXIndicates that a checksum should be performed on all files that are
  564. XXbeing stored or updated in the database. When a file is stored, a
  565. XXflag is stored with it, indicating that the file is to be summed,
  566. XXand it is automatically checked when the database is scanned.
  567. XX.TP
  568. XX.B \-u
  569. XXIndicates that the list being read should be checked for update against the
  570. XXcontents of the database. If any changes are detected, warnings are
  571. XXissued, and the changes are updated in the database. If this option is
  572. XXnot selected (the default is no update), the warning will be repeated
  573. XXevery time the file is checked.
  574. XX.TP
  575. XX.B \-w
  576. XXIndicates that the exit status of the program should be the total 
  577. XXnumber of warnings and errors. The default is the number of errors
  578. XX(a file not existing at all when it should is an error, rather than
  579. XXjust a warning).
  580. XX.TP
  581. XX.B "\-i filename"
  582. XXIndicates that 
  583. XX.B filescan
  584. XXshould read its file list from the named file. Only one file can be
  585. XXnamed in this manner.
  586. XX.TP
  587. XX.B "\-o filename"
  588. XXIndicates that 
  589. XX.B filescan
  590. XXshould send its warning messages to the named logfile. Only one file can be
  591. XXnamed in this manner.
  592. XX.TP
  593. XX.B \-a
  594. XXIndicates that the logfile should be opened for append mode, rather than
  595. XXtruncated.
  596. XX.SH EXAMPLE
  597. XX.LP
  598. XXInitializing a sample database:
  599. XX.br
  600. XXfind /usr/local/bin -type f -print | filescan -d sample -c -s
  601. XX.LP
  602. XXScanning and updating the database:
  603. XX.br
  604. XXfind /usr/local/bin -type f -print | filescan -d sample
  605. XX.fi
  606. XX.ad
  607. XX.SH "SEE ALSO"
  608. XX.BR sum (1)
  609. XX.BR find (1)
  610. XX.SH BUGS
  611. XX.LP
  612. XXThe limited options and failure to ensure security of the data base
  613. XXcould be considered a bug. Ideally, there should be an option whereby
  614. XXa different checksum could be used, or some kind of keying scheme
  615. XXshould be built into the checksum. (possibly, the program should
  616. XXread an optional checksum along with the file name?)
  617. XX.LP
  618. XXThis program should not give a false sense of security.
  619. XX.SH WARNING
  620. XXAvoid having /dev in the list of files, since the ttys change permission
  621. XXand ownership a lot. Also avoid having your database check on itself 
  622. XXin update mode, or you will always get warnings.
  623. XX.SH AUTHOR
  624. XXMarcus J. Ranum - mjr@welch.jhu.edu
  625. SHAR_EOF
  626. if test 3949 -ne "`wc -c filescan.8`"
  627. then
  628. echo shar: error transmitting filescan.8 '(should have been 3949 characters)'
  629. fi
  630. #    End of shell archive
  631. exit 0
  632.  
  633.