#!/bin/bash

force_install=false

usage() {
	cat >&2 <<EOF
Usage: $0 [-f | -h]
	-f: Force install (skip the pre-install check)
	-h: Show this usage and exit
EOF
}

while [ $# -gt 0 ]; do
	case "$1" in
		-f)
			force_install=true
			;;
		-h)
			usage
			exit 0
			;;
		*)
			usage
			exit 1
			;;
	esac
	shift
done

$force_install || {
#!/bin/sh

VERSION=8.38.19

white='\033[0m'
red='\033[91m'
yellow='\033[93m'
green='\033[92m'
blue='\033[94m'
purple='\033[95m'
cyan='\033[96m'

write_report_to_file=false
scanner_compat=0
agent_compat=0

die() {
    error "$1"
    exit 1
}

config() {
    test -z "$1" && return 0
    case "$1" in
        --no-color)
            no_color
            ;;
        *)
            ;;
    esac
}

no_color() {
white=''
red=''
yellow=''
green=''
blue=''
purple=''
cyan=''
}

# Log output
output=''

# Constants
NOT_COMPAT=-1000
MISSING_OK=0

PROCFS_COMPAT=0
PROCFS_NOT_COMPAT=$NOT_COMPAT
SYSFS_COMPAT=0
SYSFS_NOT_COMPAT=$NOT_COMPAT
PKG_COMPAT=1
PKG_NOT_COMPAT=$MISSING_OK
LOG_AUTH_COMPAT=1
LOG_SECURE_COMPAT=1
LOG_BTMP_COMPAT=1
LOG_WTMP_COMPAT=1
LOG_UTMP_COMPAT=1
LOG_SYSLOG_COMPAT=1
LOG_MESSAGES_COMPAT=1
LOG_BASH_COMPAT=3

UPDATE_COMPAT=0
UPDATE_NOT_COMPAT=$NOT_COMPAT

# Scan requirements
DARWIN_MIN_KERNVER=14.0.0
LINUX_MIN_KERNVER=2.6.23
MIN_PYVER=2.6.2
MAX_PYVER=3.9.100

# Things to check
os=''
kernver=''
pyver=''
pkg=''

if echo -e '' | grep -F -- -e >/dev/null 2>&1; then
    echo_flag=
else
    echo_flag=-e
fi

say() {
    echo $echo_flag "$1"
    append_output "$1"
}

# add_scanner_compat [num]: the more [num], the more compatible
add_scanner_compat() {
    tmp="${1:-0}"
    scanner_compat=$(expr $scanner_compat + $tmp)
    if [ -z "$2" ]; then
        return 0
    fi

    if [ $tmp -lt 0 ]; then
        error "$2"
    elif [ $tmp -eq 0 ]; then
        info "$2"
    else
        good "$2"
    fi
}

add_agent_compat() {
    tmp="${1:-0}"
    agent_compat=$(expr $agent_compat + $tmp)
    if [ -z "$2" ]; then
        return 0
    fi

    if [ $tmp -lt 0 ]; then
        error "$2"
    elif [ $tmp -eq 0 ]; then
        info "$2"
    else
        good "$2"
    fi
}

append_output() {
    if [ -n "$output" ]; then
        output="$output\n$@"
    else
        output="$@"
    fi
}

log() {
    echo $echo_flag "$@"
    append_output "$@"
}

info() {
    say "${blue}[*]${white} $1"
}

good() {
    say "${green}[+]${white} $1"
}

warn() {
    say "${yellow}[*]${white} $1"
}

error() {
    say "${red}[-]${white} $1"
}

fail() {
    error "$1"
    exit 1
}

section() {
    say "${cyan}[$1]${white}"
}

main() {
    echo "Script version: $VERSION"
    check_system
    check_scanner
    check_agent

    output_report
}

