Archivi di email in formato mbox

5 Dicembre 2022 0 Di

Diversi anni fa mi è stato chiesto di trovare un modo per fare degli archivi storici delle email aziendali. La necessità era quella di avere una raccolta di tutte le email entranti e uscenti di un server di posta, indipendentemente da quale account fossero state inviate o ricevute.
Si doveva permettere di scaricare le email dal server di posta (operazione che tipicamente viene svolta a inizio di ogni anno), per liberare lo spazio disco dallo storage sul server, e di poterle archiviare su uno o più dispositivi esterni, e che fosse di facile consultazione in caso ci fosse stata la necessità di recuperare un email qual’ora l’originale, fosse andata perduta.
Il server di posta era basato su una classica linux box con Postfix, ma il concetto è facilmente replicabile con qualsiasi altro servizio linux, e volendo, anche da sistemi cloud tipo gmail outlook o altro.
Dopo un po di considerazioni, ho preferito usare un archiviazione in formato MBOX.
Anche se si tratta di un formato vecchio, e monolitico, essendo mbox un file in formato testo, ha il vantaggio di essere facilmente letto da miriadi di applicazioni, disponibili praticamente in tutti i sistemi operativi e non ultimo, leggibile direttamente da un client multipiattaforma come Mozilla Thunderbird.
Lo svantaggio principale di questo formato e che nel caso si debba cancellare un email, essa non viene fisicamente eliminata, ma viene marcata come deleted. L’eliminazione fisica avviene con un processo successivo che ricrea l’archivio mbox escludendo appunto l’email marcata. Come comprensibile, se l’archivio mbox è di qualche decina di gigabyte, diventa una cosa estremamente lenta.
Ma poiché lo scopo era quello di avere un archivio storico in sola lettura, questo problema veniva meno.

Come prima cosa s’è dovuto quindi, affrontare il problema della raccolta delle email.
Fortunatamente essendo il tutto basato su postfix, è stato sufficente creare un account email specifico, del tipo “[email protected]” e poi attivare nel main.cf di postfix, il parametro

always_bcc = [email protected]

In questo modo, qualsiasi email inviata o ricevuta, veniva inviata in copia su questo account.
Questo però aveva il difetto che qualsiasi email, anche quelle interne, venivano inviate in copia su questo account, e nel caso un mittente, avesse inviato la stessa email a più account interni, ne sarebbero state salvate in bcc tante volte, quanti erano a quelli indirizzati.

Il problema comunque è stato facilmente risolto con uno tools, scritto in python, IMAPdedup, che provvede a scansionare un account imap o una directory Maildir, e ne rimuove i duplicati.
Un semplice script shell posto in cron, e richiamato ogni N ore, provvedeva a lanciare la scansione:

#!/bin/bash
# Credenziali e hostname
USER="[email protected]"
PASS="miapassword"
IMAPSRV="localhost"

# Recupero il nome del mese completo in lowecase
MESENOME=$(date +%B)

# Compongo il nome imap del folder Inviate e poi Ricevute, da scansionare
BOXINVIATE="INBOX.Inviate.${MESENOME}"
BOXRICEVUTE="INBOX.Ricevute.${MESENOME}"

# scansiona ed elimina i duplicati nei due folder del solo mese corrente
/usr/local/bin/imapdedup.py -s "${IMAPSRV}" -u "${USER}" -w "${PASS}" -x -c -m -S "${BOXINVIATE}"
/usr/local/bin/imapdedup.py -s "${IMAPSRV}" -u "${USER}" -w "${PASS}" -x -c -m -S "${BOXRICEVUTE}"

Volendo, per rendere più veloce l’operazione, si sarebbe potuto scansionare la maildir, in luogo dell’operazione via imap, ma ho preferito quest’ultima così nel caso in cui un giorno si fosse preferito usare un account diverso, magari posto in un altro server, si sarebbe potuto riutilizzare lo script.
Per i vari flag utilizzati vi rimando alla documentazione o all’help dell’IMAPdedup

Per le email interne, che non si voleva venissero salvate per questioni di inutilità e di spazio, è stato risolto con una regola gestita da Sieve, e impostata sullo stesso account.

Sieve pensatelo come ad un servizio che gestisce regole e filtri, come lo farebbe il vostro client di posta preferito, ma che vengono eseguiti direttamente sul server.
Esiste sia come modulo da utilizzare con i più comuni imap server, ad esempio su Dovecot con Pigeonhole, oppure implementato direttamente nell’ MTA come nel caso di Exim.
Per maggiori informazioni e la sua diverse implementazioni vi rimando direttamente al sito ufficiale sieve.info

Il filtro Sieve semplicemente non faceva altro che confrontare mittente e destinatario, e nel caso entrambi avessero avuto @miodomio.com nell’indirizzo, l’email veniva semplicemente eliminata.

