#!/bin/bash # Sat Sep 27 04:43:30 EDT 2003 # NAME: mfilter # Copyright 2003, Chris F.A. Johnson # Released under the terms of the GNU General Public License version() { echo " $progname, version $version Copyright $copyright, $author $email This is free software, released under the terms of the GNU General Public License. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. " } usage() { printf "\n %-12s - Download mail from POP3 server, deleting unwanted mail on server\n" $progname echo " USAGE: $progname [OPTIONS] DESCRIPTION: $progname reads the username and password to a POP3 mailbox from a configuration file, and retrieves the header and the top NN lines of each message, tests it (with "egrep -i") against \$HOME/.mfilter/mf_allow* and \$HOME/.mfilter/mf_deny*. The mf_allow* and mf_deny* files contain regular expressions, ont to a line. If it matches an expression in any mf_allow* file, it downloads the message and passes it to procmail for further filtering and delivery to the user's mailbox. If there is no match, it is checked against the mf_deny* files, and if there is no match, it is downloaded. OPTIONS: -c FILE - use FILE as the configuration file -D - do not delete any temporary files when finished -l FILE - use FILE as the logfile -m NUM - download NUM lines as well as the header -M SIZE - delete if larger than SIZE and SIZE > 0 -S - do not delete retrieved messages from server -h - help: print this message -H - help: print more detailed message -v - verbose: -V - print version information -X - Use experimental routines (at your own risk!) " [ $longusage -eq 1 ] && { config_file_format control_files } echo " Copyright $copyright, $author " } control_files() { echo " CONTROL FILES: The behaviour of mfilter is controlled by files in \$HOME/.mfilter. These files contain regular expressions that determine whether a piece of e-mail is to be accepted or rejected. Mail that isn't matched is accepted. These files are named mf_allow* and mf_deny*." } config_file_format() { ## PR:*** echo " CONFIGURATION FILE: The configuration file is sourced by $progname, so it must be syntactically correct. The default config file is \$mfdir/.mfrc ## Change these to your values: user=your_username pass=your_password pop=pop.server port=110 ## Configurable variables msg_lines=20 ## number of lines of body to check retrieve=1 ## 0 = do not download any messages, ## just delete spam " } ### mostly for diagnostic purposes showargs() { ## PR:** local n=1 while [ $n -le $# ] do eval echo $n=\$$n n=$(( $n + 1 )) done >&2 } ### source the configuration file readconfig() { ## PR:** [ ${configged:-0} -eq 1 ] && return 1 || configged=1 config_file=${1:-$config_file} if [ -s $config_file ] then echo "${0##*/}: configuration file: $config_file" >&2 . $config_file else echo "${0##*/}: configuration file, $config_file, not found" >&2 exit 1 fi } ### set $plural to "s" if $1 is not "1" setplural() { ## PR:*** case $1 in *[!0-9]*) plural=s; return ;; esac [ ${1:-0} -eq 1 ] && plural= || plural=s } ### mktempfile() { ## PR:*** USAGE: mktempfile [var_name] case ${template} in *XXXXXX) ;; *) template=${template}XXXXXX ;; esac _MKTEMPFILE=`mktemp $template` [ "$1" ] && eval $1=$_MKTEMPFILE tempfilelist="${tempfilelist:+$tempfilelist${NL}}$_MKTEMPFILE" chmod 600 $_MKTEMPFILE } ### check mail header (plus $msg_lines of message) ### against $mfdir/.mf_allow* and $mfdir/.mf_deny* ### TODO: add $mfdir/.mf_ALLOW* and $mfdir/.mf_DENY* ### for case-sensitive checks; ### add $mfdir/.mf_query to mark for scoring mailpass() { [ -z "$1" ] && return allow "$1" || deny "$1" } allow() { for allow in $mfdir/mf_allow* do [ -f "$allow" ] || continue notify -2 "$progname($FUNCNAME): Using $allow" if egrep -li -f "$allow" "${1:--}" >/dev/null 2>&1 then notify "$progname($FUNCNAME): ALLOWED by: ${allow##*/}" notify "`grep -ie '^From:' -e '^To:' -e '^Subject' ${hfile:-${1:--}}`" return 0 fi done return 1 } deny() { local file=$1 notify -8 "$progname($FUNCNAME): mfdir=$mfdir Header file: ${file##*/}" ##printf "\t%s\n" $mfdir/mf_deny* >&2 for deny in $mfdir/mf_deny* do notify -8 "$progname($FUNCNAME): Using $deny" [ -f "$deny" ] || { notify "$progname($FUNCNAME): $deny not a file"; continue; } if egrep -lif "$deny" "${file:--}" >/dev/null 2>&1 ## >&2 ## then { echo "$DATE user=$user size=$msize" grep -ie '^From:' -e '^To:' -e '^Subject' ${file:--} } >&2 notify "REJECTED by: ${deny##*/}" if [ ${logmatch:=0} -eq 1 ] then match=`egrep -if "$deny" "${file:--}"` notify -1 "MATCHING:~ ${match//${NL}/\\~ }" fi case $from in *@*) echo "${from#From: }" >> $mfdir/.from_deny_list ;; esac return 1 fi done notify -2 "$progname($FUNCNAME): ALLOWING:" && grep -ie '^From:' -e '^To:' -e '^Subject' ${hfile:-${file:--}} >&2 return 0 } checksize() { if [ $msize -gt $max_size ] then if [ $max_size -gt 0 ] then cmd dele $mnum del_srvr=$(( $del_srvr + 1 )) notify "$progname($FUNCNAME): $msize is too big" fi return 1 fi } ### show message, or not, depending on level of verbosity notify() { local vlevel=1 local ok=0 case $1 in -*[!0-9]*) shift ;; -[0-9]*) vlevel=${1#-}; shift ;; -*) shift ;; esac if [ $loglevel -ge $vlevel ] then printf "%s\n" "$*" >> $logfile fi if [ $verbose -ge $vlevel ] then printf "%s\n" "$*" >&2 fi } ### send a command to the pop server and get the reply cmd() { local cmd=$1 echo $* >&3 case $1 in [pP][aA][sS][sS]) ## hide password notify -5 "$progname($FUNCNAME): pass PASSWORD" ;; *) notify -5 "$progname($FUNCNAME): ${@//$CR/}" ;; esac case $1 in ## commands that return a single line [uU][sS][eE][rR] | [sS][tT][aA][tT] | \ [nN][oO][oO][pP] | \ [rR][sS][eE][tT] | [qQ][uU][iI][tT] | \ [pP][aA][sS][sS] | [dD][eE][lL][eE] ) getline || return 5 ;; ## commands that return multiple lines [lL][iI][sS][tT] | [uU][iI][dD][lL] | \ [rR][eE][tT][rR] | [tT][oO][pP] ) result ;; esac } ### Store a multi-line reply to a command in $_RESULT result() { _RESULT= msg_from= msg_to= from= result_lines=0 read -rt1 ok num x <&3 || return 5 notify "$progname($FUNCNAME): ${ok//$CR/} ${num//$CR/} ${x//$CR/}" case "$ok $line" in -ERR*) echo "$progname($FUNCNAME): $ok $line x" >&2 return 5 ;; esac mktempfile resultfile || return 5 while IFS= read -rt1 line || return 5 do case ${line%$CR} in .) break ;; -ERR*) notify "$progname($FUNCNAME): ${line%$CR}" return 5 ;; [Ff][rR][oO][mM]:*) msg_from=${line%$CR} ;; [CctT][cCoO]:*,) LE=' '; msg_to=${line%$CR} ;; [tT][oO]:*) msg_to="${line%$CR}" ;; [cC][cC]:*) msg_cc="${line%$CR}" ;; [sS][uU][bB][jJ][eE][cC][tT]:*) msg_subject=${line%$CR} ;; *) LE=${NL} ;; esac printf "%s${LE}" "$line" result_lines=$(( $result_lines + 1 )) [ -n "$msg_from" ] && { from=${msg_from#*<} from=${from%>*} } done <&3 > $resultfile _RESULT=`< $resultfile` } ### store one-line reply to command in $ok (+OK or -ERR) ### and any further info in $line getline() { read -rt2 ok line <&3 case "$ok $line" in -ERR*) notify "$progname($FUNCNAME):- $ok ${line//$CR/}" return 5 ;; esac notify -5 "$progname($FUNCNAME):: ${ok//$CR/} ${line//$CR/}" } save_header() { local template=${header_dir:-$mfdir}/${user}_$DATE.XXXXXX mktempfile echo "$header" > $_MKTEMPFILE [ $deletefile -eq 0 ] && notify -2 "$progname($FUNCNAME): saved header to $_MKTEMPFILE" } retrieve() { [ -n "$mnum" ] || { notify "$progname($FUNCNAME): no message number supplied" return 5 } if [ $msize -lt $retr_min ] then notify -2 "$progname($FUNCNAME): $msize: too small; deleting" cmd dele $mnum return fi if [ $msize -gt $retr_max -a ${retr_big:-0} -eq 0 ] then notify -2 "$progname($FUNCNAME): $msize: too big to retrieve; leaving for other program" return fi cmd retr $mnum message=${_RESULT//$CR/} [ $delete_saved -eq 1 ] && cmd dele $mnum rtrvd=$(( $rtrvd + 1 )) { echo "From ${from:-$from_def} `date "+%a %b %_d %H:%M:%S %Y"`" echo "$message${NL}" } | procmail ### >> ${savemailfile:-/var/spool/mail/$USER} ### } ### Close connection to POP3 server ### delete temporary files cleanup() { set +x trap 0 1 2 3 4 5 6 7 8 9 [ -n "$clean" ] && exit || clean=clean echo quit >&3 exec 3>&- setplural $del_srvr printf "\n%b\n" "$progname($FUNCNAME): $del_srvr message$plural deleted on server" setplural $rtrvd printf "%b\n" "$progname($FUNCNAME): $rtrvd message$plural retrieved" [ $verbose -gt 3 ] && rmopt=-v || rmopt= [ $deletefile -ge 1 ] && rm -f $rmopt $tempfilelist || printf "${tempfilelist:+TEMPLIST=${NL} $tempfilelist}\n" rm $lockfile printf "%b\n" "$progname($FUNCNAME): Good-bye" exit $1 } eval `date "+DATE=%Y-%m-%d YEAR=%Y MONTH=%m DAY=%d TIME=%H:%M:%S HOUR=%H MINUTE=%M SECOND=%S"` verbose=0 longusage=0 version="0.1" copyright=2003 author="Chris F.A. Johnson" progname=${0##*/} mfdir=$HOME/.mfilter header_dir=$mfdir/headers log_dir=$mfdir/logs config_file=$mfdir/.mfrc savemailfile=/var/spool/mail/$USER def_template=$mfdir/mfilter_$DATE.XXXXXX template=$def_template port=110 logfile= loglevel=9 msg_lines=0 deletefile=1 delete_saved=1 delete_rejects=1 mdelete=1 retr_big=0 retr_max=100000 retr_min=100 retrieve=1 ## 0 = do not download messages from_def=$USER@${hostname:=`hostname`} logmatch=1 del_srvr=0 rtrvd=0 NL=$'\n' CR=$'\r' unset LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES unset LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION LANG=C experimental=0 options= while getopts vVhH-:c:dDm:l:L:RSM:srXp: var do case "$var" in c) config_file=$OPTARG ;; d) options="$options mdelete=0" ;; D) options="$options deletefile=0" ;; l) options="$options logfile=$OPTARG" ;; L) options="$options loglevel=$OPTARG" ;; m) options="$options msg_lines=$OPTARG" ;; M) options="$options max_size=$OPTARG" ;; p) options="$options port=$OPTARG" ;; r) options="$options retr_big=1" ;; R) options="$options delete_rejects=0" ;; S) options="$options delete_saved=0" ;; X) options="$options experimental=1" ;; -) case $OPTARG in help) usage; exit ;; help_long) longusage=1; usage; exit ;; version) version; exit ;; esac ;; h) usage; exit ;; H) longusage=1; usage; exit ;; v) verbose=$(( $verbose + 1 )) ;; V) version; exit ;; esac done shift $(( $OPTIND - 1 )) readconfig options="$options verbose=$verbose" [ -d "$header_dir" ] || mkdir -p $header_dir [ -d "$log_dir" ] || mkdir -p $log_dir logfile=$log_dir/${user:-$USER}-${pop}_${DATE}_${HOUR}.log lockfile=$mfdir/${user:-$USER}-${pop}_${DATE}_LOCK if [ -f $lockfile ] then echo " Another instance of $progname is aparently running" echo " If you are sure there is no other copy running," echo " remove the lock file: $lockfile" exit 2 fi touch $lockfile || exit 2 chmod 600 $lockfile eval $options [ -n "$logfile" ] || mktempfile logfile notify "${NL}$progname: `date` verbose=$verbose" trap "cleanup;exit" 0 1 2 3 4 5 6 7 8 9 ## open connection to POP server and log in exec 3<> /dev/tcp/$pop/$port getline || cleanup 5 cmd user $user cmd pass $pass ## || cleanup 5 notify "$progname: $user connected to $pop" ###### ## get list of message numbers and sizes cmd list _LIST=${_RESULT//$CR/} setplural $num notify "$progname: $num message$plural" case $num in 0) cleanup ;; esac notify -4 "$progname: $_LIST" ###### max_mnum=50 ## get headers; delete or retrieve messages while read -rt1 mnum msize x do [ -n "$mnum" ] || continue ## [ $mnum -gt $max_mnum ] && break notify "${NL}$progname: num=$mnum size=$msize" cmd top $mnum $msg_lines || echo "TOP FAILED" >&2 header=${_RESULT//$CR/} save_header hfile=$_MKTEMPFILE if mailpass $hfile then ## checksize || continue [ ${retrieve:-1} -eq 1 ] && retrieve else notify -8 "${NL}~ ${header//${NL}/${NL}~ }${NL}" cmd dele $mnum del_srvr=$(( $del_srvr + 1 )) fi done < <(echo "$_LIST") cleanup