here are the scripts i wrote to make this easier. these really were made
for my own use, but i hope others may find them useful. i would be
interested to know if anyone else actually does find them useful. would
also be glad to know of any errors/problems/things that can go wrong i
didn't think of.


the first one (jail_new) creates a new jail (and possibly the user).
the second one (jail_pkgadd) adds a package and its dependencies to an
existing jail. they are expected to be in the same directory (jail_new
cannot add packages (-p) otherwise).

to relate to my earlier examples:

$ jail_new -tu _inmate:_chaingang /home/jail

will create the jail in /home/jail and also the user _inmate and group
_chaingang. this case it will be just be a regular shell account (just
chrooted).

$ jail_new -t _inmate:_chaingang /home/jail

will create the jail, but will not create the user:group.


a real case:

$ jail_new -tux -k /home/null/.ssh/id_rsa.pub -p w3m,feh:/usr/release/pkg 
browse /home/browse w3m -B

this command sets up the terminal (-t) and X (-x) in a directory (here
/home/browse), creates a user (-u) (in this case 'browse'), uses the given
key file (-k) for the authorized keys, installs the packages (-p) w3m and
feh (and all of their dependencies) from directory /usr/release/pkg, and
sets 'w3m -B' to run automatically via ForceCommand in sshd_config.

this is the equivalent of:

$ jail_new -tux -k /home/null/.ssh/id_rsa.pub browse /home/browse w3m -B
$ jail_pkgadd -p /usr/release/pkg w3m /home/browse
$ jail_pkgadd -p /usr/release/pkg feh /home/browse

if you want bzip2 in there as well, you can always add it later:

$ jail_pkgadd -p /usr/release/pkg bzip2 /home/browse

or, if PKG_PATH is set (and not remote) you can omit -p

$ jail_pkgadd bzip2 /home/browse

if PKG_PATH is set, and is remote, you need:

$ jail_pkgadd -r bzip2 /home/browse

(note: will only allow a single directory for PKG_PATH)

this can be used by running:

$ Xephyr :1 & env DISPLAY=:1 ssh -X browse@localhost

(side note: w3m runs 'display' to display an image, so i create a symlink
to feh to view images)


another case:

$ jail_new -tuxr -k /home/null/.ssh/id_rsa.pub -p 
xpdf:scp://null@node02/usr/release/pkg pdf /home/pdf

you need to specify -r (remote) directly to use remote pkg src.

which is the equivalent of:

$ jail_new -tux -k /home/null/.ssh/id_rsa.pub pdf /home/pdf
$ jail_pkgadd -r -p scp://null@node02/usr/release/pkg xpdf /home/pdf

which can be used:

$ cp test.pdf /home/pdf/tmp
$ Xephyr :1 & env DISPLAY=:1 ssh -X browse@localhost xpdf -fullscreen 
/tmp/test.pdf

(in this case it may be best not to use ForceCommand, since you may want to
open multiple documents.)


WARNING use at your own peril. if you can't read the scripts, you probably
shouldn't use them, and then i am certain there are other glaring security
flaws you need to know about. i include these because it is a dull pain in
the ass to do this manually, and hopefully someone may get some use out of
them.

other than that, do with it what you wish.

they are as fool-proof as i could make them, so that i don't shoot myself in
the foot accidently (and i have been around long enough to have done that a
few times, even while being careful). but you never know.

jail_new:
--------------------------------------------------
#!/bin/ksh
USAGE="${0##*/} [-jrtux] [-k authkeys] [-p pkg[,pkg2...][:pkgpath]] 
user[:group] path [cmd [args ...]]"
[[ "$1" = -h ]] && { echo "USAGE $USAGE"; return 0; }

#-t sets PermitTTY and copies files for term
#-x sets X11Forwarding and copies files for X (fonts,xauth)
#-u creates user; fails if user exists
#-j joins group; needed to join existing group
#-p pkg[,pkg2...][:pkgpath]
#-r allows remote pkg access
#uses existing PKG_PATH
#pkgpath arg overrides PKG_PATH

#only accepts a lone pkgpath

PATH=/sbin:/bin:/usr/sbin:/usr/bin

