Avanti Indietro Indice

9. Mail/Pop virtuale

9.1 Problema

La domanda per il supporto alla posta elettronica virtuale è in continua crescita. Sendmail dice di supportare la posta virtuale. Ciò che supporta in realtà è la ricezione di messaggi per domini diversi. Quindi si può specificare di reinoltrare la posta altrove. Comunque, se i messaggi vengono reinoltrati alla macchina locale e ci sono dei messaggi per bob@domain1.com e bob@domain2.com, essi finiranno nello stesso folder. Questo è un problema, dato che i `bob' sono persone diverse con posta diversa.

9.2 Soluzione

Ci si può accertare che ogni nomeutente sia unico, usando uno schema di numerazione: bob1, bob2 eccetera o preponendo pochi caratteri a ciascun nomeutente: dom1bob, dom2bob eccetera. Si potrebbe anche smanettare sui singoli programmi coinvolti, facendo in modo che eseguano queste conversioni per conto loro dietro le quinte, ma ciò potrebbe causare confusione. Inoltre i messaggi di posta in uscita hanno l'intestazione di dominio maindomain.com, mentre si vorrebbe che la posta in uscita avesse le intestazioni diversificate secondo i diversi sottodomini.

Propongo due soluzioni. Una funziona con sendmail e l'altra con Qmail. La soluzione che usa sendmail dovrebbe funzionare su un'installazione di base di sendmail. Comunque essa condivide tutte le limitazioni implicite di sendmail. Questa soluzione richiede inoltre che per ogni dominio venga eseguito un sendmail in modalità coda. Avere 50 o più processi di sendmail che si risvegliano ad ogni ora può sottoporre una macchina ad un carico non indifferente.

La soluzione che contempla l'uso di Qmail non richiede l'esecuzione di istanze multiple di Qmail e può fare a meno di una directory di coda. Richiede invece un programma extra, dato che Qmail non si appoggia a virtuald. Suppongo che una soluzione simile possa essere affrontata anche con sendmail. Ad ogni modo Qmail si presta a tale soluzione in modo più pulito.

Non appoggio comunque l'uso di un programma piuttosto che dell'altro. L'installazione di sendmail fila un po' più liscia ma Qmail è probabilmente il più potente dei due pacchetti.

9.3 La soluzione con Sendmail

Introduzione

Un filesystem virtuale per ogni dominio permette a quest'ultimo di avere il suo proprio /etc/passwd. Questo vuol dire che bob@domain1.com e bob@domain2.com sono utenti diversi presenti in file /etc/passwd diversi cosicché gestire la posta non sarà un problema. Inoltre i domini hanno ciascuno le proprie directory di spool, in modo che i folder di posta saranno file diversi in filesystem virtuali diversi.

Creare il file di configurazione di Sendmail

Si crei il file /etc/sendmail.cf come si farebbe normalmente usando m4. Io ho usato:

divert(0)
VERSIONID(`tcpproto.mc')
OSTYPE(linux)
FEATURE(redirect)
FEATURE(always_add_domain)
FEATURE(use_cw_file)
FEATURE(local_procmail)
MAILER(local)
MAILER(smtp)

Modificare il file di configurazione di Sendmail

Si modifichi /virtual/domain1.com/etc/sendmail.cf in modo che risponda con le intestazioni appropriate al proprio dominio virtuale:

vi /virtual/domain1.com/etc/sendmail.cf # Circa alla riga 86 
Dovrebbe esserci:
#Dj$w.Foo.COM

Rimpiazzarlo con:

Djdomain1.com

Consegna locale con Sendmail

Si introducano in /virtual/domain1.com/etc/sendmail.cw i nomi host locali.

vi /virtual/domain1.com/etc/sendmail.cw
mail.domain1.com
domain1.com
domain1
localhost

Posta tra domini virtuali con Sendmail: il trucco (Versioniprecedenti la 8.8.6)

In ogni caso, sendmail richiede una piccola modifica al codice sorgente. Sendmail ha un file chiamato /etc/sendmail.cw che contiene tutti i nomi delle macchine cui sendmail consegnerà la posta posta localmente invece di reindirizzarla ad un'altra macchina. Sendmail fa un controllo interno di tutti i dispositivi della macchina per inizializzare questa lista con gli indirizzi IP locali. Ciò causa un problema nel caso di invii di messaggi di posta tra domini virtuali sulla stessa macchina. Sendmail sarà portato a credere che l'altro dominio virtuale sia un indirizzo locale e tratterà i messaggi localmente. Ad esempio, bob@domain1.com invia un'e-mail a fred@domain2.com. Dato che il sendmail di domain1.com crede che domain2.com sia un indirizzo locale, metterà il messaggio nella directory di spool di domain1.com e non lo invierà mai a domain2.com. È necessario modificare sendmail (io l'ho fatto su una versione 8.8.5 senza problemi):

