#!/bin/bash
#
# Copyright (C) 2006-2009 Eric Shubert <ejs@shubes.net>
#
# Build a sandbox (aka chroot jail) for building qmail-toaster binary rpms
#
###################################################################
#
# *******************USE AT YOUR OWN RISK************************
#
###################################################################
#
# change log
# 09/23/13 shubes - added /usr/share/perl5 for SpamAssassin
# 07/16/09 shubes - added /usr/share/misc for FC11
# 07/09/09 shubes - added /usr/src/redhat/RPMS branch back in so qtp-config
#                   can test its subdirectories
# 04/05/08 shubes - ask before estimating disk usage
#                 - fixed bugs with linked sandbox - pkg directories missing
# 01/07/08 shubes - changed $ARCH to $QTARCH to not conflict with makefiles
#                 - added content to sandbox identifier file
#                 - cleaned up /home qmail,vpopmail code a bit
# 09/06/07 lucize - no more errors on output if dir is not available
#                   lite version tested in:
#                   Centos 3 -i386; Centos 4.5 -i386,x64; Centos 5 - i386,x64
# 08/24/07 lucize - some preliminary space usage
#		  - /home/vpopmail or /home/qmail without domains dir
# 08/21/07 lucize - added new named pipes for djbdns
#		  - made a more lite version of /usr and /var dir
#		  - fixed length for files returned in a5_build_tree() procedure
# 03/09/07 shubes - issue warning when package file missing for linked SB
# 01/10/07 shubes - added new named pipes for supervise/submission
# 01/03/07 shubes - ensure $UPGRADE_DIR is copied to sandbox
# 11/07/06 shubes - added check for symlinks when copying rpms in linked
#                 - removed copy_control_files, as it's not needed any more
#                 - replaced qtp-sandbox constant in boot/ with `basename $SANDBOX`
# 11/05/06 shubes - fixed /opt/qmailtoaster-plus not being created
#                 - fixed find -prune bug
# 11/03/06 shubes - added /opt/qmailtoaster-plus to SB, removed QTP_RUN
#                 - get $SANDBOX from environment instead of parameter
# 10/30/06 shubes - omit /etc/cron* from sandbox on linked SB
# 10/20/06 shubes - fixed linked SB w/ empty toaster package
# 10/12/06 shubes - fixed bug w/ symlinks introduced in v0.1.1
# 10/11/06 shubes - fixed copy of current toaster files for linked SB
#                 - fixed for named-chroot package
# 09/01/06 shubes - created, split apart from qtp-newmodel
#
###################################################################
## check and set up the environment
#
a1_initialization(){

# Make sure we're root
if [ "$UID" != "0" ]; then
  echo "Error: You are not root, please su -"
  exit 1
fi

if [ -z "$SANDBOX" ]; then
  . qtp-config -s
fi

if [ -d "$SANDBOX" ]; then
  echo "$SANDBOX already exists"
  echo "Please 'rm -rf $SANDBOX' and try again"
  exit 1
fi

if [ -z "$QTARCH" ]; then
  . qtp-whatami -s
  case $? in
    0 | 1)
    ;;
    * )
      echo "Your distro is not supported, exiting."
      exit 1
    ;;
  esac
fi

dfltIFS="$IFS"
}

###################################################################
## check for available disk space (future enhancement)
#  for now, just warn the user
#
a2_check_disk_space(){

echo
echo "Warning: This script does not check for available disk space."
echo "You'll need anywhere from 120M+ (linked) to 1.4G+ (copied)"
echo "      to build the sandbox."
echo "I can estimate the amount of space needed for a copied sandbox."
echo -n "Would you like an estimate? (yes, no) y / [n] : "

if [ "$background" ]; then
  echo
  echo "Running in background, replied 'no'"
  REPLY=n
else
  read REPLY
fi
case $REPLY in
  "y" | "yes" )
    echo "Estimating space needed for a copied sandbox (one moment please) ..."
    du -shc $branchlist 2>/dev/null | tail -n 1 
    ;;
esac

echo
echo "The sandbox may be built in any partition."
echo "A 'linked' sandbox can only be in the / partition"
echo "The sandbox will be built at $SANDBOX"
echo -n "Shall we continue? (yes, no|quit) [y] / n|q : "

if [ "$background" ]; then
  echo
  echo "Running in background, replied 'yes'"
  REPLY=y
