#!/bin/sh
set -e

# Name: add_user
# Last update: 2014-05-21
#
# Create a new unprivileged user account and set up her ssh access, optionally
# add her to specified groups, and finally commit the changes with etckeeper.
# Usage:
# $ add_user -h
# $ add_user -H
# $ add_user -I
# $ sudo add_user -l loginname -k /path/to/ssh_key.pub [-G GROUP1[,GROUP2[...]]] [-i]

PROGNAME="${0##*/}"
NICKNAME=
SSHPUB_K=
GIVEINFO=
ONLYINFO=
X_GROUPS=
ADDED_IN=

error() { #{{{
    echo "${PROGNAME}: ERROR: $@" >&2
} #}}}
usage() { #{{{
    cat <<EOF
${PROGNAME}: create a new unprivileged user account and set up her ssh access.
Usage:
    ${PROGNAME} -h|-H|-I
    ${PROGNAME} [OPTIONS] -l loginname -k /path/to/ssh_key.pub
EOF
} #}}}
help_page() { #{{{
    cat <<EOF
${PROGNAME}: create a new unprivileged user account and set up her ssh access.
Optionally add the new user to a comma separated list of groups, and finally
commit changes in /etc with etckeeper if it is installed.

Usage:
    ${PROGNAME} -h
    ${PROGNAME} -H
    ${PROGNAME} -I
    ${PROGNAME} -l loginname -k /path/to/ssh_key.pub [-G GROUP1[,GROUP2[...]]] [-i]

Options:
    -h              Display help page and exit
    -H              Display extended help page and exit
    -I              Display info about the host and exit (see also -i)
    -l loginname    Set the loginname of the new account (mandatory)
    -k ssh_key.pub  Copy the given SSH public key in the HOME of the new user
                    (as ~/.ssh/authorized_keys) (mandatory)
    -G GROUPS       Add the new user to the given comma separated list of
                    groups (optional)
    -i              At the end of the process, display the info to send to the
                    new user to allow her to login to her new account (fqdn
                    hostname, IP addresse(s), fingerprints of the server's SSH
                    public keys) (optional)
    -x              Set -x (xtrace) for debugging
EOF
} #}}}
extended_help_page() { #{{{
    help_page
    cat <<EOF

NOTES:
This script is a wrapper around the adduser command whose the configuration
file is bypassed by using the '--conf /dev/null' option, to force 'adduser'
to behave with default parameters. Additionally, to avoid unexpected results
if a custom script /usr/local/sbin/adduser.local already exists, '${PROGNAME}'
will fail before doing anything.

To reduce interactivity, the new account will be created with an empty GECOS
field. Its password, entered twice, will be immediately marked as "expire",
so the new user will have to reset her password at her first login.

Verbosity level cannot be set.

Exit codes:
    0   success
    1   internal, non managed error (set -e)
    2   bad option
    3   missing mandatory argument (login name or ssh key)
    4   bad argument
    10  /usr/local/sbin/adduser.local exists
    11  you are not root user
    12  /etc git repository is not clean
    20  no stdin available

See also: adduser(8), etckeeper(1), passwd(1)
EOF
} #}}}
display_info() { #{{{
    cat <<EOF
- the hostname:
	$(hostname --long)

- the IP address(es):
EOF

    hostname -I |
    sed -e 's,^\s*\|\s*$,,g; s,\s\+,\n,g' |
    sed -e '/^\(10\|192\.168\)\./d' |
    sed -e 's,^,\t,'

    cat <<EOF

- the SSH-server keys fingerprints:
EOF

    for k in /etc/ssh/ssh_host_*_key.pub; do
        ssh-keygen -lf ${k} | awk '{print "\t"$2,$4}'
    done
} #}}}

# Parse commandline options and arguments {{{
while getopts :G:hHiIk:l:x opt; do
    case "${opt}" in
        h)
            help_page
            exit 0
            ;;
        H)
            extended_help_page
            exit 0
            ;;
        i)
            GIVEINFO="true"
            ;;
        I)
            ONLYINFO="true"
            ;;
        G)
            X_GROUPS="${OPTARG}"
            ;;
        k)
            SSHPUB_K="${OPTARG}"
            ;;
        l)
            NICKNAME="${OPTARG}"
            ;;
        x)
            set -x
            ;;
        '?')
            error "invalid option (-${OPTARG})"
            usage >&2
            exit 2
            ;;
    esac
done
# }}}
# Display info about the host {{{
if [ "${ONLYINFO}" = "true" ]; then
    display_info
    exit
fi
# }}}

# Perform basic checks before running {{{
# Basic check: login name of the new account is mandatory {{{
if [ -z "${NICKNAME}" ]; then
    error "new user login name is missing."
    usage >&2
    exit 3
elif grep -q "^${NICKNAME}:" /etc/passwd; then
    error "user \"${NICKNAME}\" already exists."
    exit 4
elif grep -q "^${NICKNAME}:" /etc/group; then
    error "group \"${NICKNAME}\" already exists."
    exit 4
fi
# }}}
# Basic check: valid SSH public key is mandatory {{{
if [ -z "${SSHPUB_K}" ]; then
    error "you must provide a public ssh key."
    usage >&2
    exit 3
