Integrating Exim, IMAP, and webmail in Gentoo
The aim is to provide a mail server that serves any number of
domains,
and integrates access from MTAs and web browsers. The environment
uses Gentoo Linux, Exim, Courier IMAP and SquirrelMail.
The target users are clubs, societies, small companies and other
smallish, relatively low load setups.
The server I have built has no other services running on it.
Exim is configured to check for Viruses and spam. Virus infected
mails are dropped, spam is flagged, but delivered.
MySQL is used to hold the list of domains and users.
All components use the database to obtain authentication and
authorisation data.
Building the basic system
To build the basic system install a suitable Gentoo stage file,
set your USE flags in /etc/make.conf:
USE="amavis apache2 exim exiscan exiscan-acl filter imap maildir
mbox mysql mysqli php server spell spf srs syslog threads vhosts xml
-fortran ssl"
and then do 'emerge -n world' using this world file:
app-admin/procinfo
www-servers/apache
sys-libs/gpm
net-mail/courier-imap
mail-filter/spamassassin
www-apache/mod_auth_mysql
mail-client/squirrelmail
dev-db/mysql
app-admin/apache-tools
app-admin/sudo
mail-client/mailx
net-misc/rdate
dev-libs/openssl
app-portage/gentoolkit
app-portage/mirrorselect
dev-lang/php
sys-process/vixie-cron
app-editors/nano
dev-perl/Archive-Zip
sys-apps/xinetd
dev-perl/IP-Country
sys-boot/grub
sys-apps/parted
app-editors/vim
net-analyzer/netcat
mail-filter/razor
dev-python/pyzor
app-admin/sysklogd
dev-perl/Mail-ClamAV
net-misc/whois
net-analyzer/nmap
dev-perl/DBD-mysql
mail-mta/exim
sys-kernel/vanilla-sources
sys-process/cronbase
net-misc/openssh
sys-apps/module-init-tools
net-dns/bind
app-portage/ufed
app-text/rcs
www-apache/mod_perl
dev-perl/Mail-SPF-Query
net-firewall/iptables
sys-apps/less
sys-apps/baselayout
net-misc/ntp
app-antivirus/clamav
net-nds/portmap
dev-perl/MIME-tools
net-analyzer/tcpdump
Configuring Linux and core utilities
Use rc-update to configure your startup scripts as
follows:
apache2 | default
bootmisc | boot
checkfs | boot
checkroot | boot
clamd | default
clock | boot
consolefont | boot
courier-authlib | default
courier-imapd | default
courier-imapd-ssl | default
courier-pop3d-ssl | default
exim | default
hostname | boot
iptables | default
keymaps | boot
local | default nonetwork
localmount | boot
modules | boot
mysql | default
net.lo | boot
netmount | default
nfs | default
ntpd | default
rmnologin | boot
spamd | default
sshd | default
sysklogd | boot
urandom | boot
vixie-cron | default
xinetd | default
The mail is going to live in /virtual. If this is a
separate disk partition you can increase system safety by adding
mount -o ro,remount /
to /etc/conf.d/local.start.
IPTables
Here is my iptables setup, as stored in
/etc/conf.d/local.start:
PRIVATE_NET=172.16.0.0/12
LOCAL_NET=62.49.238.224/28
IPTABLES="iptables "
$IPTABLES -F INPUT
$IPTABLES -A INPUT -p udp -m multiport --dports 1024:65535 -j ACCEPT
$IPTABLES -A INPUT -p tcp -m tcp --dport domain -j ACCEPT
$IPTABLES -A INPUT -p udp -m multiport --dports domain,ntp -j ACCEPT
$IPTABLES -A INPUT -i lo -j ACCEPT
$IPTABLES -A INPUT -p tcp -m multiport \
--dports ident,ssh,smtp,http,ntp,imap,pop3,https,pop3s,imaps \
-j ACCEPT $IPTABLES -A INPUT -p tcp -s $LOCAL_NET -j ACCEPT
# This next line of for Microsoft software, which is unable to
# use the standard ports.
$IPTABLES -A INPUT -p tcp -m multiport --dports 465,587 -j ACCEPT
$IPTABLES -A INPUT -p icmp -s $PRIVATE_NET -j ACCEPT
$IPTABLES -A INPUT -p tcp -s $PRIVATE_NET -j ACCEPT
$IPTABLES -A INPUT -p icmp -s $LOCAL_NET -j ACCEPT
$IPTABLES -A INPUT -p tcp -s $LOCAL_NET -j ACCEPT
$IPTABLES -A INPUT -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPTABLES -P INPUT DROP
The variable LOCAL_NET covers the set of IP adresses that I own
and that are visible on the internet. PRIVATE_NET covers those IP
addresses that can only be used internally to my network.
NTPD
Setting 'server pool.ntp.org' in /etc/ntp.conf
should be adequate. If you have a private network with your own
NTP server you may prefer to have all your other machines pointing to
that private server.
Configuring Clam AV and SpamAssassin
Clam AV
You only need clamd and freshclam, not
amavisd.
Clamd can talk over either a Unix or a TCP socket.
Do not try to use the Unix socket, you will get all sorts of
permissions problems. Instead, use the TCP socket: it may be
slightly less efficient, but it works with no problems.
Ensure that your exim.conf matches whatever you configure here.
You might get away with using the Unix socket if you make user
'mail' part of the 'clamav' group, and make user 'clamav' part of
the 'mail' group, but I have not tested this configuration.
Failing this you have to make 'clamd' run as user 'mail, and
ensure that all its files are owned by 'mail'.
Configuring MySQL
First, set a root password in MySQL. Next, create the necessary
DB and table structure:
create database Mail;
use Mail;
-- Which domains do we service
create table Domains (domain varchar(255)) max_rows=100
if not exists Domains;
-- The users in each domain
create table if not exists Users(
user varchar(255) primary key,-- $user@$domain
password varchar(255) not null, -- clear text password
or 'cryptpwd'
forward varchar(1024), -- where to
forward all mail
expiry bigint not null default 0,
quota varchar(64),
homedir varchar(1024) not null,
maildir varchar(1024) not null,
uid int not null default 8, -- User 'mail'
gid int not null default 12,-- Group 'mail'
realName varchar(255),
force_change_pwd smallint default 1 -- New users must
change their passwords
)
max_rows=1000;
grant select on Mail.*
to mail@localhost identified by 'MailPassword';
grant select on Mail.*
to mail@127.0.0.1 identified by 'MailPassword';
grant update (password,force_change_pwd) on Mail.Users
to mail@localhost identified by 'MailPassword';
grant update (password,force_change_pwd) on Mail.Users
to mail@127.0.0.1 identified by 'MailPassword';
We store passwords in clear, but encourage users to use a form of
authentication that does not transmit clear-text passwords over
the net, either CRAM-MD5 or SSL/TLS. CRAM-MD5
requires that we store clear-text passwords. For
PLAIN and LOGIN authentication we can use either plain-text or
encrypted passwords.
Creating SSL certificates
I shall deal here only with creating a self-signed certificate.
This is adequate for small setups so long as your users are aware
that they will get a warning message the first time that they
connect to your system and that they need to explictly accept the
certificate.
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 3650
These commands will generate a private key in
privkey.pem and a self-signed certificate valid for 10
years (3650 days) in cacert.pem.
You can copy these files to wherever your configuration files
require you to have them. They will be needed for Apache SSL,
Exim SSL/TLS, IMAPS (IMAP with SSL/TLS) and for POP3 with
SSL/TLS.
As you generate the files, openssl will prompt you for
various data. The CN (common name) should be the true name of
your server, most of the other values are obvious but their
actual values are not terribly important.
Just make them sensible so that you do not confuse your users.
Other sources
Configuring Exim
The local host list is hard coded in exim.conf,
but the local domain list is obtained from a
DB query. Authentication of users is done with the DB.
Each domain may have an alias file that is checked before we check for
local user names. Aliases are private to each domain; an alias in
one domain does not interfere with an identical alias in another
domain.
Ensure that communication with Clamd for virus checking is done
over the TCP socket.
SMTP Send requires that the user be authenticated.
CRAM-MD5 authentication is permittted over an unencrypted
channel; PLAIN and LOGIN authentication require encryption,
either SSL or TLS.
The Exim configuration file is too large to include here
verbatim. Instead,
here is the diff between my exim.conf and the original
distribution file:
59,76d58
< hide mysql_servers = localhost/myDatabase/myUser/myPassword
<
< # Exim only cares whether this query succeeds, not what it returns
< DOMAIN_LIST = select domain from Domains where domain='$domain'
<
< # In these cases we *do* want the actual data
< USER_QUERY = select forward, homedir from Users \
< where user = \
< concat(lcase('$local_part'), '@', lcase('$domain'))
< PLAINAUTH_QUERY = select 1 from Users \
< where user = lcase('$auth2') \
< and password='$auth3'
< LOGINAUTH_QUERY = select 1 from Users \
< where user = lcase('$auth1') \
< and password='$auth2'
< CRAMMD5AUTH_QUERY = select password from Users \
< where user = lcase('$auth1')
<
78d59
< domainlist virtual_domains = mysql;DOMAIN_LIST
81d61
< #hostlist relay_from_hosts = 127.0.0.1 : 172.16.0.0/12 : 62.49.238.224/28
142,145c122
< # av_scanner=clamd:/var/run/clamav/clamd.sock
< av_scanner = clamd:127.0.0.1 3310
< # for sophos scanner
< # av_scanner = cmdline: /usr/local/bin/sweep -all -archive -ss %s: found:'(.+)'
---
> # av_scanner = clamd:/tmp/clamd
153c130
< spamd_address = 127.0.0.1 783
---
> # spamd_address = 127.0.0.1 783
166c143
< tls_advertise_hosts = *
---
> # tls_advertise_hosts = *
174,175c151,152
< tls_certificate = /etc/ssl/exim.crt
< tls_privatekey = /etc/ssl/exim.pem
---
> # tls_certificate = /etc/ssl/exim.crt
> # tls_privatekey = /etc/ssl/exim.pem
186,187c163,164
< daemon_smtp_ports = 25 : 465 : 587
< tls_on_connect_ports = 465
---
> # daemon_smtp_ports = 25 : 465 : 587
> # tls_on_connect_ports = 465
314c291
< split_spool_directory = true
---
> # split_spool_directory = true
363c340
< domains = +local_domains : +virtual_domains
---
> domains = +local_domains
378c355
< domains = !+local_domains : !+virtual_domains
---
> domains = !+local_domains
386c363
< domains = +local_domains :!+virtual_domains
---
> domains = +local_domains
427c404
< domains = +local_domains : +relay_to_domains : +virtual_domains
---
> domains = +local_domains : +relay_to_domains
443,445c420,422
< deny message = rejected because $sender_host_address is in a blacklist at $dnslist_domain\n$dnslist_text
< dnslists = dnsbl.sorbs.net : relays.ordb.org : spam.dnsrbl.net
<
---
> # deny message = rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
> # dnslists = black.list.example
> #
481,482c458,459
< deny malware = *
< message = This message contains a virus ($malware_name).
---
> # deny malware = *
> # message = This message contains a virus
($malware_name).
488,499c465,469
<
< # reject spam at high scores (> 8)
< deny spam = nobody:true
< condition = ${if >{$spam_score_int}{8}{1}{0}}
< message = This message scored $spam_score spam points.
<
<
< warn spam = nobody:true
< add_header = X-Spam_score: $spam_score\n\
< X-Spam_score_int: $spam_score_int\n\
< X-Spam_bar: $spam_bar\n\
< X-Spam_report: $spam_report
---
> # warn spam = nobody
> # add_header = X-Spam_score: $spam_score\n\
> # X-Spam_score_int: $spam_score_int\n\
> # X-Spam_bar: $spam_bar\n\
> # X-Spam_report: $spam_report
547c517
< domains = ! +local_domains : !+virtual_domains
---
> domains = ! +local_domains
551a522
>
555,571d525
< virtual_user:
< driver = accept
< domains = +virtual_domains
< router_home_directory=${extract {homedir}{${lookup mysql{USER_QUERY}}}{$value}fail}
< condition = ${if gt{$home}{}}
< transport = virtual_delivery
<
< virtual_alias:
< driver = redirect
< domains = +virtual_domains
< local_part_suffix = +*
< local_part_suffix_optional
< data = ${if exists{/virtual/${domain}/mail/aliases}{${lookup{$local_part}lsearch*{/virtual/${domain}/mail/aliases}}}}
< qualify_preserve_domain
< file_transport = address_file
< pipe_transport = address_pipe
<
597,598c551
< domains = +local_domains
< data = ${lookup{$local_part}lsearch*{/etc/mail/aliases}}
---
> data = ${lookup{$local_part}lsearch{/etc/mail/aliases}}
687,699d639
< # Courier-imap assumes that the delivery will be to $homedir/Maildir
< virtual_delivery:
< driver = appendfile
< directory=${extract {homedir}{${lookup mysql{USER_QUERY}}}{$value}fail}/Maildir
< delivery_date_add
< envelope_to_add
< return_path_add
< user = mail
< maildir_format
< maildir_use_size_file
< quota = ${lookup mysql{select quota from Domains where domain='$domain'}{$value}{20M}}
< quota_warn_threshold = 75%
<
762,765c702
< # Immediate bounce of over-quota messages
< * quota
< #Catchall rule for other errors
< * * F,2h,15m; G,16h,1h,1.5; F,4d,6h
---
> * * F,2h,15m; G,16h,1h,1.5; F,4d,6h
808,813c745,750
< PLAIN:
< driver = plaintext
< server_set_id = $2
< server_prompts = :
< server_condition = ${lookup mysql{PLAINAUTH_QUERY}{$value}fail}
< server_advertise_condition = ${if def:tls_cipher }
---
> #PLAIN:
> # driver = plaintext
> # server_set_id = $auth2
> # server_prompts = :
> # server_condition = Authentication is not yet configured
> # server_advertise_condition = ${if def:tls_cipher }
820,830c757,762
< LOGIN:
< driver = plaintext
< server_set_id = $1
< server_prompts = <| Username: | Password:
< server_condition = ${lookup mysql{LOGINAUTH_QUERY}{$value}fail}
< server_advertise_condition = ${if def:tls_cipher }
<
< lookup_cram:
< driver = cram_md5
< public_name = CRAM-MD5
< server_secret = ${lookup mysql{CRAMMD5AUTH_QUERY}{$value}fail}
---
> #LOGIN:
> # driver = plaintext
> # server_set_id = $auth1
> # server_prompts = <| Username: | Password:
> # server_condition = Authentication is not yet configured
> # server_advertise_condition = ${if def:tls_cipher }
832d763
< server_set_id = $1
Configuring IMAP
Basic configuration
First, configure /etc/courier-imap/imapd.
Ensure that IMAP_CAPABILITY contains 'AUTH=CRAM-MD5'. I ensure
that rubbish does not lie around for too long by including
IMAP_EMPTYTRASH=Trash:7,Sent:30,Spam:14
The MAILDIR and MAILDIRPATH are both set to
'Maildir'. A common alternative is '.maildir', but you must ensure that
courier-imap, courier-pop3, exim and the database all specify the
same name.
Configuring to use SSL/TLS
For SSL you have to alter /etc/courier-imap/imapd-ssl.
About all you need to do is to alter TLS_TRUSTCERTS to
TLS_TRUSTCERTS=/etc/ssl/certs/
which is where openssl has put all the standard certificates
and, again, to set up MAILDIR and MAILDIRPATH.
Copy the private key and certificate files that you generated to
wherever your config file says they need to go.
Configuring to authenticate using the DB
In /etc/courier/authlib ensure that you have
authmodulelist="authmysql "
Then in authmysqlrc you need
MYSQL_SERVER localhost
MYSQL_USERNAME mail
MYSQL_PASSWORD MailPassword
# The socket address varies from installation to installation,
# check that it is right for your system.
MYSQL_SOCKET /var/run/mysqld/mysqld.sock
MYSQL_PORT 3306
MYSQL_DATABASE Mail
MYSQL_USER_TABLE Users
MYSQL_CLEAR_PWFIELD password
MYSQL_UID_FIELD uid
MYSQL_GID_FIELD gid
MYSQL_LOGIN_FIELD user
MYSQL_HOME_FIELD homedir
MYSQL_NAME_FIELD realName
MYSQL_MAILDIR_FIELD maildir
These lines are scatterd though the file. You have to find them
and edit them appropriately. We do not actually care about the
uid and gid fields, but Courier-imap insists on having them.
Configuring POP3
Basic configuration
Include
POP3AUTH="CRAM-MD5"
POP3AUTH_ORIG="PLAIN LOGIN CRAM-MD5 CRAM-SHA1 CRAM-SHA256"
POP3AUTH_TLS_ORIG="LOGIN PLAIN"
POP3DSTART=YES
MAILDIRPATH=Maildir
MAILDIR=Maildir
at the appropriate places in /etc/courier-imap/pop3d.
Configuring to authenticate using the DB
The configuration you set up for IMAP will also apply for POP3;
there is no need to do anything extra.
Configuring to use SSL/TLS
Apart from making sure that the certificate file exists and is
enabled in /etc/courier-imap/pop3d-ssl (copy the files
you made earlier) there is almost
nothing
to do. You can decide for yourself whether to force TLS for all
users, or to allow also-non-encrypted sessions. This is
configured with POP3_TLS_REQUIRED.
As always, make sure you have MAILDIR and MAILDIRPATH properly
configured.
Configuring Apache and SquirrelMail
SquirrelMail
Run
/usr/share/webapps/squirrelmail/$version/htdocs/config/conf.pl
to configure your system.
Plugins
You will need the administrator, compatibility and
change_sqlpass plugins. The last two are not part of the standard
Squirrelmail package and you will have to download them separately
from the plugins repository. You may also decide to install other
plugins such as calendar, squirrelspell and filters. 'Filters'
allows users to set up rules to filter their mail and move it to
private folders or perform other actions on it.
Apache 2
Basic configuration
For security, http protocol is redirected to https, however for
initial testing I did everything on http.
Note that all my domains are named explictly in the configuration
blocks.
<VirtualHost mail.mankin.org.uk:80>
ServerName mail.mankin.org.uk
Redirect / https://mail.mankin.org.uk/
</VirtualHost>
<IfDefine SSL>
<IfDefine SSL_DEFAULT_VHOST>
<IfModule ssl_module>
# see bug #178966 why this is in here
# When we also provide SSL we have to listen to the HTTPS port
# Note: Configurations that use IPv6 but not IPv4-mapped addresses need two
# Listen directives: "Listen [::]:443" and "Listen 0.0.0.0:443"
<VirtualHost mail.mankin.org.uk:443>
ServerName mail.mankin.org.uk
ScriptAlias /cgi-bin /usr/share/webapps/squirrelmail/1.4.10a-r2/hostroot/cgi-bin
Alias /icons /usr/share/webapps/squirrelmail/1.4.10a-r2/hostroot/icons
DocumentRoot /usr/share/webapps/squirrelmail/1.4.10a-r2/htdocs/
<Location /cgi-bin>
Order deny,allow
Deny from all
Allow from 62.49.238.224/28
Allow from 172.16.0.0/12
Allow from 127.0.0.1
</Location>
<Location />
Options Indexes
DirectoryIndex index.php
Order allow,deny
Allow from All
</Location>
<Location /po>
Order deny,allow
Deny from all
</Location>
<Location /config>
Order deny,allow
Deny from all
</Location>
ErrorLog /var/log/apache2/ssl_error_log
<IfModule log_config_module>
TransferLog /var/log/apache2/ssl_access_log
</IfModule>
## SSL Engine Switch:
# Enable/Disable SSL for this virtual host.
SSLEngine on
## SSL Cipher Suite:
# List the ciphers that the client is permitted to negotiate.
# See the mod_ssl documentation for a complete list.
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
## Server Certificate:
# Point SSLCertificateFile at a PEM encoded certificate. If the certificate
# is encrypted, then you will be prompted for a pass phrase. Note that a
# kill -HUP will prompt again. Keep in mind that if you have both an RSA
# and a DSA certificate you can configure both in parallel (to also allow
# the use of DSA ciphers, etc.)
SSLCertificateFile /etc/apache2/ssl/server.crt
#SSLCertificateFile /etc/apache2/ssl/server-dsa.crt
## Server Private Key:
# If the key is not combined with the certificate, use this directive to
# point at the key file. Keep in mind that if you've both a RSA and a DSA
# private key you can configure both in parallel (to also allow the use of
# DSA ciphers, etc.)
SSLCertificateKeyFile /etc/apache2/ssl/server.key
#SSLCertificateKeyFile /etc/apache2/ssl/server-dsa.key
## Server Certificate Chain:
# Point SSLCertificateChainFile at a file containing the concatenation of
# PEM encoded CA certificates which form the certificate chain for the
# server certificate. Alternatively the referenced file can be the same as
# SSLCertificateFile when the CA certificates are directly appended to the
# server certificate for convinience.
#SSLCertificateChainFile /etc/apache2/ssl/ca.crt
## Certificate Authority (CA):
# Set the CA certificate verification path where to find CA certificates
# for client authentication or alternatively one huge file containing all
# of them (file must be PEM encoded).
# Note: Inside SSLCACertificatePath you need hash symlinks to point to the
# certificate files. Use the provided Makefile to update the hash symlinks
# after changes.
#SSLCACertificatePath /etc/apache2/ssl/ssl.crt
#SSLCACertificateFile /etc/apache2/ssl/ca-bundle.crt
## Certificate Revocation Lists (CRL):
# Set the CA revocation path where to find CA CRLs for client authentication
# or alternatively one huge file containing all of them (file must be PEM
# encoded).
# Note: Inside SSLCARevocationPath you need hash symlinks to point to the
# certificate files. Use the provided Makefile to update the hash symlinks
# after changes.
#SSLCARevocationPath /etc/apache2/ssl/ssl.crl
#SSLCARevocationFile /etc/apache2/ssl/ca-bundle.crl
## Client Authentication (Type):
# Client certificate verification type and depth. Types are none, optional,
# require and optional_no_ca. Depth is a number which specifies how deeply
# to verify the certificate issuer chain before deciding the certificate is
# not valid.
#SSLVerifyClient require
#SSLVerifyDepth 10
## Access Control:
# With SSLRequire you can do per-directory access control based on arbitrary
# complex boolean expressions containing server variable checks and other
# lookup directives. The syntax is a mixture between C and Perl. See the
# mod_ssl documentation for more details.
#<Location />
# #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \
# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \
# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \
# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \
# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \
# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/
#</Location>
## SSL Engine Options:
# Set various options for the SSL engine.
## FakeBasicAuth:
# Translate the client X.509 into a Basic Authorisation. This means that the
# standard Auth/DBMAuth methods can be used for access control. The user
# name is the `one line' version of the client's X.509 certificate.
# Note that no password is obtained from the user. Every entry in the user
# file needs this password: `xxj31ZMTZzkVA'.
## ExportCertData:
# This exports two additional environment variables: SSL_CLIENT_CERT and
# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the server
# (always existing) and the client (only existing when client
# authentication is used). This can be used to import the certificates into
# CGI scripts.
## StdEnvVars:
# This exports the standard SSL/TLS related `SSL_*' environment variables.
# Per default this exportation is switched off for performance reasons,
# because the extraction step is an expensive operation and is usually
# useless for serving static content. So one usually enables the exportation
# for CGI and SSI requests only.
## StrictRequire:
# This denies access when "SSLRequireSSL" or "SSLRequire" applied even under
# a "Satisfy any" situation, i.e. when it applies access is denied and no
# other module can change it.
## OptRenegotiate:
# This enables optimized SSL connection renegotiation handling when SSL
# directives are used in per-directory context.
#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory "/var/www/localhost/cgi-bin">
SSLOptions +StdEnvVars
</Directory>
## SSL Protocol Adjustments:
# The safe and default but still SSL/TLS standard compliant shutdown
# approach is that mod_ssl sends the close notify alert but doesn't wait
# for the close notify alert from client. When you need a different
# shutdown approach you can use one of the following variables:
## ssl-unclean-shutdown:
# This forces an unclean shutdown when the connection is closed, i.e. no
# SSL close notify alert is send or allowed to received. This violates the
# SSL/TLS standard but is needed for some brain-dead browsers. Use this when
# you receive I/O errors because of the standard approach where mod_ssl
# sends the close notify alert.
## ssl-accurate-shutdown:
# This forces an accurate shutdown when the connection is closed, i.e. a
# SSL close notify alert is send and mod_ssl waits for the close notify
# alert of the client. This is 100% SSL/TLS standard compliant, but in
# practice often causes hanging connections with brain-dead browsers. Use
# this only for browsers where you know that their SSL implementation works
# correctly.
# Notice: Most problems of broken clients are also related to the HTTP
# keep-alive facility, so you usually additionally want to disable
# keep-alive for those clients, too. Use variable "nokeepalive" for this.
# Similarly, one has to force some clients to use HTTP/1.0 to workaround
# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and
# "force-response-1.0" for this.
<IfModule setenvif_module>
BrowserMatch ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
</IfModule>
## Per-Server Logging:
# The home of a custom SSL log file. Use this when you want a compact
# non-error SSL logfile on a virtual host basis.
<IfModule log_config_module>
CustomLog /var/log/apache2/ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</IfModule>
</VirtualHost>
</IfModule>
</IfDefine>
</IfDefine>
# vim: ts=4 filetype=apache
Configuring to use SSL
Copy the private key and certificate files that you generated to
wherever your config file says they need to go.
Utility scripts
If you are going to permit access to these scripts from outside your
local network you will have to change the Allow directives
for /cgi-bin in the Apache configuration.
Adding a domain
#! /usr/bin/perl
# Author: Raphael Mankin
use strict;
use DBD::mysql;
use File::Path;
use constant DB => "Mail";
use constant HOST => "localhost";
use constant USER => "root";
use constant PWD => "RootPassword";
use constant MAIL => "mail";
my ($login, $passwd, $uid, $gid) = getpwnam(MAIL) or
die "MAIL user not found";;
$| = 1;
my $dsn = "DBI:mysql:database=".DB.";host=".HOST;
my $dbh = DBI->connect($dsn, USER, PWD)
or die "Cannot connect to database";
print "Domain: ";
my $domain = <>;
chomp $domain;
$domain = lc $domain;
my $sth = $dbh->prepare("select domain from Domains where domain='$domain'");
$sth->execute();
my $res;
while (my $ref = $sth->fetchrow_hashref()) {
$res = $ref->{domain};
}
die "Domain $domain already exists" if $res;
my $domainDir = "/virtual/$domain/mail";
mkpath $domainDir unless -d $domainDir;
my $aliasFile = "$domainDir/aliases";
unless (-f $aliasFile) {
open(FH, ">", $aliasFile) or die "Cannot create alias file: $aliasFile";
print FH "#Empty alias file for $domain\n";
close FH;
}
my $sql = qq[insert into Domains (domain)
values ( '$domain')
];
$dbh->do($sql) or die "Cannot insert domain into DB";
print "Created $domain\n";
$dbh->disconnect();
exit 0;
Adding a user
#! /usr/bin/perl
# Author: Raphael Mankin
use strict;
use DBD::mysql;
use File::Path;
use constant DB => "Mail";
use constant HOST => "localhost";
use constant USER => "root";
use constant PWD => "RootPassword";
use constant MAIL => "mail";
my ($login, $passwd, $uid, $gid) = getpwnam(MAIL) or
die "MAIL user not found";;
$| = 1;
my $dsn = "DBI:mysql:database=".DB.";host=".HOST;
my $dbh = DBI->connect($dsn, USER, PWD)
or die "Cannot connect to database";
print "Real name of User: ";
my $realName = <>;
chomp $realName;
my @realName = split(/\s+/, $realName);
my $userName = join('.', @realName);
print "Domain: ";
my $domain = <>;
chomp $domain;
$domain = lc $domain;
my $sth = $dbh->prepare("select domain from Domains where domain='$domain'");
$sth->execute();
my $res;
while (my $ref = $sth->fetchrow_hashref()) {
$res = $ref->{domain};
}
die "No such domain: $domain" unless $res;
$userName = lc $userName;
print "Password:";
my $password = <>;
chomp $password;
print "Real Name: $realName\n";
print "Login name: $userName\n";
my $homedir = "/virtual/$domain/mail/$userName";
my $maildir = "$homedir/Maildir";
# the login name is the full user@domain.
$userName .= "\@$domain";
mkpath $homedir unless -d $homedir;
system("/usr/bin/maildirmake $maildir") unless -d $maildir;
system ("chown -R mail:mail $homedir");
my $sql = qq[insert into Users (user, password, homedir, maildir, realName)
values (
'$userName',
'$password',
'$homedir',
'$maildir',
'$realName'
)
];
$dbh->do($sql) or die "Cannot insert user into DB";
print "Created $userName\n";
$dbh->disconnect();
exit 0;
Deleting a user
#! /usr/bin/perl
# Author: Raphael Mankin
use strict;
use DBD::mysql;
use File::Path;
use constant DB => "Mail";
use constant HOST => "localhost";
use constant USER => "root";
use constant PWD => "RootPassword";
use constant MAIL => "mail";
my ($login, $passwd, $uid, $gid) = getpwnam(MAIL) or
die "MAIL user not found";;
$| = 1;
my $dsn = "DBI:mysql:database=".DB.";host=".HOST;
my $dbh = DBI->connect($dsn, USER, PWD)
or die "Cannot connect to database";
print "Real name of User: ";
my $realName = <>;
chomp $realName;
my @realName = split(/\s+/, $realName);
my $userName = join('.', @realName);
print "Domain: ";
my $domain = <>;
chomp $domain;
$domain = lc $domain;
my $sth = $dbh->prepare("select domain from Domains where domain='$domain'");
$sth->execute();
my $res;
while (my $ref = $sth->fetchrow_hashref()) {
$res = $ref->{domain};
}
die "No such domain: $domain" unless $res;
$userName = lc $userName;
print "Real Name: $realName\n";
print "Login name: $userName\n";
my $homedir = "/virtual/$domain/mail/$userName";
# the login name is the full user@domain.
$userName .= "\@$domain";
my $sql = qq[delete Users where userName= '$userName') ];
$dbh->do($sql) or die "Cannot delete user from DB";
rmtree $homedir if -d $homedir;
print "Deleted $userName\n";
$dbh->disconnect();
exit 0;
Links and References
Several people have set up similar systems to this, however, I
did not find their descriptions either quite matched what I was
trying to do, or entirely explicit: there were gaps in their
documents. I hope that this document fills in some of the gaps,
but I am also providing links to sources that I found useful.
Configuring Clam AV:
http://www200.pair.com/mecham/spam/clamav-amavisd-new.html
Exim 4 Documentation:
http://exim.org/exim-html-4.50/doc/html/"
OpenSSL - Documents, Misc:
http://www.openssl.org/docs/
Courier Mail Server:
http://www.courier-mta.org/
Installing and configuring courier IMAP
http://edseek.com/~jasonb/articles/exim4_courier/courierimap.html
Virtual Domains with Exim + Courier-IMAP + MySQL:
http://www.tty1.net/virtual_domains_en.html
A Complete Virtual System - Web Access - Gentoo
Linux Wiki:
Complete Virtual Mail Server with Gentoo
This is a link into the Gentoo wiki; it changes from time to time as the article gets
edited. You may have to search for it, rather than following this link.
System -> Postfix+ Courier-IMAP +MySQL for multiple
domains HOWTO:
http://www.linuxhowtos.org/System/postfix.htm
SSL Certificate HOWTO:
http://www.gtlib.cc.gatech.edu/pub/linux/docs/HOWTO/other-formats/html_single/SSL-Certificates-HOWTO.html
HOWTO: Create a self-signed (wildcard) SSL certificate
[Archived content from justinsamuel.com]
The following commands are all you need to create a self-signed (wildcard, if you want) SSL certificate:
mkdir /usr/share/ssl/certs/hostname.domain.com
cd /usr/share/ssl/certs/hostname.domain.com
(umask 077 && touch host.key host.cert host.info host.pem)
openssl genrsa 2048 > host.key
openssl req -new -x509 -nodes -sha1 -days 3650 -key host.key > host.cert
...[enter *.domain.com for the Common Name]...
openssl x509 -noout -fingerprint -text < host.cert > host.info
cat host.cert host.key > host.pem
chmod 400 host.key host.pem
This work is licenced under a
Creative
Commons Licence.