#!/bin/sh # shellcheck disable=SC2119,SC2120 # shellcheck source=../lib/load-configuration . "${0%/*}/../lib/load-configuration" # shellcheck disable=SC2016 if [ $# -ne 0 ]; then >&2 echo '' >&2 echo 'usage: interpret-mail' >&2 echo ' Read email from stdin and interpret / execute body.' >&2 echo '' >&2 echo ' The email needs a valid hashcash-stamp (>=20 bits)' >&2 echo ' and valid encryption to buildmaster@archlinux32.org,' >&2 echo ' as well as a valid gpg-signature from anyone in the' >&2 echo ' list in `gpg_keys`. `allowed_email_actions`' >&2 echo ' determines what instructions are allowed.' >&2 echo '' >&2 echo ' Possible instructions are:' >&2 echo '' >&2 echo ' - "block: ":' >&2 echo ' Block the given build assignment for the given reason.' >&2 echo '' >&2 echo ' - "copy-to-build-support: ":' >&2 echo ' Copy the given binary package into [build-support].' >&2 echo '' >&2 echo ' - "delete:":' >&2 echo ' Delete all scheduled, safely deletable packages.' >&2 echo '' >&2 echo ' - "delete-from-build-support: ":' >&2 echo ' Delete the given package from /build-support.' >&2 echo '' >&2 echo ' - "prioritize: ":' >&2 echo ' Increase the priority of matching build assignments.' >&2 echo '' >&2 echo ' - "schedule: ":' >&2 echo ' Put the given package on the build list (again).' >&2 echo '' >&2 echo ' - "stabilize: ":' >&2 echo ' Mark the given package as tested.' >&2 echo '' >&2 echo ' - "unblock: ":' >&2 echo ' Unblock the given build assignment.' >&2 echo '' exit 1 fi # log $success $action $count [$comment_file] # shellcheck disable=SC2039 log() { local success local action local count local comment success="$1" action="$2" count="$3" if [ -z "$4" ]; then comment='' else comment=$( base64 -w0 "$4" ) fi # shellcheck disable=SC2016 { printf 'INSERT INTO `email_log` (`success`,`action`,`count`,`gpg_key`,`comment`)' printf ' SELECT ' if [ "${success}" = '1' ]; then printf '1,' else printf '0,' fi printf '`email_actions`.`id`,from_base64("%s"),`gpg_keys`.`id`,from_base64("%s")' \ "$( printf '%s' "${count}" | \ base64 -w0 )" \ "${comment}" printf ' FROM `email_actions` JOIN `gpg_keys`' printf '%s' "${gpg_keys_filter}" printf ' AND `email_actions`.`name`=from_base64("%s");\n' "$( printf '%s' "${action}" | \ base64 -w0 )" } | \ mysql_run_query } # run_and_log_on_error $action # shellcheck disable=SC2039 run_and_log_on_error() { # shellcheck disable=SC2039 local err local action action="$1" shift err=0 "$@" 2> "${tmp_dir}/stderr" > "${tmp_dir}/stdout" || \ err=$? if [ "${err}" -eq 0 ]; then return 0 fi cat "${tmp_dir}/stdout" >> "${tmp_dir}/stderr" if [ "${err}" -eq 1 ]; then printf '^ temporary error - I keep the message.\n' >> \ "${tmp_dir}/stderr" fi log '0' "${action}" '0' "${tmp_dir}/stderr" if [ "${err}" -eq 1 ]; then exit 1 else return 1 fi } tmp_dir=$(mktemp -d 'tmp.interpret-mail.XXXXXXXXXX' --tmpdir) trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT cat > \ "${tmp_dir}/mail" if ! hashcash -qXc -b 20 \ -d -f "${tmp_dir}/hashcash.db" \ -r 'archlinux32-buildmaster@eckner.net' \ -r 'buildmaster@archlinux32.org' < \ "${tmp_dir}/mail"; then # shellcheck disable=SC2016 { printf 'INSERT INTO `email_log` (`success`,`comment`)' printf ' VALUES (0,"Invalid stamp - ignoring this message.");\n' } | \ mysql_run_query exit fi if ! sed -n ' /^-----BEGIN PGP MESSAGE-----\s*$/{ :a /\n-----END PGP MESSAGE-----\s*$/!{ N ba } p } ' "${tmp_dir}/mail" | \ gpg --batch --status-file "${tmp_dir}/gpg-status" -q -d -o "${tmp_dir}/plain-content" > /dev/null 2>&1; then # shellcheck disable=SC2016 { printf 'INSERT INTO `email_log` (`success`,`comment`)' printf ' VALUES (0,from_base64("%s"));\n' \ "$( { printf 'Invalid encryption/signature - ignoring this message.\n' cat "${tmp_dir}/gpg-status" } | \ base64 -w0 )" } | \ mysql_run_query exit fi gpg_keys_filter=$( # shellcheck disable=SC2016 { printf 'SELECT DISTINCT `gpg_keys`.`id` FROM `gpg_keys`' printf ' WHERE `gpg_keys`.`fingerprint` IN (' grep '^\[GNUPG:] VALIDSIG ' "${tmp_dir}/gpg-status" | \ cut -d' ' -f3 | \ sort -u | \ base64_encode_each | \ sed ' s/^/from_base64("/ s/$/"),/ ' printf '"");\n' } | \ mysql_run_query | \ sed ' $! s/$/,/ 1 s/^/ WHERE `gpg_keys`.`id` IN (/ $ s/$/)/ ' ) if [ -z "${gpg_keys_filter}" ]; then # shellcheck disable=SC2016 { printf 'INSERT INTO `email_log` (`success`,`comment`)' printf ' VALUES (0,from_base64("%s"));\n' \ "$( { printf 'No known signature found - I found:\n' grep '^\[GNUPG:] VALIDSIG ' "${tmp_dir}/gpg-status" | \ cut -d' ' -f3 | \ sort -u | \ sed 's|^|> |' printf 'Ignoring this message.\n' } | \ base64 -w0 )" } | \ mysql_run_query exit fi # shellcheck disable=SC2016 { printf 'SELECT DISTINCT `email_actions`.`name` FROM `email_actions`' mysql_join_email_actions_allowed_email_actions mysql_join_allowed_email_actions_gpg_keys printf '%s\n' "${gpg_keys_filter}" } | \ mysql_run_query > \ "${tmp_dir}/allowed-actions" printf '\n\n' >> "${tmp_dir}/plain-content" sed -n ' /^$/!b N s/^\n// /^--/b :a N /\n$/!ba s/\n$// p ' "${tmp_dir}/plain-content" | \ sed ' :start_loop $!{ N bstart_loop } s/[=\]\s*\n//g s/:\s*\n/: /g s/\n\(\S\+[^: ]\(\s\|\n\|$\)\)/ \1/g ' > \ "${tmp_dir}/raw-content" sed -n "$( while read -r action; do if [ -z "${action}" ]; then continue fi printf \ '/^%s:/{ s/^%s:\s*//; w %s/%s\n b; }\n' \ "${action}" \ "${action}" \ "${tmp_dir}" \ "${action}" done < \ "${tmp_dir}/allowed-actions" )" "${tmp_dir}/raw-content" if [ -s "${tmp_dir}/block" ]; then if run_and_log_on_error 'block' "${base_dir}/bin/modify-package-state" --wait --block "${tmp_dir}/block"; then log 1 'block' "$(wc -l < "${tmp_dir}/block")" else log 0 'block' 0 fi fi if [ -s "${tmp_dir}/copy-to-build-support" ]; then sed -i ' /\.pkg\.tar\.xz$/!s/$/.pkg.tar.xz/ ' "${tmp_dir}/copy-to-build-support" if run_and_log_on_error 'copy-to-build-support' "${base_dir}/bin/copy-to-build-support" --wait "${tmp_dir}/copy-to-build-support"; then log 1 'copy-to-build-support' "$(wc -l < "${tmp_dir}/copy-to-build-support")" else log 0 'copy-to-build-support' 0 fi fi if [ -s "${tmp_dir}/delete" ]; then if run_and_log_on_error 'delete' "${base_dir}/bin/delete-packages" --wait; then log 1 'delete' 1 else log 0 'delete' 0 fi fi if [ -s "${tmp_dir}/delete-from-build-support" ]; then if run_and_log_on_error 'delete-from-build-support' "${base_dir}/bin/delete-packages" --wait --build-support "${tmp_dir}/delete-from-build-support"; then log 1 'delete-from-build-support' "$(wc -l < "${tmp_dir}/delete-from-build-support")" else log 0 'delete-from-build-support' 0 fi fi if [ -s "${tmp_dir}/prioritize" ]; then if run_and_log_on_error 'prioritize' "${base_dir}/bin/prioritize-build-list" --wait "${tmp_dir}/prioritize"; then log 1 'prioritize' "$(cat "${tmp_dir}/prioritize")" else log 0 'prioritize' 0 fi fi if [ -s "${tmp_dir}/schedule" ]; then # shellcheck disable=SC2046 "${base_dir}/bin/seed-build-list" --wait $( tr '[:space:]' '\n' < \ "${tmp_dir}/schedule" | \ grep -vxF '' | \ while read -r package; do printf -- '-p ^%s$\n' "$(str_to_regex "${package}")" done ) | \ sponge "${tmp_dir}/schedule" log 1 'schedule' "$(wc -l < "${tmp_dir}/schedule")" fi if [ -s "${tmp_dir}/stabilize" ]; then sed -i ' /\.pkg\.tar\.xz$/!s/$/.pkg.tar.xz/ ' "${tmp_dir}/stabilize" if run_and_log_on_error 'stabilize' "${base_dir}/bin/modify-package-state" --wait --tested "${tmp_dir}/stabilize"; then log 1 'stabilize' "$(wc -l < "${tmp_dir}/stabilize")" else log 0 'stabilize' 0 fi fi if [ -s "${tmp_dir}/unblock" ]; then if run_and_log_on_error 'unblock' "${base_dir}/bin/modify-package-state" --wait --unblock "${tmp_dir}/unblock"; then log 1 'unblock' "$(wc -l < "${tmp_dir}/unblock")" else log 0 'unblock' 0 fi fi