else
  read REPLY
fi
case $REPLY in
  "" | "y" | "yes" )
    PROCEED=y
    ;;
  * )
    echo "Exiting."
    exit 1
    ;;
esac
}

###################################################################
## determine type of sandbox to build (copied or linked)
#
a3_check_sandbox_type(){

SB_TYPE=""
omitcron=""
check_dir=$SANDBOX
q01_check_separate_partition

if [ "$SEPARATE_PARTITION" ]; then
  SB_TYPE=C
  typemsg="Copied"
else
  echo -n "Would you like to create a linked sandbox? (yes, no) y/[n] :"
  if [ "$background" ]; then
    echo
    echo "Running in background, replied 'no'"
    REPLY=n
  else
    read REPLY
  fi
  case $REPLY in
    "y" | "yes" )
      SB_TYPE=L
      typemsg="Linked"
      omitcron="*"
      ;;
    * )
      SB_TYPE=C
      typemsg="Copied"
      ;;
  esac
fi
}

###################################################################
## build sandbox tree
#
a5_build_tree(){

echo "Creating $typemsg sandbox: $SANDBOX ... (coffee anyone?)"

mkdir -p $SANDBOX/boot \
         $SANDBOX/usr/src/redhat/BUILD \
         $SANDBOX/usr/src/redhat/SOURCES \
         $SANDBOX/usr/src/redhat/SPECS \
         $SANDBOX/var/spool/authdaemon \
         $SANDBOX/var/spool/squirrelmail

# the boot/.`basename $SANDBOX` file is created so that qtp-build-rpms can tell
# if it's running in the sandbox or not,
# and so we can tell if a directory is a sandbox or a 'real' one
# so we don't go deleting something important
echo "$SB_TYPE" > $SANDBOX/boot/.$(basename $SANDBOX)

for branch in $branchlist; do
  if [ -d "$branch" ]; then
    display_copy=1
    b55_build_entire_branch
  fi
done

branchlist_files="\
$(find /usr/lib -maxdepth 1 -mindepth 1 -type f 2>/dev/null)
$(find /usr/lib64 -maxdepth 1 -mindepth 1 -type f 2>/dev/null)
$(find /usr/share -maxdepth 1 -mindepth 1 -type f 2>/dev/null)
"

echo "Creating files from /usr/[lib,lib64,share]..."

for branch in $branchlist_files; do
  if [ -f "$branch" ]; then
    display_copy=0
    b55_build_entire_branch
  fi
done

branchlist_links="\
$(find /usr/lib -maxdepth 1 -mindepth 1 -type l 2>/dev/null)
$(find /usr/lib64 -maxdepth 1 -mindepth 1 -type l 2>/dev/null)
$(find /usr/share -maxdepth 1 -mindepth 1 -type l 2>/dev/null)
"

echo "Creating links for /usr/[lib,lib64,share]..."

for branch in $branchlist_links; do
  if [ -f "$branch" ]; then
    display_copy=0
    b55_build_entire_branch
  fi
done

b57_build_stub_branches

if [ ! -d "${SANDBOX}$UPGRADE_DIR" ]; then
  branch=$UPGRADE_DIR
  b55_build_entire_branch
fi
}

