#!/bin/ksh # # $SRC_Id: abersnuik.ksh,v 1.52 2014/12/20 10:10:57 craski-shell_86 Exp $ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 noexpandtab # #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # # Copyright (c) 2013, 2014 Craig R. Skinner # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # # VERSION: 1.3.25 # # Source the sysexits library: . /usr/local/lib/sysexits.sh function usage { usg="Usage: ${this} (" usg="${usg}$(print ${protocols[*]} | tr -s ' ' '|')" usg="${usg}) URL" usg="${usg}\nRefer to /usr/local/share/doc/pkg-readmes/${this}*" print -u2 ${usg} finish ${EX_USAGE} ${usg} } function finish { local exit_code=$1 # Writable $log? print -n >> ${log} && { # Readable $run_log? tail -n 1 ${run_log} > /dev/null && { [[ ${exit_code} -eq ${EX_TEMPFAIL} ]] && { print "\nTEMPFAIL log: ${run_log}" >> ${log} exit ${exit_code} } [[ ${exit_code} -eq 0 ]] || cat ${run_log} >> ${log} rm ${run_log} } } [[ $# -gt 1 ]] && { shift print >> ${log} && { date >> ${log} print -- $* >> ${log} } print -- "$*\n\nAlso see: ${log}" | mail -s "${this} error on $(hostname -s)" ${LOGNAME} } [[ -d ${lock} ]] && rm -rf ${lock} exit ${exit_code} } function checksum { cksum -q -h ${1}.sum ${1} || return chflags nodump ${1}.sum } function backup_file { [[ -f ${1} ]] || return mv ${1} ${1}~ } function backdown_file { [[ -f ${1}~ ]] || return mv ${1}~ ${1} } function backdown_retrieved { backdown_file ${retrieved} backdown_file ${retrieved}.sum # Exit if spamd was started before retrieval # (probably previously loaded): [[ /var/run/rc.d/spamd -ot ${retrieved}.sum ]] && { rm -rf ${lock} finish ${EX_TEMPFAIL} } } this=${0##*/} user='_abersnuik' log="/var/log/${this}" protocols[0]='ftp' protocols[1]='http' protocols[2]='rsync' protocols[3]='scp' protocols[4]='sftp' # TODO protocols[5]='ftps' unset protocol for prtcl in ${protocols[*]} do [[ ${1} == ${prtcl} ]] && { protocol=${prtcl} break } done [[ $# -eq 2 && -n ${protocol} ]] || usage # Privilege separation: [[ ${LOGNAME} == ${user} ]] || { [[ ${LOGNAME} == 'root' ]] || finish ${EX_NOUSER} "Invalid system user: '${LOGNAME}'" # Drop root privileges: su -l -s /bin/ksh ${user} $0 $* exit $? } # niceness/priority could be set in login.conf(5) [[ $(ps -o nice -p $$ | tail -n 1) -eq 0 ]] && renice -n 18 $$ >/dev/null # Email an alert that spamd isn't running. /etc/rc.d/spamd check || finish ${EX_UNAVAILABLE} "spamd isn't running" # Log file check as unprivileged user print -n >> ${log} || finish ${EX_NOPERM} "Can't write to log: ${log}" run_log=$(mktemp) print "\n$0 $*" >> ${run_log} date >> ${run_log} print -n "umask:\t" >> ${run_log} umask >> ${run_log} ps -l -p $$ >> ${run_log} printenv | sort >> ${run_log} conf='/etc/mail/spamd.conf' [[ -r ${conf} ]] || finish ${EX_CONFIG} "Can't read configuration: ${conf}" alias getcap='getcap -f ${conf} -s ${capacity} ${this} 2>/dev/null' # NOTE: due to a documented bug in getcap(3), semi-colons are replaced with colons: alias getcap_colon="sed 's/;/:/g'" # Set the cache: capacity='cache' cache=$(getcap) [[ -n ${cache} ]] || cache='/var/cache/abersnuik' find ${cache%/*} -type d -perm -o=w -maxdepth 0 2>/dev/null | fgrep -q ${cache%/*} && finish ${EX_CONFIG} "Insecure: World writable cache parent ${cache%/*}" [[ -d ${cache} ]] || { mkdir -p ${cache} || finish ${EX_CANTCREAT} "Can't make ${cache}" } find ${cache} -type d -perm -o=w -maxdepth 0 2>/dev/null | fgrep -q ${cache} && finish ${EX_CONFIG} "Insecure: World writable cache ${cache}" [[ -w ${cache} ]] || finish ${EX_IOERR} "Cache '${cache}' not writable by ${LOGNAME}" cd ${cache} || finish ${EX_IOERR} "Can't cd into ${cache}" print "PWD=${PWD}" >> ${run_log} # NOTE: due to a documented bug in getcap(3), semi-colons are replaced with colons: url=$(print ${2} | getcap_colon) retrieved=$(print ${url} | tr -c '[[:alnum:].\n]' _) # Exit if another (maybe started earlier) # instance is trying to get this file too: lock="${retrieved}.lock" pid="${lock}/pid" mkdir ${lock} 2>>${run_log} || { [[ -d ${lock} ]] || finish ${EX_CANTCREAT} "Can't make ${lock}" ls -ld ${lock} >> ${run_log} [[ -f ${pid} ]] && { kill -0 $(< ${pid}) 2>>${run_log} && { ps -l -p $(< ${pid}) >> ${run_log} finish ${EX_TEMPFAIL} } } } print $$ > ${pid} chflags nodump ${lock} ${pid} trap 'finish 2 trapped signal: INT' INT trap 'finish 3 trapped signal: QUIT' QUIT trap 'finish 6 trapped signal: ABRT' ABRT trap 'finish 15 trapped signal: TERM' TERM trap 'finish 17 trapped signal: STOP' STOP # Prefer the environment's proxy printenv ${protocol}_proxy > /dev/null || { # Get the proxy (if any) # NOTE: due to a documented bug in getcap(3), semi-colons are replaced with colons: capacity="${protocol}_proxy" _proxy=$(getcap | getcap_colon) [[ -n ${_proxy} ]] && export ${protocol}_proxy=${_proxy} } # Set the tool to download with: capacity='tool' tool=$(getcap) case ${protocol} in http|ftp) # Default to ftp if curl not installed: # (curl only gets the file if it's newer, # with or without a caching proxy) tool=$(whence ${tool:-'curl'}) || tool=$(whereis ${tool:-'curl'}) || tool=$(whereis 'ftp') ;; scp|sftp|rsync) tool=$(whence ${protocol}) || tool=$(whereis ${protocol}) ;; esac [[ -x ${tool} ]] || finish ${EX_UNAVAILABLE} "Tool: ${tool} not found" [[ -f ${retrieved} ]] && { [[ -f ${retrieved}.sum ]] || checksum ${retrieved} backup_file ${retrieved}.sum # rsync needs to update the file in place [[ ${protocol} != 'rsync' ]] && backup_file ${retrieved} } # Set the tool's command line args: case ${tool##*/} in ftp) tool_args="-v -o ${retrieved} ${protocol}://${url}" ;; lynx) tool_args="-stderr -dump ${protocol}://${url} > ${retrieved}" # -source flag always returns '0', even if HTTP 404.... ;; curl) tool_args="--output ${retrieved} --fail --silent --show-error ${protocol}://${url}" [[ -f ${retrieved}~ ]] && tool_args="--time-cond ${retrieved}~ ${tool_args}" ;; rsync) tool_args="--compress --verbose --temp-dir ${TMPDIR:-'/tmp'} ${url} ${retrieved}" ;; scp) tool_args="-B ${url} ${retrieved}" ;; sftp) tool_args="${url} ${retrieved}" ;; esac # Do it: unset got_it for attempt in $(jot 12 0) do sleep $(($RANDOM % 300)) print >> ${run_log} date >> ${run_log} print "${tool} ${tool_args}" >> ${run_log} eval ${tool} ${tool_args} >> ${run_log} 2>&1 && { got_it=$? break } done [[ -n ${got_it} && -f ${retrieved} ]] || { print 'Not retrieved' >> ${run_log} # So as not to alert flood, # make a lock directory in ${cache} && alert # (removed when more than 1 day old) find . -name .!alerted -type d -maxdepth 1 -mtime +1 -execdir rmdir -- {} \; mkdir .!alerted 2>/dev/null && { chflags nodump .!alerted print "Error fetching: ${url}\n\nSee ${log}" | mail -s "${this} fetch error on $(hostname -s)" ${LOGNAME} } backdown_retrieved } chflags nodump ${retrieved} checksum ${retrieved} [[ -f ${retrieved}.sum~ ]] && { diff -q ${retrieved}.sum~ ${retrieved}.sum > /dev/null 2>&1 && { print 'No difference to checksum' >> ${run_log} backdown_retrieved } } file_type=$(file -b ${retrieved}) print ${file_type} | fgrep -wq 'gzip' && { extracted=${retrieved%.*} [[ ${retrieved} -nt ${extracted} ]] && { tmp="$$-${RANDOM}-${RANDOM}" # Preserve file attributes (times, flags, perms) mv ${retrieved} ${retrieved}.${tmp} cp -p ${retrieved}.${tmp} ${retrieved} # If extraction fails, load old cached data: gunzip -o ${extracted}.${tmp} ${retrieved} && { mv ${extracted}.${tmp} ${extracted} chflags nodump ${extracted} } [[ -f ${extracted}.${tmp} ]] && mv ${extracted}.${tmp} ${TMPDIR:-'/tmp'} mv ${retrieved}.${tmp} ${retrieved} [[ -f ${extracted} ]] || finish ${EX_DATAERR} "No (cached) extracted ${retrieved}" } retrieved=${extracted} file_type=$(file -b ${retrieved}) } print ${file_type} | fgrep -wq 'text' && reader='cat' [[ -n ${reader} ]] || finish ${EX_DATAERR} "Don't know how to open ${retrieved}" egrep -vq '^[[:digit:]]|\#|^$' ${retrieved} && reader="awk '/^[[:digit:]]/ { if (\$1 !~ /127.0.0./ ) print \$1 }'" eval ${reader} ${retrieved} finish $?