check_system() {
    section System

    if [ "$(uname -m)" = "x86_64" ]; then
        add_agent_compat 0 "Arch: x86_64"
    else
        add_agent_compat $NOT_COMPAT "Agent doesn't support $(uname -m)"
    fi

    # kernel version check
    case "$(uname -s)" in
        Darwin)
            kernver=$(uname -r | cut -d- -f1)
            if _is_version_less_than "$kernver" $DARWIN_MIN_KERNVER; then
                add_agent_compat $NOT_COMPAT "Kernel too old: $kernver"
            else
                add_agent_compat 0 "Kernel: $kernver"
            fi
            ;;
        Linux)
            kernver=$(uname -r | cut -d- -f1)
            if _is_version_less_than "$kernver" $LINUX_MIN_KERNVER; then
                add_agent_compat $NOT_COMPAT "Kernel too old: $kernver"
            else
                add_agent_compat 0 "Kernel: $kernver"
            fi
            ;;
    esac

    # OS
    os=$(_get_os)
    if [ $? -ne 0 ]; then
        add_scanner_compat 0 "Not officially supported: $os"
    else
        add_scanner_compat 0 "Officially supported OS: $os"
    fi
    if [ "$(uname -s)" = Linux ]; then
        check_init
        check_libc
    fi

    if [ $(df -P /tmp|awk 'NR==2{print$4}') -gt 102400 ]; then
        add_agent_compat 0 "Enough disk size for /tmp: $(df -Ph /tmp|awk 'NR==2{print$4}')"
    else
        add_agent_compat $NOT_COMPAT "Not enough disk size for /tmp: $(df -Ph /tmp|awk 'NR==2{print$4}') (need 100MB)"
    fi

    if [ $(df -P /var/log|awk 'NR==2{print$4}') -gt 102400 ]; then
        add_agent_compat 0 "Enough disk size for /var/log: $(df -Ph /var/log|awk 'NR==2{print$4}')"
    else
        add_agent_compat $NOT_COMPAT "Not enough disk size for /var/log: $(df -Ph /var/log|awk 'NR==2{print$4}') (need 100MB)"
    fi

    if [ $(df -P /var/lib|awk 'NR==2{print$4}') -gt 409600 ]; then
        add_agent_compat 0 "Enough disk size for /var/lib: $(df -Ph /var/lib|awk 'NR==2{print$4}')"
    else
        add_agent_compat $NOT_COMPAT "Not enough disk size for /var/lib: $(df -Ph /var/lib|awk 'NR==2{print$4}') (need 400MB)"
    fi
}

check_init() {
    _is_container && add_agent_compat 0 "Init system: container" && return 0

    _init=$(_get_init_system)
    test $? -eq 0 && add_agent_compat 0 "Init system: $_init" && return 0
    add_agent_compat $NOT_COMPAT "No compatible init system found"
}

_is_container() {
    egrep '[a-fA-F0-9]{64}$' /proc/self/cpuset >/dev/null 2>&1
}

_get_init_system() {
    # /proc/[pid]/comm (since 2.6.33)
    case "$(cat /proc/1/comm 2>/dev/null||echo init)" in
        systemd)
            test -d /run/systemd/system && echo systemd && return 0
            ;;
        upstart)
            test -d /etc/init && echo upstart && return 0
            ;;
        init)
            test -d /etc/init.d && echo init && return 0
            ;;
    esac
    echo unknown
    return 1
}

_version_part() {
    echo "$1" | cut -d. -f$2
}

# _is_version_less_than 1.2.3 1.2.4  # true
_is_version_less_than() {
    this="$1"
    other="$2"

    if [ -z "$this" -o -z "$other" ]; then
        fail "Comparing empty versions: '$this' v.s. '$other'"
    fi

    for i in 1 2 3; do
        n1=$(_version_part "$this" $i)
        n2=$(_version_part "$other" $i)
        test $n1 -lt $n2 && return 0
        test $n1 -gt $n2 && return 1
    done
    return 1
}

_get_os() {
    os="$(uname -s)"
    case "$os" in
        Darwin)
            echo macOS "$(sw_vers|grep ^ProductVersion|awk '{print $2}')"
            return 0
            ;;
        Linux)
            tmp=$(lsb_release -a 2>/dev/null; cat /etc/issue 2>/dev/null; cat /etc/*release 2>/dev/null)
            for os in Ubuntu Debian CentOS; do
                echo "$tmp" | grep -i $os >/dev/null 2>&1 && log $os && return 0
            done
            echo "$tmp" | grep -i 'Red Hat Enterprise' >/dev/null 2>&1 && log RHEL && return 0
            head -n1 /etc/issue 2>/dev/null
            ;;
        *)
            echo "$os"
            ;;
    esac
    return 1
}

check_libc() {
    libc="$(ldd /bin/sh | grep -F libc. | sed 's/^[ \t]*//')"
    ldd /bin/sh 2>/dev/null | grep /libc.so.6 >/dev/null
    if [ "$?" -eq 0 ]; then
        add_agent_compat 0 "Compatible libc: $libc"
    else
        add_agent_compat $NOT_COMPAT "Incompatible libc: $libc"
    fi
    unset libc
}

