#include "generic.h" /* will pick up most everything else */ #ifndef TIMESTAMP /* timestamp code normally enabled, but */ #define TIMESTAMP 1 /* this lets you take it out if it's */ #endif /* biting you for some reason. */ #include #include #include #include /* general portability stuff, somewhat sleazed from TW */ #if (defined(SYSV) && (SYSV < 3)) #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_PARAM_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifndef HAVE_LSTAT #define lstat stat /* braindead */ #endif /* tw leftovers */ #if defined (XENIX) || defined (MSDOS) /* XXX */ # include #else # include #endif /* XENIX, MSDOS */ #ifndef MSDOS #ifdef HAVE_DIRENT_H # include #else # ifndef XENIX /* ohferkrissake. */ # include # else /* XENIX */ # include # endif /* XENIX */ #endif /* DIRENT_H */ #else /* MSDOS */ #include "dosdir.h" /* readdir() wrapper stuff */ #define HAVE_DIRENT_H 1 /* we do now! */ #endif /* MSDOS */ #ifdef HAVE_STRINGS_H #include #else #include #endif /* ========================== */ /* Version: 1.2 -- some small fixes remain TBD but it's very portable now. */ /* Thrown together during 9408, *Hobbit*. Improved slightly 9411, and old "flist" timestamp-file functionality added back in 9502. I can't really consider this mine, since it's got bits of so many other things in it. The format concept is mine, or at least independently developed. Regardless, keep your lawyers out of my face. _H*/ /* Walk down given filesystems and generate security-relevant listings. A meld of "flist", some code from Tripwire, and some other ideas, notably "script detection" and a reliable [and non-spoofable] way of delimiting pathnames. Produces fairly "numeric" output, intended for running through subsequent hairy regexes, diffs, substitutions, and the occasional backup script. */ #ifndef MAXNAMLEN #define MAXNAMLEN 256 /* *how* big is a path? */ #endif /* Globals */ #define BIGBUFSIZ 8192 unsigned char bigbuf [BIGBUFSIZ]; /* general purpose buffer */ static char atbuf [MAXNAMLEN]; /* filename buffer */ int dirflag = 0; /* -d don't descend into directories that /* weren't explicitly stated */ int qflag = 0; /* -q only list filenames [like flist or find] */ /* Some uppity clown will probably tell me that -h should be -x instead. */ int printhex = 0; /* -h to output hashes in hex, not rad64 */ int nohash = 0; /* -5 don't do the hash at all */ int dash = 0; /* - alone reads path list from stdin, overriding */ /* any other args. */ /* any other args are taken as paths. */ /* with *no* path args, read *data* from stdin. */ int givenarg; /* used to filter directory-walking */ int didwork = 0; /* see if we accomplished anything */ dev_t thisdev = 0; /* don't cross device boundaries */ #ifdef TIMESTAMP time_t stamp = 0; /* timestamp file's mod-time */ struct stat tsb; #endif #ifndef major /* SOME system I'll run into someday isn't going to have this as #defines! If major/minor wasn't picked up in types.h, it might be here. Or it might not, or it might be hiding where you least expect it. Bwahaha... */ #ifdef HAVE_SYSMACROS_H #ifdef SOLARIS /* the reason for *this* braindeath is in */ #define _KERNEL /* sysmacros.h -- minor devs are 18 bits??! ... */ #include #undef major /* ... and is more evidence of Sun once again */ #undef minor /* inventing arbitrary STUPID pseudo-standards */ #define major getmajor /* in a complete vacuum. I couldn't see any really */ #define minor getminor /* portable way to do this. Those *PINHEADS*... */ #undef _KERNEL #else #include #endif /* SOLARIS */ #endif /* SYSMACROS_H */ #endif /* major */ #ifndef major /* we lose, try to do something reasonable that sysmacros usually does and hope that a dev_t is 16 bits */ #define major(x) ((int)(((unsigned)(x)>>8)&0377)) #define minor(x) ((int)((x)&0377)) #endif /* major */ /* ========================== */ bugger () { fprintf (stderr, "?Usage: L5 [-d] [-q] [-h] [-5] [-] [path] [path] ...\n"); fprintf (stderr, " -d don't descend into lower directories\n"); fprintf (stderr, " -q only print pathnames\n"); #ifdef TIMESTAMP fprintf (stderr, " -t use following filespec as a since-timestamp\n"); #endif fprintf (stderr, " -h print MD5 hashes in hex instead of radix 64\n"); fprintf (stderr, " -5 don't do MD5 hashes at all\n"); fprintf (stderr, " - read path list from standard input\n"); fprintf (stderr, "Normal output is of the form:\n"); fprintf (stderr, " path/name//Type inode mode links uid/gid size mtime XXX\n"); fprintf (stderr, " where XXX is a secure hash, or other information.\n"); fprintf (stderr, "With no arguments at all, prints a hash of stdin.\n"); fflush (stderr); exit (1); } /* ========================== */ atstdin () { int x; while ((fgets (atbuf, MAXNAMLEN - 1, stdin)) != NULL) { atbuf [MAXNAMLEN-1] = '\0'; x = strlen (atbuf) - 1; while ((x >= 0) && ((atbuf[x] == '\r') || (atbuf[x] == '\n'))) { atbuf[x] = '\0'; x--; } if (atbuf[0] != '\0') { givenarg = 1; /* @-stdin lines count as given args! */ walk (atbuf); } } /* while fgets stdin */ exit (0); } /* atstdin */ /* ========================== */ #include "md5.h" int Readcnt; /* global so main() can see it */ /* Generate hash for passed-in fd; return ptr to internal buffer. */ char * Dofd (f) int f; { /* Begin gratuitous ripoff from md5wrapper.c [tripwire and friends]. *NOTE: Using structure names from Tripwire's md5.h. The CERT version uses a differently-named struct, but the results are the same. If they weren't, there would be hell to pay! */ static MD5_CTX mdstr; /* MD5 data structure */ MD5_CTX *mdbuf; /* keep compatible... */ static unsigned char digest[16]; /* *we* pass this to md5final */ static unsigned char s[128]; static char ps_signature[256]; /* for output hash */ int readin; int i; strcpy (s, "?MD5 Failed"); /* preload with the worst assumption */ memset (ps_signature, 0, 254); mdbuf = &mdstr; /* More ripoff, but using read() instead of dup/freopen/fread. */ MD5Init (mdbuf); #ifdef DEBUG printf ("initted md5. s = %x ps_sig = %x\n", &s, &ps_signature); #endif if (f != 0) /* Can't seek stdin!! */ if (lseek (f, 0L, 0) == -1) /* rewind(), ahem */ return ((char *)s); while ((readin = read (f, bigbuf, BIGBUFSIZ)) > 0) { MD5Update (mdbuf, bigbuf, readin); Readcnt = Readcnt + readin; #ifdef DEBUGOLD printf (" md1 %x ", mdbuf->digest[0]); #endif } if (readin < 0) return ((char *)s); /* Changed such that we hand in the "digest" arg. Every other md5 implementation [cert, tiger, skey-nrl, ...] does this; why didn't TW?! */ MD5Final (digest, mdbuf); #ifdef DEBUG printf ("final digest blk: %x %x %x %x %x %x ... size %d\n", digest[0],digest[1],digest[2],digest[3],digest[4],digest[5], sizeof (digest)); #endif if (printhex) { for (i = 0; i < 16; i++) { sprintf (s, "%02x", digest[i]); /* %02lx ??? */ strcat(ps_signature, s); } } /* printhex */ /* base 64 */ else { btob64(digest, ps_signature, 128); } #ifdef DEBUG printf ("end of Dofd -- s: /%s/ ps_sig: /%s/ at %x\n", s, ps_signature, &ps_signature); #endif return ((char *)ps_signature); } /* Dofd */ /* ========================== */ /* Collect stat stuff, additional info, and/or hash, and print it */ Do (name, statp) char *name; struct stat *statp; { char p = 'X'; /* type byte */ static char lp[MAXNAMLEN]; /* additional stuff */ char *op; int f; /* fd */ int x; unsigned long offconv; /* for off_t back-conversion */ memset (lp, 0, 4); /* Gratuitously sleazed from tripwire/src/utils.c */ switch (statp->st_mode & S_IFMT) { case S_IFDIR: p = 'D'; /* do mntdev */ sprintf (lp, "%x", statp->st_dev); break; #ifndef MSDOS case S_IFCHR: p = 'C'; /* do maj/min */ sprintf(lp, "%d,%d", /* snarfed from bsd4.4 "ls" */ major(statp->st_rdev), minor(statp->st_rdev)); break; case S_IFBLK: p = 'B'; /* do maj/min */ sprintf(lp, "%d,%d", major(statp->st_rdev), minor(statp->st_rdev)); break; case S_IFIFO: p = 'P'; /* xxx: ??? */ break; #if !defined(SYSV) || (SYSV > 3) #ifndef apollo /* Foolish Apollos define S_IFSOCK same as S_IFIFO in /bsd4.3/usr/include/sys/stat.h */ case S_IFSOCK: p = 'S'; /* xxx: skt? */ break; #endif /* apollo */ #ifdef HAVE_LSTAT case S_IFLNK: p = 'L'; /* do readlink */ if ((x = readlink (name, lp, sizeof (lp))) < 0) strcpy (lp, "?Couldn't read symlink"); if (x < sizeof (lp)) lp[x] = '\0'; else lp[sizeof (lp) - 1] = '\0'; break; #endif /* LSTAT */ #endif /* SVR3 */ #endif /* MSDOS */ case S_IFREG: p = 'F'; break; default: strcpy (lp, "?WTF"); /* Captain, I've never seen this */ /* type of lifeform before */ } /* switch */ op = lp; /* default to weird-text */ if (p == 'F') { /* plainfile: do some other tests */ /* oh Bill Gates, FUCK ME HARDER... */ #ifdef O_BINARY if ((f = open (name, O_RDONLY | O_BINARY)) == -1) { #else if ((f = open (name, O_RDONLY)) == -1) { #endif strcpy (lp, "?Cannot open"); } else { /* open */ if (read (f, bigbuf, 20) == -1) { strcpy (lp, "?Cannot read"); } else { /* read */ bigbuf[20] = '\0'; #ifdef DEBUG printf ("%s\n", bigbuf); /* might produce bizarre stuff! */ #endif x = 0; /* try to skip initial newlines */ if ((bigbuf[x] == '\r') || (bigbuf[x] == '\n')) x++; if ((bigbuf[x] == '\r') || (bigbuf[x] == '\n')) x++; if ((bigbuf[x] == '#') && (bigbuf[x+1] == '!')) p = 'K'; /* skript of some sort! */ if (nohash) op = "-"; /* md5 punted */ else op = Dofd (f); /* point to hash str */ #ifdef DEBUG printf ("Back from dofd: addr = %x, str /%s/\n", op, op); #endif bigbuf[0] = 0; /* reset for next file! */ } /* if read */ } /* if open */ close (f); } /* if 'F' */ /* XXX: maybe we should use that escaped-filename-chars hack from TW */ /* Finally report back */ /* The fucking berkloids went off and decided that 4Gb wasn't a big enough file, and doubled the size_t. Fix A is to printf "%qd", statp->st_size; fix B is to convert the size back into a long. Using fix B for now, since even these days you could go out and eat a lot of ravs before MD5 of a 4Gb file finished!! */ offconv = statp->st_size & 0xffffffff; #ifndef MSDOS printf ("%s//%c %d %o %d %d/%d %d %lx %s\n", name, p, statp->st_ino, statp->st_mode, statp->st_nlink, statp->st_uid, statp->st_gid, offconv, statp->st_mtime, op); #else /* for some reason, MSC trashed "op" when it was included in the Big Printf. Separating it out to another call seems to work, go figure. */ printf ("%s//%c %d %o %d %d/%d %ld %lx", name, p, statp->st_ino, statp->st_mode, statp->st_nlink, statp->st_uid, statp->st_gid, statp->st_size, statp->st_mtime); printf (" %s\n", op); #endif /* MSDOS */ fflush (stdout); } /* Do */ /* ========================== */ walk (name) char *name; { DIR *current; #ifdef HAVE_DIRENT_H struct dirent *fent; #else struct direct *fent; #endif struct stat currentsb; /* can we find our own inode with both hands? */ char fn[MAXNAMLEN]; int fnl; register char *fp; /* Nuke any trailing "/", unless I *said* "/". */ fp = name + (strlen (name) - 1); while ((fp != name) && (*fp == '/')) *fp-- = '\0'; if ((lstat (name, ¤tsb)) == -1) { printf ("?Not found: %s\n", name); return 0; } #ifdef DEBUG printf ("WALK called with name %s\n", name); #endif #ifdef TIMESTAMP /* Timestamp mode is special. Compare *ctime* of the file with timestamp, print if newer, always print directories, and ignore everything else. This is what "flist" was written for lo these many years ago, although it didn't do handy stuff like different-device checks. Why, you might ask? Because "find -ctime xxx" didn't do the right thing. Long story... */ if (stamp) { /* if we're in "timestamp mode" ... */ if ((currentsb.st_mode & S_IFMT) == S_IFDIR) /* take this out */ goto typeit; /* to be "fascist" */ if ((currentsb.st_mode & S_IFMT) == S_IFREG) if (currentsb.st_ctime > stamp) /* new file! */ goto typeit; return 0; /* punt everything else */ } typeit: #endif /* TIMESTAMP */ if (qflag) printf ("%s\n", name); else Do (name, ¤tsb); didwork++; /* See if it's a dir. Note: just ANDing with S_IFDIR doesnt work, cuz S_IFBLK is S_IFDIR and S_IFCHR, so we have to do all this other masking and *then* a straight compare. */ if (((currentsb.st_mode & S_IFMT) == S_IFDIR) && ((givenarg) || (!dirflag))) { /* Stop at device boundaries. If this is a given path, go down it anyway, but don't randomly go off into other mountpoints. */ #ifdef DEBUG printf ("Pretest Thisdev: %x statted dev: %x\n", thisdev, currentsb.st_dev); #endif if (currentsb.st_dev != thisdev) if (givenarg) thisdev = currentsb.st_dev; else return 0; #ifdef DEBUG printf ("Thisdev now %x; gonna opendir %s\n", thisdev, name); #endif /* Got a valid directory, try to go down it. */ givenarg = 0; if ((current = (DIR *)opendir(name)) == NULL) { printf ("?Can't open directory %s\n", name); return 0; } /* Are . and .. guaranteed to be first from readdir?? They seem to be, but we'll check as we go just for paranoia's sake.. */ if ((fent = readdir(current)) == NULL) { printf ("?Premature end of directory %s\n", name); return 0; } #ifdef DEBUG printf ("first dir: %s\n", fent->d_name); #endif fp = fent->d_name; if (( *fp++ != '.') && ( *fp++ != '\0')) { printf ("?WARNING: First entry in %s is not \".\"\n", name); #ifdef WALK_ODDBALL_DIRS /* NOT doing this, normally */ strcpy (fn, name); strcat (fn, "/"); strcat (fn, fent->d_name); walk (fn); #endif /* ODDBALLS */ } /* if not "." */ if ((fent = readdir(current)) == NULL) { printf ("?Premature end of directory %s\n", name); return 0; } #ifdef DEBUG printf ("second dir: %s\n", fent->d_name); #endif fp = fent->d_name; if (( *fp++ != '.') && /* inlined for speed and !strcmp(). */ ( *fp++ != '.') && /* Deal. */ ( *fp++ != '\0')) { printf ("?WARNING: Second entry in %s is not \"..\"\n", name); #ifdef WALK_ODDBALL_DIRS /* NOT doing this, normally. */ strcpy (fn, name); /* YMMV on a DOS box. */ strcat (fn, "/"); strcat (fn, fent->d_name); walk (fn); #endif /* ODDBALLS */ } /* if not ".." */ fent = readdir (current); /* proceed ... */ while (fent != NULL) { #ifdef DEBUG printf ("Continuing dir: %s\n", fent->d_name); #endif strcpy (fn, name); /* . ; ./ ; ./foo */ fnl = strlen(fn); if ((fnl == 1) && (*fn == '/')) /* might be "/" */ fnl--; fn[fnl] = '/'; fn[fnl+1] = '\0'; /* add a slash, and ... */ strcat (fn, fent->d_name); /* make full path */ walk (fn); /* recurse for it */ fent = readdir(current); } /* while fent valid */ closedir (current); } /* if (name) is a dir */ } /* walk */ /* ========================== */ main (argc, argv) int argc; char **argv; { char *last; int x; int y; if (argc == 1) { /* no args, do stdin itself */ last = (char *) Dofd(0); /* XXX: doesnt allow -h ... */ printf ("-STANDARD INPUT-//X - - -/- %d - %s\n", Readcnt, last); exit (0); } /* Preparse all the args, flagging and zeroing any options */ for ( x = 1; x < argc; x++ ) { if (! *argv[x]) continue; if (argv[x][0] == '-') { /* options follow ... */ if ((argv[x][1] == 'd') && (!dirflag)) { dirflag++; argv[x][0] = '\0'; #ifdef DEBUG printf (" -d "); #endif continue; /* for-x */ } if ((argv[x][1] == 'q') && (!qflag)) { qflag++; argv[x][0] = '\0'; #ifdef DEBUG printf (" -q "); #endif continue; } #ifdef TIMESTAMP if ((argv[x][1] == 't') && (stamp == 0)) { if (((x + 1) < argc) && (lstat (argv[x+1], &tsb) == 0)) { stamp = tsb.st_mtime; argv[x+1][0] = '\0'; argv[x][0] = '\0'; } else { fprintf (stderr, "?Can't open timestamp file\n"); } /* if lstat */ #ifdef DEBUG printf (" -t,stamp=%lx ", stamp); #endif continue; } #endif /* TIMESTAMP */ if ((argv[x][1] == 'h') && (!printhex)) { printhex++; argv[x][0] = '\0'; #ifdef DEBUG printf (" -h "); #endif continue; } if ((argv[x][1] == '5') && (!nohash)) { nohash++; argv[x][0] = '\0'; #ifdef DEBUG printf (" -5 "); #endif continue; } if ((argv[x][1] == '\0') && (!dash)) { dash++; argv[x][0] = '\0'; #ifdef DEBUG printf (" - "); #endif continue; } bugger() ; /* invalid or repeated option! */ } /* if '-' */ } /* for x */ #ifdef DEBUGOLD /* broken printf on solaris! */ printf ("fell off - clause with %s\n", argv[x]); #endif didwork = 0; if (dash) /* overrides other args, and */ atstdin (); /* exits by itself */ /* Now reparse; we should only have pathname args left, so do 'em */ for ( x = 1; x < argc; x++ ) { if (! argv[x][0]) continue; givenarg = 1; #ifdef DEBUG printf ("main: gonna walk %s\n", argv[x]); #endif walk (argv[x]); } /* for x */ if (didwork == 0) { fprintf (stderr, "?No valid arguments given\n"); exit (1); } exit (0); } /* main */ /* ========================== */