elif [ ! -f "${SSHPUB_K}" ]; then
    error "\"${SSHPUB_K}\" file does not exist."
    exit 4
elif ! head -n1 ${SSHPUB_K} | grep -qE '^(ssh-(dss|rsa|ed25519)|ecdsa-sha2-nistp(256|384|521)) '; then
    error "${SSHPUB_K} is not an OpenSSH public key."
    exit 4
fi
# }}}
# Basic check: avoid conflicts with adduser.local {{{
if [ -x /usr/local/sbin/adduser.local ]; then
    error "there is already a custom script (adduser.local)."
    exit 10
fi
# }}}
# Basic check: only root can do that {{{
if [ "$(id -u)" != "0" ]; then
    error "you must be root to run me."
    exit 11
fi
# }}}
# Basic check: avoid to mix actions in etckeeper's history {{{
if [ -x /usr/bin/etckeeper ]; then
    if etckeeper unclean; then
        error "please commit previous changes in /etc before running me."
        etckeeper vcs status
        exit 12
    fi
fi
# }}}
# Basic check: we need an interactive terminal {{{
if ! test -t 0; then
    error "no standard input file descriptor available."
    exit 20
fi
# }}}
# }}}

### RUN NOW ###

# Quietly create account with empty GECOS field (only prompt for password,
# twice):
echo "Creating user account for ${NICKNAME}..."
if adduser --conf /dev/null --quiet --gecos "" ${NICKNAME}; then
    # Force the user to change her password the first time she logs in:
    passwd --quiet --expire ${NICKNAME}

    # Parse /etc/password to query relevant info:
    getent passwd ${NICKNAME} |
    (
        IFS=':';
        read NICKNAME X NICKUID NICKGID GECOS NICKHOME NICKSHELL

        # Add her public ssh key into her .ssh directory, and set
        # ownership/permissions:
        mkdir -p --mode=0700 ${NICKHOME}/.ssh
        cp ${SSHPUB_K} ${NICKHOME}/.ssh/authorized_keys
        chmod 600 ${NICKHOME}/.ssh/authorized_keys
        #chmod g+s ${NICKHOME}
        chown -R ${NICKUID}:${NICKGID} ${NICKHOME}
    )
    # Add the new user to extra groups; silently ignore unexisting groups:
    for xg in $(IFS=','; echo ${X_GROUPS}); do
        grep -q "^${xg}:" /etc/group || continue
        if adduser --quiet ${NICKNAME} ${xg}; then
            ADDED_IN="${ADDED_IN:+${ADDED_IN}, }${xg}"
        fi
    done
else
    # Usual cases of failure are:
    # - invalid login name (does not match a regex, etc.)
    # - first and second passwords don't match
    # At this step, nothing has been done; so in case of failure, there is
    # nothing to undo.
    RET=$?
    echo "${PROGNAME}: failed to create ${NICKNAME} account." >&2
    exit ${RET}
fi

# Allow the new user to connect to the ssh server if AllowUsers or AllowGroups
# is defined in the ssh server configuration file.
# /!\ Here we don't care about DenyUsers and DenyGroups directives, and also we
#     even don't care about matching patterns: if the loginname is not included
#     verbatim in the Allow(Users|Groups) list, just append it.
if grep -q '^\s*AllowUsers\s' /etc/ssh/sshd_config; then
    if ! grep '^\s*AllowUsers\s' /etc/ssh/sshd_config | grep -q "\s${NICKNAME}\(\s\|$\)"; then
        echo
        echo "Adding ${NICKNAME} to the list of users allowed to log in via SSH."
        sed -i "s,^\s*AllowUsers\s.*,& ${NICKNAME}," /etc/ssh/sshd_config
        # And don't forget to restart the server:
        service ssh restart
    fi
elif grep -q '^\s*AllowGroups\s' /etc/ssh/sshd_config; then
    ALLOWED="false"
    for xg in $(groups ${NICKNAME} | cut -d\: -f2); do
        if grep '^\sAllowGroups\s' /etc/ssh/sshd_config | grep -q "\s${xg}\(\s\|$\)"; then
            ALLOWED="true"
            break
        fi
    done
    if [ "${ALLOWED}" = "false" ]; then
        echo
        echo "Adding ${NICKNAME} to the list of groups allowed to log in via SSH."
        sed -i "s,^\s*AllowGroups\s.*,& ${NICKNAME}," /etc/ssh/sshd_config
        # And don't forget to restart the server:
        service ssh restart
    fi
fi

# Commit the changes:
if [ -x /usr/bin/etckeeper ]; then
    etckeeper commit "Create user account and enable ssh access for ${NICKNAME}${ADDED_IN:+ (+ in group(s): ${ADDED_IN})}."
    echo
    etckeeper vcs log -1
fi

if [ "${GIVEINFO}" != "true" ]; then
    echo
    echo "${PROGNAME}: done."
    exit
fi

# Display things to do now:
cat <<EOF

The account of the new user ${NICKNAME} has been successfully created and
its access to $(hostname)'s ssh service is now enabled.

The next step is to send an encrypted e-mail to the owner of this account,
by specifying:

- the temporary password (you should know it)
- the login name:
	${NICKNAME}

EOF

display_info

echo
echo "${PROGNAME}: done."

# vim: et ts=4 sts=4 sw=4