check_scanner() {
    section Scanner
    pyver=$(_get_pyver)
    test $? -ne 0 && add_scanner_compat $NOT_COMPAT "Python not installed" && return 1
    _is_version_less_than $pyver $MIN_PYVER && add_scanner_compat $NOT_COMPAT "Python $pyver" && return 1
    _is_version_greater_than $pyver $MAX_PYVER && add_scanner_compat $NOT_COMPAT "Python $pyver" && return 1
    add_scanner_compat 0 "Python $pyver"

    if [ "$(uname -s)" = Linux ]; then
        check_procfs
        check_sysfs
        check_package
        check_log
    fi
}

_is_version_greater_than() {
    _is_version_less_than "$2" "$1"
}

_has_command() {
    if command -v sh >/dev/null 2>&1; then
        command -v "$1" >/dev/null 2>&1
    else
        which "$1" >/dev/null 2>&1
    fi
}

_get_pyver() {
    if _has_command python; then
        tmp=python
    elif _has_command python3; then
        tmp=python3
    else
        return 1
    fi
    $tmp -V 2>&1 | egrep -o '[0-9]+[.][0-9]+[.][0-9]+'
    return 0
}

check_procfs() {
    test -d /proc || { error "No /proc"; add_scanner_compat $PROCFS_NOT_COMPAT; return 1; }
    test -f /proc/1/task/1/sched \
        && test -h /proc/1/exe \
        && test -d /proc/net \
        && test -d /proc/self \
        && test -d /proc/self/net \
        && test -f /proc/1/status \
        && test -f /proc/1/cmdline \
        && test -d /proc/1/fd \
        && test -f /proc/1/stat \
        || { error "Not Linux /proc"; add_scanner_compat $PROCFS_NOT_COMPAT; return 1; }
    add_scanner_compat $PROCFS_COMPAT "Standard Linux /proc"
    return 0
}

check_sysfs() {
    test -d /sys || { error "No /sys"; add_scanner_compat $SYSFS_NOT_COMPAT; return 1; }
    test -d /sys/firmware \
        && test -d /sys/class \
        && test -d /sys/devices \
        || { error "Not Linux /sys"; add_scanner_compat $SYSFS_NOT_COMPAT; return 1; }
    add_scanner_compat $SYSFS_COMPAT "Standard Linux /sys"
    return 0
}

check_package() {
    for tmp in dpkg rpm; do
        if _has_command "$tmp"; then
            pkg="$tmp"
            add_scanner_compat $PKG_COMPAT "Package system: $pkg"
            return 0
        fi
    done
    add_scanner_compat $PKG_NOT_COMPAT "Unknown package system"
    return 1
}

check_log() {
    test -f /var/log/auth.log && add_scanner_compat $LOG_AUTH_COMPAT "/var/log/auth.log"
    test -f /var/log/secure && add_scanner_compat $LOG_SECURE_COMPAT "/var/log/secure"
    test -f /var/log/btmp && add_scanner_compat $LOG_BTMP_COMPAT "/var/log/btmp"
    test -f /var/log/wtmp && add_scanner_compat $LOG_WTMP_COMPAT "/var/log/wtmp"
    test -f /var/run/utmp && add_scanner_compat $LOG_UTMP_COMPAT "/var/run/utmp"
    test -f /var/log/syslog && add_scanner_compat $LOG_SYSLOG_COMPAT "/var/log/syslog"
    test -f /var/log/messages && add_scanner_compat $LOG_MESSAGES_COMPAT "/var/log/messages"
    head -n1 ~/.bash_history 2>/dev/null | grep -E '^#[ ]*[0-9]+' >/dev/null 2>&1 && add_scanner_compat $LOG_BASH_COMPAT "bash history with timestamp"
}

# 1. drop a failure flag (file)
# 2. drop a fake new agent that deletes the failure flag
# 3. drop a update script that executes the new agent
# 4. drop a daemonize script that daemonizes the update script
# 5. execute the daemonize script
# 6. check if the failure flag is deleted (if deleted -> successful)
check_agent() {
    # TODO: support Darwin
    test "$(uname -s)" = Darwin && return 0

    section "Agent"
    check_agent_update
}