vi v8.8.5/src/main.c # Circa alla riga 494
Dovrebbe esserci:

load_if_names();

Rimpiazzarlo con:

/* load_if_names(); Commentato perché da problemi con i domini virtuali */

Da notare che questo passo è necessario solo se si vuole essere in grado di spedire posta tra i domini virtuali, cosa che ritengo probabile.

Ciò risolverà il problema. Comunque il device ethernet principale eth0 non viene rimosso. Quindi se si invia un messaggio di posta da un IP virtuale a quello usato da eth0 sulla stessa macchina, esso verrà consegnato localmente. Per questo io non faccio altro che usarlo come un IP posticcio virtual1.maindomain.com (10.10.10.157). Non invierò mai posta a questo host, né lo faranno i domini virtuali. Questo è anche l'IP che userei per collegarmi alla macchina a mezzo ssh per controllare se tutto va bene.

Posta tra domini virtuali con Sendmail: Nuove funzionalità(Versioni successive alla 8.8.6)

Dalla versione 8.8.6 di Sendmail è disponibile una nuova opzione, che permette di disabilitare il caricamento delle interfacce extra di rete. Ciò significa che NON è più necessario modificare il sorgente in alcun modo. Tale opzione è chiamata DontProbeInterfaces.

Modificare /virtual/domain1.com/etc/sendmail.cf

vi /virtual/domain1.com/etc/sendmail.cf # Aggiungere la linea
O DontProbeInterfaces=True

Sendmail.init

