Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- VERBOSITY=0
- TEMP_D=""
- DEFAULT_USER="backdoor"
- error() { echo "$@" 1>&2; }
- Usage() {
- cat <<EOF
- Usage: ${0##*/} [ options ] target
- add a 'backdoor' user to a image or filesystem at 'target'
- options:
- --import-id U use 'ssh-import-id' to get ssh public keys
- may be used more than once.
- --force required to operate on / filesystem
- --password P set password P, implies --password-auth
- --password-auth enable password auth
- --pubkeys F add public keys from file 'F'
- default: ~/.ssh/id_rsa.pub unless --password
- or --import-id specified
- --user U use user 'U' (default: '${DEFAULT_USER}')
- EOF
- }
- bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
- cleanup() {
- [ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
- }
- debug() {
- local level=${1}; shift;
- [ "${level}" -gt "${VERBOSITY}" ] && return
- error "${@}"
- }
- mod_sshd_bool() {
- local cfg="$1" kn="$2" target="$3" dry=${4:-false}
- local ws=$' \t' msg=""
- local match="^\([#]\{0,1\}\)[#$ws]*$kn\([$ws]\+\)\(yes\|no\)"
- local cur="" hsh="#"
- cur=$(sed -n "s/$match/\1\3/p" "$cfg") ||
- { error "failed to read $cfg"; return 1; }
- if [ -n "$cur" ]; then
- case "$cur" in
- "#$target") msg="uncommenting, '$target' line";;
- "#*") msg="uncommenting, changing '${cur#$hsh}' to '$target'";;
- "$target") msg="nochange";;
- "*") msg="changing '$cur' to '$target'";;
- esac
- if [ "$msg" = "nochange" ]; then
- debug 1 "no change to $cfg necessary"
- else
- debug 1 "updating $cfg: $msg"
- $dry && return
- sed -i "s/$match/$kn\2${target}/" "$cfg" ||
- { error "failed to update $cfg"; return 1; }
- fi
- else
- debug 1 "appending entry for '$kn $target' to $cfg"
- $dry && return
- echo "$kn $target" >> "$cfg" ||
- { error "failed to append entry to $cfg"; return 1; }
- fi
- return 0
- }
- test_mod_sshd_cfg() {
- local kn="PasswordAuthentication"
- echo "#$kn yes" > f1
- echo "#$kn no" > f2
- echo "$kn yes" > f3
- echo "$kn no" > f4
- : > f5
- for f in f1 f2 f3 f4 f5; do
- mod_sshd_bool "$f" PasswordAuthentication yes true
- done
- }
- add_group_ent() {
- local group="$1" gid="$2" fgroup="$3" dry="${4:-false}"
- local grent="$group:x:$gid:"
- if grep -q "^$group:" "$fgroup"; then
- debug 1 "remove $group from group file"
- $dry || sed -i "/^$group:/d" "$fgroup" ||
- { error "failed to remove user from group"; return 1; }
- fi
- debug 1 "append entry to group: $grent"
- if ! $dry; then
- echo "$grent" >> "$fgroup" ||
- { error "failed to update group file"; return 1; }
- fi
- return 0
- }
- add_passwd_ent() {
- local user="$1" uid="$2" gid="$3" home="$4" fpasswd="$5" dry=${6:-false}
- if grep -q "^$user:" "$fpasswd"; then
- debug 1 "remove $user from password file"
- $dry || sed -i "/^$user:/d" "$fpasswd" ||
- { error "failed to remove user from password file"; return 1; }
- fi
- local pwent="$user:x:$uid:$gid:backdoor:$home:/bin/bash"
- debug 1 "append entry to passwd: $pwent"
- if ! $dry; then
- echo "$pwent" >> "$fpasswd" ||
- { error "failed to update passwd file"; return 1; }
- fi
- }
- encrypt_pass() {
- local pass="$1" fmt="${2-\$6\$}"
- enc=$(echo "$pass" |
- perl -e '
- $p=<STDIN>; chomp($p);
- $salt = join "", map { (q(a)..q(z))[rand(26)] } 1 .. 8;
- if (${ARGV[0]}) { $salt = "${ARGV[0]}$salt\$"; }
- print crypt($p, "$salt") . "\n";' "$fmt") || return
- [ -n "${enc}" ] && [ -z "${fmt}" -o "${enc#${fmt}}" != "${fmt}" ] &&
- _RET="$enc"
- }
- add_shadow_ent() {
- local user="$1" pass="$2" fshadow="$3" dry="$4"
- local encrypt_pre="\$6\$" shent="" encpass="" pwchange=""
- # if input was '$6$' format, just use it verbatum
- if [ "${pass#${encrypt_pre}}" != "${pass}" ]; then
- debug 1 "using encrypted password from cmdline"
- encpass="$pass"
- else
- encrypt_pass "$pass" && encpass="$_RET" ||
- { error "failed to encrypt password"; return 1; }
- fi
- # pwchange is number of days since 1970
- pwchange=$(($(date +"(%Y-1970)*365 + 10#%j")))
- shent="$user:$encpass:$pwchange:0:99999:7:::"
- if grep -q "^$user:" "$fshadow"; then
- debug 1 "remove $user from shadow file"
- $dry || sed -i "/^$user:/d" "$fshadow" ||
- { error "failed to remove user from shadow"; return 1; }
- fi
- debug 1 "append entry to shadow: $shent"
- if ! $dry; then
- echo "$shent" >> "$fshadow" ||
- { error "failed to update shadow file"; return 1; }
- fi
- return 0
- }
- add_sudo_ent() {
- local user="$1" mp="$2" dry="$3"
- local target="/etc/sudoers.d/99-$user"
- local ent="$user ALL=(ALL) NOPASSWD:ALL"
- local start="#BACKDOOR_START_${user}"
- local end="#BACKDOOR_end_${user}"
- local content=$(printf "%s\n%s\n%s\n" "$start" "$ent" "$end")
- if [ -f "$mp/etc/lsb-release" ] &&
- grep -i lucid -q "$mp/etc/lsb-release"; then
- target="/etc/sudoers"
- debug 2 "$mp does not seem to support sudoers.d"
- debug 1 "add sudoers ($mp,$target): $ent"
- if grep -q "^$start$" "$mp/$target"; then
- debug 2 "removing $user entry from $target"
- if ! $dry; then
- sed -i "/^${start}$/,/^${end}$/d" "$target" ||
- { error "failed update $target"; return 1; }
- fi
- fi
- if ! $dry; then
- ( umask 226 && echo "$content" >> "$mp/$target" ) ||
- { error "failed to add sudoers entry to $target"; return 1; }
- fi
- else
- debug 1 "add sudoers ($mp,$target): $ent"
- if ! $dry; then
- rm -f "$mp/$target" &&
- ( umask 226 && echo "$content" > "$mp/$target" ) ||
- { error "failed to add sudoers entry to $target"; return 1; }
- fi
- fi
- }
- add_user() {
- local user="$1" pass="$2" uid="$3" gid="$4" home="$5"
- local rootd="$6" dry="${7:-false}"
- local fpasswd="$rootd/etc/passwd" fshadow="$rootd/etc/shadow"
- local fgroup="$rootd/etc/group"
- [ -f "$fpasswd" ] || { error "no password file"; return 1; }
- [ -f "$fshadow" ] || { error "no shadow file"; return 1; }
- [ -f "$fgroup" ] || { error "no group file"; return 1; }
- local group="$user" f="" t=""
- add_passwd_ent "$user" "$uid" "$gid" "$home" "$fpasswd" "$dry" || return 1
- add_group_ent "$group" "$gid" "$fgroup" "$dry" || return 1
- add_shadow_ent "$user" "$pass" "$fshadow" "$dry" || return 1
- debug 1 "create $rootd/home/$user"
- if ! $dry; then
- mkdir -p "$rootd/home/$user" &&
- chown $uid:$gid "$rootd/home/$user" ||
- { error "failed to make home dir"; return 1; }
- for f in "$rootd/etc/skel/".* "$rootd/etc/skel/"*; do
- [ -e "$f" ] || continue
- t="$rootd/home/$user/${f##*/}"
- [ ! -e "$t" ] || continue
- cp -a "$f" "$t" && chown -R "$uid:$gid" "$t" ||
- { error "failed to copy $f to $t"; return 1; }
- done
- fi
- }
- add_user_keys() {
- local keys="$1" dir="$2" ownership="$3" dry="${4:-false}"
- debug 1 "add ssh keys to $dir with $ownership"
- $dry && return
- mkdir -p "$dir" &&
- cp "$keys" "$dir/authorized_keys" &&
- chmod 600 "$dir/authorized_keys" &&
- chown "$ownership" "$dir" "$dir/authorized_keys" &&
- chmod 700 "$dir" ||
- { error "failed to add user keys"; return 1; }
- if [ $VERBOSITY -ge 1 ]; then
- debug 1 "added ssh keys:"
- sed "s,^,| ," "$keys"
- fi
- }
- gen_ssh_keys() {
- local mp="$1" types="${2:-rsa}" dry="${3:-false}"
- local ktype="" file="" ftmpl="/etc/ssh/ssh_host_%s_key" out=""
- for ktype in $types; do
- file=${ftmpl//%s/$ktype}
- if [ -f "$mp/$file" ]; then
- debug 2 "existing key for $mp/$file"
- continue
- fi
- debug 1 "ssh-keygen -t $ktype -N '' -f '$file' -C backdoor"
- $dry && continue
- out=$(ssh-keygen -t "$ktype" -N '' -f "$mp/$file" -C backdoor 2>&1) || {
- error "$out"
- error "failed generate keytype $ktype";
- return 1;
- }
- out=$(ssh-keygen -l -f "$mp/$file")
- debug 1 "$out"
- done
- }
- apply_changes() {
- local mp="$1" user="$2" password="$3" pwauth="$4" pubkeys="$5"
- local dry="${6:-false}"
- local home="/home/$user" key=""
- local uid="9999" gid="9999"
- local sshcfg="$mp/etc/ssh/sshd_config"
- [ -f "$sshcfg" ] ||
- { error "$sshcfg did no exist"; return 1; }
- key="PubkeyAuthentication"
- mod_sshd_bool "$sshcfg" "$key" "yes" "$dry" ||
- { error "failed to set $key to yes"; return 1; }
- if $pwauth; then
- key="PasswordAuthentication"
- mod_sshd_bool "$sshcfg" "$key" "yes" "$dry" ||
- { error "failed to set $key to yes"; return 1; }
- fi
- gen_ssh_keys "$mp" "rsa" "$dry" || return 1
- add_user "$user" "$password" "$uid" "$gid" "$home" "$mp" "$dry" || return 1
- [ -z "$pubkeys" ] ||
- add_user_keys "$pubkeys" "$mp/$home/.ssh" "$uid:$gid" || return 1
- add_sudo_ent "$user" "$mp" "$dry" || return 1
- }
- main() {
- short_opts="hv"
- long_opts="help,dry-run,force,import-id:,password:,password-auth,pubkeys:,user:,verbose"
- getopt_out=$(getopt --name "${0##*/}" \
- --options "${short_opts}" --long "${long_opts}" -- "$@") &&
- eval set -- "${getopt_out}" ||
- bad_Usage
- local user="" password="" pwauth=false pubkeys="" import_ids="" dry=false
- local target="" pkfile="" force=false
- user="${DEFAULT_USER}"
- local args=""
- args=( "$@" )
- unset args[${#args[@]}-1]
- while [ $# -ne 0 ]; do
- cur=${1}; next=${2};
- case "$cur" in
- -h|--help) Usage ; exit 0;;
- --dry-run) dry=true;;
- --force) force=true;;
- --import-id)
- import_ids="${import_ids:+${import_ids} }$next";
- shift;;
- --password) password=$next; shift;;
- --password-auth) pwauth=true;;
- --pubkeys) pubkeys=$next; shift;;
- --user) user=$next; shift;;
- -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
- --) shift; break;;
- esac
- shift;
- done
- [ $# -ne 0 ] || { bad_Usage "must provide image"; return 1; }
- [ $# -ge 2 ] && { bad_Usage "too many arguments: $*"; return 1; }
- [ "$(id -u)" = "0" ] ||
- { error "sorry, must be root"; return 1; }
- target="$1"
- if [ -d "$target" ]; then
- if [ "$target" -ef "/" ] && ! $force; then
- error "you must specify --force to operate on /"
- return 1
- fi
- elif [ -f "$target" ]; then
- local vopt="" mcu="mount-callback-umount"
- local mic="mount-image-callback"
- if [ ${VERBOSITY} -ge 2 ]; then
- vopt="-v"
- fi
- if command -v "$mic" >/dev/null 2>&1; then
- :
- elif [ -x "${0%/*}/$mcu" ]; then
- PATH="${0%/*}:$PATH"
- mic="$mcu"
- else
- error "No '$mcu' or '$mic' in PATH"
- return 1
- fi
- exec "$mic" $vopt -- "$target" "$0" "${args[@]}" _MOUNTPOINT_
- else
- [ -f "$target" ] || { error "$target: not a file"; return 1; }
- fi
- if [ -n "$password" ] && ! which perl >/dev/null 2>&1; then
- { error "perl required for making password"; return 1; }
- pwauth=true
- fi
- { [ -z "$import_ids" ] || which ssh-import-id >/dev/null 2>&1; } ||
- { error "you do not have ssh-import-id"; return 1; }
- TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
- { error "failed to make tempdir"; return 1; }
- trap cleanup EXIT
- pkfile="${TEMP_D}/pubkeys"
- if [ -z "$password" -a -z "$pubkeys" -a -z "$import_ids" ]; then
- [ -f ~/.ssh/id_rsa.pub ] || {
- error "must specify one of --password, --pubkeys, --import-id"
- error "either pass an argument or create ~/.ssh/id_rsa.pub"
- return 1
- }
- debug 1 "set pubkeys to ~/.ssh/id_rsa.pub"
- pubkeys=$(echo ~/.ssh/id_rsa.pub)
- fi
- if [ -n "$pubkeys" ]; then
- cp "$pubkeys" "$pkfile" ||
- { error "failed to copy $pubkeys"; return 1; }
- fi
- if [ -n "$import_ids" ]; then
- ssh-import-id --output "$pkfile.i" ${import_ids} &&
- cat "$pkfile.i" >> "$pkfile" ||
- { error "failed to import ssh users: $import_ids"; return 1; }
- fi
- [ -f "$pkfile" ] || pkfile=""
- apply_changes "$target" "$user" "$password" "$pwauth" "$pkfile"
- [ $? -eq 0 ] || { error "failed to apply changes"; return 1; }
- error "added user '$user' to $target"
- [ -n "$password" ] && error "set password to $password."
- $pwauth && error "enabled password auth" ||
- error "did not enable password auth"
- [ -n "$pubkeys" ] && error "added pubkeys from $pubkeys."
- [ -n "$import_ids" ] && error "imported ssh keys for $import_ids"
- return 0
- }
- main "$@"
- # vi: ts=4 noexpandtab
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement