Linux as a Server
Install Ubuntu 9.04 Server
This tutorial is based on Ubuntu 9.04, i386-based 32bit Server edition. It should work just as good on 64bit editions and other architectures, although at some point you might need to install more dependencies. I don't think that's a problem though ;)
Install ssh and set the firewall
Login to your new Ubuntu installation and install the packages we will need.
Go to root - we will be doing most stuff as root
sudo su -
vi /etc/apt/sources.list # comment out all deb sources. I put three ### in front to know which onces I commented out # then put this line just above the first deb http:// (which should now be commented out) deb http://glonek.co.uk/repo/ jaunty main # close the file and exit editor # now we need to run the below to get the new list of packages aptitude update
aptitude update && aptitude upgrade
aptitude install openssh-server openssh-client wget vim lynx
iptables -N SSHD iptables -A INPUT -m state --state established,related -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p tcp -m tcp --dport 22 -j SSHD iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT iptables -A INPUT -j DROP # if you want to add an exception to sshd blocking rules (e.g. to your local network) # you would then execute this command line (assuming 192.168.0.0/16 is your local network) iptables -I INPUT -p tcp -m tcp -s 192.168.0.0/16 --dport 22 -j ACCEPT # in case you added a rule you wish to remove, execute it with -D instead. Example: iptables -D INPUT -p tcp -m tcp -s 192.168.0.0/16 --dport 22 -j ACCEPT
iptables-save > /etc/iptables-rules vi /etc/init.d/networking
start)
process_options
log_action_begin_msg "Configuring network interfaces"
if ifup -a; then
log_action_end_msg $?
else
log_action_end_msg $?
fi
;;
/sbin/iptables-restore < /etc/iptables-rules
mkdir /root/src cd /root/src wget http://www.glonek.co.uk/sshdfilter-1.5.5.tar.gz tar -zxvf sshdfilter-1.5.5.tar.gz cd sshdfilter-1.5.5 vi install_aswrapper.pl
} else {
printf "System type does not appear to be Fedora, RedHat pre Fedora, Debian, \n";
printf "CentOS, SUSE, RH Enterprise, Gentoo or Slackware. So, you will have to install manually \n";
printf "(see INSTALL) and send me some hints on how to identify your system.\n";
exit 1; } else {
printf "System type: Debian system\n";
$pattype="deb31";
$inittype="$pattype";
$confpath="/etc/";
$exepath="/usr/local/sbin/";
$initname="ssh";
$logconf="/etc/log.d/conf/services/";
$logserv="/usr/share/logwatch/scripts/services/";./install_aswrapper.pl # you will get some errors there about Hunk failing. Let's fix that cp /etc/init.d/ssh /etc/init.d/ssh-original sed 's/--exec \/usr\/sbin\/sshd/--exec \/usr\/local\/sbin\/sshdfilter/g' /etc/init.d/ssh-original > /etc/init.d/ssh # now edit sshdfilter config file to your liking. It's well commented and doesn't really need much tweaking vi /etc/sshdfilterrc # you will want to wteak the main rules (5,3d = DEFAULT etc) to remove the examples # you will also want to look at SECTION IPPOLICY for ip based filtering to remove examples # restart ssh and we are ready /etc/init.d/ssh restart
Install MySQL, PHP, APACHE
Let's install the services now for LAMP (Linux Apache MySQL PHP)
aptitude install mysql-server mysql-client apache2 php5
And some php common modules
aptitude install php5-cli php5-curl php5-gd php5-imap php5-mcrypt php5-mhash php5-mysql
Edit the file below and put the correct contents inside.
vi /etc/apache2/mods-available/php5.conf # the contents should read <IfModule mod_php5.c> AddType application/x-httpd-php .php AddType application/x-httpd-php-source .phps </IfModule> # let's restart apache now to read in new changes /etc/init.d/apache2 restart
cd /etc/apache2
cat ports.conf
# you should see uncommented NameVirtualHost and Listen. These tell apache to listen on port 80 and do virtual hosting there.
# let's create a new virtual host now for example.com website (simple one)
cd sites-available
vi example.com
<VirtualHost *:80>
ServerAdmin webmaster@localhost
ServerName www.example.com
ServerAlias example.com
DocumentRoot /var/www/example.com
ErrorLog /var/log/apache2/example.com-error.log
LogLevel warn
# can change to debug, info, notice, warn, error, crit, alert, emerg
CustomLog /var/log/apache2/example.com-access.log combined
</VirtualHost>
# now save the file and let's create the actual directory
mkdir /var/www/example.com
# enable the site
ln -s /etc/apache2/sites-available/example.com /etc/apache2/sites-enabled/example.com
# check sanity of config file
apache2ctl -t
# reload configuration
/etc/init.d/apache2 reload
# let's put test file in there
echo "<?php phpinfo(); ?>" > /var/www/example.com/index.php
# that's it, easy and it works. Once we setup DNS in BIND for the test domain, we will be able to test it out
iptables -I INPUT -p tcp -m tcp --dport 80 -j ACCEPT iptables -I INPUT -p tcp -m tcp --dport 443 -j ACCEPT iptables-save > /etc/iptables-rules
Install BIND9 DNS server
Install BIND
aptitude install bind9 cd /etc/bind
vi /etc/bind/named.conf.local
# put the below in the end of the file
zone "example.com" {
type master;
file "/etc/bind/zones/example.com.db";
};
zone "0.168.192.in-addr.arpa" {
type master;
file "/etc/bind/zones/rev.0.168.192.in-addr.arpa";
};
# now let's create the zone files
mkdir /etc/bind/zones
cd /etc/bind/zones
vi example.com.db
$TTL 1h
example.com. IN SOA ns.example.com. webadmin@example.com. (
2009010910 ;serial
3600 ;refresh
3600 ;retry
3600 ;expire
3600 ;minimum TTL
)
example.com. IN NS ns.example.com.
example.com. IN MX 10 mail.example.com.
example.com. IN MX 20 mail.example.com.
@ IN A 192.168.0.50
www IN A 192.168.0.50
mail IN A 192.168.0.50
ns IN A 192.168.0.50
example.com. IN TXT "v=spf1 a mx ip4:192.168.0.50 -all"
example.com. IN SPF "v=spf1 a mx ip4:192.168.0.50 -all"
vi rev.0.168.192.in-addr.arpa
# put this in the file
$TTL 1h
@ IN SOA ns.example.com. webadmin@example.com. (
2008112111 ;serial
3600 ;refresh
3600 ;retry
3600 ;expire
3600 ;minimum TTL
)
IN NS ns.example.com.
50 IN PTR example.com
Now a very important step is to stop this server from being an open DNS server. To the outside world it should only respond to queries for domains it is configured as an authoritative server.
vi /etc/bind/named.conf.options
# at the end of the file, just above the enclosure "};" which ends the options part, insert this line
# this is assuming you want to allow all lookups from your internal network
# and that your internal network is 192.168.24.0/24
allow-recursion { 127.0.0.1; 192.168.24.0/24; };
# restart bind name server (named) /etc/init.d/bind9 restart # if that hangs, ctrl+c the restart. Then run the below 2 commands: NMD=`ps -A |grep named |grep -v grep |cut -d " " -f 1`; kill -9 $NMD /etc/init.d/bind9 start # test new configuration. you should get your 192.168.0.50 dig @localhost example.com dig @localhost -x 192.168.0.50 # configure machine to use our DNS server as the main one vi /etc/resolv.conf # add the below line as the first nameserver entry nameserver 127.0.0.1 # save the file, no need to restart anything # now can can also check our vhost from apache lynx http://example.com # you should see a lot of output with the configuration of your apache and php. Hit Q and Y to exit
Install FTP server
Install proftpd
aptitude install libmysqlclient-dev proftpd proftpd-mod-mysql
Now, let's create ftp user, group and mysql tables. Replace password with an actual password
groupadd -g 2001 ftpgroup
useradd -u 2001 -s /bin/false -d /bin/null -c "proftpd user" -g ftpgroup ftpuser
mysql -u root -p
create database ftp;
GRANT SELECT, INSERT, UPDATE, DELETE ON ftp.* TO 'proftpd'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON ftp.* TO 'proftpd'@'localhost.localdomain' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
USE ftp;
CREATE TABLE ftpgroup (
groupname varchar(16) NOT NULL default '',
gid smallint(6) NOT NULL default '5500',
members varchar(16) NOT NULL default '',
KEY groupname (groupname)
) TYPE=MyISAM COMMENT='ProFTP group table';
CREATE TABLE ftpquotalimits (
name varchar(30) default NULL,
quota_type enum('user','group','class','all') NOT NULL default 'user',
per_session enum('false','true') NOT NULL default 'false',
limit_type enum('soft','hard') NOT NULL default 'soft',
bytes_in_avail int(10) unsigned NOT NULL default '0',
bytes_out_avail int(10) unsigned NOT NULL default '0',
bytes_xfer_avail int(10) unsigned NOT NULL default '0',
files_in_avail int(10) unsigned NOT NULL default '0',
files_out_avail int(10) unsigned NOT NULL default '0',
files_xfer_avail int(10) unsigned NOT NULL default '0'
) TYPE=MyISAM;
CREATE TABLE ftpquotatallies (
name varchar(30) NOT NULL default '',
quota_type enum('user','group','class','all') NOT NULL default 'user',
bytes_in_used int(10) unsigned NOT NULL default '0',
bytes_out_used int(10) unsigned NOT NULL default '0',
bytes_xfer_used int(10) unsigned NOT NULL default '0',
files_in_used int(10) unsigned NOT NULL default '0',
files_out_used int(10) unsigned NOT NULL default '0',
files_xfer_used int(10) unsigned NOT NULL default '0'
) TYPE=MyISAM;
CREATE TABLE ftpuser (
id int(10) unsigned NOT NULL auto_increment,
userid varchar(32) NOT NULL default '',
passwd varchar(32) NOT NULL default '',
uid smallint(6) NOT NULL default '5500',
gid smallint(6) NOT NULL default '5500',
homedir varchar(255) NOT NULL default '',
shell varchar(16) NOT NULL default '/sbin/nologin',
count int(11) NOT NULL default '0',
accessed datetime NOT NULL default '0000-00-00 00:00:00',
modified datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (id),
UNIQUE KEY userid (userid)
) TYPE=MyISAM COMMENT='ProFTP user table';
quit;
vi /etc/proftpd/proftpd.conf
# add the following lines to the file (don't need to remove comments from it)
DefaultRoot ~
# The passwords in MySQL are encrypted using CRYPT
SQLBackend mysql
SQLEngine on
SQLAuthTypes Plaintext Crypt
SQLAuthenticate users* groups*
# used to connect to the database
# databasename@host database_user user_password
SQLConnectInfo ftp@localhost proftpd password
# Here we tell ProFTPd the names of the database columns in the "usertable"
# we want it to interact with. Match the names with those in the db
SQLUserInfo ftpuser userid passwd uid gid homedir shell
# Here we tell ProFTPd the names of the database columns in the "grouptable"
# we want it to interact with. Again the names match with those in the db
SQLGroupInfo ftpgroup groupname gid members
# set min UID and GID - otherwise these are 999 each
SQLMinID 500
# create a user's home directory on demand if it doesn't exist
SQLHomedirOnDemand on
# Update count every time user logs in
SQLLog PASS updatecount
SQLNamedQuery updatecount UPDATE "count=count+1, accessed=now() WHERE userid='%u'" ftpuser
# Update modified everytime user uploads or deletes a file
SQLLog STOR,DELE modified
SQLNamedQuery modified UPDATE "modified=now() WHERE userid='%u'" ftpuser
# User quotas
# ===========
QuotaEngine on
QuotaDirectoryTally on
QuotaDisplayUnits Mb
QuotaShowQuotas on
SQLNamedQuery get-quota-limit SELECT "name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail FROM ftpquotalimits WHERE name = '%{0}' AND quota_type = '%{1}'"
SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used, files_in_used, files_out_used, files_xfer_used FROM ftpquotatallies WHERE name = '%{0}' AND quota_type = '%{1}'"
SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used = files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name = '%{6}' AND quota_type = '%{7}'" ftpquotatallies
SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4}, %{5}, %{6}, %{7}" ftpquotatallies
QuotaLimitTable sql:/get-quota-limit
QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally
RootLogin off
RequireValidShell off
SQLNamedQuery userquota SELECT "IF ((SELECT (@availmbytes:=ROUND((`bytes_in_avail`/1048576),2)) FROM `ftpquotalimits` WHERE `name`='%u') = 0, \"No user quota applies.\", CONCAT(\"User quota: Used \", (SELECT (@usedmbytes:=ROUND((`bytes_in_used`/1048576),2)) FROM `ftpquotatallies` WHERE `name`='%u'), \"MB from \", @availmbytes, \"MB. You have \", ROUND(@availmbytes-@usedmbytes,2), \"MB available space.\"))"
SQLShowInfo LIST "226" "%{userquota}"
PassivePorts 60000 65000
vi /etc/proftpd/modules.conf #uncomment (or add if not exist) the below lines LoadModule mod_sql.c LoadModule mod_sql_mysql.c
/etc/init.d/proftpd restart
mysql -u root -p
USE ftp;
INSERT INTO `ftpgroup` (`groupname`, `gid`, `members`) VALUES ('ftpgroup', 2001, 'ftpuser');
# now we are done with table ftpgroup, no need to add any new entries here
# new users are added to ftpquotalimits and ftpuser, let's do that now
INSERT INTO `ftpquotalimits` (`name`, `quota_type`, `per_session`, `limit_type`, `bytes_in_avail`, `bytes_out_avail`, `bytes_xfer_avail`, `files_in_avail`, `files_out_avail`, `files_xfer_avail`) VALUES ('exampleuser', 'user', 'true', 'hard', 15728640, 0, 0, 0, 0, 0);
INSERT INTO `ftpuser` (`id`, `userid`, `passwd`, `uid`, `gid`, `homedir`, `shell`, `count`, `accessed`, `modified`) VALUES (1, 'exampleuser', 'secret', 2001, 2001, '/home/www.example.com', '/sbin/nologin', 0, '', '');
quit;
ftp localhost user: exampleuser pass: secret ls quit ls /home # you should see folder called www.example.com created automatically for the user. iptables -I INPUT -p tcp -m tcp --dport 21 -j ACCEPT iptables -I INPUT -p tcp -m tcp --dport 60000:65000 -j ACCEPT iptables-save > /etc/iptables-rules
Install MAIL server
Install required packages
# postfix aptitude install postfix postfix-mysql # sasl aptitude install libsasl2-modules-sql libgsasl7 libauthen-sasl-cyrus-perl # courier aptitude install courier-base courier-authdaemon courier-authlib-mysql courier-imap courier-imap-ssl courier-ssl # clamav aptitude install clamav-base libclamav6 clamav-daemon clamav-freshclam # amavis, spamassassin aptitude install amavisd-new aptitude install spamassassin spamc # squirrelmail aptitude install squirrelmail squirrelmail-locales php-pear # phpmyadmin aptitude install phpmyadmin # maildrop aptitude install maildrop
Configure mailname - put your server name here. Example: smtp.domain.name
vi /etc/mailname
vi /etc/postfix/main.cf myorigin=example.com smtpd_banner = $myhostname ESMTP $mail_name inet_interfaces = all mynetworks_style = host local_recipient_maps = mydestination = # how long if undelivered before sending warning update to sender delay_warning_time = 4h # will it be a permanent error or temporary unknown_local_recipient_reject_code = 450 # how long to keep message on queue before return as failed. maximal_queue_lifetime = 7d # max and min time in seconds between retries if connection failed minimal_backoff_time = 1000s maximal_backoff_time = 8000s # how long to wait when servers connect before receiving rest of data smtp_helo_timeout = 60s # how many address can be used in one message. # effective stopper to mass spammers, accidental copy in whole address list # but may restrict intentional mail shots. smtpd_recipient_limit = 16 # how many error before back off. smtpd_soft_error_limit = 3 # how many max errors before blocking it. smtpd_hard_error_limit = 12 # Requirements for the HELO statement smtpd_helo_restrictions = permit_mynetworks, warn_if_reject reject_non_fqdn_hostname, reject_invalid_hostname, permit # Requirements for the sender details smtpd_sender_restrictions = permit_mynetworks, warn_if_reject reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit # Requirements for the connecting server smtpd_client_restrictions = reject_rbl_client sbl.spamhaus.org, reject_rbl_client blackholes.easynet.nl, reject_rbl_client dnsbl.njabl.org # Requirement for the recipient address smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_pipelining, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, permit smtpd_data_restrictions = permit_mynetworks, reject_unauth_pipelining # require proper helo at connections smtpd_helo_required = yes # waste spammers time before rejecting them smtpd_delay_reject = yes disable_vrfy_command = yes # not sure of the difference of the next two # but they are needed for local aliasing alias_maps = hash:/etc/postfix/aliases alias_database = hash:/etc/postfix/aliases # this specifies where the virtual mailbox folders will be located virtual_mailbox_base = /var/spool/mail/virtual # this is for the mailbox location for each user virtual_mailbox_maps = mysql:/etc/postfix/mysql_mailbox.cf # and their user id virtual_uid_maps = mysql:/etc/postfix/mysql_uid.cf # and group id virtual_gid_maps = mysql:/etc/postfix/mysql_gid.cf # and this is for aliases virtual_alias_maps = mysql:/etc/postfix/mysql_alias.cf # and this is for domain lookups virtual_mailbox_domains = mysql:/etc/postfix/mysql_domains.cf # this is how to connect to the domains (all virtual, but the option is there) # not used yet, so commented out transport_maps = mysql:/etc/postfix/mysql_transport.cf # relayhost, leave empty to deliver mail yourself # or replace with a domain name if you intend to use your ISP # eg: relayhost = smtp.yourisp.com relayhost = # comment out the following lines #myhostname= mail.example.com #masquerade_domains = mail.example.com www.example.com !sub.dyndomain.com #masquerade_exceptions = root #mailbox_command = procmail -a "$EXTENSION"
cp /etc/aliases /etc/postfix/aliases postalias /etc/postfix/aliases mkdir /var/spool/mail/virtual groupadd virtual -g 5000 useradd virtual -u 5000 -g 5000 chown -R virtual:virtual /var/spool/mail/virtual
vi /etc/postfix/mysql_transport.cf user=mail password=mailPASSWORD dbname=maildb table=domains select_field=transport where_field=domain hosts=127.0.0.1 additional_conditions = and enabled = 1 vi /etc/postfix/mysql_mailbox.cf user=mail password=mailPASSWORD dbname=maildb table=users select_field=maildir where_field=id hosts=127.0.0.1 additional_conditions = and enabled = 1 vi /etc/postfix/mysql_uid.cf user=mail password=mailPASSWORD dbname=maildb table=users select_field=uid where_field=id hosts=127.0.0.1 vi /etc/postfix/mysql_gid.cf user=mail password=mailPASSWORD dbname=maildb table=users select_field=gid where_field=id hosts=127.0.0.1 vi /etc/postfix/mysql_alias.cf user=mail password=mailPASSWORD dbname=maildb table=aliases select_field=destination where_field=mail hosts=127.0.0.1 additional_conditions = and enabled = 1 vi /etc/postfix/mysql_domains.cf user=mail password=mailPASSWORD dbname=maildb table=domains select_field=domain where_field=domain hosts=127.0.0.1 additional_conditions = and enabled = 1
mysql -u root -p create database maildb; GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON maildb.* TO 'mail'@'localhost' IDENTIFIED by 'mailPASSWORD'; GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP ON maildb.* TO 'mail'@'%' IDENTIFIED by 'mailPASSWORD'; exit; mysql -u mail -p maildb CREATE TABLE `aliases` ( `pkid` smallint(3) NOT NULL auto_increment, `mail` varchar(120) NOT NULL default '', `destination` varchar(120) NOT NULL default '', `enabled` tinyint(1) NOT NULL default '1', PRIMARY KEY (`pkid`), UNIQUE KEY `mail` (`mail`) ) ; CREATE TABLE `domains` ( `pkid` smallint(6) NOT NULL auto_increment, `domain` varchar(120) NOT NULL default '', `transport` varchar(120) NOT NULL default 'virtual:', `enabled` tinyint(1) NOT NULL default '1', PRIMARY KEY (`pkid`) ) ; CREATE TABLE `users` ( `id` varchar(128) NOT NULL default '', `name` varchar(128) NOT NULL default '', `uid` smallint(5) unsigned NOT NULL default '5000', `gid` smallint(5) unsigned NOT NULL default '5000', `home` varchar(255) NOT NULL default '/var/spool/mail/virtual', `maildir` varchar(255) NOT NULL default 'blah/', `enabled` tinyint(3) unsigned NOT NULL default '1', `change_password` tinyint(3) unsigned NOT NULL default '1', `clear` varchar(128) NOT NULL default 'ChangeMe', `crypt` varchar(128) NOT NULL default 'sdtrusfX0Jj66', `quota` varchar(255) NOT NULL default '', `procmailrc` varchar(128) NOT NULL default '', `spamassassinrc` varchar(128) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`) ) ; # To visualise the tables created: describe aliases; describe domains; describe users; exit;
vi /etc/mysql/my.cnf log = /var/log/mysql/mysql.log #save the file and exit vi /etc/init.d/mysql restart
POP/IMAP Configuration
Configure Courier IMAP to use MySQL
vi /etc/courier/authdaemonrc authmodulelist="authmysql" DEBUG_LOGIN=2 vi /etc/courier/authmysqlrc MYSQL_USERNAME mail MYSQL_PASSWORD mailPASSWORD MYSQL_DATABASE maildb MYSQL_USER_TABLE users # change the below 2 lines to allow clear instead of crypt passwords MYSQL_CRYPT_PWFIELD crypt # MYSQL_CLEAR_PWFIELD clear MYSQL_MAILDIR_FIELD concat(home,'/',maildir) MYSQL_WHERE_CLAUSE enabled=1 # have a look at the below file to see if you want to change anything # now changes should be required though vi /etc/courier/imapd
Configure content checks (anti-spam and anti-virus)
Take a look at the below files. No need to change anything:
cd /etc/amavis/conf.d less 05-domain-id less 05-node-id less 15-av_scanners less 20-debian_defaults
vi 05-node_id # oncomment and edit the below line $myhostname = "example.com"; #and save the file now vi 15-content_filter_mode # uncomment the below content checks @bypass_virus_checks_maps = ( \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re); @bypass_spam_checks_maps = ( \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
vi 50-user @local_domains_acl = qw(.); $log_level = 2; # change to 2 for debugging $syslog_priority = 'info'; # change to debug for debugging $sa_kill_level_deflt = 8.0; # triggers spam evasive actions $final_spam_destiny = D_DISCARD; # change to D_PASS to allow spam to go through
#enable clamav to scan amavis temp files adduser clamav amavis #edit postfix vi /etc/postfix/master.cf # append these lines at the end of the file (make sure they are not there before) # note the -o switches must have spaces before them amavis unix - - - - 2 smtp -o smtp_data_done_timeout=1200 -o smtp_send_xforward_command=yes -o disable_dns_lookups=yes -o max_use=20 127.0.0.1:10025 inet n - - - - smtpd -o content_filter= -o local_recipient_maps= -o relay_recipient_maps= -o smtpd_restriction_classes= -o smtpd_delay_reject=no -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_helo_restrictions= -o smtpd_sender_restrictions= -o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_data_restrictions=reject_unauth_pipelining -o smtpd_end_of_data_restrictions= -o mynetworks=127.0.0.0/8 -o smtpd_error_sleep_time=0 -o smtpd_soft_error_limit=1001 -o smtpd_hard_error_limit=1000 -o smtpd_client_connection_count_limit=0 -o smtpd_client_connection_rate_limit=0 -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks # add the following 2 lines just below "pickup" -o content_filter= -o receive_override_options=no_header_body_checks vi /etc/postfix/main.cf # add the follwing line to this file content_filter = amavis:[127.0.0.1]:10024 vi /etc/default/spamassassin ENABLED=1 # one thing you can do is tweak the below file to enable Bayes and auto-learning vi /etc/spamassassin/local.cf
mysql -u root -p
# your mysql root password
create database spamassassin;
grant all on spamassassin.* to spamassassin identified by 'password';
flush privileges;
exit;
mysql -u spamassassin -p spamassassin
# enter the spamassassin password we just set
# here we will have user preferences
CREATE TABLE userpref (
username varchar(100) NOT NULL default '',
preference varchar(30) NOT NULL default '',
value varchar(100) NOT NULL default '',
prefid int(11) NOT NULL auto_increment,
PRIMARY KEY (prefid),
KEY username (username)
) TYPE=MyISAM;
# how about whitelist table
CREATE TABLE awl (
username varchar(100) NOT NULL default '',
email varchar(200) NOT NULL default '',
ip varchar(10) NOT NULL default '',
count int(11) default '0',
totscore float default '0',
PRIMARY KEY (username,email,ip)
) TYPE=MyISAM;
# Bayes configuration, this is going to make your head hurt:
CREATE TABLE bayes_expire (
id int(11) NOT NULL default '0',
runtime int(11) NOT NULL default '0',
KEY bayes_expire_idx1 (id)
) TYPE=MyISAM;
CREATE TABLE bayes_global_vars (
variable varchar(30) NOT NULL default '',
value varchar(200) NOT NULL default '',
PRIMARY KEY (variable)
) TYPE=MyISAM;
INSERT INTO bayes_global_vars VALUES ('VERSION','3');
CREATE TABLE bayes_seen (
id int(11) NOT NULL default '0',
msgid varchar(200) binary NOT NULL default '',
flag char(1) NOT NULL default '',
PRIMARY KEY (id,msgid)
) TYPE=MyISAM;
CREATE TABLE bayes_token (
id int(11) NOT NULL default '0',
token char(5) NOT NULL default '',
spam_count int(11) NOT NULL default '0',
ham_count int(11) NOT NULL default '0',
atime int(11) NOT NULL default '0',
PRIMARY KEY (id, token),
INDEX bayes_token_idx1 (token),
INDEX bayes_token_idx2 (id, atime)
) TYPE=MyISAM;
CREATE TABLE bayes_vars (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(200) NOT NULL default '',
spam_count int(11) NOT NULL default '0',
ham_count int(11) NOT NULL default '0',
token_count int(11) NOT NULL default '0',
last_expire int(11) NOT NULL default '0',
last_atime_delta int(11) NOT NULL default '0',
last_expire_reduce int(11) NOT NULL default '0',
oldest_token_age int(11) NOT NULL default '2147483647',
newest_token_age int(11) NOT NULL default '0',
PRIMARY KEY (id),
UNIQUE bayes_vars_idx1 (username)
) TYPE=MyISAM;
# now a few defaults that we need in order for this to work
INSERT INTO userpref (username,preference,value) VALUES ('$GLOBAL','required_hits','5.0');
INSERT INTO userpref (username,preference,value) VALUES ('$GLOBAL','report_safe','1');
INSERT INTO userpref (username,preference,value) VALUES ('$GLOBAL','use_bayes','1');
INSERT INTO userpref (username,preference,value) VALUES ('$GLOBAL','use_dcc','1');
exit;
# ok, let's configure spamassassin now
vi /etc/spamassassin/local.cf
# put this chunck in at the end of file
# don't forget to change DBUSER to spamassassin and DBPASSWORD to the password we set eariler
bayes_store_module Mail::SpamAssassin::BayesStore::MySQL
bayes_sql_dsn DBI:mysql:spamassassin:localhost:3306
bayes_sql_username DBUSER
bayes_sql_password DBPASSWORD
auto_whitelist_factory Mail::SpamAssassin::SQLBasedAddrList
user_awl_dsn DBI:mysql:spamassassin:localhost:3306
user_awl_sql_username DBUSER
user_awl_sql_password DBPASSWORD
user_scores_dsn DBI:mysql:spamassassin:localhost:3306
user_scores_sql_username DBUSER
user_scores_sql_password DBPASSWORD
user_scores_sql_custom_query SELECT preference, value FROM _TABLE_ WHERE username = _USERNAME_ OR username = '$GLOBAL' OR username = CONCAT('%',_DOMAIN_) ORDER BY username ASC
# now save the file
# need to reconfigure spamd options to use sql
vi /etc/init.d/spamassassin
# find the DOPTIONS line and at the end of it, add this
-c -q -u virtual -H
# and restart spamassassin
/etc/init.d/spamassassin restart
Now we will want to enhance SpamAssassin to actually do something. The below will create 2 folders per user if not yet exist (Spam, Safe) and run the learning app for SA on those folders. Any emails in the Spam folder will be used for spamassassin to learn what spam is and any mail in Safe folder will be used by spamassassin to learn what is a safe never to move to spam. Then we configure this script to run every midnight. All you have to do to fight spam successfully then is educate your users to use Spam and Safe folders in order to help SpamAssassin learn.
# let's create the script first
vi /usr/local/bin/sa-learn.sh
#!/bin/bash
for i in `ls /var/spool/mail/virtual`
do
echo "Checking user: $i"
cd "/var/spool/mail/virtual/$i"
if [ -d ".Spam" ]
then
echo -n ""
else
echo " -> Didn't find .Spam folder, attempting to create"
mkdir ".Spam"
mkdir ".Spam/cur"
mkdir ".Spam/new"
mkdir ".Spam/tmp"
chown -R virtual:virtual ".Spam"
chmod -R 2700 ".Spam"
fi
if [ -d ".Safe" ]
then
echo -n ""
else
echo " -> Didn't find .Safe folder, attempting to create"
mkdir ".Safe"
mkdir ".Safe/cur"
mkdir ".Safe/new"
mkdir ".Safe/tmp"
chown -R virtual:virtual ".Safe"
chmod -R 2700 ".Safe"
fi
if [ -f subscriptions ]; then
echo -n ""
else
touch subscriptions
chown root:virtual subscriptions
chmod 644 subscriptions
fi
if [ -f courierimapsubscribed ]; then
echo -n ""
else
touch courierimapsubscribed
chown root:virtual courierimapsubscribed
chmod 644 courierimapsubscribed
fi
safe=`cat subscriptions |grep -i Safe |wc -l`
if [ $safe -lt 1 ]; then
echo " -> Adding 'safe' subscription"
echo "Safe" >> subscriptions
fi
spam=`cat subscriptions |grep -i Spam |wc -l`
if [ $spam -lt 1 ]; then
echo " -> Adding 'spam' subscription"
echo "Spam" >> subscriptions
fi
safe=`cat courierimapsubscribed |grep -i Safe |wc -l`
if [ $safe -lt 1 ]; then
echo " -> Adding 'safe' curriersubscription"
echo "INBOX.Safe" >> courierimapsubscribed
fi
spam=`cat courierimapsubscribed |grep -i Spam |wc -l`
if [ $spam -lt 1 ]; then
echo " -> Adding 'spam' curriersubscription"
echo "INBOX.Spam" >> courierimapsubscribed
fi
done
echo "Done"
for i in `ls /var/spool/mail/virtual`
do
echo "Checking user: $i"
cd "/var/spool/mail/virtual/$i"
if [ -d ".Spam" ]
then
echo " -> Found .Spam folder, updating spam list"
/usr/bin/sa-learn --spam /var/spool/mail/virtual/$i/.Spam/{cur,new} --username="$i";
fi
if [ -d ".Safe" ]
then
echo " -> Found .Safe folder, updating safe senders list (ham)"
/usr/bin/sa-learn --ham /var/spool/mail/virtual/$i/.Safe/{cur,new} --username="$i";
fi
done
echo "Done"
# now save and close the file
# and run the file to check if it does the job
chmod 755 /usr/local/bin/sa-learn.sh
/usr/local/bin/sa-learn.sh
# add the script to cron
echo -e "\n10 0 * * * root /usr/local/bin/sa-learn.sh" >> /etc/crontab
# reload configuration
/etc/init.d/cron restart
# be default freshclam checks for antivirus updates 24 times a day # I find that a bit excessive, so I change that to once a day dpkg-reconfigure clamav-freshclam # if you really want to tweak clamav, then run the below command # it will ask a lot of questions. Optional, no action really needed dpkg-reconfigure clamav-base
vi /etc/postfix/main.cf # edit the receipient restrictions smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_pipelining, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, permit
Authentication
SASL secures the actual authentication while TLS encrypts the whole traffic. Let's configure SASL first
vi /etc/postfix/main.cf # modify the existing smtpd_recipient_restrictions smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_pipelining, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unauth_destination, permit # modify the existing smtpd_sender_restrictions smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit # then add these smtpd_sasl_auth_enable = yes smtp_sasl_auth_enable = yes broken_sasl_auth_clients = yes #smtpd_sasl_path = /etc/postfix/sasl:/usr/lib/sasl2 smtpd_sasl_security_options = noanonymous smtpd_sasl_local_domain = smtp_sasl_password_maps = mysql:/etc/postfix/mysql_maps.cf
vi /etc/postfix/mysql_maps.cf user = mail password = mailPASSWORD hosts = 127.0.0.1 dbname = maildb query = SELECT id,clear FROM users WHERE id='%s'
# May already exist mkdir /etc/postfix/sasl vi /etc/postfix/sasl/smtpd.conf pwcheck_method: auxprop auxprop_plugin: sql mech_list: plain login cram-md5 digest-md5 sql_engine: mysql sql_hostnames: 127.0.0.1 sql_user: mail sql_passwd: mailPASSWORD sql_database: maildb sql_select: select clear from users where id='%u@%r' and enabled = 1 vi /etc/courier/imapd # confirm, uncomment the below line IMAP_CAPABILITY="IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA AUTH=CRAM-MD5 AUTH=CRAM-SHA1 IDLE"
vi /etc/postfix/main.cf
# some of the below might already exist, I commented them out and put my configs in the bottom
# TLS parameters
#smtp_use_tls = no
smtp_tls_security_level = may
#smtpd_use_tls=yes
smtpd_tls_security_level = may
#smtpd_tls_auth_only = no
smtp_tls_note_starttls_offer = yes
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
#smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
#smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
vi /etc/postfix/master.cf
# by default only smtp service is enabled. I also enable submission (port 587) and force TLS there
# I also enable smtps (port 465) for compatibility wit some older clients
submission inet n - n - - smtpd
-o smtpd_sasl_auth_enable=yes
# if you do not want to restrict it encryption only, comment out next line
-o smtpd_tls_auth_only=yes
# -o smtpd_tls_security_level=encrypt
# -o header_checks=
# -o body_checks=
-o smtpd_client_restrictions=permit_sasl_authenticated,reject_unauth_destination, reject
-o smtpd_sasl_security_options=noanonymous,noplaintext
-o smtpd_sasl_tls_security_options=noanonymous
# -o milter_macro_daemon_name=ORIGINATING
smtps inet n - - - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sasl_security_options=noanonymous,noplaintext
-o smtpd_sasl_tls_security_options=noanonymous
# -o milter_macro_daemon_name=ORIGINATING
cd /etc/courier openssl req -x509 -newkey rsa:1024 -keyout imapd.pem \ -out imapd.pem -nodes -days 999 vi /etc/courier/imapd-ssl # ensure this line is fine TLS_CERTFILE=/etc/courier/imapd.pem # also, if you want to restrict IMAP users to SSL/TLS only, toggle this setting to 1 IMAP_TLS_REQUIRED=1
cp /etc/squirrelmail/apache.conf /etc/apache2/sites-available/squirrelmail ln -s /etc/apache2/sites-available/squirrelmail /etc/apache2/sites-enabled/500-squirrelmail vi /etc/apache2/sites-available/squirrelmail # you might want to accept defaults or change to your liking (eg comment out alias and uncomment virtual hosting) apache2ctl -t /etc/init.d/apache2 reload squirrelmail-configure # Type 2, then A, then 8. Enter "courier" (without the quotes) # Now type 7, type Y. Now type 5. Enter 993 # Type S, Enter, Q
cp /etc/phpmyadmin/apache.conf /etc/apache2/sites-available/phpmyadmin ln -s /etc/apache2/sites-available/phpmyadmin /etc/apache2/sites-enabled/400-phpmyadmin rm /etc/apache2/conf.d/phpmyadmin.conf vi /etc/apache2/sites-available/phpmyadmin # here you can edit settings if you want to # for example comment out alias and uncomment virtual hosting apache2ctl -t /etc/init.d/apache2 reload
mysql -u root -p maildb
# add default stuff
INSERT INTO domains (domain) VALUES ('localhost'), ('localhost.localdomain');
INSERT INTO aliases (mail,destination) VALUES ('postmaster@localhost','root@localhost'), ('sysadmin@localhost','root@localhost'), ('webmaster@localhost','root@localhost'), ('abuse@localhost','root@localhost'), ('root@localhost','root@localhost'), ('@localhost','root@localhost'), ('@localhost.localdomain','@localhost');
INSERT INTO users (id,name,maildir,crypt) VALUES ('root@localhost','root','root/', encrypt('apassword') );
# add example users
INSERT INTO domains (domain) VALUES ('example.com'),('example2.com');
INSERT INTO aliases (mail,destination) VALUES ('@example.com','@example2.com'),('postmaster@example2.com','webadmin@example2.com'),('abuse@example2.com','webadmin@example2.com'),('webadmin@example2.com','webadmin@example2.com');
INSERT INTO users (id,name,maildir,clear,crypt) VALUES ('webadmin@example2.com','WebAdmin','webadmin@example2.com/','apassword',encrypt('apassword'));
exit;
Aliases can be used eg. to deliver mail going to Email1 over to User1 and User2, or to external gmail account or to deliver mail going to Email2 to an external gmail address and a local User3. Anything you can think of
Including pointing email going to domain1.com over to domain2.com or mail going to anything at domain3.com to deliver to a single external gmail user. Really, anything you think of
Right, now for mail filtering. We need maildrop as procmail does not support virtual users
Create configuration files for maildrop first
vi /etc/maildroprc
LOGNAME=tolower("$LOGNAME")
EXTENSION="$1"
RECIPIENT=tolower("$2")
USER="$3"
HOST="$4"
SENDER="$5"
DEFAULT="/var/spool/mail/virtual/$USER@$HOST/."
if ( "$EXTENSION" ne "" )
{
DELIMITER="+"
}
if (!$SENDER)
{
SENDER = ""
}
`test -e /var/spool/mail/virtual/$USER\@$HOST`
if ( $RETURNCODE != 0 )
{
`mkdir /var/spool/mail/virtual/$USER\@$HOST`
`chmod -R 0700 /var/spool/mail/virtual/$USER\@$HOST`
}
`test -f /var/spool/mail/virtual/.mailfilters/$LOGNAME`
if ( $RETURNCODE == 0 )
{
include "/var/spool/mail/virtual/.mailfilters/$LOGNAME"
}
else
{
if ( "$DEFAULT" ne "" )
{
to "$DEFAULT"
}
else
{
EXITCODE=75
exit
}
}
vi /etc/postfix/master.cf
# find the maildrop line and flags and make sure they look like this:
# the -w 90 means that maildrop will warn user with message every day when user is 90% of mailbox use
maildrop unix - n n - - pipe
flags=DRhu user=virtual argv=/usr/bin/maildrop -w 90 -d ${user}@${nexthop} ${extension} ${recipient} ${user} ${nexthop} ${sender}
# now save the file and let's change transport for a domain
/etc/init.d/postfix restart
mysql -u root -p maildb
update domains set transport='maildrop' where domain='example2.com';
vi /usr/local/bin/mail-create.sh
#!/bin/bash
FOLD=$1
mkdir "/var/spool/mail/virtual/$FOLD"
mkdir "/var/spool/mail/virtual/$FOLD/new"
mkdir "/var/spool/mail/virtual/$FOLD/cur"
mkdir "/var/spool/mail/virtual/$FOLD/tmp"
mkdir "/var/spool/mail/virtual/$FOLD/.Spam"
mkdir "/var/spool/mail/virtual/$FOLD/.Safe"
mkdir "/var/spool/mail/virtual/$FOLD/.Spam/new"
mkdir "/var/spool/mail/virtual/$FOLD/.Safe/new"
mkdir "/var/spool/mail/virtual/$FOLD/.Spam/cur"
mkdir "/var/spool/mail/virtual/$FOLD/.Safe/cur"
mkdir "/var/spool/mail/virtual/$FOLD/.Spam/tmp"
mkdir "/var/spool/mail/virtual/$FOLD/.Safe/tmp"
chown -R virtual:virtual "/var/spool/mail/virtual/$FOLD"
chmod -R 2700 "/var/spool/mail/virtual/$FOLD"
cd "/var/spool/mail/virtual/$FOLD"
if [ -f subscriptions ]; then
echo -n ""
else
touch subscriptions
chown root:virtual subscriptions
chmod 644 subscriptions
fi
if [ -f courierimapsubscribed ]; then
echo -n ""
else
touch courierimapsubscribed
chown root:virtual courierimapsubscribed
chmod 644 courierimapsubscribed
fi
safe=`cat subscriptions |grep -i Safe |wc -l`
if [ $safe -lt 1 ]; then
echo "Safe" >> subscriptions
fi
spam=`cat subscriptions |grep -i Spam |wc -l`
if [ $spam -lt 1 ]; then
echo "Spam" >> subscriptions
fi
safe=`cat courierimapsubscribed |grep -i Safe |wc -l`
if [ $safe -lt 1 ]; then
echo "INBOX.Safe" >> courierimapsubscribed
fi
spam=`cat courierimapsubscribed |grep -i Spam |wc -l`
if [ $spam -lt 1 ]; then
echo "INBOX.Spam" >> courierimapsubscribed
fi
# now save the file and run it for a test
chmod 755 /usr/local/bin/mail-create.sh
/usr/local/bin/mail-create.sh 'webadmin@example2.com'
ls -lh /var/spool/mail/virtual
vi /etc/init.d/courier-authdaemon
# find these 2 lines
$daemonscript start
log_end_msg 0
# and put this line in between
chmod -R 777 /var/run/courier/authdaemon
# now save the file and restart courier authdaemon
/etc/init.d/courier-authdaemon restart
# and test maildrop. It should succeed
su -c "echo 'test' |maildrop -V 9 -d 'webadmin@example2.com' '' 'webadmin' 'example2.com' 'test@example.com'" virtual
Edit/Create the below files in order to add mailbox size limits. Full content provided
vi /etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop
auxprop_plugin: sql
mech_list: plain login cram-md5 digest-md5
sql_engine: mysql
sql_hostnames: 127.0.0.1
sql_user: mail
sql_passwd: mailPASSWORD
sql_database: maildb
sql_select: select clear from users where id='%u@%r' and enabled = 1 and (current_size<send_limit or send_limit=0)
vi /etc/postfix/mysql_mailbox.cf
user=mail
password=mailPASSWORD
dbname=maildb
table=users
select_field=maildir
where_field=id
hosts=127.0.0.1
additional_conditions = and enabled = 1 and (current_size<receive_limit or receive_limit=0)
vi /etc/postfix/mysql_maps.cf
user = mail
password = mailPASSWORD
hosts = 127.0.0.1
dbname = maildb
query = SELECT id,clear FROM users WHERE id='%s' and (current_size<send_limit or send_limit=0)
vi /etc/courier/authmysqlrc
# insert this line
MYSQL_QUOTA_FIELD concat(receive_limit,'S')
vi /etc/quotawarnmsg
Your mailbox is close to reaching it's set limit. In order to avoid any problems, please archive and/or remove some mails.
Once this limit is exceeded, you may not be able to send or receive any emails.
Kind regards,
Mailbox Management Team
vi /usr/local/bin/quota-check.sh
#!/bin/bash
# this script will update the quotas current_size in the database
for i in `ls /var/spool/mail/virtual`
do
SIZE=`du -bs /var/spool/mail/virtual/$i |awk '{print $1}'`
MBOX=$i
UPD="update users set current_size=$SIZE where id='$MBOX';"
done
# adjust the 20 to something you like between 1-60. It is basically how often in minutes should we check the mailbox current-size and update it. # a value of 30 is every 30 minutes, which is fine for huge systems, for small ones 10 is ok, for medium ones, 20 should do. Adjust accordingly should you need to echo -e "\n*/20 * * * * root /usr/local/bin/quota-check.sh" >> /etc/crontab mysql -u root -p maildb ALTER TABLE `users` ADD `send_limit` INT( 11 ) NOT NULL DEFAULT '0', ADD `receive_limit` INT( 11 ) NOT NULL DEFAULT '0', ADD `current_size` INT( 11 ) NOT NULL DEFAULT '0', ADD INDEX ( send_limit, receive_limit, current_size ); exit; /etc/init.d/cron restart
/etc/init.d/postfix restart /etc/init.d/amavis restart /etc/init.d/clamav-daemon restart /etc/init.d/clamav-freshclam restart /etc/init.d/courier-authdaemon restart /etc/init.d/courier-imap restart /etc/init.d/courier-imap-ssl restart /etc/init.d/spamassassin restart
#smtp iptables -I INPUT -p tcp -m tcp --dport 25 -j ACCEPT #imap iptables -I INPUT -p tcp -m tcp --dport 143 -j ACCEPT #submission iptables -I INPUT -p tcp -m tcp --dport 587 -j ACCEPT #smtps iptables -I INPUT -p tcp -m tcp --dport 465 -j ACCEPT #imap-ssl iptables -I INPUT -p tcp -m tcp --dport 993 -j ACCEPT iptables-save > /etc/iptables-rules
Let's go through the testing part now:
Execute the below on the mail server (the one you just installed using the above configuration)
tail -f /var/log/mail.log
Now, VERY IMPORTANT, to properly test this, you need to execute the below from a remote machine (one that's not localhost of the mail server). Should you be getting problems look at the output of the tail command on the mail server, it will give you details as to why it is failing (e.g. misspelled password in one of the mysql_*.cf files in /etc/postfix)
# send email to local user # replace IP with the IP of the mail server telnet 192.168.0.50 25 EHLO example3.com MAIL FROM: test@example3.com RCPT TO: webadmin@example.com DATA this test should deliver mail to test@example2.com as per aliases . QUIT # test open relay (send email to a non-local user without auth) telnet 192.168.0.50 25 EHLO example3.com MAIL FROM: webadmin@example2.com RCPT TO: test@example4.com # at this point you should get error message saying relay is denied. # If not, something is wrong and you are and open relay!!! QUIT # auth to smtp and send email to external user # execute the below 2 lines on any linux server with base64 installed # alternatively use a base64 online encoder to encode username and password # simple one is here: http://www.glonek.co.uk/base64/ echo -n "webadmin@example2.com" | base64 echo -n "apassword" | base64 # and now back to the place where we are using telnet from to test telnet 192.168.0.50 25 EHLO local AUTH LOGIN # now put in the base64 encoded username here and press enter # now put in the base64 encoded password here and press enter # you should see login successfull, if not, troubleshoot MAIL FROM: webadmin@example2.com RCPT TO: #put some existing email address here that you can check DATA this test should pass and I should receive the email . QUIT # test imap auth telnet 192.168.0.50 143 . LOGIN webadmin@example2.com apassword # you should see auth successfull . SELECT INBOX # you should see more then 0 recent and more then 0 exist messages! # if not, troubleshoot smtp, as test number 1 failed . LOGOUT # now login to the existing valid email address you given in test 3 and check if you received the messages # all should be fine and it should work. if not, retry test 3 with an email address that doesn't have spam tests
Backup MX Server Guide
Setup postfix for backup MX - do this on trhe backup MX server
vi /etc/postfix/main.cf
relay_domains = mysql:/etc/postfix/mysql_backups.cf
transport_maps = mysql:/etc/postfix/mysql_transport.cf
proxy_interfaces = 1.2.3.4 # should contain your IP address
vi /etc/postfix/mysql_backups.cf
user=mail
password=apassword
dbname=maildb
table=backups
select_field=domain
where_field=domain
hosts=127.0.0.1
additional_conditions = and enabled = 1
vi /etc/postfix/mysql_transport.cf
user=mail
password=apassword
dbname=maildb
table=backups
select_field=transport
where_field=domain
hosts=127.0.0.1
additional_conditions = and enabled = 1
mysql -u root -p maildb
CREATE TABLE `backups` ( `pkid` smallint(6) NOT NULL auto_increment, `domain` varchar(128) NOT NULL default '', `transport` varchar(128) NOT NULL default ':[]', `enabled` smallint(6) NOT NULL default '1', PRIMARY KEY (`pkid`), UNIQUE KEY `domain` (`domain`) );
# say you want to backup your friend's server called mail.friend.com
# for domains friend1.com and friend2.com. You would insert this:
INSERT INTO backups (domain,transport) VALUES ('friend1.com' , ':[mail.friend.com]' ), ('friend2.com' , ':[mail.friend.com]' );
System Administration Guide
Firewall
To look at all the tables and their rules with verbose info, you would normally execute:
iptables -L -vn
There are some states of packets that you can configure. As you noticed in step 1 of this tutorial, I am allowing all related and established connection packets. I do that as for example DNS lookups wouldn't work otherwise (especially that they are port 53 UDP). I also allow all connections on loopback interface (which is what you should always do). Then we just add rules on top of the chain to allow other ports (opening ports on firewall). The last rule is the one that gets executed if no other rules match, and that is our DROP policy (which DROPS the packets).
You can have -j ACCEPT to accept packets, -J DROP to drop packets or -j REJECT which will block like DROP, but it will respond to the connecting client with port-closed.
So, now some examples:
# add a rule to open port 40000 tcp incoming (-I - add on top ; -A - add on bottom) iptables -I INPUT -p tcp -m tcp --dport 40000 -j ACCEPT # and delete the rule iptables -D INPUT -p tcp -m tcp --dport 40000 -j ACCEPT # noew add a rule to allow IP 192.168.0.66 to connect from port 123 to our IP 192.168.0.50 on our interface eth0 to port 333 udp iptables -I INPUT -i eth0 -p udp -m udp --source 192.168.0.66 --sport 123 --destination 192.168.0.50 --dport 333 -j ACCEPT # block our computer (don't know why, but here goes) from connecting to IP 192.168.0.100 to port 53 from port 999 iptables -I OUTPUT -p udp -m udp --sport 999 --destination 192.168.0.100 --dport 53 -j DROP # instead of DROP, let's use reject (which will send proper reject message to connecting client) iptables -A INPUT -j REJECT --reject-with icmp-host-unreachable # or to say that only this port is closed instead of whole host iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable # see a list of available reject types iptables -j REJECT --help
BIND is quite easy to manage. If you want to add new zone (domain), you should just follow the instructions that we used in BIND Configuration manual for the example.com domain. Few things to note:
$TTL 1h - this is the time to live value for the domain cache. You should have something like 8h or 1d (8 hours / 1 day). 1 hour is a bit excessive. It's good for testing and migrations.
2009010910 ;serial - this is the serial number. After you make a change to the zone, you should always incement this to a higher value. I use yyyymmdd and then a number. This gives me up to 100 changes per day ensuring this will get incemented as it should
3600 ;refresh - refresh frequency of the slave server
3600 ;retry - retry frequency of slave in case of a communication problem
3600 ;expire - exiration time of the domain for the slave
3600 ;minimum TTL - minimum caching time in case of failed lookups
TXT and SPF records - these are the records that control mail spam using SPF from your domain. Check http://en.wikipedia.org/wiki/Sender_Policy_Framework for more details
Use your external IPs when configuring your domains. Using your LAN IP (if you use NAT) won't help external clients reach you using DNS lookups.
FTP
Information about the tables used by proftpd and how to fill them in is a follows. For the actual mysql commands, refer to examples in FTP configurations guide. Alternatively use phpmyadmin.
ftpuser Table:
The important columns are these (the others are handled by MySQL or Proftpd automatically, so do not fill these manually!):
* userid: The name of the virtual Proftpd user (e.g. exampleuser).
* passwd: The unencrypted (i.e., clear-text) password of the user.
* uid: The userid of the ftp user you created at the end of step two (e.g. 2001).
* gid: The groupid of the ftp group you created at the end of step two (e.g. 2001).
* homedir: The home directory of the virtual Proftpd user (e.g. /home/www.example.com). If it does not exist, it will be created when the new user logs in the first time via FTP. The virtual user will be jailed into this home directory, i.e., he cannot access other directories outside his home directory.
* shell: It is ok if you fill in /sbin/nologin here by default.
ftpquotalimits Table:
The important columns are these (the others are handled by MySQL or Proftpd automatically, so do not fill these manually!):
* name: The name of the virtual Proftpd user (e.g. exampleuser).
* quota_type: user or group. Normally, we use user here.
* per_session: true or false. true means the quota limits are valid only for a session. For example, if the user has a quota of 15 MB, and he has uploaded 15 MB during the current session, then he cannot upload anything more. But if he logs out and in again, he again has 15 MB available. false means, that the user has 15 MB at, no matter if he logs out and in again.
* limit_type: hard or soft. A hard quota limit is a never-to-exceed limit, while a soft quota can be temporarily exceeded. Normally you use hard here.
* bytes_in_avail: Upload limit in bytes (e.g. 15728640 for 15 MB). 0 means unlimited.
* bytes_out_avail: Download limit in bytes. 0 means unlimited.
* bytes_xfer_avail: Transfer limit in bytes. The sum of uploads and downloads a user is allowed to do. 0 means unlimited.
* files_in_avail: Upload limit in files. 0 means unlimited.
* files_out_avail: Download limit in files. 0 means unlimited.
* files_xfer_avail: Tranfer limit in files. 0 means unlimited.
The ftpquotatallies table is used by Proftpd internally to manage quotas so you do not have to make entries there!
Ok, first of all, here is the table description output:
mysql describe aliases; +-------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+----------------+ | pkid | smallint(3) | NO | PRI | NULL | auto_increment | | mail | varchar(120) | NO | UNI | | | | destination | varchar(120) | NO | | | | | enabled | tinyint(1) | NO | | 1 | | +-------------+--------------+------+-----+---------+----------------+ 4 rows in set (0.02 sec) mysql describe domains; +-----------+--------------+------+-----+----------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+----------+----------------+ | pkid | smallint(6) | NO | PRI | NULL | auto_increment | | domain | varchar(120) | NO | | | | | transport | varchar(120) | NO | | virtual: | | | enabled | tinyint(1) | NO | | 1 | | +-----------+--------------+------+-----+----------+----------------+ 4 rows in set (0.00 sec) mysql describe users; +-----------------+----------------------+------+-----+-------------------------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------------+----------------------+------+-----+-------------------------+-------+ | id | varchar(128) | NO | PRI | | | | name | varchar(128) | NO | | | | | uid | smallint(5) unsigned | NO | | 5000 | | | gid | smallint(5) unsigned | NO | | 5000 | | | home | varchar(255) | NO | | /var/spool/mail/virtual | | | maildir | varchar(255) | NO | | blah/ | | | enabled | tinyint(3) unsigned | NO | | 1 | | | change_password | tinyint(3) unsigned | NO | | 1 | | | clear | varchar(128) | NO | | ChangeMe | | | crypt | varchar(128) | NO | | sdtrusfX0Jj66 | | | quota | varchar(255) | NO | | | | | procmailrc | varchar(128) | NO | | | | | spamassassinrc | varchar(128) | NO | | | | +-----------------+----------------------+------+-----+-------------------------+-------+ 13 rows in set (0.00 sec)
In order to add aliases only fill in mail and destination. Mail is the email address we are accepting and destination is where we are delivering/relaying it to
To add a user, fill in ID (email address here), name (Full Name), maildir (which directory mail is delivered to), crypt and optionally clear password, quota (the limit of the mailbox, leave 0 for no limit).
Here is the template to use:
INSERT INTO domains (domain) VALUES ('domain.tld');
INSERT INTO aliases (mail,destination) VALUES
('@domain.tld','email@address'),
('postmaster@domain.tld','email@address'),
('abuse@domain.tld','email@address');
INSERT INTO users (id,name,maildir,clear) VALUES
('email@address','short description','foldername/',encrypt('password'));
INSERT INTO aliases (mail,destination) VALUES
('email@address','email@address');
Right, now that we gout spamassassin in mysql with userpreferences configurable by username/domain, we should configure some stuff for our domains.
The lookup normally goes like this: USERNAME(EMAIL) CONFIGURATION EXIST? => DOMAIN-WIDE CONFIGURATION EXIST? => GLOBAL CONFIGURATION EXIST?
As an example, required_hits (score) configuration part, once email arrives for a selected user, bayes will check if that user has required_hits configuration. If not, it will check for this domain configuration, if not found there, then only it will look for required_hits in the GLOBAL configuration.
The advantages if this are awsome! I can have 20 customers with their comains all using global configuration. Then I can also have 3 picky customers with domains that want separate spamassassin configuration. And then I can have the manager of 2 of those picky customers have their own spamassassin configuration as well. Get the point?
Now, I will give you the example MySQL command line queries to add configuration and then an example output for an example domain describing the entries of my table. You can then add/remove entries, based on your preferences.
mysql -u spamassassin -p spamassassin
# show all user preferences
select * from userpref;
# show all user preferences for anyone ending with domain.com (case insensitive)
select * from userpref where username like "%domain.com";
# describe the table
describe userpref;
# insert an entry
insert into userpref (username,preference,value) values ('%domain.com','use_bayes','1');
# change an entry
update userpref set value=0 where username='someone@domain.com' and preference='use_bayes';
# remove an entry
delete from userpref where username='someone@domain.com' and preference='use_bayes';
### and now the example configuration you could use
### !!!make sure you google for each line if you are unsure of the meaning before you use them!!!
### for more options, consult spamassassin configuration manual, all is there and it is outside of the scope of this tutorial
---------------------------------------------------------------------------
| username | preference | value |
+----------------------+-------------------------+------------------------+
| $GLOBAL | required_hits | 4.00 |
| $GLOBAL | subject_tag | [SPAM-_HITS_]- |
| $GLOBAL | score USER_IN_WHITELIST | -10 |
| $GLOBAL | whitelist_from | *@example.com |
| $GLOBAL | score USER_IN_BLACKLIST | 10 |
| $GLOBAL | report_safe | 0 |
| $GLOBAL | use_razor2 | 1 |
| $GLOBAL | use_pyzor | 1 |
| $GLOBAL | use_dcc | 1 |
| $GLOBAL | skip_rbl_checks | 1 |
| $GLOBAL | use_bayes | 1 |
| $GLOBAL | ok_locales | en |
| $GLOBAL | ok_languages | en |
| $GLOBAL | whitelist_from | *@xaw.com |
| $GLOBAL | use_auto_whitelist | 1 |
| $GLOBAL | rewrite_header Subject | [SPAM-_HITS_]- |
| %example.com | use_bayes | 1 |
| %example.com | whitelist_from | *@hp.com |
| %example.com | score USER_IN_WHITELIST | -10 |
| %example.com | score USER_IN_BLACKLIST | 10 |
| %example.com | ok_locales | en |
| %example.com | ok_languages | en |
| %example.com | use_razor2 | 1 |
| %example.com | use_pyzor | 1 |
| %example.com | use_dcc | 1 |
| %example.com | skip_rbl_checks | 1 |
| %example.com | report_safe | 0 |
| %example.com | required_hits | 5 |
| webadmin@example.com | use_razor2 | 1 |
| webadmin@example.com | use_bayes | 1 |
| webadmin@example.com | use_pyzor | 1 |
| webadmin@example.com | use_dcc | 1 |
| webadmin@example.com | skip_rbl_checks | 0 |
| webadmin@example.com | whitelist_from | *@paypal.com |
| webadmin@example.com | ok_languages | en |
| webadmin@example.com | ok_locales | en |
| webadmin@example.com | score USER_IN_WHITELIST | -10 |
| webadmin@example.com | score USER_IN_BLACKLIST | 10 |
| webadmin@example.com | required_hits | 4.10 |
| webadmin@example.com | report_safe | 0 |
| webadmin@example.com | use_auto_whitelist | 1 |
| webadmin@example.com | rewrite_header Subject | [SPAM-_HITS_]- |
---------------------------------------------------------------------------