576 lines
14 KiB
Bash
576 lines
14 KiB
Bash
#!/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 <<EOF
|
|
usage: ${BIN} [-di] FILE...
|
|
d dump message (default)
|
|
i inspect message tree
|
|
|
|
EOF
|
|
return
|
|
}
|
|
|
|
local F ACTION=dump
|
|
|
|
for F in "$@"
|
|
do
|
|
case "$F" in
|
|
-i)
|
|
ACTION=inspect
|
|
continue
|
|
;;
|
|
-d)
|
|
ACTION=dump
|
|
continue
|
|
;;
|
|
-*)
|
|
echo "error: unkown flag '$F'" >&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
|