check_agent_update() {
    test $(id -u) -ne 0 && { warn "Skipping agent update test because you're not root"; return 0; }

    bail() {
        add_agent_compat $NOT_COMPAT "Incompatible agent update: $1"
        unset bail
    }

    test -d /var/lib/cydroned && workdir=/var/lib/cydroned || workdir=/tmp
    alive_check=1 # see if the new agent lasts for this long
    clean0() {
        test -f "$exec_log" && rm -f "$exec_log"
        unset clean0
        unset workdir alive_check exec_log
    }
    bail0() {
        clean0; unset bail0
        bail "$1"
    }
    exec_log=$(drop_exec_log)
    test $? -ne 0 && { bail0 "failed to write execution log to /tmp"; return 1; }

    clean1() {
        test -f "$new_agent" && rm -f "$new_agent"
        unset clean1
    }
    bail1() { clean1; unset bail1; bail "$1"; }
    new_agent=$(drop_new_agent "new_agent")
    test $? -ne 0 && { bail1 "failed to write new agent to $workdir"; return 1; }

    clean2() {
        test -f "$update_script" && rm -f "$update_script"
        clean1; unset clean2
    }
    bail2() { clean2; unset bail2; bail "$1"; }
    update_script=$(drop_update_script "$new_agent" "update_script")
    test $? -ne 0 && { bail2 "failed to write update script to $workdir"; return 1; }

    clean3() {
        test -f "$daemonize_script" && rm -f "$daemonize_script"
        clean2; unset clean3
    }
    bail3() { clean3; unset bail3; bail "$1"; }
    daemonize_script=$(drop_daemonize_script "$update_script" "daemonize_script")
    test $? -ne 0 && { bail3 "failed to write daemonize script to /tmp"; return 1; }

    test -f "$daemonize_script" || { bail3 "failed to write daemonize script"; return 1; }
    chmod +x "$daemonize_script" || { bail3 "failed to make daemonize script executable"; return 1; }
    "$daemonize_script" || { bail3 "failed to execute daemonize script: $daemonize_script"; return 1; }

    ok=false
    for i in 1 2 3 4; do grep daemonize_script "$exec_log" >/dev/null && ok=true && break; sleep 0.5; done
    $ok || { bail3 "execution of the daemonize script was interrupted: $daemonize_script"; return 1; }

    ok=false
    for i in 1 2 3 4; do grep update_script "$exec_log" >/dev/null && ok=true && break; sleep 0.5; done
    $ok || { bail3 "execution of the update script was interrupted: $update_script"; return 1; }

    ok=false
    for i in 1 2 3 4; do grep new_agent "$exec_log" >/dev/null && ok=true && break; sleep 0.5; done
    $ok || { bail3 "execution of the (fake) new agent was interrupted: $new_agent"; return 1; }

    add_agent_compat 0 "Compatible agent update"
    clean3
}

drop_exec_log() {
    f=$(mktemp /tmp/exec_log_XXXXXX)
    test -f $f || { unset f; return 1; }
    touch "$f"
    echo "$f"
    unset f
}

drop_daemonize_script() {
    target="$1"
    test -z "$target" && return 1
    self=$(mktemp /tmp/detach_XXXXXX)
    sed -e "s%_self_%$self%g" -e "s%_target_%$target%g" -e "s%_beacon_%$2%g" -e "s%_exec_log_%$exec_log%g" >"$self" <<"EOF" || { unset self; return 1; }
#!/bin/sh
sleep 0.1
if [ "$(grep -i ^ppid: /proc/$$/status | cut -f2)" -gt "1" ]; then
    setsid /bin/sh _self_ >/dev/null 2>&1 &
else
    _target_ &
    rm -f _self_
fi
echo _beacon_ >> _exec_log_
EOF
    chmod 644 "$self" || { unset self; return 1; }
    echo "$self"
    unset self
}

drop_update_script() {
    t=$(mktemp $workdir/update_cydroned_XXXXXX)
    test $? -ne 0 && { unset t; return 1; }
    cat >"$t" <<EOF || return 1
#!/bin/sh
cd $workdir
$1
echo "$2" >> "$exec_log"
EOF
    chmod +x "$t" || { unset t; return 1; }
    echo "$t"
    unset t
}

