From 6cf31d1290a07878777f54f9c335be1ab99521b7 Mon Sep 17 00:00:00 2001 From: Stephan Geisler Date: Sun, 25 Aug 2019 18:38:19 +0000 Subject: [PATCH] =?UTF-8?q?=E2=80=9EMailAttachmentParser=5Fnativ.sh?= =?UTF-8?q?=E2=80=9C=20hinzuf=C3=BCgen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailAttachmentParser_nativ.sh | 575 ++++++++++++++++++++++++++++++++++ 1 file changed, 575 insertions(+) create mode 100644 MailAttachmentParser_nativ.sh diff --git a/MailAttachmentParser_nativ.sh b/MailAttachmentParser_nativ.sh new file mode 100644 index 0000000..7378abe --- /dev/null +++ b/MailAttachmentParser_nativ.sh @@ -0,0 +1,575 @@ +#!/bin/bash +# /volume1/homes/admin/script/MailAttachmentParser/MailAttachmentParser_nativ.sh +# 2019-08-25 @ geimist +# durchsucht den angegebenen Quellordner nach E-Mails mit Dateianlage, extrahiert und entpackt diese ggf. in den Zielordner mit laufender Nr. +# (eingebette Anlagen (Content-Transfer-Encoding: quoted-printable) können derzeit nicht gesichert werden - war bei .csv bei mir der Fall) + +# https://www.synology-forum.de/showthread.html?103185-Mail-an-NAS-und-den-Anhang-verwerten&p=833583&viewfull=1#post833583 + +################################# +# # +# PARAMETER ANPASSEN # +# # +################################# + +MAILDIR="/volume1/homes/admin/.Maildir/cur" # Quellverzeichnis +DESTDIR="/volume1/homes/admin/Mailanlagen/" # Zielverzeichnis +DELDIR="/volume1/homes/admin/.Maildir/.Trash/cur" # Löschverzeichnis +delmail="yes" # "yes" um abgearbeite E-Mails zu löschen +unzipPW="0000" # Kennwort zum entpacken + + +################################# +# # +# ab hier nichts mehr ändern! # +# # +################################# + +# Arbeitsverzeichnis auslesen und hineinwechseln: + OLDIFS=$IFS # ursprünglichen Fieldseparator sichern + APPDIR=$(cd $(dirname $0);pwd) + cd ${APPDIR} + + if [ -d "$DESTDIR" ]; then + DESTDIR="${DESTDIR%/}/" + else + mkdir -p "$DESTDIR" + DESTDIR="${DESTDIR%/}/" + fi + +mime_inspect() +{ +################################################################ +#### MIME interface #### +#### analysiert E-Maildateien und gibt sie geordnet aus #### +#### https://gist.github.com/markusfisch/2649043 #### +################################################################ + + # Parse message in MIME format and create a temporary cache directory + mime_parse() + { + MIME_CACHE=${MIME_CACHE:-`mktemp -d ${BIN}.XXXXXXXXXX`} +# trap 'rm -rf "$MIME_CACHE"; exit' EXIT + + local D=$MIME_CACHE + local HEADER=1 + local LAST= + local BOUNDARY= + + while read + do + REPLY=${REPLY%$CR} + + [ "$REPLY" == '.' ] && break + + # in mime header + if [ "$HEADER" ] + then + # header closed + [ "$REPLY" ] || { + HEADER= + + [ -r "$D/content-type" ] && { + local VALUE + value "`< "$D/content-type"`" \ + '[Bb][Oo][Uu][Nn][Dd][Aa][Rr][Yy]=' + [ "$VALUE" ] && { + BOUNDARY=$VALUE + echo "$BOUNDARY" > "$D/boundary" + } + } + + [ -r "$D/content-disposition" ] && { + local VALUE + value "`< $D/content-disposition`" \ + '[Ff][Ii][Ll][Ee][Nn][Aa][Mm][Ee]=' + [ "$VALUE" ] && { + echo "$VALUE" >> "$MIME_CACHE/attachments" + echo "$D" >> "$MIME_CACHE/attachments-paths" + } + } + + continue + } + + local F + if [[ "$REPLY" == [' '$'\t']* ]] + then + [ "$LAST" ] || continue + F=$LAST + else + F=`lower "${REPLY%%:*}"` + LAST=$F + fi + + echo ${REPLY#*:} >> "$D/$F" + continue + elif [ "$BOUNDARY" ] && [ "${REPLY:0:2}" == '--' ] + then + [[ "$REPLY" == --$BOUNDARY* ]] && { + [ "$D" == "$MIME_CACHE" ] || D=${D%/*} + + if [ "$REPLY" == "--$BOUNDARY--" ] + then + if [ -r "$D/boundary" ] + then + BOUNDARY=`< "$D/boundary"` + else + BOUNDARY= + fi + + HEADER= + else + local PART=1 + + [ -r "$D/parts" ] && { + PART=`< "$D/parts"` + (( ++PART )) + } + + echo $PART > "$D/parts" + D="$D/part-$PART" + + mkdir "$D" || return 1 + + HEADER=1 + fi + } + + continue + fi + + echo "$REPLY"$CR >> $D/body + done + } + + # Free MIME data structure + mime_free() + { + rm -rf $MIME_CACHE + MIME_CACHE= + } + + # Decode possibly encoded message text + # + # @param 1 - message directory + mime_decode_message() + { + local F="$1/body" + + [ -r "$F" ] && { + local T=`< "$1/content-type"` CS='cat' + + case "$T" in + [Tt][Ee][Xx][Tt]/*) + local VALUE + value "$T" '[Cc][Hh][Aa][Rr][Ss][Ee][Tt]=' + [ "$VALUE" ] && + CS="iconv -f $VALUE -t utf-8" + ;; + esac + + case "`< "$1/content-transfer-encoding"`" in + *[Qq][Uu][Oo][Tt][Ee][Dd]-[Pp][Rr][Ii][Nn][Tt][Aa][Bb][Ll][Ee]*) + decode_quoted_printable + ;; + *[Bb][Aa][Ss][Ee]64*) + base64 -d -i + ;; + *) + cat + ;; + esac < "$F" | $CS + } 2>/dev/null + } + + # Display message with header information + # + # @param 1 - message directory + mime_display_message() + { + # echo headers + { + local H HEADERS=${HEADERS:-from to subject date attachments} + local M=0 + + # get length of longest header label + { + local L + for H in $HEADERS + do + L=${#H} + (( L > M )) && + M=$L + done + } + + local W=$(( ${WIDTH:-80}-(M+2) )) + for H in $HEADERS + do + local F="$1/$H" + while ! [ -r "$F" ] + do + [ "$F" == "$MIME_CACHE/$H" ] && break + F="$MIME_CACHE/$H" + done + [ -r "$F" ] || continue + + local S + if [ "$H" == 'attachments' ] + then + S=`< "$F"` + S=${S//$'\n'/ } + else + S=`decode_encoded_word < "$F"` + fi + + local N L=${#S} LABEL=$H + for (( N = 0; N < L; N += W )) + do + printf "%-${M}s %-${W}s\n" "$LABEL" "${S:$N:$W}" + LABEL= + done + done + + [ "$H" ] && echo + } + + mime_decode_message "$1" + } + + # Returns true if content type is text + # + # @param 1 - file with content type + mime_content_is_text() + { + case "`< "$1"`" in + *[Tt][Ee][Xx][Tt]/[Pp][Ll][Aa][Ii][Nn]*|\ + *[Tt][Ee][Xx][Tt]/[Hh][Tt][Mm][Ll]*) + return 0 + ;; + esac 2>/dev/null + + return 1 + } + + # Traverse message tree to find message text + # + # @param 1 - directory in MIME tree + # @param 2 - callback function + mime_find_message() + { + (( $# < 2 )) && return 1 + + local TYPE=0 + + case "`< "$1/content-type"`" in + *[Mm][Uu][Ll][Tt][Ii][Pp][Aa][Rr][Tt]/[Aa][Ll][Tt][Ee][Rr][Nn][Aa][Tt][Ii][Vv][Ee]*) + TYPE=1 + ;; + *[Mm][Uu][Ll][Tt][Ii][Pp][Aa][Rr][Tt]/[Dd][Ii][Gg][Ee][Ss][Tt]*) + TYPE=2 + ;; + esac 2>/dev/null + + local N PARTS=`< "$1/parts"` + + for (( N=1; N < PARTS; ++N )) + do + local P="$1/part-$N" + + [ -r "$P/body" ] && + mime_content_is_text "$P/content-type" && { + $2 "$P" + (( TYPE == 2 )) || return 0 + } + + (( TYPE == 2 )) && return 0 + + [ -r "$P/parts" ] && mime_find_message "$P" "$2" && return 0 + done + + return 1 + } + + # Echo message from MIME data structure + # + # @param 1 - callback function (optional) + mime_message() + { + local C=${1:-mime_display_message} + + [ -r "$MIME_CACHE/parts" ] && + mime_find_message "$MIME_CACHE" $C && + return + + [ -r "$MIME_CACHE/content-type" ] && { + mime_content_is_text "$MIME_CACHE/content-type" || + return + } + + $C "$MIME_CACHE" + } + + ############################################################################## + #### Encoding/Decoding + ############################################################################## + + # Decode quoted-printable-encoded stream + decode_quoted_printable() + { + local C=0 EOF=0 + + while (( ! EOF )) + do + read -d '=' || EOF=1 + + (( C )) && + if [[ $REPLY == [$'\r'$'\n']* ]] + then + REPLY=${REPLY:1} + else + printf \\x"${REPLY:0:2}" + REPLY=${REPLY:2} + fi + + echo -n "$REPLY" + C=1 + done + } + + # Decode MIME encoded-word syntax + decode_encoded_word() + { + while read + do + while [[ $REPLY == *'=?'* ]] + do + echo -n ${REPLY%%'=?'*} + local A=${REPLY#*'?='} V=${REPLY#*'=?'} + V=${V%%'?='*} + local P=( ${V//\?/ } ) + if (( ${#P[@]} == 3 )) + then + case "${P[1]}" in + [Qq]) + echo -n "${P[2]}" | decode_quoted_printable + ;; + [Bb]) + echo -n "${P[2]}" | base64 -d -i + ;; + esac | iconv -f "${P[0]}" -t utf-8 + else + echo -n $V + fi + + REPLY=$A + done + + echo -n $REPLY + done + } + + which iconv &>/dev/null || iconv() { + cat + } + + which base64 &>/dev/null || { + echo 'error: base64 not found!' >&2 + echo 'Either install it or get this fallback implementation:' >&2 + echo 'https://gist.github.com/2648733' >&2 + exit 1 + } + + ############################################################################## + #### String auxiliaries + ############################################################################## + + # Make string lower case + # + # @param 1 - some string + if [ $BASH_VERSINFO ] && (( ${BASH_VERSINFO[0]} > 3 )) + then + lower() + { + echo "${1,,}" + } + else + lower() + { + echo "$1" | tr '[:upper:]' '[:lower:]' + } + fi + + # Find a key/value pair in the given string and set VALUE accordingly + # + # @param 1 - string + # @param 2 - pattern of key + value() + { + [[ "$1" == *$2* ]] || { + VALUE= + return + } + + VALUE=${1#*$2} + + local QUOTE="${VALUE:0:1}" + case "$QUOTE" in + '"'|"'") + ;; + *) + QUOTE= + ;; + esac + + if [ "$QUOTE" ] + then + VALUE=${VALUE:1} + VALUE=${VALUE%%$QUOTE*} + else + VALUE=${VALUE%% *} + fi + } + + ############################################################################## + #### Features + ############################################################################## + + # Manually check data structure + # + # @param 1 - message file + inspect() + { + [ -r "$1" ] || { + echo "error: file $1 not found" >&2 + return 1 + } + + echo "(unpacking \"$1\")" + + mime_parse < "$1" && + cd "$MIME_CACHE" && \ + ls && \ + PS1='inspect> ' bash && \ + cd .. + +# mime_free + } + + # Dump message text + # + # @param 1 - message file + dump() + { + mime_parse < "$1" && + mime_message + +# mime_free + } + + ############################################################################## + #### Command processing + ############################################################################## + + # Process arguments + # + # @param ... - arguments + mime() + { + (( $# < 1 )) && { + cat <&2 + return + ;; + esac + + $ACTION "$F" + done + } + + #readonly BIN=${0##*/} + BIN=${0##*/} + #readonly CR=$'\r' + CR=$'\r' + + mime "$@" +} + +IFS=$'\012' +for i in $(find "$MAILDIR" -type f) + do + IFS=$OLDIFS + filename=$(basename "$i") + if ( cat "$i" | grep -q "base64" ) && ( cat "$i" | egrep -q "attachment" ) ; then # prüfen, ob "base64" und "attachment" in der Mail vorkommen + echo "verarbeite: $i" + mime_inspect "$i" > /dev/null + + mkdir "${APPDIR}/$MIME_CACHE/decoded/" + rowid=0 + + while read j # Quelle zeilenweise einlesen + do + ((rowid+=1)) + echo -n " gefundene Anlage: $j " + base64log=$(base64 -di "$(sed -n "${rowid}p" "${APPDIR}/$MIME_CACHE/attachments-paths")/body" > "${APPDIR}/$MIME_CACHE/decoded/$j") + + # check Extension / ggf. entpacken + if echo "${APPDIR}/$MIME_CACHE/decoded/$j" | egrep -q "*.7z|*.zip"; then + echo "(wird entpackt)" + 7z e "${APPDIR}/$MIME_CACHE/decoded/$j" -o"${APPDIR}/$MIME_CACHE/decoded/" -p${unzipPW} -aou > /dev/null + rm "${APPDIR}/$MIME_CACHE/decoded/$j" + else + echo -e + fi + + # nach Zielordner verschieben mit Zähler je Extension: + IFS=$'\012' + for d in $(find "${APPDIR}/$MIME_CACHE/decoded/" -type f) + do + IFS=$OLDIFS + name_d=$(basename "$d") + fileextension="${name_d##*.}" + name_d="${name_d%.*}" + + count=$(ls -t "${DESTDIR}" | egrep -o "^$name_d.*.$fileextension$" | wc -l) + + decfilename="${DESTDIR}${name_d}_(${count}).${fileextension}" + mv "$d" "$decfilename" + echo " Zieldatei: ${name_d}_(${count}).${fileextension}" + done + done < "${APPDIR}/$MIME_CACHE/attachments" + + # Quelldatei löschen: + if [ $delmail = "yes" ]; then + echo " Quelldatei wird nach $DELDIR verschoben" + mv "$i" "$DELDIR" + fi + + # Temp löschen: + mime_free + echo -e + fi + done