###################################################################
## build an entire main branch off of root
#
b55_build_entire_branch(){

if [ $display_copy == 1 ] ; then
  echo "Creating ${SANDBOX}$branch ..."
fi

display_copy=1
msg_pkgver=""
linkflag=""

if [ "$SB_TYPE" == "L" ]; then
  check_dir=$branch
  q01_check_separate_partition
  if [ ! "$SEPARATE_PARTITION" ]; then
    linkflag="--link"
  fi
fi

IFS="^"
for file in `find $branch -path "*$SANDBOX" -prune -o -path /var/named/chroot/proc -prune -o -path "/etc/cron$omitcron" -prune -o -path "/var/log" -prune -o -path "/lib/modules" -prune -o -path "/home/vpopmail/domains/*" -prune -o -path "/home/qmail/domains" -prune -o -printf '%p^'`; do
  IFS="$dfltIFS"
# be sure to check for symlinks first,
#     since they'll also pass the file and directory tests
  if [ -L "$file" ]; then
#   bypass files in the /var/run and /var/named/chroot/run directories
    if [ -d "$file" ] || \
          [ "$file" == "${file#/var/run}" ] && \
          [ "$file" == "${file#/var/named/chroot/var/run}" ]; then
      ls=`	ls -ld "$file"`
      target=${ls#*-> }
      ln -s $target "${SANDBOX}$file"
    fi
  else
    if [ -d "$file" ]; then
      mkdir -p "${SANDBOX}$file"
    else
#     bypass files in the /var/run and /var/named/chroot/run directories
      if [ "$file" == "${file#/var/run}" ] && \
         [ "$file" == "${file#/var/named/chroot/var/run}" ]; then
        if [ -f "$file" ]; then
          cp -p $linkflag "$file" "${SANDBOX}$file"
        elif [ -p "$file" ]; then
          q02_check_named_pipe
        elif [ -S "$file" ]; then
          continue
        elif [ -c "$file" ]; then
          c556_check_character
        else
          echo "Unexpected file type for $file"
          echo "`ls -l "$file"`"
          echo "Continuing ..."
        fi
      fi
    fi
  fi
done
IFS="$dfltIFS"
}

###################################################################
## check for recognized character special files
#
c556_check_character(){
 
# shubes - `ls -l $file` and build a general 'mknod' command
case "$file" in
  "/var/named/chroot/dev/null" )
    mknod "${SANDBOX}$file" c 1 3
    chmod 644 "${SANDBOX}$file"
    ;;
  "/var/named/chroot/dev/random" )
    mknod "${SANDBOX}$file" c 1 8
    chmod 644 "${SANDBOX}$file"
    ;;
  "/var/named/chroot/dev/zero" )
    mknod "${SANDBOX}$file" c 1 5
    chmod 644 "${SANDBOX}$file"
    ;;
  * )
    echo "Unexpected character special file: $file"
    echo "`ls -l "$file"`"
    echo "Continuing ..."
    ;;
esac
}

###################################################################
## build stubs of branches 'manually' (hard-coded)
#
# most of this stuff is needed by rpm -U
# note - this could likely need to be customized per distro - shubes
#
b57_build_stub_branches(){

echo "Creating $SANDBOX/ misc directories ..."

mkdir -p $SANDBOX/dev/pts
mkdir -m 1777 $SANDBOX/dev/shm
mknod -m 666 $SANDBOX/dev/null c 1 3
mknod -m 620 $SANDBOX/dev/pts/0 c 136 0
mknod -m 620 $SANDBOX/dev/pts/2 c 136 2
mknod -m 620 $SANDBOX/dev/pts/3 c 136 3
mknod -m 644 $SANDBOX/dev/random c 1 8
mknod -m 444 $SANDBOX/dev/urandom c 1 9

mkdir $SANDBOX/proc

mkdir $SANDBOX/root

mkdir $SANDBOX/sys

mkdir $SANDBOX/tmp

if [ -d ${SANDBOX}/var/named/chroot ]; then
  mkdir $SANDBOX/var/named/chroot/proc
fi
}

