#!/bin/sh
#
# Sets permissions for device files and programs and takes care of cgroups
#


export PATH="/bin:/sbin:/usr/bin:/usr/sbin"

[ "$(whoami)" == "root" ] || exit 1


#-----------------------------------------------------------------
# Make sure 'root' and 'eichrecht accounts are disabled
# on a locked system

disable_accounts()
{
    if [ $is_eichrecht ]
    then
        # Disabling root and eichrecht account

        sed -i 's/^root:\(.*\)\/bin\/sh/root:\1\/bin\/false/' /etc/passwd
        passwd -d root > /dev/null
        passwd -l root > /dev/null

        passwd -d eichrecht > /dev/null
        passwd -l eichrecht > /dev/null

        rm /etc/init.d/S50dropbear 2>/dev/null
    fi
}

#-----------------------------------------------------------------
# Set permissions on device files

set_device_permissions()
{
    echo -n "Checking device file permissions: "

    # Give read and write permissions to the serial ports to members of the
    # 'dialout' group. If eichrecht is active ttyS1 (for communication with
    # the modbus meter) only may be accessed by the 'eichrecht' account.

    chown root:dialout /dev/ttyS*
    chmod ug=rw,o=     /dev/ttyS*

    if  [ $is_eichrecht ]
    then
        chown eichrecht:eichrecht /dev/ttyS1
        chmod u=rw,go= /dev/ttyS1
    fi

    # Write permission to the device file for the module controlling
    # power must belong exclusively to the 'eichrecht' user and group
    # when eichrecht is enabled, otherwise it's controlled by 'charge'.
    # Read permissions can be given to all as that doesn't allow to
    # start or stop charging.

    if  [ $is_eichrecht ]
    then
        chown eichrecht:eichrecht /dev/ebee_powerctrl
    else
        chown root:charge /dev/ebee_powerctrl
    fi
    chmod ug=rw,o=r           /dev/ebee_powerctrl

    # Give read and write permissions to ADCs and timers to 'charge' group.
    # They're needed for car state detection and PWM creation.

    chown root:charge /dev/adc
    chmod ug=rw,o=    /dev/adc

    chown root:charge /dev/tc*
    chmod ug=rw,o=    /dev/tc*

    # 'charge' group also needs access to the I2C and SPI (for PIC24
    # or emergency opener communication) as well as control RF comms.

    chown root:charge /dev/i2c-0
    chmod ug=rw,o=    /dev/i2c-0

    chown root:charge /dev/spidev0.* 2> /dev/null
    chmod ug=rw,o=    /dev/spidev0.* 2> /dev/null

    chown root:charge /dev/rfkill 2> /dev/null
    chmod ug=rw,o=    /dev/rfkill 2> /dev/null

    # ebee variant is readable for everyone. It can't be written to.

    chmod ugo=r /dev/ebee_variant

    # 'charge' group must be able to control the hardware watchdog by
    # writing to its device file.

    chown root:charge /dev/watchdog
    chmod ug=rw,o=    /dev/watchdog

    # And to the device file of the phase monitoring module (if present)

    if [ -c /dev/ebee_phasemon ]
    then
        chown root:charge /dev/ebee_phasemon
        chmod u=rw,g=r,o= /dev/ebee_phasemon
    fi

    # Give all read permissions to the  bootloader (mtd0 and
    # mtd7), DTB (mtd1 and mtd2) and kernel (mtd4 and mtd5)
    # partitions.

    for p in /dev/mtd[0-57] /dev/mtd[0-57]ro
    do
        if [ -c $p ]
        then
            chmod go+r $p
        fi
    done

    # Let no-one try to poison the random generator

    if [ $is_eichrecht ]
    then
        chmod ug=rw,o=r /dev/random
        chmod ug=rw,o=r /dev/urandom
    fi

    echo "done"
}

#-----------------------------------------------------------------
# Set up the cgroup all processes of the 'charge' user are to
# be members of and restrict their resource usage.

