#!/bin/bash # # O ,- # ° o . -´ ' ,- # ° .´ ` . ´,´ # ( ° )) . ( # `-;_ . -´ `.`. # `._' ´ # # Copyright (c) 2006,2011 Markus Fisch # # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php # # @version 1.2.0 # # Open connection to pop box and login popOpen() { exec 6<>/dev/tcp/$POP_HOST/$POP_PORT || return 1 local RESPONSE read RESPONSE <&6 [[ $RESPONSE == +OK* ]] && popRequest "USER $POP_ACCOUNT" && popRequest "PASS $POP_PASSWORD" && return 0 popClose echo 'authentification failed!' return 1 } # Logout and close connection to pop box popClose() { popRequest 'QUIT' exec 6<&- exec 6>&- } # Send request and read succeeding response # # @param 1 - some request popRequest() { echo "$1" >&6 read RESPONSE <&6 [[ $RESPONSE == -ERR* ]] && return 1 return 0 } # Return current mailbox status getStatus() { popOpen || return 1 popRequest 'STAT' || (popClose; return 1) read -d $CR STATUS MAILS SIZE < /dev/null && continue # skip empty sets [ "${MAILBOX[$N]}" ] || continue echo "${MAILBOX[$N]}" >> "$CACHE" done } # Display a message # # @param 1 - message to list readMessage() { local MESSAGE="$1" local N=0 local ID= [ "$MESSAGE" ] || return popOpen || return # get message id and confirm existence popRequest "UIDL $MESSAGE" || (popClose; return) read -d $CR OK N ID < /dev/null; then echo "$ID|1|$FROM|$SUBJECT" >> "$CACHE" else ( local CACHECONTENT=`< $CACHE` local IFS='|' rm "$CACHE" while read MID READ FROM SUBJECT do [ $MID == $ID ] && READ=1 echo "$MID|$READ|$FROM|$SUBJECT" >> "$CACHE" done << EOF $CACHECONTENT EOF ) fi popClose [[ $ENCODING == *quoted-printable* ]] && BODY=`echo $BODY | decodeQuotedPrintable` less << EOF `printf "%-8s: %s\n" "From" "$FROM"` `printf "%-8s: %s\n" "To" "$TO"` `printf "%-8s: %s\n" "Subject" "$SUBJECT"` `printf "%-8s: %s\n" "Date" "$DATE"` `echo "$BODY"` EOF } # Read next new message readNextMessage() { # if no cache file exists try to display first message [ -f "$CACHE" ] || { getStatus || return readMessage 1 return } popOpen || return popRequest 'UIDL' || (popClose; return) local NEXT=0 # find first unread while read -d $CR N ID <&6 do [[ $N == . ]] && break [ "$ID" ] || continue (( $NEXT )) || grep "^$ID|1|" "$CACHE" &> /dev/null || NEXT=$N done popClose (( $NEXT )) && readMessage $NEXT } # Check for new messages getNewMessages() { popOpen || return 1 popRequest 'UIDL' || (popClose; return 1) NEW_MAILS=0 while read -d $CR N ID <&6 do [[ $N == . ]] && break [ "$ID" ] || continue if ! [ -f $CACHE ] || ! grep "^$ID" "$CACHE" &> /dev/null; then (( NEW_MAILS++ )) fi done popClose } # Download message to disk # # @param 1 - message to file fileMessage() { MESSAGE="$1" [ "$MESSAGE" ] || return popOpen || return popRequest "RETR $MESSAGE" || (popClose; return) local DATE=`date +%Y%m%d%H%M%S` local FILE="unknown-$DATE" while read -d $CR RESPONSE <&6 do [[ $RESPONSE == . ]] && break case "$RESPONSE" in From:*|from:*|FROM:*) FROM="${RESPONSE#*:}" ;; esac echo "$RESPONSE" >> "$FILE" done popClose FROM=${FROM#*<} FROM=`echo ${FROM%>*}` [ "$FROM" ] && [ -f "$FILE" ] && mv "$FILE" "$FROM-$DATE" } # Delete messages from server # # @param 1 - list of indexes of messages to delete (optional) deleteMessages() { local MESSAGES="$1" [ "$MESSAGES" ] || { getStatus || return for (( N=1; $N <= $MAILS; N++ )) do MESSAGES="$N $MESSAGES" done } popOpen || return for N in $MESSAGES do popRequest "DELE $N" || break done popClose } # Open connection to smtp server and log on smtpOpen() { exec 7<>/dev/tcp/$SMTP_HOST/$SMTP_PORT || return 1 local RESPONSE read RESPONSE <&7 if [ "$SMTP_PASSWORD" ]; then smtpRequest "EHLO $SMTP_HOST" && smtpRequest 'AUTH LOGIN' && smtpRequest "`echo -n "$SMTP_ACCOUNT" | base64`" && smtpRequest "`echo -n "$SMTP_PASSWORD" | base64`" && return 0 else smtpRequest "HELO $SMTP_HOST" && return 0 fi smtpClose return 1 } # Close connection to smtp server and log off smtpClose() { smtpRequest 'QUIT' exec 7<&- exec 7>&- } # Send request and read succeeding response # # @param 1 - some command smtpRequest() { echo "$1" >&7 for (( ;; )) do read RESPONSE <&7 [ "${RESPONSE:3:1}" != "-" ] && break done [[ $RESPONSE == [23]* ]] || return 1 return 0 } # Write a message # # @param 1 - recipient of the message # @param 2 - subject of the mail (optional) writeMessage() { local TO="$1" local SUBJECT="$2" local BODY= [ "$TO" ] && echo "To : $TO" while [ -z "$TO" ] do printf 'To : ' read TO done local RCPT="$TO" local FROM="$SMTP_ACCOUNT" if [[ $RCPT == *\<* ]] ; then RCPT=${RCPT#*<} RCPT=`echo ${RCPT%>*}` fi if [[ $FROM == *\<* ]] ; then FROM=${FROM#*<} FROM=`echo ${FROM%>*}` fi if [ -z "$SUBJECT" ]; then printf 'Subject: ' read SUBJECT else echo "Subject: $SUBJECT" fi while [ -z "$BODY" ] do echo 'Message: (hit CTRL-D when finished)' BODY="`cat`" done smtpOpen || return smtpRequest "MAIL FROM: <$FROM>" && smtpRequest "RCPT TO: <$RCPT>" && smtpRequest "DATA" && smtpRequest "From: $SMTP_ACCOUNT To: $TO Subject: $SUBJECT Date: `export LANG=; date +"%a, %d %b %Y %H:%M:%S %z"` MIME-Version: 1.0 Content-Type: text/plain; Content-Transfer-Encoding: 8bit; `echo "$BODY" | while read -n 76 LINE; do echo "$LINE"; done` ." && smtpClose && return echo 'error: message could NOT be sent!' smtpClose } # Answer a message # # @param 1 - index of message answerMessage() { local N="$1" popOpen || return popRequest "TOP $N 0" || (popClose; return) local FROM= local SUBJECT= while read -d $CR RESPONSE <&6 do [[ $RESPONSE == . ]] && break case "$RESPONSE" in From:*|from:*|FROM:*) FROM=`echo ${RESPONSE#*:}` ;; Subject:*|subject:*|SUBJECT:*) SUBJECT=`echo ${RESPONSE#*:}` ;; esac done popClose FROM=${FROM#*<} FROM=`echo ${FROM%>*}` [ "$FROM" ] || return writeMessage "$FROM" "Re: $SUBJECT" } # Decode quoted-printable-encoded stream decodeQuotedPrintable() { local IFS= local T= local ENCODED=0 while read -r -n 1 -d $'\r' C do [ "$C" == '=' ] && ENCODED=2 && CODE="" && continue (( ! $ENCODED )) && T="$T""$C" && continue CODE=$CODE$C (( ENCODED-- )) (( ! $ENCODED )) && eval "C=\$'\\x$CODE'" && T="$T""$C" done echo $T } # Base64 encoder, this is a fallback for systems that lack base64 # # @param ... - flags which base64 &>/dev/null || base64() { local SET='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local DECODE local F for F in $@ do case "$F" in -d) DECODE=1 ;; esac done (( DECODE )) && { local N=0 local V=0 local C IFS= while read -d '' -r -n1 C do [ $C == $'\n' ] && continue if [ $C == '=' ] then V=$(( V << 6 )) else local P if [ $C == '+' ] then P=`expr index "$SET" "\+"` else P=`expr index "$SET" $C` fi V=$(( V << 6 | --P )) fi (( ++N == 4 )) && { local S for (( B=3, S=16; B--; S-=8 )) do printf \\$( printf '%03o' $(( V >> S & 255 ))) done V=0 N=0 } done return } local V=0 local W=0 local SH=16 local X local EOF=0 local M=4 IFS= while true do (( EOF )) || read -d '' -r -n1 X || { EOF=1 if (( SH == 8 )) then M=2 elif (( SH == 0 )) then M=3 else break fi } local C=0 (( EOF )) || case "$X" in '') C=0 ;; *) C=`printf '%d' "'$X"` ;; esac V=$(( C << SH | V )) (( SH -= 8 )) (( SH < 0 )) && { local B local S for (( B=0, S=18; B> S & 63 )) echo -n ${SET:$C:1} (( ++W > 75 )) && { echo W=0 } done for (( B=0, MM=4-M; B $MAILS )); then STOP=$MAILS fi (( $START == $STOP )) && NUMBERS=$NUMBERS$N$LF && continue for (( C=$START; $C <= $STOP; C++ )) do NUMBERS=$NUMBERS$C$LF done done [ $SORTED ] && NUMBERS="`echo "$NUMBERS" | sort -nr`" } # Execute a command # # @param 1 - a command handleCommand() { local CMD="$1" case "$CMD" in h*|\?*) echo ' p(eek) peek for new messages' echo ' P(eek) peek continuously & notify on mail' echo ' s(tatus) request mailbox status' echo ' n(ew) list (only) new messages' echo ' l(ist) [N[-N]]... list messages' echo ' r(ead) [N[-N]]... read message' echo ' f(ile) N[-N]... file message to current directory' echo ' d(elete) N[-N]...|all remove message' echo ' a(nswer) N answer message' echo ' w(rite) ADDRESS write a message to ADDRESS' echo ' c(lear) clear screen' echo ' h(elp) show this info' echo ' v(ersion) show version' echo ' q(uit) quit' echo ' Q(uit) quit background P(eek) instance' ;; v*) local VERSION=`grep "^#[ \t]*@version" $0` echo ${VERSION##* } ;; p*) getNewMessages echo "$NEW_MAILS new mails" ;; P*) if [ -z "$NOTIFICATION" ] || [ -z "$INTERVAL" ] then echo 'error missing notification command!' return fi [ -f "$PIDFILE" ] && kill `< $PIDFILE` &> /dev/null while true do # this will NOT mark messages as listed, so you need to call # "readmail l q" in your notification if you're using this # script just to be notified, this wouldn't really be a peek # because it changes the cache getNewMessages || break # allow a list of commands to be executed (( NEW_MAILS > 0 )) && /bin/bash << EOF #!/bin/bash $NOTIFICATION EOF sleep $INTERVAL done & echo "$!" > "$PIDFILE" exit ;; s*) getStatus echo "$MAILS mails ($SIZE bytes)" ;; n*) listMessages 'NEW' ;; l*) [[ $CMD == l*' '* ]] || { listMessages return } parseNumbers "$CMD" [ "$NUMBERS" ] && listMessages "$NUMBERS" ;; d*) case $CMD in *' all') deleteMessages ;; *) # because the following indexes will change after # a message is deleted it is very important to # sort list reverse parseNumbers "$CMD" 1 [ "$NUMBERS" ] && deleteMessages "$NUMBERS" ;; esac ;; r*) [[ $CMD == r*' '* ]] || { readNextMessage return } parseNumbers "$CMD" [ "$NUMBERS" ] && for N in $NUMBERS do clear readMessage $N done ;; f*) parseNumbers "$CMD" [ "$NUMBERS" ] && for N in $NUMBERS do fileMessage $N done ;; a*) [[ $CMD == a*' '* ]] || return local N=${CMD#* } answerMessage $N ;; w*) local TO= [[ $CMD == w*' '* ]] && TO=${CMD#* } writeMessage $TO ;; c*) clear ;; q*|x*|exit) exit ;; Q*) [ -f "$PIDFILE" ] && { kill `< $PIDFILE` &> /dev/null rm "$PIDFILE" } exit ;; *) [ "$CMD" ] && { echo "error: unknown command '$CMD'" return } readNextMessage ;; esac } # Ask for a setting and put it into $CONFIG # # @param 1 - name of the setting # @param 2 - question phrase askFor() { local ANSWER= PRESET= DEFAULT= eval "DEFAULT=\$$1" [ "$DEFAULT" ] && PRESET=" [$DEFAULT]" while [ -z "$ANSWER" ] do printf "$2$PRESET: " read -e ANSWER [ "$ANSWER" ] || { [ "$DEFAULT" ] && ANSWER="$DEFAULT" } done echo "$1='$ANSWER'" >> "$CONFIG" eval "$1=\$ANSWER" } # Check configuration check() { [ "$POP_HOST" ] && [ "$POP_ACCOUNT" ] && [ "$POP_PASSWORD" ] && [ "$SMTP_HOST" ] && [ "$SMTP_ACCOUNT" ] && return 0 if [ -f "$CONFIG" ]; then cat << EOF Missing settings in $CONFIG! Do you like to change/complete your configuration now ([yes]/no) EOF else cat << EOF Missing configuration file! This seems to be the first time you are running this script. Do you like to set up a configuration now? ([yes]/no) EOF fi read -e ANSWER case "$ANSWER" in y*|Y*|"") # remove any previous configuration [ -f "$CONFIG" ] && rm "$CONFIG" ;; *) return 1 ;; esac askFor POP_HOST 'POP3 host' askFor POP_PORT 'POP3 port' askFor POP_ACCOUNT 'POP3 account' askFor POP_PASSWORD 'POP3 password' askFor SMTP_HOST 'SMTP host' askFor SMTP_PORT 'SMTP port' askFor SMTP_ACCOUNT 'SMTP account' askFor SMTP_PASSWORD 'SMTP password' echo 'setup complete.' return 0 } readonly BASENAME=${0##*/} readonly CONFIG="$HOME/.${BASENAME}rc" readonly CACHE="$HOME/.${BASENAME}cache" readonly PIDFILE="$HOME/.${BASENAME}pid" readonly CR=$'\r' readonly LF=$'\n' # read configuration [ -r $CONFIG ] && . $CONFIG POP_PORT=${POP_PORT:-110} SMTP_PORT=${SMTP_PORT:-25} INTERVAL=${INTERVAL:-60} LINES_VIEW=${LINES_VIEW:-256} check || exit 1 # process command line arguments for CMD in $@ do handleCommand "$CMD" done # interactive operation for (( ;; )) do printf "$POP_ACCOUNT> " read -e CMD handleCommand "$CMD" done