###################################################################
## copy files for a linked sandbox so they won't be altered
#
# It may seem unnecessary to use --remove-destination,
# but we're removing the hard link then making a copy,
# so that we don't touch the live file outside of the sandbox.
# If we don't remove it, bash will remind us that they're the same file!
#
a7_protect_linked_files(){

echo "Copying the RPM database to the linked sandbox ..."
cp -p --remove-destination /var/lib/rpm/* $SANDBOX/var/lib/rpm/.

for RPM_PKGVER in `rpm -qa | grep toaster`; do
  RPM_PKGNAME=${RPM_PKGVER-[^-]*-[^-]*}
  b74_copy_package
done
}

###################################################################
## copy toaster package from main root to sandbox
#
b74_copy_package(){

echo "Copying files from $RPM_PKGVER to the linked sandbox ..."
msg_pkgver=" in $RPM_PKGVER"

for file in `rpm -ql ${RPM_PKGNAME} | grep -v '\(contains no files\)'`; do
# be sure to check for symlinks first,
#     since they'll also pass the file and directory tests
  if [ ! -L "$file" ]; then
    if [ -f $file ]; then
      cp -p --remove-destination "$file" "${SANDBOX}$file"
    elif [ -p $file ]; then
      q02_check_named_pipe
    elif [ -d $file ]; then
      if [ ! -d ${SANDBOX}$file ]; then
        echo "Directory ${SANDBOX}$file does not exist - script error"
        echo "Exiting."
        exit 1
      fi
    elif [ -a $file ]; then
      echo "$file in package $RPM_PKGVER is not a recognized file type:"
      echo "`ls -l $file`"
      echo "This script needs to be fixed to handle it properly."
      echo "Exiting."
      exit 1
    else
      echo "unexpected: $file in package $RPM_PKGVER does not exist"
      echo "Continuing ..."
    fi
  fi
done
}

###################################################################
## check to see if a directory is in a separate partition
#
q01_check_separate_partition(){

SEPARATE_PARTITION=""
while read word1 word2 word3 word4
do
  if [ "$word2" != "/" ]; then
    testdir=`echo $check_dir | sed -e "s,^$word2,,"`
    if [ "$testdir" != $check_dir ]; then
      SEPARATE_PARTITION=y
      break
    fi
  fi
done < /etc/mtab
}
###################################################################
## check for recognized named pipes
#
q02_check_named_pipe(){

case "$file" in
  "/var/qmail/supervise/authlib/log/supervise/control" | \
  "/var/qmail/supervise/authlib/log/supervise/ok" | \
  "/var/qmail/supervise/authlib/supervise/control" | \
  "/var/qmail/supervise/authlib/supervise/ok" | \
  "/var/qmail/supervise/pop3-ssl/log/supervise/control" | \
  "/var/qmail/supervise/pop3-ssl/log/supervise/ok" | \
  "/var/qmail/supervise/pop3-ssl/supervise/control" | \
  "/var/qmail/supervise/pop3-ssl/supervise/ok" | \
  "/var/qmail/supervise/send/log/supervise/control" | \
  "/var/qmail/supervise/send/log/supervise/ok" | \
  "/var/qmail/supervise/send/supervise/control" | \
  "/var/qmail/supervise/send/supervise/ok" | \
  "/var/qmail/supervise/imap4-ssl/log/supervise/control" | \
  "/var/qmail/supervise/imap4-ssl/log/supervise/ok" | \
  "/var/qmail/supervise/imap4-ssl/supervise/control" | \
  "/var/qmail/supervise/imap4-ssl/supervise/ok" | \
  "/var/qmail/supervise/pop3/log/supervise/control" | \
  "/var/qmail/supervise/pop3/log/supervise/ok" | \
  "/var/qmail/supervise/pop3/supervise/control" | \
  "/var/qmail/supervise/pop3/supervise/ok" | \
  "/var/qmail/supervise/smtp/log/supervise/control" | \
  "/var/qmail/supervise/smtp/log/supervise/ok" | \
  "/var/qmail/supervise/smtp/supervise/control" | \
  "/var/qmail/supervise/smtp/supervise/ok" | \
  "/var/qmail/supervise/submission/log/supervise/control" | \
  "/var/qmail/supervise/submission/log/supervise/ok" | \
  "/var/qmail/supervise/submission/supervise/control" | \
  "/var/qmail/supervise/submission/supervise/ok" | \
  "/var/qmail/supervise/smtp-auth/log/supervise/control" | \
  "/var/qmail/supervise/smtp-auth/log/supervise/ok" | \
  "/var/qmail/supervise/smtp-auth/supervise/control" | \
  "/var/qmail/supervise/smtp-auth/supervise/ok" | \
  "/var/qmail/supervise/clamd/log/supervise/control" | \
  "/var/qmail/supervise/clamd/log/supervise/ok" | \
  "/var/qmail/supervise/clamd/supervise/control" | \
  "/var/qmail/supervise/clamd/supervise/ok" | \
  "/var/qmail/supervise/spamd/log/supervise/control" | \
  "/var/qmail/supervise/spamd/log/supervise/ok" | \
  "/var/qmail/supervise/spamd/supervise/control" | \
  "/var/qmail/supervise/spamd/supervise/ok" | \
  "/var/qmail/supervise/imap4/log/supervise/control" | \
  "/var/qmail/supervise/imap4/log/supervise/ok" | \
  "/var/qmail/supervise/imap4/supervise/control" | \
  "/var/qmail/supervise/imap4/supervise/ok" | \
  "/var/service/qmail-pop3d/log/supervise/control" | \
  "/var/service/qmail-pop3d/log/supervise/ok" | \
  "/var/service/qmail-pop3d/supervise/control" | \
  "/var/service/qmail-pop3d/supervise/ok" | \
  "/var/service/qmail-send/log/supervise/control" | \
  "/var/service/qmail-send/log/supervise/ok" | \
  "/var/service/qmail-send/supervise/control" | \
  "/var/service/qmail-send/supervise/ok" | \
  "/var/service/qmail-smtp/log/supervise/control" | \
  "/var/service/qmail-smtp/log/supervise/ok" | \
  "/var/service/qmail-smtp/supervise/control" | \
  "/var/service/qmail-smtp/supervise/ok" | \
  "/var/djbdns/dnscache/log/supervise/control" | \
  "/var/djbdns/dnscache/log/supervise/ok" | \
  "/var/djbdns/dnscache/supervise/control" | \
  "/var/djbdns/dnscache/supervise/ok" )
#  /var/service directory is from an older toaster version
    if [ ! -p "${SANDBOX}$file" ]; then
      mknod "${SANDBOX}$file" p
      chmod 600 "${SANDBOX}$file"
    fi
    ;;
  "/var/qmail/queue/lock/trigger" )
    if [ ! -p "${SANDBOX}$file" ]; then
      mknod "${SANDBOX}$file" p
      chmod 622 "${SANDBOX}$file"
      chown qmails:qmail "${SANDBOX}$file"
    fi
    ;;
  "/var/gdm/.gdmfifo" )
    if [ ! -p "${SANDBOX}$file" ]; then
      mknod "${SANDBOX}$file" p
      chmod 660 "${SANDBOX}$file"
    fi
    ;;
  * )
    echo "Info: unexpected named pipe${msg_pkgver}: $file"
    echo "`ls -l "$file"`"
    echo "Continuing ..."
    ;;
esac
}

###################################################################
## main script execution begins here
#
me=${0##*/}
myver=v0.3.2
echo "$me $myver starting $(date)"
if [ ! -z "$1" ]; then
  case $1 in
    -b )
      background=$1
      ;;
    * )
      echo "$me usage: $me [-b]"
      exit 1
      ;;
  esac