Sendmail non può più essere lanciato come demone `standalone' (indipendente), è necessario eseguirlo attraverso inetd. Ciò è inefficiente e causerà un peggioramento dei tempi di avvio, ma nel caso si avesse un sito con traffico piuttosto alto non gli si dovrebbe far comunque condividere un box virtuale con altri domini. È da notare che sendmail NON viene eseguito con l'opzione -bd. Si noti anche che è necessario venga eseguito un sendmail -q per ogni dominio, per processare la coda dei messaggi da consegnare. Ecco il nuovo file sendmail.init:

#!/bin/sh

. /etc/rc.d/init.d/functions

case "$1" in
  start)
        echo -n "Avvio di sendmail: "
        daemon sendmail -q1h
        echo
        echo -n "Avvio del sendmail virtuale: "
        for i in /virtual/*
        do
                if [ ! -d "$i" ]
                then
                        continue
                fi
                if [ "$i" = "/virtual/lost+found" ]
                then
                        continue
                fi
                chroot $i sendmail -q1h
                echo -n "."
        done
        echo " done"
        touch /var/lock/subsys/sendmail
        ;;
  stop)
        echo -n "Arresto di sendmail: "
        killproc sendmail
        echo
        rm -f /var/lock/subsys/sendmail
        ;;
  *)
        echo "Utilizzo: sendmail {start|stop}"
        exit 1
esac

exit 0

Configurazione di inetd

Il servizio pop si dovrebbe installare normalmente senza lavoro aggiuntivo. Basta solo che alla sua voce in inetd venga aggiunta la parte per virtuald. Ecco le voci di inetd.conf per sendmail e pop:

pop-3 stream tcp nowait root /usr/local/bin/virtuald \
        virtuald /virtual/conf.pop in.qpop -s 
smtp stream tcp nowait root /usr/local/bin/virtuald \
        virtuald /virtual/conf.mail sendmail -bs

9.4 La soluzione con Qmail

Introduzione

Questa soluzione scavalca qmail-local nelle mansioni di consegna, quindi i file .qmail nelle directory home virtuali non funzioneranno più. Comunque ogni dominio avrà ancora un utente responsabile del controllo sugli alias dell'intero dominio. A tale scopo verrano usati due programmi esterni per i file .qmail-default di tali utenti responsabili. La posta passerà attraverso questi due programmi per essere consegnata correttamente ad ogni dominio.

Sono richiesti due programmi poiché uno di essi viene eseguito con i privilegi di root. È un piccolo programma che cambia di volta in volta i propri privilegi ad un utente non root e manda in esecuzione il secondo. Si consulti un sito di documentazione sulla sicurezza per una disamina dei motivi per cui ciò è necessario.

Questa soluzione evita il bisogno di usare virtuald. Qmail è abbastanza flessibile da non richiere una configurazione tramite virtuald. Il modello progettuale su cui è basato Qmail utilizza il concatenamento di vari programmi per consegnare la posta. Questo modello rende molto facile inserire una sezione virtuale nel processo di consegna della posta di Qmail senza alterare l'installazione di base.

Occorre ricordare che, dato che si sta usando un unico server Qmail, qualunque nome di dominio non completamente qualificato verrà espanso usando il nome di dominio del server principale. Questo perché non si utilizza un server Qmail separato per ogni dominio. Perciò bisogna assicurarsi che i propri client (Eudora, elm, mutt, ecc.) siano configurati per espandere tutti i propri nomi di dominio non completamente qualificati.

Configurare i domini virtuali

Qmail dev'essere configurato per accettare messaggi di posta per ciascuno dei domini virtuali cui si vuole fornire il servizio. Si digitino i seguenti comandi:

echo "domain1.com:domain1" >> /var/qmail/control/virtualdomains

Configurare l'utente responsabile per il dominio

Si aggiunga al file /etc/passwd principale l'utente domain1. Meglio attribuirgli la shell /bin/false in modo che tale utente non possa accedere ad una console. Tale utente potrà aggiungere file .qmail e tutta la posta indirizzata al dominio virtuale domain1 passerà attraverso tale account. Si noti che i nomiutente possono essere lunghi solo otto caratteri mentre i nomi di dominio possono essere più lunghi. I caratteri che avanzano vengono troncati. Ciò significa che gli utenti dominio12 e dominio123 finiranno per essere lo stesso utente e Qmail potrebbe far confusione. Bisogna perciò fare attenzione a scegliere bene le proprie regole di denominazione dell'utente responsabile del dominio.

Si creino i file .qmail del responsabile di dominio con i seguenti comandi. Si aggiunga qualsiasi altro alias di sistema a questo punto, per es. webmaster o hostmaster.

echo "user@domain1.com" > /home/d/domain1/.qmail-mailer-daemon
echo "user@domain1.com" > /home/d/domain1/.qmail-postmaster
echo "user@domain1.com" > /home/d/domain1/.qmail-root

Si crei il file .qmail-default del responsabile di dominio. Questo file filtrerà tutta la posta indirizzata al dominio virtuale.

echo "| /usr/local/bin/virtmailfilter" > /home/d/domain1/.qmail-default

Tcpserver

Qmail richiede uno speciale programma pop, in grado di supportare il formato Maildir. Il programma pop dev'essere reso virtuale. L'autore di Qmail raccomanda di usare a questo scopo tcpserver (un rimpiazzo di inetd) con Qmail, quindi nei miei esempi userò tcpserver e NON inetd.

Tcpserver non richiede un file di configurazione. Tutte le informazioni necessarie gli possono essere passate da riga di comando. Segue il file tcpserver.init che si dovrebbe usare per i demoni di consegna e prelievo della posta (`mail demon' e `popper'):

#!/bin/sh

. /etc/rc.d/init.d/functions

QMAILDUSER=`grep qmaild /etc/passwd | cut -d: -f3`
QMAILDGROUP=`grep qmaild /etc/passwd | cut -d: -f4`

# Dare uno sguardo a come vengono chiamati.
case "$1" in
  start)
        echo -n "Avvio di tcpserver: "
        tcpserver -u 0 -g 0 0 pop-3 /usr/local/bin/virtuald \
                /virtual/conf.pop qmail-popup virt.domain1.com \
                /bin/checkpassword /bin/qmail-pop3d Maildir &
        echo -n "pop "  
        tcpserver -u $QMAILDUSER -g $QMAILDGROUP 0 smtp \
                /var/qmail/bin/qmail-smtpd &
        echo -n "qmail "
        echo
        touch /var/lock/subsys/tcpserver
        ;;
  stop)
        echo -n "Arresto di tcpserver: "
        killall -TERM tcpserver 
        echo -n "killing "
        echo 
        rm -f /var/lock/subsys/tcpserver
        ;;
  *)
        echo "Utilizzo: tcpserver {start|stop}"
        exit 1
esac

exit 0