set_cgroups()
{
    # Nothing to be done if kernel doesn't provide cgroups

    if [ ! -e /proc/cgroups ]
    then
        return
    fi

    echo -n "Setting up cgroups: "

    # Mount /sys/fs/cgroup if not already done

    mountpoint -q /sys/fs/cgroup \
    || mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup

    # Start all cgroup controllers and add 'charge' cgroup

    for d in $(awk '/^[^#]/ {print $1}' /proc/cgroups)
    do
        mkdir -p /sys/fs/cgroup/$d
        mountpoint -q /sys/fs/cgroup/$d \
        || (mount -n -t cgroup -o $d cgroup /sys/fs/cgroup/$d \
            || rmdir /sys/fs/cgroup/$d || true)
        mkdir -p /sys/fs/cgroup/$d/charge
    done

    # Give cgroup 'charge' only half of the default CPU shares of 1024 -
    # this makes sure processes not belonging to that group get twice as
    # much of the CPU time if they need it.

    echo 512 > /sys/fs/cgroup/cpu/charge/cpu.shares

    # The system has about 56 MB memory, thus with the following settings
    # at least 6 MB always should be left for processes not belonging to
    # the 'charge' cgroup.

    echo 45M > /sys/fs/cgroup/memory/charge/memory.soft_limit_in_bytes
    echo 50M > /sys/fs/cgroup/memory/charge/memory.limit_in_bytes

    # Make sure there never are more than 512 processes at once in the
    # 'charge' cgroup.

    echo 512 > /sys/fs/cgroup/pids/charge/pids.max

    # Set weight for block-IO for the 'charge' cgroup to half the default
    # of 1000, so processes not belonging to that cgroup get proportionally
    # more bandwidth to the NAND when they need it.

    echo 500 > /sys/fs/cgroup/blkio/charge/blkio.weight

    echo "done"
}

#-----------------------------------------------------------------
# Set permissions on home directory of the 'eichrecht' account

setup_eichrecht()
{
    echo -n "Checking file and directory permissions: "

    # The home directory of the 'eichrecht' user must be as well-protected as
    # possible, nothing but the 'eichrecht' account and group can acccess it,
    # at least under Eichrecht conditions. We temporarily remove the immutable
    # attribute on both the configuration files as otherwise the chown and
    # chmod commands would fail.

    chown -R eichrecht:eichrecht /home/eichrecht
    if [ $is_eichrecht ]
    then
        chmod -R go= /home/eichrecht

        # The evmd configuration files may never be changed, so they're
        # owned by 'root' but still readable by 'eichrecht'.

        chown root:eichrecht /home/eichrecht/type.conf.json
        chmod ug=r,o=        /home/eichrecht/type.conf.json

        chown root:eichrecht /home/eichrecht/device.conf.json
        chmod ug=r,o=        /home/eichrecht/device.conf.json
    else
        chmod go=rx   /home/eichrecht
        chmod -R go-w /home/eichrecht
    fi
}

#-----------------------------------------------------------------
# Some init scripts below /etc must belong to the users 'charge'
# or 'eichrecht', and a few corrections in /etc need to be made

setup_etc()
{
    # The directory for init scripts are to be owned by
    # the account that runs the scripts there-in.

    chmod -R u=rwX,go=rX         /etc/init.d

    chown -R eichrecht:eichrecht /etc/init.d/eichrecht
    chmod u=rwx,go=rx            /etc/init.d/eichrecht

    chown -R charge:charge       /etc/init.d/charge
    chmod u=rwx,go=rx            /etc/init.d/charge

    # These files may only be modified by root
    # (and need to go into the system hash)

    for f in /etc/passwd          \
             /etc/group           \
             /etc/mdev.action     \
             /etc/mdev.conf       \
             /etc/inittab         \
             /etc/fstab           \
             /etc/profile
    do
        chmod u=rwX,go=rX $f
    done

    chmod -R ugo=rwx /etc/dbus-1

    chmod u=rwx,go=rx  /etc/profile.d
    for f in $(ls /etc/profile.d)
    do
        chmod u=rw,go=r /etc/profile.d/$f
    done

    # The shadow file must also be readable by 'charge' on locked
    # systems as otherwise the ssh daemon, then running as 'charge',
    # couldn't do password logins. Also, system_hash wouldn't be
    # able to check that the 'root' and 'eichrecht' account have
    # disabled passwords. But that's no cause for concern - on a
    # locked system the only user with a password is 'charge',
    # and whoever logs in as 'charge' already knows it.

    if [ $is_eichrecht ]
    then
        chown root:charge /etc/shadow
        chmod u=rw,g=r,o= /etc/shadow
    else
        chmod u=rw,go=    /etc/shadow
    fi

    # File for caching entropy data over reboots is only to be
    # read and modified by root.

    if [ -e /etc/random-seed ]
    then
        chmod u=rw,go=  /etc/random-seed
    fi

    # The directory for the Eichrecht 'locked' and 'clean' files belong
    # to 'eichrecht', so it can delete the 'clean' file and readable by
    # everyone.

    chown root:eichrecht  /etc/eichrecht
    chmod ug=rwx,o=rx     /etc/eichrecht

    if [ $is_eichrecht ]
    then
        chown root:root /etc/eichrecht/eichrecht.locked
        chmod ugo=r     /etc/eichrecht/eichrecht.locked

        if [ -e /etc/eichrecht/eichrecht.clean ]
        then
            chown eichrecht:eichrecht /etc/eichrecht/eichrecht.clean
            chmod ug=rw,o=r           /etc/eichrecht/eichrecht.clean
        fi
    fi
}