echov() {  eval echo \"\$$1\";  }
isemptyv() {  eval [ \${#$1} -eq 0 ];  }
notemptyv() {  eval [ \${#$1} -gt 0 ];  }
alias xt='set -o xtrace'
alias xt-='set +o xtrace'

if [ $(id -u) -eq 0 ];then
  echo "ERR cannot run as root"
  return 1
fi

_sshd_config=/etc/ssh/sshd_config
_sshd_config_tmp=/tmp/sshd_config

trap "rm -f $_sshd_config_tmp" 0 2

#for convenience
_fontdir=/usr/X11R6/lib/X11/fonts
_terminfo=/usr/share/misc/terminfo.db
_termcap=/usr/share/misc/termcap

_do_x=no
_do_tty=no
_do_useradd=
_do_joingrp=
_do_remote=
_authkeys=
_pkg=
_pkgpath=
_userhome=/home/cell
while getopts :jrtuxk:p: _opt;do
  case "$_opt" in
    j) _do_joingrp=yes ;;
    r) _do_remote=-r ;;
    t) _do_tty=yes ;;
    u) _do_useradd=yes ;;
    x) _do_x=yes ;;
    k) _authkeys=$OPTARG
       if [ ! -f "$_authkeys" ];then
         echo "ERR no such file '$_authkeys'"
         return 1
       fi
       ;;
    p) _pkg=$OPTARG
       if [[ "$_pkg" = *:* ]];then
         _pkgpath=${_pkg#*:}
         _pkg=${_pkg%%:*}
         export PKG_PATH=$_pkgpath
       else
         if isemptyv PKG_PATH;then
           echo "ERR PKG_PATH not set and none given"
           return 1
         fi
         _pkgpath=$PKG_PATH
       fi
       if [[ "$_pkgpath" = *://* ]];then
         if isemptyv _do_remote;then
           echo "ERR pkgpath is remote; use -r"
           return 1
         fi
         if [[ "$_pkgpath" = *:*:* ]];then
           echo "ERR invalid pkgpath '$_pkgpath'; only one dir permitted"
           return 1
         fi
       else
         if [[ "$_pkgpath" = *:* ]];then
           echo "ERR invalid pkgpath '$_pkgpath'; only one dir permitted"
           return 1
         fi
       fi
       if isemptyv _do_remote && [ ! -d "$_pkgpath" ];then
         echo "ERR no such dir '$_pkgpath'"
         return 1
       fi
       _cmd_jailpkgadd=$(dirname $0)/jail_pkgadd
       if [ ! -f $_cmd_jailpkgadd ];then
         echo "ERR cannot locate jail_pkgadd script"
         return 1
       fi
       ;;
    :) echo "ERR no arg for '-$OPTARG'"; return 1 ;;
    *) echo "ERR invalid arg '-$OPTARG'"; return 1 ;;
  esac
done
shift $((OPTIND-1))

_user=$1
_jailroot=$2
_cmd=$3

if isemptyv _user;then
  echo "ERR user not given"
  return 1
fi
if isemptyv _jailroot;then
  echo "ERR jailroot path not given"
  return 1
fi
shift 2
if [ -e $_jailroot ];then
  echo "ERR $_jailroot already exists"
  return 1
fi

_authkeysfile=$_jailroot.authkeys
_dbdir=$_jailroot/var/db/pkg

#check/setup user/group
getfreeuid() {
  local _uid=1000
  local _uid_list="$(awk -F : '{printf $3"\n"}' /etc/passwd)"
  local _gid_list="$(awk -F : '{printf $3"\n"}' /etc/group)"
  while [ $_uid -gt 900 ];do
    _uid=$((_uid-1))
    echov _uid_list | grep -q "^$_uid$" && continue
    echov _gid_list | grep -q "^$_uid$" && continue
    break
  done
  [ $_uid -lt 900 ] && return 1
  echo $_uid
}

_group=${_user#*:}
_user=${_user%:*}
if grep -q ^$_user: /etc/passwd;then
  if notemptyv _do_useradd;then
    echo "ERR user '$_user' already exists"
    return 1
  fi
  _userhome=$(grep ^$_user: /etc/passwd | awk -F : '{printf $4":"$6}')
  _group=${_userhome%:*}
  _userhome=${_userhome#*:}
  _jailhome=$_jailroot$_userhome
else
  if isemptyv _do_useradd;then
    echo "ERR user '$_user' does not exist; use -u to create"
    return 1
  fi
  _uid=$(getfreeuid) || {  echo "ERR no uid free";  return 1;  }
  if [[ "$_user" = "$_group" ]];then
    _group="=uid"
  else
    if grep -q ^$_group: /etc/group;then
      if isemptyv _do_joingrp;then
        echo "ERR group '$_group' exists; use -j to join"
        return 1
      fi
    else
      if notemptyv _do_joingrp;then
        echo "ERR group '$_group' doesn't exist; cannot use -j to join"
        return 1
      fi
      if ! sudo groupadd -g $_uid $_group;then
        echo "ERR failed to add group '$_group' gid '$_uid'"
        return 1
      fi
    fi fi
  if ! sudo useradd -u $_uid -g $_group -c "public jail" \
                    -s /bin/sh -d $_userhome $_user 2>&1
  then
    echo "ERR useradd failed"
    return 1
  fi | grep -v "Warning: home"
  _group=$_uid
fi

_jailhome=$_jailroot$_userhome

sudo mkdir -p $_jailhome || return 1


cat >$_sshd_config_tmp <<CONF
Match User $_user
    ChrootDirectory $_jailroot
    AuthorizedKeysFile $_authkeysfile
    X11Forwarding $_do_x
    PermitTTY $_do_tty
CONF
set -f
notemptyv _cmd && echo "    ForceCommand $*" >>$_sshd_config_tmp
set +f


#setup filesystem

isemptyv _authkeys && _authkeys=/dev/null
sudo sh -c "cat $_authkeys >$_authkeysfile" || return 1
sudo chown $_user:$_group $_jailhome $_authkeysfile || return 1

sudo mkdir -p $_jailroot/dev || return 1
if [[ "$_do_tty" = yes ]];then
  sudo mknod -m 644 $_jailroot/dev/arandom c 45 3 || return 1
  sudo mknod -m 666 $_jailroot/dev/null    c 2  2 || return 1
  sudo mknod -m 666 $_jailroot/dev/stderr  c 22 2 || return 1
  sudo mknod -m 666 $_jailroot/dev/stdin   c 22 0 || return 1
  sudo mknod -m 666 $_jailroot/dev/stdout  c 22 1 || return 1
  sudo mknod -m 666 $_jailroot/dev/tty     c 1  0 || return 1
  sudo mknod -m 666 $_jailroot/dev/zero    c 2 12 || return 1
  #sudo mknod -m 666 $_jailroot/dev/ttyp0    c 5 0 || return 1
  #might want to add ttypN to make sshd happy
fi

sudo mkdir -p $_jailroot/{bin,sbin,etc,tmp,var/run} $_dbdir
sudo mkdir -p $_jailroot/usr/{libexec,{,X11R6,local}/{bin,lib}}
sudo cp -p /sbin/ldconfig $_jailroot/sbin
sudo cp -p /usr/libexec/ld.so $_jailroot/usr/libexec
sudo chroot $_jailroot ldconfig /usr/{,X11R6,local}/lib
sudo chmod 1777 $_jailroot/tmp

sudo cp -p /bin/sh $_jailroot/bin

sudo cp -pR /etc/{hosts,resolv.conf,localtime} $_jailroot/etc


if [[ "$_do_x" = yes ]];then
  _xauth=/usr/X11R6/bin/xauth
  sudo cp -p /bin/sh $_jailroot/bin
  sudo cp -p $_xauth $_jailroot$_xauth
  for _lib in $(ldd $_xauth | grep ' rlib ' | awk '{printf $7" "}');do
    [ -f $_jailroot/$_lib ] && continue
    sudo cp -p $_lib $_jailroot/$_lib || return 1
  done
  sudo cp -rp /etc/fonts $_jailroot/etc || return 1
  sudo mkdir -p $_jailroot/${_fontdir%/*} || return 1
  sudo cp -rp $_fontdir $_jailroot/$_fontdir || return 1
fi

if [[ "$_do_tty" = yes ]];then
  #for terminal eg w3m
  sudo mkdir -p $_jailroot${_terminfo%/*} || return 1
  sudo cp -p $_terminfo $_termcap $_jailroot${_terminfo%/*} || return 1
  #may still need /etc/termcap
  #sudo cp -R /etc/termcap $_jailroot/etc || return 1
fi

echo "WARNING will update /etc/ssh/sshd_config with the following:\n"
cat $_sshd_config_tmp
echo
read _update_ssh?"update sshd_config and restart sshd? (type 'yes') "
if notemptyv _update_ssh && [[ "$_update_ssh" = yes ]];then
  sudo sh -c "cat $_sshd_config_tmp >>$_sshd_config"
  [ -f /var/run/sshd.pid ] && sudo kill -1 $(</var/run/sshd.pid)
fi
 
if notemptyv _pkg;then
  IFS=,
  for _thispkg in $_pkg;do
    $_cmd_jailpkgadd $_do_remote -p $_pkgpath $_thispkg $_jailroot
  done
fi

--------------------------------------------------


jail_pkgadd:
--------------------------------------------------
#!/bin/ksh
USAGE="${0##*/} [-r] [-p pkg_path] pkg [jailroot]"
[[ "$1" = -h ]] && { echo "USAGE $USAGE"; return 0; }

PATH=/sbin:/bin:/usr/sbin:/usr/bin

#a few functions of my own
#these are easier to type, and save me debugging time due to common things
#like quotes, $, etc
#takes a variable name as an arg, hence 'v' suffix
echov() {  eval echo \"\$$1\";  }
isemptyv() {  eval [ \${#$1} -eq 0 ];  }
notemptyv() {  eval [ \${#$1} -gt 0 ];  }
alias usage='echo "USAGE $USAGE"'
alias xt='set -o xtrace'
alias xt-='set +o xtrace'

if [ $(id -u) -eq 0 ];then
  echo "ERR should not run as root"
  return 1
fi
if [[ "$(id -nG)" != *wsrc* ]];then
  echo "ERR user not in wsrc; needed for pkg_add"
  return 1
fi

#_pkg_dir read from PKG_PATH
#-p overrides PKG_PATH
#if neither set, uses default

_do_remote=
_pkg_dir=$PKG_PATH
while getopts :rp: _opt;do
  case "$_opt" in
    r) _do_remote=true ;;
    p) _pkg_dir=$OPTARG
       ;;
    :) echo "ERR no arg for '-$OPTARG'";  return 1 ;;
    *) echo "ERR invalid arg '-$OPTARG'";  return 1 ;;
  esac
done
shift $((OPTIND-1))
_pkg_dir=${_pkg_dir:-/usr/ports/packages/`arch -s`/all}
_pkg_dbdir=/var/db/pkg
export PKG_PATH="$_pkg_dir"

if [[ "$_pkg_dir" = *://* ]];then
  if isemptyv _do_remote;then
    echo "ERR invalid pkg dir; need -r for remote"
    return 1
  fi
  if [[ "$_pkg_dir" = *:*:* ]];then
    echo "ERR invalid pkg dir; only one dir allowed"
    return 1
  fi
else
  _do_remote=
  if [[ "$_pkg_dir" = *:* ]];then
    echo "ERR invalid pkg dir; only one dir allowed"
    return 1
  fi
  if [ ! -d "$_pkg_dir" ];then
    echo "ERR pkg dir '$_pkg_dir' does not exist"
    return 1
  fi
fi

_pkg=$1
_jailroot=$2
if isemptyv _pkg;then
  echo "ERR no package given"
  usage
  return 1
fi
_jailroot=${_jailroot:-/home/jail}
if [ ! -d "$_jailroot" ];then
  echo "ERR no such dir '$_jailroot'"
  return 1
fi
_jail_dbdir=$_jailroot$_pkg_dbdir

#set to /tmp to ignore installed package and use PKG_PATH
_pkg_file=$(PKG_DBDIR=/tmp pkg_info -c $_pkg | sed -n 's!^Information 
for.*/!!p')
if isemptyv _pkg_file;then
  echo "ERR could not find pkg '$_pkg'"
  return 1
fi

##get list of required packages
##only works if /usr/ports is in sync with packages in PKG_PATH
#_pkgpath=$(pkg_info -P $_pkg | grep ^[a-z])
#if isemptyv _pkgpath;then
#  echo "ERR cannot find port '$_pkg'"
#  return 1
#fi
#_pkgpath=/usr/ports/$_pkgpath
#cd $_pkgpath
#_pkg_list=$(make print-run-depends) || return $?
#_pkg_list=$(echov _pkg_list | sed -e 's/.*"\([^"]*\)".*/\1 /' -e 's/ /.tgz /g')
#_pkg_list="${_pkg_list}$_pkg_file"

_depend_list=
function get_depends {
  local _pkg=$1 _depend
  for _depend in $(pkg_info -f $_pkg | sed -n 's/^@depend.*://p');do
    echo $_pkg $_depend
    [[ "$_depend_list" = *\ $_depend\ * ]] && continue
    get_depends $_depend
    _depend_list="$_depend_list $_depend "
  done
}
_pkg_list="$(get_depends ${_pkg_file%.tgz} | tsort -r)"
isemptyv _pkg_list && _pkg_list=${_pkg_file%.tgz}


#if local go thru list to make sure pkgs are all there
if isemptyv _do_remote;then
  for _pkg in $_pkg_list;do
    [ -f $_pkg_dir/$_pkg.tgz ] && continue
    echo "ERR pkg '$_pkg' not found"
    return 1
  done
fi

#keep list of libraries to avoid repeating find
_lib_tree=$(find /usr/{,X11R6}/lib -type f)

for _pkg in $_pkg_list;do
  #check if package already installed
  [ -d $_jail_dbdir/$_pkg ] && continue

  #get list of @wantlib's that need copying
  _lib_list=$(pkg_info -f $_pkg | sed -n 's/^@wantlib //p')

  #get list of binaries in the pkg to check for more dependencies
  _bin_list=$(pkg_info -f $_pkg | sed -n 's/^@bin //p')

  #hopefully this handles all the quicks for @lib
  for _lib in $_lib_list;do
    _lib_relpath=
    if [[ "$_lib" = */* ]];then
      _lib_relpath=${_lib%/*}
      _lib=${_lib##*/}
    fi
    _lib_ver_min=${_lib##*.}
    _lib_name=${_lib%.*}
    _lib_ver_maj=${_lib_name##*.}
    _lib_name=${_lib_name%.*}
    _lib_fn=lib$_lib_name.so.$_lib_ver_maj.$_lib_ver_min

    if isemptyv _lib_relpath;then
      #ASSUME want libraries only -maxdepth 1
      _lib_pathn=$(find $_jailroot/usr/{,X11R6,local}/lib -maxdepth 1 -name 
$_lib_fn)
    else
      _lib_pathn=$_jailroot/usr/local/$_lib_relpath/$_lib_fn
    fi
    [ -f "$_lib_pathn" ] && continue
    #lib not in jail
    _lib_pathn=$(echov _lib_tree | grep $_lib_fn$)
    if isemptyv _lib_pathn || notemptyv _lib_relpath;then
      #never copy from /usr/local
      echo "WARN could not find $_lib_fn"
    else
      sudo cp -p $_lib_pathn $_jailroot/$_lib_pathn
    fi
  done

  #ignore unsigned packages (-D unsigned) since i built them
  #sudo env PKG_DBDIR=$_jail_dbdir pkg_add -m -B $_jailroot -D unsigned $_pkg
  sudo env PKG_DBDIR=$_jail_dbdir pkg_add -m -B $_jailroot $_pkg

  #rebuild ld.so.hints with new libraries
  sudo chroot $_jailroot ldconfig /usr/{,X11R6,local}/lib

  #go thru binaries in package and get additional dependencies
  (cd $_jailroot/usr/local
   for _bin in $_bin_list;do
     for _lib in $(ldd $_bin | grep ' rlib ' | grep -v /usr/local/lib | awk 
'{printf $7" "}');do
        [ -f $_jailroot/$_lib ] || sudo cp -p $_lib $_jailroot/$_lib
     done
   done)
done

#env PKG_DBDIR=$_dbdir pkg_add -B /home/chroot $_pkg
#env PKG_DBDIR=$_dbdir pkg_delete -B /home/chroot $_pkg

Reply via email to