changecom(`**')dnl
changequote({{,}})dnl
#!INTERP
#V snatchmail 0.8 -- Copyright (C) 2011-2018 Dario Niedermann
dnl Calls `fetchmail` to get mail from POP3 servers, clean up messages
dnl downloaded more than a (configurable) number of days ago.
dnl Assumes you are not running fetchmail as a daemon. And that you are
dnl only polling POP3 servers (at least in the given config file).
# $Id$
dnl ________________________________________________________________________
#----------------------------------------------------- User-configurable section
#
daysToWait=7 # days before flushing downloaded messages
fmailOpts='--bad-header accept'
verbosity=0 # 0 = quiet, 1 = verbose, >1 = debug
checkNetFirst=0
unset cfg
#
#---------------------------------------------- End of User-configurable section
myName="${0##*/}"
if [ -w /dev/stderr ]; then
echoerr()
{
echo $myName: $@ >/dev/stderr
}
else
echoerr()
{
echo $myName: $@
}
fi
** -------------------------------------------------------- Command line parsing
showVersion()
{
sed -n '/^#V/s/^#V //p' "$0"
}
usageAndExit()
{
echoerr try \`$myName --help\' for usage information.
exit 1
}
TEMP=`getopt -o cf:hLVvr: \
-l check-net,fetchmailrc:,help,license,licence \
-l version,verbose,retention: \
-n "$myName" -- "$@"` || usageAndExit
eval set -- "$TEMP"
while true ; do
case "$1" in
-r|--retention)
daysToWait="$2"; shift
;;
-f|--fetchmailrc)
cfg="$2"; shift
;;
-c|--check-net)
checkNetFirst=1 ;;
-v|--verbose)
verbosity=$((++verbosity)) ;;
-V|--version)
showVersion; exit 0 ;;
-L|--license|--licence)
showVersion; echo
sed -n '/^#%L$/,/^#/p' "$0" | sed '$d;1d' \
ifelse(OPTIMIZE,1,{{
| gunzip -c 2>&-}},{{ ;
}})
exit 0 ;;
-h|--help)
sed -n '/^#%H$/,$p' "$0" | sed 1d \ifelse(OPTIMIZE,1,{{
| gunzip -c \}})
| sed "s,%PRG%,$myName,"
exit 0 ;;
--)
shift; break ;;
*) ** should never happen:
echoerr "\'$opt\' -- option unimplemented, ignoring..."
;;
esac
shift
done
** -------------------------------------------------------------------------/CLI
if [ "$checkNetFirst" -gt 0 ]; then ** if no connection, abort without error
ping -c1 'PINGSRV' >/dev/null 2>&1 \
|| exit 0
fi
** else...
[ $verbosity -eq 0 ] && fmailOpts="-s $fmailOpts" ** silence fetchmail
[ $verbosity -gt 1 ] \
&& echoerr fetchmail will be called with these options: \"$fmailOpts\"
alreadyRunning=8 ** err code for "another fetchmail is running"
** ---------------------------- Check if we have a suitable md5 hasher installed
suitableMD5progs="md5 md5sum openssl"
unset md5prog
for prg in $suitableMD5progs ; do
whichMd5=`which "$prg"`
if [ $? -eq 0 ]; then
md5prog="$whichMd5"
[ "$prg" = openssl ] \
&& md5prog="$md5prog dgst -md5"
[ "$verbosity" -gt 1 ] \
&& echoerr \"$md5prog\" will be used for hashing
break
fi
done
if [ -z "$md5prog" ]; then
echoerr 'no suitable md5 hasher found:'\
'Please install "openssl" or "md5sum"'
exit 72
fi
if [ -n "$FETCHMAILHOME" ]; then
base="$FETCHMAILHOME/"
smHome="$FETCHMAILHOME"
pidFile="$smHome/.fetchmail.pid"
else
base="$HOME/."
smHome=$HOME
** Where's the state directory on this system? We'll need it
** to check if another fetchmail instance is running:
if [ -e /var/run ]; then
stateDir=/var/run ** Linux et al.
else
stateDir=/etc ** Other Unixes supported by fetchmail
fi
pidFile=$stateDir/fetchmail.pid
fi ** TODO:compatibility w/ $HOME_ETC envvar
[ -z "$cfg" ] && cfg="${base}fetchmailrc" ** standard fetchmail configfile
fDir="${base}sm_fetchids.d" ** here we keep collection of old fetchid files
curFids="$fDir/.current.fetchids" ** the current fetchids file
if [ ! -e "$fDir" ]; then ** if the collection dir doesn't exist
mkdir -m700 "$fDir" ** create it
** since we also had no ".current.fetchids" file
** (probably snatchmail's 1st run) copy and use the
** standard .fetchids file (if any) as our ".current.fetchids"
[ -e "$smHome/.fetchids" ] \
&& cp "$smHome/.fetchids" "$curFids"
fi
** The "fetching" fetchmail: just download new mail, if any.
** No flushing for now.
fetchmail -f "$cfg" -k --uidl $fmailOpts -i $curFids
fmResult=$?
** if fetchmail returned error, exit with fm's return code:
[ $fmResult -gt 1 ] && exit $fmResult
** Else... Is another copy of this script running already?
[ -e "${cfg}.away" ] && exit $alreadyRunning ** Harakiri
** Else... should another {sna,fe}tchmail instance be launched
** *while* we're working, it won't be able to interfere:
mv "$cfg" "${cfg}.away" ** 'cause it won't find the default config file we use
** Get name of latest fetchids file we had copied off to our collection:
previousFids=`ls -1ct "$fDir" |head -n1`
RestoreConfigFile()
{
mv "${cfg}.away" "$cfg"
}
if [ -z $previousFids ]; then # if there's none, copy current
cp "$curFids" "$fDir/00000000000000.fetchids" # fetchids file there
RestoreConfigFile
exit $fmResult # and quit
fi
** Else...
** We'll now check if the current fetchids file is different from
** the last one we archived, by checking the respective hashes.
md5prune="tr '[:blank:]' '\n' | sed -n -e '/^[0-9a-f]\{32,32\}$/p'"
** md5prune will heuristically return only the part of the hasher's output
** that looks like an md5 hash, discarding file names and other strings
** get current fetchids file hash:
newFetchidsHash=`$md5prog $curFids | eval $md5prune`
** Then get previous fetchids file hash:
prevFetchidsHash=`$md5prog $fDir/$previousFids | eval $md5prune`
** If the hashes say that the contents are different, copy the
** "current fetchids" file into our collection:
[ "$newFetchidsHash" != "$prevFetchidsHash" ] \
&& cp "$curFids" "$fDir/`date +%Y%m%d%H%M%S`.fetchids"
** Now... do we have in our collection any fetchIDs files
** that sat there untouched for more than X days?
idsToClean=`find $fDir -type f -name '[0-9]*.fetchids' -mtime +$daysToWait`
if [ -z "$idsToClean" ]; then # but if there are none,
[ $verbosity -gt 0 ] && echoerr No ripe old messages to flush, exiting.
RestoreConfigFile # goodbye
exit $fmResult
fi
if [ $verbosity -gt 0 ]; then
echoerr '----- FLUSHING PHASE -----'
echoerr cleaning up messages as per $idsToClean
fi
** Now flush those old messages, using the old fetchids & our usual
** (albeit renamed) config file. Since we don't want to fetch any mail at this
** time, and unfortunately fetchmail lacks a 'just flush, don't fetch' mode,
** we use `cat >/dev/null` as a fake MDA. Yes, it is a bit of a waste.
** Anyhow, if any mail is fetched (& thus devnulled) the deed won't leave any
** trace: fetchmail will keep note of those fetched UIDLs in a temp file that
** will be deleted.
** So the new mail will be fetched again & delivered properly next time.
** Sum all the $idsToClean files into a single file, eliminating repeated lines
** and use that to flush all msgs older than X days. Note that, if new mail has
** arrived on the server during the execution of this script, its (devnulled)
** retrieval won't alter the original $idsToClean files. We must prevent that,
** because - if fetchmail errors out *after* having retrieved some mail, we'd be
** left with a modified (and not deleted) $idsToClean file: its modification
** date would then # become recent, and snatchmail would have to wait X more
** days to clean up the stale UIDLs it contains:
sort -u -o /tmp/.msgs_to_flush.fetchids $idsToClean
chmod 600 /tmp/.msgs_to_flush.fetchids ** newer fetchmail wants this
** is another fetchmail instance running?
while [ -e $pidFile -o -e ~/.fetchmail.pid ]; do
sleep 3 ** wait... (it'll error out OR use a
** different config file anyway, since
done ** we moved the default conf.file away)
** The "flushing" fetchmail:
fetchmail -F -f "${cfg}.away" -i /tmp/.msgs_to_flush.fetchids $fmailOpts \
--mda 'cat >/dev/null'
fmResult=$?
** rm $idsToClean.tmp # always delete the temp $idsToClean copy, errors or not
** If no errors from the flushing fetchmail, also delete the used fetchids file
verboseRm=''; [ $verbosity -gt 1 ] && verboseRm='v'
[ $fmResult -lt 2 ] && rm -f$verboseRm $idsToClean
RestoreConfigFile
exit $fmResult
#%L
“Snatchmail” is Copyright © Dario Niedermann —
Released with no warranty under the terms of the
GPLv3 license. Written and tested on Linux using GNU tools.