diff options
-rw-r--r-- | LICENSE | 25 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | README.md | 93 | ||||
-rwxr-xr-x | lrpb | 227 |
4 files changed, 356 insertions, 0 deletions
@@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2022, Evgeny Zinoviev +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b5a3e46 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +PREFIX = /usr/local +INSTALL = /usr/bin/env install + +all: + @echo "run \"make install\" to install to $(PREFIX)" + +install: + $(INSTALL) lrpb $(PREFIX)/bin + +.PHONY: all install + diff --git a/README.md b/README.md new file mode 100644 index 0000000..465945c --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ + +# lrpb + +Means "last resort primitive backdoor". + +For when you can't log in via ssh but something's still alive... + + +## Requirements + +- bash 4+ +- GNU tar +- curl or wget +- signify from openbsd +- rsync +- ssh + + +## Installation + + make install + + +## Client side usage + +First, create a pair of keys: + + signify -G -p myname.pub -s myname.sec + +Then write a script you want to be launched on remote side: + + #!/bin/sh + touch ~/helloworld-it-works + +Write lrpb client config to `client.conf`: + + # ssh config + upload_host=mydomain.org + upload_port=22 + upload_user=user + upload_path=/home/user/www/lrpb + + name=myname + + signify_path=/bin/signify + seckey_path=./myname.sec + +Finally, sign and upload it to some remote server you control: + + lrpb upload -c ./client.conf -f ./script.sh + +On remote server, set up some http server (nginx, lighttpd, apache, whatever) that serves directory you upload to. +The script will upload archive name `myname.tar.gz` and it must be accessible by http. + + +## Server side usage + +Copy public key to `/etc/lrpb.pub` on the server (or anywhere you +want, just make sure to set correct path in the config). + +Write lrpb server config and save it to `/etc/lrpb.conf`: + + url="https://mydomain.org/lrpb/" + pubkey_path=/etc/lrpb.pub + name=myname + signify_path=/usr/bin/signify-openbsd + cwd=/var/lrpbfsq + cache_file=/var/lrpbfs/cache + +> Optionally, make `/var/lrpbfs` a tmpfs mountpoint. Add to `/etc/fstab`: +> +> ``` +> tmpfs /var/lrpbfs tmpfs size=1M,mode=1755,uid=1000,gid=1000 0 0 +> ``` +> +> Then mount it: +> ``` +> mount /var/lrpbfs +> ``` + +Test that it works: + + lrpb exec + +Add cron task (`crontab -e`): + + 0,30 * * * * /usr/local/bin/lrpb exec + + +## License + +BSD-2c + @@ -0,0 +1,227 @@ +#!/bin/bash + +set -e +#set -x + +PROGNAME="$0" +BOLD=$(tput bold) +RST=$(tput sgr0) + +file= +config_file="/etc/lrpb.conf" +declare -A config=() +command= + +die() { + echoerr "error: $@" + exit 1 +} + +echoerr() { + >&2 echo "$@" +} + +usage() { + cat <<EOF +usage: $PROGNAME OPTIONS COMMAND + +Options: + -c|--config FILE path to config file (default: $config_file) + -f|--file FILE path to script + -x set -x + +Commands: + upload + exec +EOF + exit 1 +} + +installed() { + command -v "$1" > /dev/null + return $? +} + +download() { + local source="$1" + local target="$2" + + if installed curl; then + curl -f -s -o "$target" "$source" + elif installed wget; then + wget -q -O "$target" "$source" + else + die "neither curl nor wget found, can't proceed" + fi +} + +signify() { + "${config[signify_path]}" "$@" +} + +read_config() { + local n=0 + local failed= + + while read line; do + n=$(( n+1 )) + + # skip empty lines or comments + if [ -z "$line" ] || [[ "$line" =~ ^#.* ]]; then + continue + fi + + if [[ $line = *"="* ]]; then + config[${line%%=*}]=${line#*=} + else + echoerr "config: invalid line $n" + failed=1 + fi + done < <(cat "$config_file") + + [ -z "$failed" ] || exit 1 +} + +check_config() { + local failed= + + for key in $1; do + if [ -z "${config[$key]}" ]; then + echoerr "config: ${BOLD}${key}${RST} is missing" + failed=1 + fi + done + + [ -z "$failed" ] || exit 1 +} + +delete_unpacked_script() { + local path= + for f in script.sh script.sh.sig; do + path="${config[cwd]}/$f" + if [ -f "$path" ]; then rm "$path"; fi + done +} + +do_upload() { + local file="$1" + local dir=$(mktemp -d) + + pushd "$dir" >/dev/null + cp "$file" . + + local bname="$(basename "$file")" + + signify -S -s "${config[seckey_path]}" -m "$bname" || \ + die "signify failed: $?" + + local archive="${config[name]}.tar.gz" + + tar --owner=0 --group=0 \ + --transform="flags=r;s|$bname|script.sh|" \ + --transform="flags=r;s|$bname.sig|script.sh.sig|" \ + -czf "$archive" "$bname" "$bname.sig" >/dev/null + + rsync -p --chmod=u+rw,g+r,o+r -e "ssh -p${config[upload_port]}" \ + $archive \ + "${config[upload_user]}"@"${config[upload_host]}":"${config[upload_path]}" || \ + die "rsync failed: $?" + + popd >/dev/null + rm -rf "$dir" +} + +do_exec() { + pushd "${config[cwd]}" >/dev/null || die "failed to change to ${config[cwd]}" + + local archive="${config[name]}.tar.gz" + download "${config[url]}$archive" "$archive" || exit 0 + + local checksum + local same=0 + local f + + checksum=$(md5sum "$archive" | awk '{print $1}') + if [ -f "${config[cache_file]}" ] && [ "$(cat "${config[cache_file]}")" = "$checksum" ]; then + same=1 + fi + + if [ "$same" = "0" ]; then + delete_unpacked_script + tar --no-same-owner -xzf "$archive" + + signify -V -p "${config[pubkey_path]}" -m script.sh >/dev/null || die "signify failed: $?" + echo "$checksum" > "${config[cache_file]}" + + chmod +x script.sh + sh script.sh || echoerr "script.sh exited with $?" + else + echoerr "archive have not changed, skipping" + fi + + rm "$archive" + delete_unpacked_script + + popd >/dev/null +} + +case $BASH_VERSION in + ''|[0-3].*) + die "bash 4.0 is required" + ;; +esac + +[[ $# -lt 1 ]] && usage + +while [[ $# -gt 0 ]]; do + case $1 in + upload|exec) + command="$1" + shift + ;; + + -c|--config) + config_file="$2" + shift; shift + ;; + + -f|--file) + file="$2" + shift; shift + ;; + + -x) + set -x + shift + ;; + + *) + die "unrecognized option $1" + exit 1 + ;; + esac +done + +[ -z "$config_file" ] && die "config is not specied" +[ -f "$config_file" ] || die "config file (${BOLD}${config}${RST}) not found" +read_config + +[ -z "$command" ] && die "command not specified" +case "$command" in + upload) + [ -z "$file" ] && die "file is not specified" + check_config "upload_host upload_port upload_user upload_path name signify_path seckey_path" + do_upload "$(realpath "$file")" + ;; + + exec) + check_config "url name pubkey_path signify_path cwd cache_file" + [ -d "${config[cwd]}" ] || die "invalid ${BOLD}cwd${RST}: no such directory" + do_exec + ;; + + *) + die "invalid command '$command'" + ;; +esac + |