Lo stesso filtro Sieve, si occupa anche di suddividere le email ricevute, nei diversi folder, divisi per mese che in email inviate e ricevute.
In questo modo, si sarebbero avute N cartelle imap del tipo: Gennaio_Ricevute; Gennaio_Inviate; Febbraio_Ricevute; Febbraio_Inviate, etc.etc.
Qui potete trovare le regole impostate nel file .sieve per l’account [email protected]:

require ["body","date","fileinto"];
# rule:[Elimina_Interne]
if allof (header :contains "from" "@miodominio.com", header :contains "to" "@miodominio.com")
{
        discard;
        stop;
}
# rule:[Inviate_Gennaio]
if allof (currentdate :is "month" "01", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.gennaio";
        stop;
}
# rule:[Ricevute_Gennaio]
if allof (currentdate :is "month" "01", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.gennaio";
        stop;
}
# rule:[Inviate_Febbraio]
if allof (currentdate :is "month" "02", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.febbraio";
        stop;
}
# rule:[Ricevute_Febbraio]
if allof (currentdate :is "month" "02", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.febbraio";
        stop;
}
# rule:[Inviate_Marzo]
if allof (currentdate :is "month" "03", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.marzo";
        stop;
}
# rule:[Ricevute_Marzo]
if allof (currentdate :is "month" "03", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.marzo";
        stop;
}
# rule:[Inviate_Aprile]
if allof (currentdate :is "month" "04", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.aprile";
        stop;
}
# rule:[Ricevute_Aprile]
if allof (currentdate :is "month" "04", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.aprile";
        stop;
}
# rule:[Inviate_Maggio]
if allof (currentdate :is "month" "05", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.maggio";
        stop;
}
# rule:[Ricevute_Maggio]
if allof (currentdate :is "month" "05", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.maggio";
        stop;
}
# rule:[Inviate_Giugno]
if allof (currentdate :is "month" "06", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.giugno";
        stop;
}
# rule:[Ricevute_Giugno]
if allof (currentdate :is "month" "06", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.giugno";
        stop;
}
# rule:[Inviate_Luglio]
if allof (currentdate :is "month" "07", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.luglio";
        stop;
}
# rule:[Ricevute_Luglio]
if allof (currentdate :is "month" "07", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.luglio";
        stop;
}
# rule:[Inviate_Agosto]
if allof (currentdate :is "month" "08", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.agosto";
        stop;
}
# rule:[Ricevute_Agosto]
if allof (currentdate :is "month" "08", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.agosto";
        stop;
}
# rule:[Inviate_Settembre]
if allof (currentdate :is "month" "09", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.settembre";
        stop;
}
# rule:[Ricevute_Settembre]
if allof (currentdate :is "month" "09", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.settembre";
        stop;
}
# rule:[Inviate_Ottobre]
if allof (currentdate :is "month" "10", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.ottobre";
        stop;
}
# rule:[Ricevute_Ottobre]
if allof (currentdate :is "month" "10", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.ottobre";
        stop;
}
# rule:[Inviate_Novembre]
if allof (currentdate :is "month" "11", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.novembre";
        stop;
}
# rule:[Ricevute_Novembre]
if allof (currentdate :is "month" "11", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.novembre";
        stop;
}
# rule:[Inviate_Dicembre]
if allof (currentdate :is "month" "12", header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Inviate.dicembre";
        stop;
}
# rule:[Ricevute_Dicembre]
if allof (currentdate :is "month" "12", not header :contains "from" "@miodominio.com")
{
        fileinto "INBOX.Ricevute.dicembre";
        stop;
}

A questo punto restava solo da risolvere il problema di recuperare le email e salvarle in formato Mbox.
In questo, ci possono essere diverse soluzioni, anche accedere all’account imap usando uno dei tanti client di posta e di esportarle in un file unico. Ad esempio, per i miei usi personali, quando voglio fare la stessa cosa dal mio account gmail, uso Thunderbird e un plugin chiamato ImportExportTools.
Esso permette sia l’esportazione che l’importazione in qualsiasi formato, compreso l’mbox.
E’ un operazione che faccio per esportare le email più vecchie di un paio d’anni, che poi tengo localmente in un pc, con una copia di backup in un nas o disco esterno usb.

In questo caso specifico, ho preferito ancora una volta, fare uso di uno dei tanti tools di linux, formail, inserito in uno script shell che potete vedere qui:

#!/bin/bash
# Definisco alcune variabili globali
SUFFISSOANNO=`date +%Y`
DESTDIR="/mnt/DatiVari/FolderCondiviso"

