1 changed files with 575 additions and 0 deletions
@ -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 <<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 |
Loading…
Reference in new issue