#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 <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>

/* architecture dependencies?  XXX: UNTESTED -- someone?  please?? */
#ifdef __alpha	/* XXX */
#define long int
#endif

/* general portability stuff, somewhat sleazed from TW */
#if (defined(SYSV) && (SYSV < 3))
#include <limits.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef HAVE_LSTAT
#define lstat stat		/* braindead */
#endif

/* tw leftovers */
#if defined (XENIX) || defined (MSDOS)	/* XXX */
# include <time.h>
#else
# include <sys/time.h>
#endif 	/* XENIX, MSDOS */
#ifndef MSDOS
#ifdef HAVE_DIRENT_H
# include <dirent.h>
#else
# ifndef XENIX			/* ohferkrissake. */
#  include <sys/dir.h>
# else		/* XENIX */
#  include <sys/ndir.h>
# 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 <strings.h>
#else
#include <string.h>
#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 <sys/sysmacros.h> 
#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 <sys/sysmacros.h>
#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, &currentsb)) == -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, &currentsb);
  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 */

/* ========================== */
