YANZ UPLOAD ANJG

#!/bin/bash OPTS_DIR=/usr/share/i360-php-opts LIVE=${OPTS_DIR}/phpimunity.compiled.rules LOCKFILE=${OPTS_DIR}/i360storagemkcustom.lock # Referenced from rules.yaml as `danger_files: 89100`; staged below. DANGER_LIST=suspicious_files_list.89100 if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then echo "already running" 1>&2 exit 1 fi # Staging directory, next to the live one so the publish below is a rename # within the same filesystem. Cleaned up on every exit path. STAGE= # Holds the previous rules for the two-rename window in which the live path # does not exist, so an interrupted publish can be undone. BACKUP= cleanup() { # Killed between the two publish renames: the live path is gone and BACKUP is # the only copy of the previous rules. Put them back before releasing the # lock, so the next run cannot start from an empty rule set. Never remove # BACKUP while it is that only copy. if [ -n "${BACKUP}" ] && [ -d "${BACKUP}" ]; then if [ -e "${LIVE}" ]; then rm -rf "${BACKUP}" elif mv -T "${BACKUP}" "${LIVE}"; then echo "restored the previous rules after an interrupted publish" 1>&2 else echo "cannot restore the previous rules, they are left in ${BACKUP}" 1>&2 fi fi [ -n "${STAGE}" ] && rm -rf "${STAGE}" rm -f "${LOCKFILE}" exit } trap cleanup HUP INT TERM EXIT echo $$ > ${LOCKFILE} chmod 600 ${LOCKFILE} # SIGKILL, a panic or a power cut inside the publish window leaves no live # directory and the rules stranded in a ${LIVE}.old. backup — the trap # above never ran. We hold the lock, so nothing else is publishing and every # such directory is an orphan: restore the newest, then drop them all. newest_backup= for orphan in "${LIVE}".old.*; do [ -d "${orphan}" ] || continue if [ -z "${newest_backup}" ] || [ "${orphan}" -nt "${newest_backup}" ]; then newest_backup="${orphan}" fi done if [ -n "${newest_backup}" ] && [ ! -e "${LIVE}" ]; then echo "restoring the rules left by an interrupted publish (${newest_backup})" 1>&2 mv -T "${newest_backup}" "${LIVE}" || exit 1 fi for orphan in "${LIVE}".old.*; do [ -d "${orphan}" ] && rm -rf "${orphan}" done if [ ! -e "${LIVE}" ]; then mkdir "${LIVE}" chmod 755 "${LIVE}" fi if [ ! -e "${LIVE}" ]; then echo "Can't find ${LIVE} directory" 1>&2 exit 1 fi if [ -z "$1" -o ! -e "$1" ]; then echo "Can't find $1 file" 1>&2 exit 1 fi rules_file=$(readlink -f "$1") STAGE=$(mktemp -d "${OPTS_DIR}/phpimunity.compiled.rules.new.XXXXXX") || exit 1 chmod 755 "${STAGE}" # The compiler slurps the danger lists relative to its own cwd and bakes them # into the compiled output, so they have to be in the staging directory before # it runs. # # A list it cannot read is invisible: add_susp_file_to_list() skips a file that # fails access(), read_rev_ordered_set_from_list() returns on an empty list and # is_list is set anyway, so the compiler exits 0 having built the # `danger_files: 89100` rules with an empty set — they match nothing and still # pass the validation below. In-place compiles never hit that because the list # stayed in the live directory between runs; a fresh staging directory has to # carry it over. Copy through a .part name so an interrupted cp cannot leave a # truncated list behind, and fall back to the published copy when the source is # unavailable. # TODO: move to i360 agent # Every match is copied over the same .part, so the last one wins — as it did # with the old `find -exec cp`. staged_list therefore tracks only the last copy: # a later failure that truncates .part must not be promoted because an earlier # one happened to succeed. staged_list= while IFS= read -r -d '' src; do if cp -f "${src}" "${STAGE}/${DANGER_LIST}.part"; then staged_list=1 else staged_list= fi done < <(find /var/imunify360/files/php-immunity/ -name "${DANGER_LIST}" -print0 2>/dev/null) if [ -n "${staged_list}" ]; then mv -f "${STAGE}/${DANGER_LIST}.part" "${STAGE}/${DANGER_LIST}" || exit 1 else rm -f "${STAGE}/${DANGER_LIST}.part" if [ -f "${LIVE}/${DANGER_LIST}" ]; then echo "cannot stage ${DANGER_LIST}, reusing the published copy" 1>&2 cp -f "${LIVE}/${DANGER_LIST}" "${STAGE}/${DANGER_LIST}" || exit 1 else # Nothing was ever published either: a host whose agent has not delivered # the list yet. Compiling without it is what used to happen there, so this # is a warning and not a failure. echo "no ${DANGER_LIST} available, danger_files rules will be compiled empty" 1>&2 fi fi # Compile into the staging directory. Until this succeeds the live rules are # untouched: a failure or a kill mid-compile used to leave the live directory # emptied by the rm below and half-repopulated. pushd "${STAGE}" &>/dev/null i360-storage.immunity mksbins "${rules_file}" rc=$? popd &>/dev/null if [ ${rc} -ne 0 ]; then echo "compilation failed (${rc}), keeping the current rules" 1>&2 exit ${rc} fi # Do not trust the exit code alone: the compiler exits 0 on "No host scripts # rules find" (make-storage.c), and an empty yaml would otherwise publish an # empty directory over a working rule set. Publish only a complete-looking set. # # find -print -quit rather than a glob: the shell would expand one argument per # artifact and blow past ARG_MAX at ~26k of them, so a large but perfectly good # compile would fail validation and never publish — the very case this change # exists to unlock. -quit also stops at the first match instead of listing all. # # Nothing written at all is that "No host scripts rules find" path: not a # failure, so exit 0 — the postinst runs this under `set -e` and a non-zero # exit leaves the package unconfigured. first_srule=$(find "${STAGE}" -maxdepth 1 -name 'autorules.*.srule' -print -quit) if [ ! -e "${STAGE}/autorules.slist" ] && [ -z "${first_srule}" ]; then echo "no host scripts rules to compile, keeping the current ones" 1>&2 exit 0 fi if [ ! -s "${STAGE}/autorules.slist" ] || [ -z "${first_srule}" ]; then echo "compilation produced no rules, keeping the current ones" 1>&2 exit 1 fi # Publish by swapping the directories. Two renames, so the live path is absent # for microseconds rather than for the whole compile; a worker that looks in # that window simply finds no rules and reloads on the next request. Dying # inside the window is undone by the trap, or by the orphan recovery above when # the signal was one no trap can catch. # Deliberately not a glob: with one artifact pair per script a rule set of a few # tens of thousands of scripts blows past ARG_MAX (~2 MiB) in a single `rm -f # dir/*.srule`. BACKUP="${LIVE}.old.$$" rm -rf "${BACKUP}" if ! mv -T "${LIVE}" "${BACKUP}"; then echo "cannot move the current rules aside, keeping them" 1>&2 BACKUP= exit 1 fi if ! mv -T "${STAGE}" "${LIVE}"; then # cleanup moves BACKUP back and drops the staging directory. echo "cannot publish the new rules, restoring the previous ones" 1>&2 exit 1 fi STAGE= exit 0