#!/bin/bash

#
# lxc: linux Container library

# Authors:
# Daniel Lezcano <daniel.lezcano@free.fr>

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This library 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
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

LXC_MAPPED_UID=
LXC_MAPPED_GID=

# Make sure the usual locations are in PATH
export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin

am_in_userns() {
    [ -e /proc/self/uid_map ] || { echo no; return; }
    [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || { echo yes; return; }
    line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map)
    [ "$line" = "0 0 4294967295" ] && { echo no; return; }
    echo yes
}

in_userns=0
[ $(am_in_userns) = "yes" ] && in_userns=1

install_busybox()
{
    rootfs=$1
    name=$2
    res=0
    tree="\
$rootfs/selinux \
$rootfs/dev \
$rootfs/home \
$rootfs/root \
$rootfs/etc \
$rootfs/etc/init.d \
$rootfs/bin \
$rootfs/usr/bin \
$rootfs/sbin \
$rootfs/usr/sbin \
$rootfs/proc \
$rootfs/sys \
$rootfs/mnt \
$rootfs/tmp \
$rootfs/var/log \
$rootfs/usr/share/udhcpc \
$rootfs/dev/pts \
$rootfs/dev/shm \
$rootfs/lib \
$rootfs/usr/lib \
$rootfs/lib64 \
$rootfs/usr/lib64"

    mkdir -p $tree || return 1
    chmod 755 $tree || return 1

    pushd $rootfs/dev > /dev/null || return 1

    # minimal devices needed for busybox
    if [ $in_userns -eq 1 ]; then
        for dev in tty console tty0 tty1 ram0 null urandom; do
            echo "lxc.mount.entry = /dev/$dev dev/$dev    none bind,optional,create=file 0 0" >> $path/config
        done
    else
        mknod -m 666 tty c 5 0       || res=1
        mknod -m 666 console c 5 1   || res=1
        mknod -m 666 tty0 c 4 0      || res=1
        mknod -m 666 tty1 c 4 0      || res=1
        mknod -m 666 tty5 c 4 0      || res=1
        mknod -m 600 ram0 b 1 0      || res=1
        mknod -m 666 null c 1 3      || res=1
        mknod -m 666 zero c 1 5      || res=1
        mknod -m 666 urandom c 1 9   || res=1
    fi

    popd > /dev/null

    # root user defined
    cat <<EOF >> $rootfs/etc/passwd
root:x:0:0:root:/root:/bin/sh
EOF

    cat <<EOF >> $rootfs/etc/group
root:x:0:root
EOF

    # mount everything
    cat <<EOF >> $rootfs/etc/init.d/rcS
#!/bin/sh
/bin/syslogd
/bin/mount -a
/bin/udhcpc
EOF

    # executable
    chmod 744 $rootfs/etc/init.d/rcS || return 1

    # launch rcS first then make a console available
    # and propose a shell on the tty, the last one is
    # not needed
    cat <<EOF >> $rootfs/etc/inittab
::sysinit:/etc/init.d/rcS
tty1::respawn:/bin/getty -L tty1 115200 vt100
console::askfirst:/bin/sh
EOF
    # writable and readable for other
    chmod 644 $rootfs/etc/inittab || return 1

    cat <<EOF >> $rootfs/usr/share/udhcpc/default.script
#!/bin/sh
case "\$1" in
    deconfig)
        ip addr flush dev \$interface
        ;;

    renew|bound)
        # flush all the routes
        if [ -n "\$router" ]; then
            ip route del default 2> /dev/null
        fi

        # check broadcast
        if [ -n "\$broadcast" ]; then
            broadcast="broadcast \$broadcast"
        fi

        # add a new ip address
        ip addr add \$ip/\$mask \$broadcast dev \$interface

        if [ -n "\$router" ]; then
            ip route add default via \$router dev \$interface
        fi

        [ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf
        for i in \$dns ; do
            echo nameserver \$i >> /etc/resolv.conf
        done
        ;;
esac
exit 0
EOF

    chmod 744 $rootfs/usr/share/udhcpc/default.script

    return $res
}