Qmail.init

Si può utilizzare l'`init script' standard fornito con Qmail. La documentazione che accompagna Qmail è descrive ottimamente come farlo.

Sorgenti

Per far funzionare i servizi di posta virtuali con Qmail sono richiesti altri due programmi. Essi sono virtmailfilter e virtmaildelivery. Segue sotto il sorgente C di virtmailfilter. Il programma andrebbe installato in /usr/local/bin con modi 4750, utente root e gruppo nofiles.

#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>

#define VIRTPRE                 "/virtual"

#define VIRTPWFILE              "etc/passwd"
#define VIRTDELIVERY            "/usr/local/bin/virtmaildelivery"
#define VIRTDELIVERY0           "virtmaildelivery"

#define PERM                    100
#define TEMP                    111
#define BUFSIZE                 8192

int main(int argc,char **argv)
{
        char *username,*usernameptr,*domain,*domainptr,*homedir;
        char virtpath[BUFSIZE];
        struct passwd *p;
        FILE *fppw;
        int status;
        gid_t gid;
        pid_t pid;

        if (!(username=getenv("EXT")))
        {
                fprintf(stdout,"environment variable EXT not set\n");
                exit(TEMP);
        }

        for(usernameptr=username;*usernameptr;usernameptr++)
        {
                *usernameptr=tolower(*usernameptr);
        }

        if (!(domain=getenv("HOST")))
        {
                fprintf(stdout,"environment variable HOST not set\n");
                exit(TEMP);
        }

        for(domainptr=domain;*domainptr;domainptr++)
        {
                if (*domainptr=='.' && *(domainptr+1)=='.')
                {
                        fprintf(stdout,"environment variable HOST has ..\n");
                        exit(TEMP);
                }
                if (*domainptr=='/')
                {
                        fprintf(stdout,"environment variable HOST has /\n");
                        exit(TEMP);
                }

                *domainptr=tolower(*domainptr);
        }

        for(domainptr=domain;;)
        {
                snprintf(virtpath,BUFSIZE,"%s/%s",VIRTPRE,domainptr);
                if (chdir(virtpath)>=0)
                        break;

                if (!(domainptr=strchr(domainptr,'.')))
                {
                        fprintf(stdout,"domain failed: %s\n",domain);
                        exit(TEMP);
                }

                domainptr++;
        }

        if (!(fppw=fopen(VIRTPWFILE,"r+")))
        {
                fprintf(stdout,"fopen failed: %s\n",VIRTPWFILE);
                exit(TEMP);
        }

        while((p=fgetpwent(fppw))!=NULL)
        {
                if (!strcmp(p->pw_name,username))
                        break;
        }

        if (!p)
        {
                fprintf(stdout,"user %s: not exist\n",username);
                exit(PERM);
        }

        if (fclose(fppw)==EOF)
        {
                fprintf(stdout,"fclose failed\n");
                exit(TEMP);
        }

        gid=p->pw_gid;
        homedir=p->pw_dir;

        if (setgid(gid)<0 || setuid(p->pw_uid)<0)
        {
                fprintf(stdout,"setuid/setgid failed\n");
                exit(TEMP);
        }

        switch(pid=fork())
        {
                case -1:
                        fprintf(stdout,"fork failed\n");
                        exit(TEMP);
                case 0:
                        if (execl(VIRTDELIVERY,VIRTDELIVERY0,username,homedir,NULL)<0)
                        {
                                fprintf(stdout,"execl failed\n");
                                exit(TEMP);
                        }
                default:
                        if (wait(&status)<0)
                        {
                                fprintf(stdout,"wait failed\n");
                                exit(TEMP);
                        }
                        if (!WIFEXITED(status))
                        {
                                fprintf(stdout,"child did not exit normally\n");
                                exit(TEMP);
                        }
                        break;
        }

        exit(WEXITSTATUS(status));
}

Sorgenti

Per far funzionare i servizi di posta virtuali con Qmail sono richiesti altri due programmi. Essi sono virtmailfilter e virtmaildelivery. Segue sotto il sorgente C di virtmaildelivery. Andrebbe installato in /usr/local/bin con modi 0755, utente root e gruppo root.

#include <sys/stat.h>
#include <sys/file.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

#define TEMP                    111
#define BUFSIZE                 8192
#define ATTEMPTS                10

