#!/bin/sh

# This is a Public Domain script. Use at your own risk.
# This is NOT an Intrusion Detection System (IDS).
# This must be run as root. However, mtree is run in check mode not update mode, so no filesystem changes are made.
# Output is in integrity_check.out.

# This script needs a mtree exclusion file called exclude.mtree. A reasonable exclusion file is:
#   ./tmp/*
#   ./var/*
#   ./dev/*
#   ./proc/*
#   ./media/*
# This excludes directories containing temporary, transient and device files.

SEP=';'

BASE_ONLY=Y  # Only check base system packages.
# BASE_ONLY=N  # Check all packages.

if [ $BASE_ONLY = Y ]; then

  pkg query -x "%Sp${SEP}%Su${SEP}%Sg${SEP}%Sm${SEP}%Sf" ^FreeBSD-  # List of (sub-)directories.

  pkg query -x "%Fp${SEP}%Fs${SEP}%Fu${SEP}%Fg${SEP}%Fm${SEP}%Ff${SEP}%Ft" ^FreeBSD-  # List of files.

else

  pkg query "%Sp${SEP}%Su${SEP}%Sg${SEP}%Sm${SEP}%Sf"  # List of (sub-)directories.

  pkg query "%Fp${SEP}%Fs${SEP}%Fu${SEP}%Fg${SEP}%Fm${SEP}%Ff${SEP}%Ft"  # List of files.

fi | sort -u -t "$SEP" -k 1,1 - |  # Remove duplicates and sort by path.

  awk -F "${SEP}" 'BEGIN { print (". type=dir"); }  # The first path name entry listed must be a directory named ‘.’.

		   { if (NF == 5)  # Directory.
		       {
		       p = "." $1;                               # path
		       u = " uname=" $2;                         # owner name
		       g = " gname=" $3;                         # group name
		       m = " mode=" $4;                          # mode
		       f = (($5 == "-") ? "" : (" flags=" $5));  # file flags
		       t = " type=dir";                          # type

		       gsub (" ",  "\\040", p);  # Encode embedded spaces in path.
		       gsub ("\t", "\\011", p);  # Encode embedded tabs in path.
		       gsub ("\n", "\\012", p);  # Encode embedded new lines in path.

		       while (p != "." && r[p] == 0) { print (p u g m f t); r[p] = 1; sub ("/[^/]+$", "", p); }
		       }
		     else if (NF == 7)  # File.
		       {
		       p = "." $1;                               # path
		       u = " uname=" $3;                         # owner name
		       g = " gname=" $4;                         # group name
		       m = " mode=" $5;                          # mode
		       f = (($6 == "-") ? "" : (" flags=" $6));  # file flags
		       t = " type=file";                         # type
		       l = "";                                   # no symbolic link target

		       if (substr ($2, 1, 2) == "1$" && length (substr ($2, 3)) == 64)

		         s = " sha256=" substr ($2, 3);  # sha256 checksum

		       if ($7 != "")
			 {
			 s = "";            # no sha256 checksum
		         t = " type=link";  # type
		         l = " link=" $7;   # symbolic link target
			 }

		       gsub (" ",  "\\040", p);  # Encode embedded spaces in path.
		       gsub ("\t", "\\011", p);  # Encode embedded tabs in path.
		       gsub ("\n", "\\012", p);  # Encode embedded new lines in path.

		       print (p s u g m f t l);

		       sub ("/[^/]+$", "", p);

		       u = " uname=root";
		       g = " gname=wheel";
		       m = " mode=755";
		       f = "";
		       t = " type=dir";

		       while (p != "." && r[p] == 0) { print (p u g m f t); r[p] = 1; sub ("/[^/]+$", "", p); }

		       } }' - | sort -t "$SEP" -k 1,1 - | mtree -p / -X exclude.mtree >integrity_check.out