configure_busybox()
{
    rootfs=$1

    which busybox >/dev/null 2>&1

    if [ $? -ne 0 ]; then
        echo "busybox executable is not accessible"
        return 1
    fi

    file -L $(which busybox) | grep -q "statically linked"
    if [ $? -ne 0 ]; then
        echo "warning : busybox is not statically linked."
        echo "warning : The template script may not correctly"
        echo "warning : setup the container environment."
    fi

    # copy busybox in the rootfs
    cp $(which busybox) $rootfs/bin
    if [ $? -ne 0 ]; then
        echo "failed to copy busybox in the rootfs"
        return 1
    fi

    # symlink busybox for the commands it supports
    # it would be nice to just use "chroot $rootfs busybox --install -s /bin"
    # but that only works right in a chroot with busybox >= 1.19.0
    pushd $rootfs/bin > /dev/null || return 1
    ./busybox --help | grep 'Currently defined functions:' -A300 | \
      grep -v 'Currently defined functions:' | tr , '\n' | \
      xargs -n1 ln -s busybox
    popd > /dev/null

    # relink /sbin/init
    ln $rootfs/bin/busybox $rootfs/sbin/init

    # passwd exec must be setuid
    chmod +s $rootfs/bin/passwd
    touch $rootfs/etc/shadow

    # setting passwd for root
    CHPASSWD_FILE=$rootfs/root/chpasswd.sh

    cat <<EOF >$CHPASSWD_FILE
echo "setting root password to \"root\""

mount -n --bind /lib $rootfs/lib
if [ \$? -ne 0 ]; then
    echo "Failed bind-mounting /lib at $rootfs/lib"
    exit 1
fi

chroot $rootfs chpasswd <<EOFF 2>/dev/null
root:root
EOFF


if [ \$? -ne 0 ]; then
    echo "Failed to change root password"
    exit 1
fi

umount $rootfs/lib

EOF

    lxc-unshare -s MOUNT -- /bin/sh < $CHPASSWD_FILE
    rm $CHPASSWD_FILE

    # add ssh functionality if dropbear package available on host
    which dropbear >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        # copy dropbear binary
        cp $(which dropbear) $rootfs/usr/sbin
        if [ $? -ne 0 ]; then
            echo "Failed to copy dropbear in the rootfs"
            return 1
        fi

        # make symlinks to various ssh utilities
        utils="\
            $rootfs/usr/bin/dbclient \
            $rootfs/usr/bin/scp \
            $rootfs/usr/bin/ssh \
            $rootfs/usr/sbin/dropbearkey \
            $rootfs/usr/sbin/dropbearconvert \
        "
        echo $utils | xargs -n1 ln -s /usr/sbin/dropbear

        # add necessary config files
        mkdir $rootfs/etc/dropbear
        dropbearkey -t rsa -f $rootfs/etc/dropbear/dropbear_rsa_host_key > /dev/null 2>&1
        dropbearkey -t dss -f $rootfs/etc/dropbear/dropbear_dss_host_key > /dev/null 2>&1

        echo "'dropbear' ssh utility installed"
    fi

    return 0
}

copy_configuration()
{
    path=$1
    rootfs=$2
    name=$3

grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config
cat <<EOF >> $path/config
lxc.haltsignal = SIGUSR1
lxc.utsname = $name
lxc.tty = 1
lxc.pts = 1
lxc.cap.drop = sys_module mac_admin mac_override sys_time

# When using LXC with apparmor, uncomment the next line to run unconfined:
#lxc.aa_profile = unconfined

lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed
lxc.mount.entry = shm /dev/shm tmpfs defaults 0 0
EOF

    libdirs="\
        lib \
        usr/lib \
        lib64 \
        usr/lib64"

    for dir in $libdirs; do
        if [ -d "/$dir" ] && [ -d "$rootfs/$dir" ]; then
            echo "lxc.mount.entry = /$dir $dir none ro,bind 0 0" >> $path/config
        fi
    done
    echo "lxc.mount.entry = /sys/kernel/security sys/kernel/security none ro,bind,optional 0 0" >>$path/config
}

remap_userns()
{
    path=$1

    if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then
        chown $LXC_MAPPED_UID $path/config >/dev/null 2>&1
        chown -R root $path/rootfs >/dev/null 2>&1
    fi

    if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then
        chgrp $LXC_MAPPED_GID $path/config >/dev/null 2>&1
        chgrp -R root $path/rootfs >/dev/null 2>&1
    fi
}

usage()
{
    cat <<EOF
$1 -h|--help -p|--path=<path>
EOF
    return 0
}

options=$(getopt -o hp:n: -l help,rootfs:,path:,name:,mapped-uid:,mapped-gid: -- "$@")
if [ $? -ne 0 ]; then
    usage $(basename $0)
    exit 1
fi
eval set -- "$options"

while true
do
    case "$1" in
        -h|--help)      usage $0 && exit 0;;
        -p|--path)      path=$2; shift 2;;
        --rootfs)       rootfs=$2; shift 2;;
        -n|--name)      name=$2; shift 2;;
        --mapped-uid)   LXC_MAPPED_UID=$2; shift 2;;
        --mapped-gid)   LXC_MAPPED_GID=$2; shift 2;;
        --)             shift 1; break ;;
        *)              break ;;
    esac
done

if [ "$(id -u)" != "0" ]; then
    echo "This script should be run as 'root'"
    exit 1
fi

if [ -z "$path" ]; then
    echo "'path' parameter is required"
    exit 1
fi

# detect rootfs
config="$path/config"
if [ -z "$rootfs" ]; then
    if grep -q '^lxc.rootfs' $config 2>/dev/null ; then
        rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' $config)
    else
        rootfs=$path/rootfs
    fi
fi

install_busybox $rootfs $name
if [ $? -ne 0 ]; then
    echo "failed to install busybox's rootfs"
    exit 1
fi

configure_busybox $rootfs
if [ $? -ne 0 ]; then
    echo "failed to configure busybox template"
    exit 1
fi

copy_configuration $path $rootfs $name
if [ $? -ne 0 ]; then
    echo "failed to write configuration file"
    exit 1
fi

remap_userns $path
if [ $? -ne 0 ]; then
    echo "failed to remap files to user"
    exit 1
fi
