#! /bin/sh

# cvsd-buildroot - build a usable root-filesystem for cvsd
# Copyright (C) 2001, 2002, 2003, 2004, 2005 Arthur de Jong
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# this script creates and updates
#  /bin
#     cvs
#  /lib
#     all libraries required by files in bin (auto)
#  /dev
#     null,zero
#  /etc
#     passwd
#  /tmp
#     (cleans up old files)

# use hardcoded path to avoid trojans
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
export PATH

# which binaries to install (use spaces as separator)
BINARIES="/usr/bin/cvs"

# where the cvsd configfile is located
CONFIGFILE="/etc/cvsd/cvsd.conf"

# which libraries to install (aside from the libraries needed by
# the specified libraries)
# for generic Linux:
EXTRALIBS="libnsl.so libnss_compat.so /lib/ld-linux.so.2"
# for 64 bit Linux systems:
EXTRALIBS="$EXTRALIBS /lib64/ld-linux-x86-64.so.2"
# for FreeBSD:
EXTRALIBS="$EXTRALIBS ld-elf.so"
# for Redhat 7.2:
EXTRALIBS="$EXTRALIBS libnss_compat.so.2 libnss_files.so.2"
# for OpenBSD:
EXTRALIBS="$EXTRALIBS /usr/libexec/ld.so"
# for Solaris:
EXTRALIBS="$EXTRALIBS /usr/lib/ld.so.1 nss_files.so.1"

# users to take from /etc/passwd and put in ROOT/etc/passwd
# (if they exist)
USERS="root nobody cvsd cvs"

# find the flavor of the echo command (stolen from configure)
case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
  *c*,-n*) ECHO_N= ECHO_C='
' ;;
  *c*,*  ) ECHO_N=-n ECHO_C= ;;
  *)       ECHO_N= ECHO_C='\c' ;;
esac

# check command line parameter
if echo "$1" | grep -v '^/' > /dev/null 2>&1 || [ -n "$2" ]
then
  echo "Usage: $0 DIRECTORY"
  echo "Create and populate a directory for use as a chroot jail for running cvsd in."
  echo "The directory should be specified as an absolute path."
  echo ""
  echo "Report bugs to <arthur@tiefighter.et.tudelft.nl>."
  exit 1
fi

# where to make root filesystem
ROOT=$1

# check user id
if ( id -u > /dev/null 2>&1 && [ "`id -u`" != "0" ] ) || \
   ( /usr/xpg4/bin/id -u > /dev/null 2>&1 && [ "`/usr/xpg4/bin/id -u`" != "0" ] )
then
  echo "WARNING: you should probably run this as a ROOT user" >&2
fi

# create dirs with right permissions
echo $ECHO_N "creating directory structure under $ROOT... $ECHO_C"
mkdir -p "$ROOT/bin" "$ROOT/lib" "$ROOT/dev" "$ROOT/etc" "$ROOT/usr" "$ROOT/tmp"
chmod 755 "$ROOT" "$ROOT/bin" "$ROOT/lib" "$ROOT/dev" "$ROOT/etc" "$ROOT/usr"
chmod 1777 "$ROOT/tmp"
# create $ROOT/usr/lib for some systems with fixed linker paths
ls "$ROOT/usr/lib" > /dev/null 2>&1 || \
  ( cd "$ROOT/usr" ; ln -s ../lib ./lib )
# also for bin
ls "$ROOT/usr/bin" > /dev/null 2>&1 || \
  ( cd "$ROOT/usr" ; ln -s ../bin ./bin )
# also create $ROOT/usr/libexec on systems that have such a thing
ls /usr/libexec > /dev/null 2>&1 && \
  ( ls "$ROOT/usr/libexec" > /dev/null 2>&1 || \
    ( cd "$ROOT/usr" ; ln -s ../lib ./libexec ) )
# also create $ROOT/libexec on systems that have such a thing
ls /libexec > /dev/null 2>&1 && \
  ( ls "$ROOT/libexec" > /dev/null 2>&1 || \
    ( cd "$ROOT/" ; ln -s lib ./libexec ) )
# also create $ROOT/usr/lib64 on systems that have such a thing
ls /usr/lib64 > /dev/null 2>&1 && \
  ( ls "$ROOT/usr/lib64" > /dev/null 2>&1 || \
    ( cd "$ROOT/usr" ; ln -s ../lib ./lib64 ) )
# also create $ROOT/lib64 on systems that have such a thing
ls /lib64 > /dev/null 2>&1 && \
  ( ls "$ROOT/lib64" > /dev/null 2>&1 || \
    ( cd "$ROOT/" ; ln -s lib ./lib64 ) )