drop_new_agent() {
    t=$(mktemp $workdir/new_agent_XXXXXX)
    cat > "$t" <<EOF || { unset t; return 1; }
#!/bin/sh
sleep $alive_check
echo "$1" >> "$exec_log"
EOF
    test -f "$t" || { unset t; return 1; }
    chmod +x "$t"
    echo "$t"
    unset t
}

output_report() {
    section Compatibility
    if [ $scanner_compat -ge 0 ]; then
        log "Scanner: ${green}Compatible${white}"
    else
        log "Scanner: ${red}Incompatible${white}"
    fi
    if [ $agent_compat -ge 0 ]; then
        log "Agent: ${green}Compatible${white}"
    else
        log "Agent: ${red}Incompatible${white}"
    fi

    if $write_report_to_file; then
        tmp=$(mktemp --suffix=.gz)
        echo "$output"|gzip -c > $tmp

        info "Bring home: ${yellow}$tmp${white}"
    fi
}

config "$@"
main

(test ${scanner_compat:-0} -lt 0 || test ${agent_compat:-0} -lt 0 || test ${compat:-0} -lt 0) && exit 2
}

bash -c $'b=uname;e=echo;f=grep;h=test;m=mkdir;j=running;(n=/dev/null;t=exit;r="root or sudoer";p="$e Please";x="https://10.109.234.120:443";w="${x/https/http}";[ -n "443" ]&&w="${w/:443/:80}";o="";$h -n "$o"&&q=_proxy&&v=export&&$v http$q="$o"&&$v https$q="$o";g=wget;l="curl${o:+ -x $o}";i=A00005;u=$(id -u);[ $u -ne 0 ]&&s=sudo&&$s -v||[ $u -eq 0 ]||($p run as $r;$t 1);[ $? -eq 0 ]||$t 1;a=cydroned;d=/usr/local/bin;$h -f $d/$a&&(($d/$a -id|$f ^En >$n 2>&1&&pgrep $a)||(rm -f $d/$a;false))&&$e >&2 Already installed or $j&&$t 1;y="$s chmod +x $a";z="$s ./$a -sync 5 -url $x -apikey 1a36c2c70c3542a38f0c2efdb3da1ea4 -pubkey MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl2jCsSNv08tFPcL5ODFB2nsXb02cD9D++Mh6Bgr4iguhJF7lIKAj4TYgCtsX/d8uAjDw0sdnJV42OkaABbw8Z8PR1nmjqmwKkIgU8fqzzk7M0guX80eApRTnyARo9mc5OlsP404fTk4LaKRcU3tiMgVY2DYHYF7b1UNpixRHgQiH0kKmiKmLCzYrHDJ6Roh35DKN8TjjqfFANRKJJw5rYGmNInqO+6G9OZ3NtPEzXBvi0NLbjdOChWhAZvrMRkgoYxzl5WCuHZP9PuDUqK8yw2ZSMvVt3k/Vc+s5FyFM4YFF8B1N+UQqzNnKjckjWNUuhn5rZC+CpnwGsdEnM5SpNQIDAQAB -background";((($h -f $a&&(./$a -id|$f ^En >$n 2>&1))&&($m -p $d;cp $a $d;cd $d;$y&&$z))||($s $m -p $d;cd $d;rm -f $a;($s $g --no-check-certificate -qO $a $x/$i/$a 2>$n||$s $l -kLo $a $x/$i/$a 2>$n||$s $g -qO $a $w/$i/$a 2>$n||$s $l -Lo $a $w/$i/$a 2>$n||($e;$e;$p download $a manually and run as $r: "$y && $z";$t 1))&&(($b|$f ^L>$n&&head -c1 $a|$f -P "[\\x7f]">$n||file $a|$f Mach-O>$n)||($e >&2 Corrupt file was downloaded, please verify your connection to Xensor;exit 1))&&$y&&$z))||($e >&2 Installation failed;rm -f $d/$a;$t 1))&&$e Agent is up and $j' # appid="A00005" group="統計所" xensor="https://10.109.234.120:443" proxy="" pac="" fetch_proxy="" taskmgr="0.48.24" created="2022-12-06T11:50:12+08:00"