#-----------------------------------------------------------------

set_dac_and_owner()
{
    chown -R charge:charge /opt/ebee

    # We allow 'charge' to update firmware being transferred onto USB sticks
    # or WLAN dongles etc.

    chown -R root:charge /lib/firmware
    chmod -R g+w         /lib/firmware

    # The reboot utility needs to be available to 'charge'. It's a copy of
    # busybox because busybox itself might be overwritten during an
    # update and thus lose its suid bit. In that case a reboot would
    # be impossible if it was a mere symlink to busybox.

    cp /bin/busybox /sbin/reboot

    # These programs require suid root, no matter what kind of
    # system this is running on.

    for p in /bin/busybox             \
             /usr/sbin/gpio_x         \
             /usr/sbin/pppd           \
             /usr/sbin/system_update  \
             /sbin/reboot
    do
        chmod u+s $p
    done
}

#-----------------------------------------------------------------

# param1: absolute location in FS
# param2: "dir" or "file"

ensure_no_symlink_and_grant_charge()
{
    if [ -L $1 ]
    then
        rm -f $1
        if [ "file" == "$2" ]
        then
            touch $1
        else
            mkdir -p $1
        fi
    fi

    if [ "file" == "$2" ]
    then
        chown root:charge $1
        chmod ug=rw,o=r $1
    else
        chown -R root:charge $1
        chmod -R ug=rwX,o=rX $1
    fi
}

set_access_exceptions()
{
    # conf files
    ensure_no_symlink_and_grant_charge /etc/dropbear dir
    ensure_no_symlink_and_grant_charge /etc/MAC dir
    ensure_no_symlink_and_grant_charge /etc/network/interfaces file
    ensure_no_symlink_and_grant_charge /etc/resolv.conf file
    ensure_no_symlink_and_grant_charge /etc/dnsmasq.conf file
    ensure_no_symlink_and_grant_charge /etc/hostname file
    ensure_no_symlink_and_grant_charge /etc/ebee_timezone file

    # scripts, which are definitely called as charge
    ensure_no_symlink_and_grant_charge /etc/mdev.action.charge file
    chmod +x /etc/mdev.action.charge
    ensure_no_symlink_and_grant_charge /etc/ifplugd/ifplugd.action file
    chmod +x /etc/ifplugd/ifplugd.action

    # few charge-executables we still may want to update in future
    chown root:charge /usr/sbin/er_lock
    chown root:charge /usr/bin/opkg
}

#-----------------------------------------------------------------

start_permissions()
{
    [ -c /dev/ubi0 ] && is_ubi="true"

    # if we are coming back from tweak execution, remove the noevmd marker
    rm -f /home/charge/noevmd

    if [ $is_ubi ]
    then
        # Check if we're on a system locked down for Eichrecht by checking
        # for the presence of the file '/etc/eichrecht/eichrecht.locked
        # and that this file is owned by root alone.

        if    [ -e /etc/eichrecht/eichrecht.locked ]       \
           && [ $(ls -l /etc/eichrecht/eichrecht.locked    \
                | awk '{print $3":"$4}') == "root:root" ]
        then
            echo "Detected eichrecht.locked"
            is_eichrecht="true"
        fi

        . /etc/init.d/s00permissions.ubi
    else
        echo "Non-UBI FS detected, eichrecht not supported"
        rm -f /etc/eichrecht/eichrecht.locked
        rm -f /etc/eichrecht/eichrecht.clean

        . /etc/init.d/s00permissions.jffs2
    fi

    # Start out with hardened permissions

    for d in /bin   \
             /sbin  \
             /usr   \
             /etc   \
             /lib
    do
        chown -R root:root $d
        chmod -R ug=rwX,o=rX $d
    done

    disable_accounts
    set_device_permissions
    set_file_permissions
    set_prog_permissions
    set_cgroups
    set_access_exceptions
}

#-----------------------------------------------------------------

case "$1" in
    start)
        start_permissions
        ;;
    stop)
        ;;
    restart|reload)
        "$0" start
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac


# Local variables:
# tab-width: 4
# indent-tabs-mode: nil
# End:
