Next Previous Contents

9. Virtuell Mail/POP

9.1 Problem

Virtuell mail støtte trengs stadig flere steder. Sendmail sier at det støtter virtuell mail. Hva den egentlig støtte, er at den hører etter innkommende mail fra forskjellige domener. Du kan deretter spesifisere at mailen skal sendes videre et eller annet sted. Men, hvis du sender mailen videre til den lokale maskin og har innkommende mail til bob@domain1.com og bob@domain2.com, vil mailen gå til samme mail folder. Dette er et problem siden begge bob'ene er forskjellige folk med forskjellig mail.

9.2 Løsningen

Du kan være sikker på at hvert brukernavn er unikt hvis du bruker et nummersystem: bob1, bob2, osv, eller henge på et par bokstaver på hvert brukernavn: dom1bob, dom2bob, osv. Du kunne også hacke mail og pop til å gjøre disse konverteringene "på bakrommet" men det kan bli sølete. Utgående mail har også navnet maindomain.com, og du vil ha hvert subdomene's utgående mailnavn til å være forskjellig.

Jeg har to løsninger. Den ene virker med Sendmail og den andre virker med Qmail. Løsningen til Sendmail burde virke med en vanlig installasjon av Sendmail. Men den deler alle de begrensningene som sendmail har. Det krever også at en Sendmail må bli kjørt i kø modus for hvert domene. Å ha 50 eller flere Sendmail køprosesser som våkner opp hver time kan beslaglegge ganske mye prosessorkraft på en singel maskin.

Løsningen med Qmail er at du trenger ikke kjøre flere Qmail prosesser og kan kjøre utifra en eneste køkatalog. Du må imidlertid ha ett ekstra program siden Qmail ikke baserer seg på virtuald. Jeg tror at en similiær prosedyre kan bli gjort med Sendmail, men Qmail er mye bedre utrustet til å motta løsningen som jeg har.

Jeg favoriserer ikke noe program. Sendmail installasjonen er litt mer rett fram, men Qmail er antakeligvis den kraftigste av de to mail programmene.

9.3 Sendmail Løsning

Introduksjon

Hvert virtuelt filsystem gir et domene dets egen /etc/passwd. Dette betyr at bob@domain1.com og bob@domain2.com er forskjellige brukere i forskjellige /etc/passwd's så mail vil ikke bli et problem. Domenet har også sin egen spool katalog, så mail folderne vil være forskjellige filer på forskjellige virtuelle filsystemer.

Lag Sendmail Konfigurasjonsfil

Lag /etc/sendmail.cf slik som du vanligvis ville gjort gjennom m4. Jeg brukte:

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

Editer Sendmail Konfigurasjonsfil

Editer /virtual/domain1.com/etc/sendmail.cf til å svare som ditt virtuelle domene:

vi /virtual/domain1.com/etc/sendmail.cf # Approximately Line 86 
Den skal si:

#Dj$w.Foo.COM

Erstatt linjen med:

Djdomain1.com

Sendmail Lokal Levering

Editer /virtual/domain1.com/etc/sendmail.cw med de lokale hostnavn.

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

Sendmail Mellom Virtuelle Domener: Hacken (PRE8.8.6)

Sendmail krever en liten forandring i kildekoden. Sendmail har en fil som heter /etc/sendmail.cw og denne filen inneholder alle maskinnavnene som Sendmail vil levere mail til lokalt istedet for å videresende til en annen maskin. Sendmail gjør intern sjekking på alle komponenter på maskinen for å initiere denne listen med de lokale IP'ene. Dette vil bli et problem hvis du mailer fram og tilbake mellom virtuelle domener på samme maskin. Sendmail vil bli lurt til å tro at et annet virtuelt domene er en lokal adresse og vil spoole mailen lokalt. For eksempel, bob@domain1.com sender mail til fred@domain2.com. Siden domain1.com's Sendmail tror at domain2.com er lokalt, vil den spoole mailen på domain1.com og aldri sende den til domain2.com. Du må modifisere Sendmail (Jeg gjorde dette på v8.8.5 uten problemer):

vi v8.8.5/src/main.c # Approximately Line 494
Linjen skal si:

load_if_names();

Erstatt med:

/* load_if_names(); Kommentert ut siden den er skadelig */

Merk: bare gjør dette hvis du trenger å sende mail imellom virtuelle domener, noe som jeg tror er ganske sannsynlig.

Dette vil fikse problemet, men hovedethernet komponenten, eth0, er ikke tatt bort. Hvis du derfor sender mail fra en virtuell IP til den IP'en som er bundet til eth0 på samme maskin, vil den levere lokalt. Derfor bruker jeg denne som en dummy IP virtual1.maindomain.com (10.10.10.157). Jeg vil aldri sende mail til denne hosten, og det vil da heller ikke noen av de virtuelle domenene. Dette er altså den IP'en jeg ville brukt til å ssh inn til maskinen for å sjekke om systemet er ok.

Sendmail Mellom Virtuelle Domener: Ny Sendmail Funksjon (POST8.8.6)

Fra versjon 8.8.6 er det en ny opsjon for å deaktivere lastingen av de ekstra nettverkskomponentene. Dette betyr at du IKKE trenger å forandre kildekoden på noen måte. Det er kalt DontProbeInterfaces .

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

vi /virtual/domain1.com/etc/sendmail.cf # Legg til linjen
O DontProbeInterfaces=True

Sendmail.init

Sendmail kan ikke bli startet som et alenestående program lengre, så du må kjøre det gjennom inetd. Dette er ueffektivt og vil resultere i tregere start tid, men hvis du hadde en slik høy hit site, burde du ikke ha delt det på en virtuell maskin med andre domener. Merk at du IKKE kjører med -bd flagget. Husk også på at du trenger en sendmail -q kjørende for hvert domene for å ta seg av å køe ulevert mail. Den nye sendmail.init filen:

#!/bin/sh

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

case "$1" in
  start)
        echo -n "Starting sendmail: "
        daemon sendmail -q1h
        echo
        echo -n "Starting virtual sendmail: "
        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 "Stopping sendmail: "
        killproc sendmail
        echo
        rm -f /var/lock/subsys/sendmail
        ;;
  *)
        echo "Usage: sendmail {start|stop}"
        exit 1
esac

exit 0

Inetd Oppsett

Pop skal installeres normalt uten ekstra arbeid. Den vil bare trenge inetd linjene for den med virtuald linjene addet. inetd.conf konfigurasjon for Sendmail og 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 Qmail Løsning

Introduksjon

Denne løsningen tar over leveringsansvaret for qmail-local, så bruk av .qmail filene i de virtuelle hjemmekatalogene vil ikke fungere. Likevel vil hvert domene få en domene master bruker som vil kontrollere aliasingen for hele domenet. To eksterne programmer vil bli brukt for domain masters .qmail standard filen. Mailen vil bli levert igjennom disse to programmene for å kunne levere mail for hvert domene.

To programmer trengs siden en av dem blir kjørt setuid root. Det er et lite program som forandrer til en ikke-root bruker og deretter kjører det andre programmet. Kikk på din nærmeste sikkerhetsrelaterte site for diskusjoner om hvorfor dette er nødvendig.

Denne løsningen eliminerer den ellers nødvendige bruken av virtuald. Qmail er fleksibelt nok til ikke å trenge ett generelt virtuald oppsett. Qmail's design tar i bruk flettingen av disse programmene sammen for å levere mail. Dette designet gjør det veldig lett å sette inn de virtuelle seksjonene i Qmail's leveringsprosess uten å forandre på Qmail's installasjon.

En merknad: Siden du bruker en Qmail, vil hvilket som helst ukvalifisert domenenavn bli ekspandert med domenet for hovedserveren. Dette skjer fordi at du ikke har en separat Qmail server for hvert domene. Vær derfor sikker på at din klient (Eudora, elm, mutt osv.) vet hvordan ekspandere alle dine ukvalifiserte domenenavn.

Oppsett Av Virtuelle Domener

Qmail må bli konfigurert til å akseptere mail for hvert av de virtuelle domenene du vil hoste. Skriv følgende kommandoer:

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

Oppsett Av Domain Master Bruker

Legg til brukeren domain1 til din hoved /etc/passwd fil. Jeg ville ha satt shellet /bin/false slik at domene master brukeren ikke kan logge inn. Den brukeren vil være istand til å legge til .qmail filer og all mail for domain1vil bli rutet gjennom den kontoen. Merk at brukernavn kan bare være åtte bokstaver og domenenavn kan være lengre. De resterende bokstavene vil bli skramblet. Dette betyr at bruker domain12 og bruker domain123 vil være den samme brukeren og Qmail kan bli forvirret. Så vær forsiktig med ditt master domene brukernavn.

Lag domene masteren's .qmail filer med følgende kommandoer. Legg til alle andre systemaliaser (hvis noen) nå. For eksempel, webmaster eller 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

Lag domene master's .qmail standard fil: Dette vil filtrere all mail til det virtuelle domenet.

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

Tcpserver

Qmail trenger en spesiell pop som kan støtte Maildir formatet. Pop programmet må virtualiserers. Programmereren av Qmail anbefaler bruken av tcpserver (en erstatter for inetd) med Qmail, så mine eksempler bruker tcpserver, IKKE inetd.

Tcpserver trenger ingen konfigurasjonsfil. All informasjon kan bli sent til den via kommandolinjen. Her er tcpserver.init filen som du må bruke for mail daemonen og 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`

# See how we were called.
case "$1" in
  start)
        echo -n "Starting 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 "Stopping tcpserver: "
        killall -TERM tcpserver 
        echo -n "killing "
        echo 
        rm -f /var/lock/subsys/tcpserver
        ;;
  *)
        echo "Usage: tcpserver {start|stop}"
        exit 1
esac

exit 0

Qmail.init

Du kan bruke det standard Qmail init scriptet. Qmail kommer med veldig god dokumentasjon om hvordan dette skal settes opp.

Kildekode

Du trenger to andre programmer for å få virtuell mail til å fungere med Qmail. De er virtmailfilter og vortmaildelivery. Dette er C kildekoden til virtmailfilter. Det burde bli installert i /usr/local/bin med rettigheter 4750, bruker root og gruppe 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));
}

Kildekode

Du trenger to andre programmer for å få virtuell mail til å fungere med Qmail. De er virtmailfilter og virtmaildelivery. Dette er C kildekoden til virtmaildelivery. Det burde bli installert i /usr/local/bin med rettigheter 0755, bruker root og gruppe 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 Takk

Takk til Vicente Gonzalez (vince@nycrc.net) for hjelpen med å få Qmail løsningen en realitet. Du kan virkelig maile din takk til Vince, men alle spørsmål og kommentarer som inkluderer Qmail i denne HOWTO'en skal fremdeles bli sendt til forfatter.


Next Previous Contents