fi

a1_initialization

branchlist="\
/bin
/command
/etc
/home/qmail
/home/vpopmail
/lib
/lib64
/opt/qmailtoaster-plus
/service
/sbin
/usr/bin
/usr/etc
/usr/include
/usr/kerberos
/usr/libexec
/usr/local
/usr/man
/usr/sbin
/usr/src/qtp-upgrade
/usr/src/redhat/RPMS
/usr/lib/autofs
/usr/lib/courier-authlib
/usr/lib/courier
$(find /usr/lib/expect* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
/usr/lib/mysql
/usr/lib/pkgconfig
/usr/lib/rpm
/usr/lib/rpmdb
/usr/lib/gcc
/usr/lib/gcc-lib
/usr/lib/locale
$(find /usr/lib/python* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
$(find /usr/lib/perl* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
/usr/lib/sse2
/usr/lib64/autofs
/usr/lib64/courier-authlib
/usr/lib64/courier
$(find /usr/lib64/expect* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
/usr/lib64/pkgconfig
/usr/lib64/rpm
/usr/lib64/rpmdb
/usr/lib64/gcc
/usr/lib64/gcc-lib
$(find /usr/lib64/python* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
$(find /usr/lib64/perl* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
/usr/lib64/mysql
$(find /usr/share/aclocal* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
/usr/share/autoconf
$(find /usr/share/automake* -mindepth 0 -maxdepth 0 -type d 2>/dev/null)
/usr/share/clamav
/usr/share/courier
/usr/share/doc
/usr/share/file
/usr/share/info
/usr/share/libtool
/usr/share/locale
/usr/share/maildrop
/usr/share/man
/usr/share/misc
/usr/share/pear
/usr/share/perl5
/usr/share/qmailadmin
/usr/share/spamassassin
/usr/share/squirrelmail
/usr/share/toaster
/usr/share/zoneinfo
/var/djbdns
/var/lib
/var/local
/var/lock
/var/named
/var/opt
/var/qmail
/var/run
/var/log/qmail
/var/log/clamav
/var/log/maildrop
/var/spool/squirrelmail
"

a2_check_disk_space

a3_check_sandbox_type

a5_build_tree

if [ "$SB_TYPE" == "L" ]; then
  a7_protect_linked_files
fi

echo "Sandbox has been built successfully!"
exit 0