int main(int argc,char **argv)
{
        char *user,*homedir,*dtline,*rpline,buffer[BUFSIZE],*p,mail[BUFSIZE];
        char maildir[BUFSIZE],newmaildir[BUFSIZE],host[BUFSIZE];
        int fd,n,nl,i,retval;
        struct stat statp;
        time_t thetime;
        pid_t pid;
        FILE *fp;

        retval=0;

        if (!argv[1])
        {
                fprintf(stdout,"invalid arguments: need username\n");
                exit(TEMP);
        }

        user=argv[1];

        if (!argv[2])
        {
                fprintf(stdout,"invalid arguments: need home directory\n");
                exit(TEMP);
        }

        homedir=argv[2];

        if (!(dtline=getenv("DTLINE")))
        {
                fprintf(stdout,"environment variable DTLINE not set\n");
                exit(TEMP);
        }

        if (!(rpline=getenv("RPLINE")))
        {
                fprintf(stdout,"environment variable RPLINE not set\n");
                exit(TEMP);
        }

        while (*homedir=='/')
                homedir++;
        snprintf(maildir,BUFSIZE,"%s/Maildir",homedir);
        if (chdir(maildir)<0)
        {
                fprintf(stdout,"chdir failed: %s\n",maildir);
                exit(TEMP);
        }

        time(&thetime);
        pid=getpid();
        if (gethostname(host,BUFSIZE)<0)
        {
                fprintf(stdout,"gethostname failed\n");
                exit(TEMP);
        }

        for(i=0;i<ATTEMPTS;i++)
        {
                snprintf(mail,BUFSIZE,"tmp/%u.%d.%s",thetime,pid,host);
                errno=0;
                stat(mail,&statp);
                if (errno==ENOENT)
                        break;

                sleep(2);
                time(&thetime);
        }
        if (i>=ATTEMPTS)
        {
                fprintf(stdout,"could not create %s\n",mail);
                exit(TEMP);
        }

        if (!(fp=fopen(mail,"w+")))
        {
                fprintf(stdout,"fopen failed: %s\n",mail);
                retval=TEMP; goto unlinkit;
        }

        fd=fileno(fp);

        if (fprintf(fp,"%s",rpline)<0)
        {
                fprintf(stdout,"fprintf failed\n");
                retval=TEMP; goto unlinkit;
        }

        if (fprintf(fp,"%s",dtline)<0)
        {
                fprintf(stdout,"fprintf failed\n");
                retval=TEMP; goto unlinkit;
        }

        while(fgets(buffer,BUFSIZE,stdin))
        {
                for(p=buffer;*p=='>';p++)
                        ;

                if (!strncmp(p,"From ",5))
                {
                        if (fputc('>',fp)<0)
                        {
                                fprintf(stdout,"fputc failed\n");
                                retval=TEMP; goto unlinkit;
                        }
                }

                if (fprintf(fp,"%s",buffer)<0)
                {
                        fprintf(stdout,"fprintf failed\n");
                        retval=TEMP; goto unlinkit;
                }
        }

        p=buffer+strlen(buffer);
        nl=2;
        if (*p=='\n')
                nl=1;

        for(n=0;n<nl;n++)
        {
                if (fputc('\n',fp)<0)
                {
                        fprintf(stdout,"fputc failed\n");
                        retval=TEMP; goto unlinkit;
                }
        }

        if (fsync(fd)<0)
        {
                fprintf(stdout,"fsync failed\n");
                retval=TEMP; goto unlinkit;
        }

        if (fclose(fp)==EOF)
        {
                fprintf(stdout,"fclose failed\n");
                retval=TEMP; goto unlinkit;
        }

        snprintf(newmaildir,BUFSIZE,"new/%u.%d.%s",thetime,pid,host);
        if (link(mail,newmaildir)<0)
        {
                fprintf(stdout,"link failed: %s %s\n",mail,newmaildir);
                retval=TEMP; goto unlinkit;
        }

unlinkit:
        if (unlink(mail)<0)
        {
                fprintf(stdout,"unlink failed: %s\n",mail);
                retval=TEMP;
        }

        exit(retval);
}

9.5 Ringraziamenti

Ringrazio Vicente Gonzalez (vince@nycrc.net) per l'aiuto che ha reso possibile la soluzione presentata per Qmail. È certo possibile ringraziare Vince tramite e-mail, comunque le domande e i commenti su questioni che riguardano Qmail nel contesto di questo HOWTO dovrebbero essere indirizzati al sottoscritto.


Avanti Indietro Indice