## funzioni richiamate
function creaMboxFile() {
  DESTFILESUFF="${DESTARCHIVIO}_${SUFFISSOANNO}"
  echo " "
  echo " "
  echo "Creo file ${DESTFILESUFF}"
  echo "nella directory ${DESTDIR}"
  echo "la cui origine dei dati e ${ORIGDIR}"
  echo "Richiedera' molto tempo, Attendi prego..." 
  echo " "
  NUMCURMAIL=`find $ORIGDIR/cur/ -type f | wc -l`
  NUMNEWMAIL=`find $ORIGDIR/new/ -type f | wc -l`
  echo "Nel folder di origine ci sono ${NUMCURMAIL} emails lette, e ${NUMNEWMAIL} nuove non lette."
  CONTA=1
  for i in $ORIGDIR/cur/*
  do
   echo -ne " processo email ${CONTA} di ${NUMCURMAIL}\033[0K\r"
   formail -I "Status: RO" <"$i" >>"$DESTDIR/$DESTFILESUFF"
   CONTA=$((CONTA+1))
  done
  echo " "
  if [ ${NUMNEWMAIL} -gt 0 ]
  then
   echo "Email lette terminato. Ora processo le email nuove non lette"
   CONTA=1
   for i in $ORIGDIR/new/*
   do
    echo -ne " processo email ${CONTA} di ${NUMNEWMAIL}\033[0K\r"
    formail -I "Status: RO" <"$i" >>"$DESTDIR/$DESTFILESUFF"
    CONTA=$((CONTA+1))
   done
   echo " "
   echo "Email nuove non lette terminato."
  fi
  chmod 660 "$DESTDIR/$DESTFILESUFF"
  echo " "
  echo "Il file Mbox totale e' creato nel folder condiviso"
}

function creaRicevute() {
  ORIGDIR="/mnt/mailvolume/miodominio.com/raccoltaemail/Maildir/.Ricevute.$DESTARCHIVIO"
  creaMboxFile
}

function creaInviate() {
  ORIGDIR="/mnt/mailvolume/miodominio.com/raccoltaemail/Maildir/.Inviate.$DESTARCHIVIO"
  creaMboxFile
}

## da questo punto inizia la parte principale
clear
echo "Verranno creati archivi email per l'anno: ${SUFFISSOANNO}"

# Prima scelgo il mese
while true
do
echo "Prima scegli il mese di cui vuoi creare l' Mbox:"
echo "------------------------------------------------"
echo " "
echo " 1 - Gennaio"
echo " 2 - Febbraio"
echo " 3 - Marzo"
echo " 4 - Aprile"
echo " 5 - Maggio"
echo " 6 - Giugno"
echo " 7 - Luglio"
echo " 8 - Agosto"
echo " 9 - Settembre"
echo "10 - Ottobre"
echo "11 - Novembre"
echo "12 - Dicembre"
echo " "
echo -n "Digita il numero del mese scelto da 1 a 12  seguito da [INVIO]: "
read SCELTAMESE

case "$SCELTAMESE" in
        1)
          MESE="gennaio"
          break
          ;;
        2)
          MESE="febbraio"
          break
          ;;
        3)
          MESE="marzo"
          break
          ;;
        4)
          MESE="aprile"
          break
          ;;
        5)
          MESE="maggio"
          break
          ;;
        6)
          MESE="giugno"
          break
          ;;
        7)
          MESE="luglio"
          break
          ;;
        8)
          MESE="agosto"
          break
          ;;
        9)
          MESE="settembre"
          break
          ;;
        10)
          MESE="ottobre"
          break
          ;;
        11)
          MESE="novembre"
          break
          ;;
        12)
          MESE="dicembre"
          break
          ;;
        *)
          echo "--------------------------------------------------------"
          echo "| Scelta del mese e' errata. Immetti il numero corretto|"
          echo "--------------------------------------------------------"
          echo " "
          echo " "
          ;;
esac
done

echo "Ok, hai scelto di processare il mese: ${MESE}"
echo " "

# Poi scegli se inviate o ricevute, ed eseguo.
while true
do
echo "Ora scegli se vuoi il mese delle email:"
echo "1 - Ricevute"
echo "2 - Inviate"
echo " "
echo -n "Digita 1 o 2  seguito da [INVIO]: "
read TIPO
case "$TIPO" in
        1)
          echo "Hai scelto RICEVUTE"
          DESTARCHIVIO=${MESE}
          creaRicevute
          break
          ;;
        2)
          echo "Hai scelto INVIATE"
          DESTARCHIVIO=${MESE}
          creaInviate
          break
          ;;
        *)
          echo "---------------------------------------------------------"
          echo "| Scelta del tipo non corretto. Digita il numero corretto|"
          echo "---------------------------------------------------------"
          echo " "
          echo " "
          ;;
esac
echo " "
done

## ed infine esco.
echo "Script terminato.... bye, bye !"
echo "-------------------------------"
echo " "
exit 0

La persona preposta allo scopo, richiama lo script al bisogno, e sceglie di quale folder e mese preferisce generare l’archivio, che viene salvato in mbox in un volume e folder condiviso con samba, ftp, webdav o qualsiasi protocollo voluto, definito all’inizio con la variabile DESTDIR.
A quel punto, da un qualsiasi pc o sistema di backup, si può prelevare l’mbox e farne una copia dove meglio si preferisce.