echo "done."

# populate /bin
echo $ECHO_N "installing binaries...$ECHO_C"
for i in $BINARIES
do
  cp "$i" "$ROOT/bin"
  chmod 755 "$ROOT/bin/`basename $i`"
  echo $ECHO_N " `basename $i`$ECHO_C"
done
echo "."

# report files in bin that don't belong there
for i in $ROOT/bin/*
do
  found=0
  for j in $BINARIES
  do
    if [ `basename $i` = `basename $j` ]
    then
      found=1
    fi
  done
  if [ $found -eq 0 ]
  then
    echo "WARNING: extra (unknown) file found: $i" >&2
  fi
done

# find specified libraries
LIBPATH="/lib /usr/lib /usr/local/lib /usr/X11R6/lib /usr/lib/libc5-compat /lib/libc5-compat /usr/libexec"
LIBRARIES=""
for i in $EXTRALIBS
do
  echo $ECHO_N "locating $i... $ECHO_C"
  found=`( ( ls "$i".*
             ls "$i"*
             for j in $LIBPATH
             do
               ls "$j/$i".*
               ls "$j/$i"*
             done ) | head -1 ) 2> /dev/null`
  if [ -n "$found" ]
  then
    echo "$found"
    LIBRARIES="$LIBRARIES $found"
  else
    echo "not found (probably not fatal)"
  fi
done

# figure out libraries used by stuff in bin directory
LIBARIES="$LIBRARIES `ldd $ROOT/bin/* | sed -n 's/^.* =>[^/]*\([^ ]*\).*$/\1/p'`"
# filter out double entries
LIBRARIES="`( for i in $LIBRARIES ; do echo $i ; done ) | sort -u`"

# install needed libraries
echo $ECHO_N "installing libraries...$ECHO_C"
for i in $LIBARIES
do
  cp "$i" "$ROOT/lib"
  chmod 755 "$ROOT/lib/`basename $i`"
  echo $ECHO_N " `basename $i`$ECHO_C"
done
echo "."

# checking for unknown libraries
for i in $ROOT/lib/*
do
  found=0
  for j in $LIBARIES
  do
    if [ `basename $i` = `basename $j` ]
    then
      found=1
    fi
  done
  if [ $found -eq 0 ]
  then
    echo "WARNING: extra (unknown) file found: $i" >&2
  fi
done

# populate /dev (need root privileges for this)
echo $ECHO_N "creating $ROOT/dev devices... $ECHO_C"
DEVICES="null zero"
if (cd /dev ; tar chpf - $DEVICES) | \
   ( cd "$ROOT/dev" ; tar xpf - > /dev/null 2>&1 )
then
  # check if we can use the devices
  if ( echo TEST > "$ROOT/dev/null" && \
       echo TEST > "$ROOT/dev/zero" )  2> /dev/null
  then
    echo "done."
  else
    echo "FAILED (unable to use devices)"
  fi
else
  echo "FAILED."
fi

# update /etc/passwd
echo $ECHO_N "adding users to $ROOT/etc/passwd...$ECHO_C"
for i in $USERS
do
  if grep "^$i:" /etc/passwd > /dev/null 2>&1
  then
    if grep "^$i:" "$ROOT/etc/passwd" > /dev/null 2>&1
    then
      :
    else
      # take uname:x:uid:gid:fullname:/:shell from /etc/passwd
      sed -n \
        's|^'$i':[^:]*:\([^:]*\):\([^:]*\):\([^:]*\):[^:]*:\([^:]*\)$|'$i':x:\1:\2:\3:/:\4|p' \
        < /etc/passwd \
        >> "$ROOT/etc/passwd"
      echo $ECHO_N " $i$ECHO_C"
    fi
  fi
done
echo "."

# check users in $ROOT/etc/passwd
save_IFS="$IFS"; IFS=":"
cat "$ROOT/etc/passwd" | while read i pass uid gid fullname home shell
do
  IFS="$save_IFS"
  if grep "^$i:" /etc/passwd > /dev/null 2>&1
  then
    grep "^$i:[^:]*:$uid:$gid:" /etc/passwd > /dev/null 2>&1 || \
      echo "WARNING: user $i in $ROOT/etc/passwd does not match the one in /etc/passwd" >&2
  else
    echo "WARNING: user $i in $ROOT/etc/passwd does not exist in /etc/passwd" >&2
  fi
  [ -d "$ROOT/$home" ] || \
    echo "WARNING: home directory of user $i in $ROOT/etc/passwd does not exist inside $ROOT" >&2
  IFS=":"
done
IFS="$save_IFS"

# check if all users in repositories are in $ROOT/etc/passwd
for repos in `find "$ROOT" \( -name tmp -prune \) -o \( -name 'CVSROOT' -print \) 2> /dev/null`
do
  if [ -r "$repos/passwd" ]
  then
    sed -n 's/^[^:]*:[^:]*:\(.*\)$/\1/p;s/^\([^:]*\):[^:]*$/\1/p' < "$repos/passwd" \
      | while read usr
    do
      if grep "^$usr:" "$ROOT/etc/passwd" > /dev/null 2>&1
      then
        :
      else
        if grep "^$usr:" "/etc/passwd" > /dev/null 2>&1
        then
          echo "adding user $usr (referenced in $repos/passwd) to $ROOT/etc/passwd"
          # take uname:x:uid:gid:fullname:/:shell from /etc/passwd
          sed -n \
            's|^'$usr':[^:]*:\([^:]*\):\([^:]*\):\([^:]*\):[^:]*:\([^:]*\)$|'$usr':x:\1:\2:\3:/:\4|p' \
            < /etc/passwd \
            >> "$ROOT/etc/passwd"
        else
          echo "WARNING: system user $usr is referenced in $repos/passwd but not in $ROOT/etc/passwd or /etc/passwd" >&2
        fi
      fi
    done
  else
    echo "WARNING: no passwd file in $repos" >&2
  fi
done

# check if every user in a repository passwd file is mapped to cvsd
if [ -r "$CONFIGFILE" ]
then
  uid=`sed -n 's/^ *Uid *\([^ ]*\) *$/\1/p' < "$CONFIGFILE"`
  if [ "x$uid" != "x" ] && [ "x$uid" != "xroot" ] && [ "x$uid" != "x0" ]
  then
    for repos in `find "$ROOT" -name 'CVSROOT' 2> /dev/null`
    do
      if [ -r "$repos/passwd" ]
      then
        if sed 's/^[^:]*:[^:]*:'$uid'//' < "$repos/passwd" | \
           grep '^..*$' > /dev/null 2> /dev/null
        then
          echo "WARNING: not all users in $repos/passwd are mapped to system user $uid" >&2
        fi
      fi
    done
  fi
fi

# for systems with strange password files (OpenBSD)
if [ -r /etc/master.passwd ] && [ -r /etc/pwd.db ] && [ -x /usr/sbin/pwd_mkdb ]
then
  echo $ECHO_N "making $ROOT/etc/pwd.db...$ECHO_C"
  # convert /etc/passwd to /etc/master.passwd
  sed 's|\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\):\([^:]*\)|\1:\2:\3:\4::0:0:\5:\6:\7|' \
    < "$ROOT/etc/passwd" > "$ROOT/etc/master.passwd"
  /usr/sbin/pwd_mkdb -p -d "$ROOT/etc" "$ROOT/etc/master.passwd"
  echo "done."
fi

# TODO: maybe create some /etc/group for systems that requires it (even OpenBSD doesn't)

# clean /tmp
TMPREAPER="`which tmpreaper 2> /dev/null`"
if [ -x "$TMPREAPER" ]
then
  echo $ECHO_N "cleaning $ROOT/tmp... $ECHO_C"
  $TMPREAPER 7d $ROOT/tmp
  echo "done."
fi

# create directories mentioned in CVSROOT/config:LockDir=...
for repos in `find "$ROOT" -name 'CVSROOT' 2> /dev/null`
do
  if [ -r "$repos/config" ]
  then
    sed -n 's/^ *LockDir *= *\([^ ]*\) *$/\1/p' < "$repos/config" \
      | while read lockdir
    do
      if [ -d "$ROOT/$lockdir" ]
      then
        :
      else
        echo $ECHO_N "creating $ROOT/$lockdir for lockfiles" $ECHO_C
        mkdir -p "$ROOT/$lockdir"
        chmod 1777 "$ROOT/$lockdir"
        echo "done."
      fi
    done
  fi
done

# change owner (need root privileges for this)
echo $ECHO_N "fixing ownership... $ECHO_C"
if chown 0:0 "$ROOT" "$ROOT/tmp" > /dev/null 2>&1 && \
   chown -R 0:0 "$ROOT/bin" "$ROOT/lib" "$ROOT/dev" "$ROOT/etc" "$ROOT/usr"  > /dev/null 2>&1
then
  echo "done."
else
  echo "FAILED."
fi

echo "chrooted system created in $ROOT"
echo "if your cvs binary changes (new version) you should rerun cvsd-buildroot"
