Go to content Go to navigation Go to search

ProFTPD with virtual users

August 5th, 2010 by jde

Installation

# cd /usr/ports/ftp/proftpd-mysql
# make install clean

[ ] BAN              Include mod_ban (Requires CTRLS)
[ ] CLAMAV           Include mod_clamav
[ ] CTRLS            Include controls
[ ] EXEC             Include mod_exec
[X] HTMLDOCS         Include HTML documentation
[X] IFSESSION        Include mod_ifsession
[ ] IPV6             Use IPv6
[ ] LDAP             Use LDAP
[ ] LDAP_TLS         Use LDAP TLS (Requires LDAP, OPENSSL)
[X] MYSQL            MySQL auth
[X] NLS              Use nls (builds mod_lang)
[ ] ODBC             ODBC
[X] OPENSSL          Include mod_tls
[ ] PGSQL            Postgres auth
[X] QUOTA            Include mod_quota
[ ] QUOTATAB_RADIUS  include mod_quotatab_radius
[ ] SHAPER           Shaper module
[ ] SQLITE           SQLite auth
[X] RADIUS           Include mod_radius
[X] RATIO            Include mod_ratio
[X] README           Include mod_readme
[X] REWRITE          Include mod_rewrite
[ ] TLS_SHMCACHE     TLS SHM session cache (requires OPENSSL)
[ ] TDS              Include mod_sql_tds
[X] SFTP             Include mod_sftp
[X] SFTP_SQL         Include mod_sftp_sql
[ ] SFTP_PAM         Include mod_sftp_pam
[ ] SITE_MISC        Include mod_site_misc
[ ] SQL_PASSWD       Include mod_sql_passwd
[ ] UNIQUE           Include mod_unique_id
[X] WRAP             Include mod_wrap2
[ ] WRAP_FILE        Include mod_wrap2_file (requires WRAP)
[ ] WRAP_SQL         Include mod_wrap2_sql (requires WRAP)

After installation add this line to /etc/rc.conf:

proftpd_enable="YES"

As you probably know, this will ensure that ProFTPD is started automatically when the server is booted.

Dedicating a range of user id’s

You must decide on a range of user id’s which will be dedicated to ProFTPD and never be used for a unix user. Here is the rules I am following:

Range Used by…
0 – 199 the operating system. System privileged users.
200 – 999 various applications
1000 – 1999 Unix users. People with ssh access
2000 – 2999 virtual ProFTPD users

The above is just a guideline. You can of course decide on a range of your choice, but don’t mess with usernumbers below 200. As you can see I have dedicated the range 2000-2999 to virtual ProFTPD users.

Setup MySQL tables

If you wish to have ProFTPD related data in a seperate database (recommended), create the new database – remember to login to mysql first or use an interface like phpMyAdmin:

create database proftpd;

Create a mysql user for the proftpd daemon. The below will give access to all tables in the database, which is fine if you created the proftpd database. But if the tables is in an existing database, make sure that this user only has access to proftpd related tables.

GRANT SELECT , INSERT , UPDATE , DELETE ON `proftpd` . * TO 'proftpd'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;

Remember to replace ‘password’ with one of your choice.

If you are in a mysql prompt, use this command to make sure you have selected the proftpd database, before you attempt to create the tables:

use proftpd;

Next create the necessary tables – just copy/paste the below into your mysql prompt or use it in your mysql interface (e.g. phpmyadmin):

CREATE TABLE ftpgroup (
groupname varchar(16) NOT NULL default '',
gid smallint(6) NOT NULL default '80',
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) NOT NULL auto_increment,
userid varchar(32) NOT NULL default '',
passwd varchar(32) NOT NULL default '',
uid smallint(6) NOT NULL default '',
gid smallint(6) NOT NULL default '80',
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';

Note that the gid field in the ftpgroup and ftpuser tables are set to be 80 by default. Change this to reflect the group id of your systems webserver user.

Configure ProFTPD

Open /usr/local/etc/proftpd.conf and uncomment this line:

DefaultRoot ~

If you do not want or have support for IPv6, comment this line:

#UseIPv6 on

Add this at the end of the file and replace password with the one you chose for the proftpd user (see SQLConnectInfo):

# The passwords in MySQL are encrypted using CRYPT
SQLAuthTypes Plaintext Crypt
SQLAuthenticate users* groups*

# used to connect to the database
# databasename@host database_user user_password
SQLConnectInfo proftpd@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 2000

# create a user's home directory on demand if it doesn't exist. Create it with 755 permission (rwx-r-x-r-x)
CreateHome on dirmode 755

# 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

Create an ftp user

Before you can test your setup, let’s create an ftp user.:

First create a row in the ftpgroup table. Remember to change the values to reflect your webservers unix user:

INSERT INTO ftpgroup (groupname, gid, members) VALUES ('www', 80, 'www');

You only have to run the above query once. The inserts below is the ones you’ll use every time you add a new ftp user:

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 ('username', 'user', 'true', 'hard', 15728640, 0, 0, 0, 0, 0);

INSERT INTO ftpuser (userid, passwd, uid, gid, homedir, shell, count, accessed, modified) VALUES ('username', 'password', 2000, 80, '/usr/local/www/domain.tld', '/sbin/nologin', 0, '', '');

Remember to make any changes to reflect your own settings. Each field of the ftpquotalimits and ftpuser tables are explained at the bottom of this page.

Next time you create an ftp user, you’ll set the user id to 2001 and so forth.

If the home directory (homedir) is not created yet, ProFTPD will do this automatically. But if it exist, you must ensure that it has the correct user and group. The following command will set user id to 2000 and group to www recursively:

chown -R 2000:www /usr/local/www/domain.tld

Testing…

Start ProFTPD:

# /usr/local/etc/rc.d/proftpd start

Fire up your favorite ftp client from your desktop and try to connect to your server.

ftpuser and ftpquotalimits explained

This is the fields which you’ll fill when creating a new user. Don’t touch any other fields, since they are handled by MySQL or Proftpd automatically

ftpuser table:

* userid: The name of the virtual Proftpd user (e.g. exampleuser).
* passwd: The unencrypted (i.e., clear-text) password of the user.
* uid: The users id (e.g. 2000, 2001 an so forth).
* gid: The groupid of the systems webserver (e.g. 80).
* homedir: The home directory of the virtual Proftpd user (e.g. /usr/local/www/domain.tld). 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. Ftp users do not need a shell.

ftpquotalimits table:

* 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.

Troubleshooting

If you need to debug, run the daemon in the foreground as shown here. I will output information to your screen:

#proftpd -n -d 9
- mod_tls/2.1.2: using OpenSSL 0.9.7e-p1 25 Oct 2004
- SQLAuthenticate: use of * in SQLAuthenticate has been deprecated. Use AuthOrder for setting authoritativenes
- SQLAuthenticate: use of * in SQLAuthenticate has been deprecated. Use AuthOrder for setting authoritativeness
- warning: the SQLHomedirOnDemand directive is deprecated, and will be removed in the next release
your.hostname.tld -
your.hostname.tld - Config for ProFTPD Default Installation:
your.hostname.tld - Limit
your.hostname.tld - DenyAll
your.hostname.tld - DefaultServer
[...]

In this example the loglevel is set to the highest possible value (9).

Press [CTRL]+[C] to exit and stop the proftpd daemon.

Thanks to Howto forge – Virtual Hosting With Proftpd And MySQL

9. Roundcube webmail

August 3rd, 2010 by jde

Postfix Tutorial – Step 9

Roundcube webmail

Roundcube is a modern webmail GUI with sieve support.

Install Roundcube webmail:

# cd /usr/ports/mail/roundcube
# make install clean

Options for roundcube 0.4.b,1 

[X] MYSQL     Use MySQL backend
[ ] PGSQL     Use PostgreSQL backend
[ ] SQLITE    Use SQLite backend
[X] SSL       Enable SSL support (imaps or google spellcheck)
[X] LDAP      Enable LDAP support (address book)
[X] PSPELL    Enable PSpell support (internal spellcheck)
[X] NSC       Install network spellchecker
[X] AUTOCOMP  Enable autocomplete in login form

Connect to mysql, create a user and a database for rundcube. Then run the initial sql script:
Remember to replace username and password with something else.

 # mysql -u root -p
Enter password:

mysql> CREATE DATABASE roundcubemail;
mysql> GRANT ALL PRIVILEGES ON roundcubemail.* TO username@localhost IDENTIFIED BY 'password';
mysql> FLUSH PRIVILEGES;
mysql> USE roundcubemail;
mysql> SOURCE /usr/local/www/roundcube/SQL/mysql.initial.sql

Open the Roundcube database configuration file /usr/local/www/roundcube/config/db.inc.php and edit the database connection string:

$rcmail_config['db_dsnw'] = 'mysql://username:password@localhost/roundcubemail';

Open the Roundcube main configuration file /usr/local/www/roundcube/config/main.inc.php and activate the sieve plugin:

$rcmail_config['plugins'] = array('managesieve');

This plugin presents itself in the GUI with a ‘Filter’ tab under “Personal settings”.

The login form includes a ‘Server’ field. You can remove it by setting ‘default_host’:

$rcmail_config['default_host'] = 'mail.example.com';

8. Server-side mail filtering

August 3rd, 2010 by jde

Postfix Tutorial – Step 8

Server-side mail filtering

Many people maintain a set of filtering rules in their email clients. Incoming emails from grandma are automatically moved to the ‘family’ folder, and emails from your ex is moved to ‘trash’. Whatever suits your needs. This is a neat way of keeping things in order… but only until you start reading mail on your smart phone, netbook or use a webmail. With server side filters, no matter where you are or what gadget you use to fetch your mail, the same filters are applied.

Dovecot comes with a Sieve plugin to make this happen.

At the end of /usr/local/etc/dovecot.conf you will find a plugin section. Within that section locate the commented section about Sieve and add this line:

sieve_global_path=/usr/local/virtual/globalsieverc

In the protocol lda section uncomment this line (or add it if it’s not there):

mail_plugins = sieve

The globalsieverc file is a set of global filtering rules which applies to all users until they create their own rules.

Create /usr/local/virtual/globalsieverc with the following content:

require ["fileinto"];
# Move spam to spam folder
if header :contains "X-spam-flag" ["YES"] {
  fileinto "spam";
  stop;
}

Make the file readable by the vmail user:

# chown vmail /usr/local/virtual/globalsieverc

Restart Dovecot:

# /usr/local/etc/rc.d/dovecot restart
Stopping dovecot.
Waiting for PIDS: 56704.
Starting dovecot.

!!NOTE!! The name of the folder is case sensitive and if the folder does not exist, Dovecot will not fail or report it in the log. The folder is just created.

Send a spam mail to one of your accounts. It should be delivered in the spam folder of the account.

# sendmail my@examplemail.com < gtube.txt

Watch the dovecot-deliver.log:

Info: sieve: msgid=<GTUBE1.1010101@example.net>: stored mail into mailbox 'spam'

7. Spam and virus detection

August 1st, 2010 by jde

Postfix Tutorial – Step 7

Spam and virus detection

AMaViS (A Mail Virus Scanner) gives you protection against spam through Spamassassin and virus protection with Clamav (optional):

Let’s install AMaViS:

# cd /usr/ports/security/amavisd-new/
# make install clean

Options for amavisd-new 2.6.4_7,1

[X] BDB           Use BerkeleyDB for nanny/cache/snmp
[ ] SNMP          Install amavisd snmp subagent
[ ] SQLITE        Use SQLite for lookups
[X] MYSQL         Use MySQL for lookups/logging/quarantine
[ ] PGSQL         Use PgSQL for lookups/logging/quarantine
[ ] LDAP          Use LDAP for lookups
[ ] SASL          Use SASL authentication
[X] MILTER        Sendmail milter support
[X] SPAMASSASSIN  Use mail/p5-Mail-SpamAssassin
[ ] P0F           Passive operating system fingerprinting
[ ] ALTERMIME     Use AlterMime for defanging/disclaimers
[X] FILE          Use newer file(1) utility from port
[ ] RAR           RAR support with archivers/rar
[X] UNRAR         RAR support with archivers/unrar
[X] ARJ           ARJ support with archivers/arj
[ ] UNARJ         ARJ support with archivers/unarj
[X] LHA           LHA support with archivers/lha
[X] ARC           ARC support with archivers/arc
[ ] NOMARCH       ARC support with archivers/nomarch
[X] CAB           CAB support with archivers/cabextract
[X] RPM           RPM support with archivers/rpm2cpio
[X] ZOO           ZOO support with archivers/zoo
[ ] UNZOO         ZOO support with archivers/unzoo
[X] LZOP          LZOP support with archivers/lzop
[X] FREEZE        FREEZE support with archivers/freeze
[X] P7ZIP         P7ZIP support with archivers/p7zip
[X] MSWORD        Ms Word support with textproc/ripole
[ ] TNEF          Add external tnef decoder converters/tnef

Some time during the installation, you will also be prompted for SpamAssassin options:

Options for p5-Mail-SpamAssassin 3.3.1

[X] AS_ROOT        Run spamd as root (recommended)
[X] SPAMC          Build spamd/spamc (not for amavisd)
[ ] SACOMPILE      sa-compile
[X] DKIM           DKIM/DomainKeys Identified Mail
[X] SSL            Build with SSL support for spamd/spamc
[X] GNUPG          Install GnuPG (for sa-update)
[X] MYSQL          Add MySQL support
[ ] PGSQL          Add PostreSQL support
[X] RAZOR          Add Vipul's Razor support
[ ] SPF_QUERY      Add SPF query support
[ ] RELAY_COUNTRY  Relay country support
[ ] DCC            Add DCC support (see LICENSE)

Install Clamav:

# cd /usr/ports/clamav/
# make install clean

Options for clamav 0.96.1

[X] ARC           Enable arch archives support
[X] ARJ           Enable arj archives support
[X] LHA           Enable lha archives support
[X] UNZOO         Enable zoo archives support
[X] UNRAR         Enable rar archives support
[X] LLVM          Enable JIT Bytecode compiler
[ ] MILTER        Compile the milter interface
[ ] LDAP          libmilter was built with LDAP
[ ] ICONV         Enable ICONV support
[ ] STDERR        Print logs to stderr instead of stdout
[ ] EXPERIMENTAL  Build experimental code

In /etc/rc.conf:

amavis_milter_enable="YES"
amavisd_enable="YES"
clamav_clamd_enable="YES"
clamav_freshclam_enable="YES"
spamd_enable="YES"

Run sa-update to download configuration files (rules, scores, etc,) for SpamAssassin (Read more…), then start AMaViS and make sure it starts up correctly:

# rehash
# sa-update
# /usr/local/etc/rc.d/amavisd start
Starting amavisd.
# ps -ax | grep amavis
55743  ??  Ss     0:02.77 amavisd (master) (perl)
55744  ??  I      0:00.01 amavisd (virgin child) (perl)
55745  ??  I      0:00.01 amavisd (virgin child) (perl)
# netstat -p tcp -a | grep 10024
tcp4       0      0 localhost.10024        *.*                    LISTEN

Note that the startup command for AMaViS may fail silently, so if you don’t see it listening consult /var/log/maillog.

In /usr/local/etc/postfix/main.cf add these lines:

# Amavis content filtering
content_filter=smtp-amavis:[127.0.0.1]:10024
receive_override_options=no_address_mappings

In /usr/local/etc/postfix/master.cf add these lines:

smtp-amavis unix -       -       n       -       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
    -o local_header_rewrite_clients=

Reload Postfix:

# postfix reload
postfix/postfix-script: refreshing the Postfix mail system

Add clamav user to the vscan group in /etc/group:

Find these lines:

vscan:*:110:
clamav:*:106:

Change them to:

vscan:*:110:clamav
clamav:*:106:vscan

Start clamav (be patient, it may take a while before you get the prompt back):

# /usr/local/etc/rc.d/clamav-clamd start
Starting clamav_clamd.
LibClamAV Warning: **************************************************
LibClamAV Warning: ***  The virus database is older than 7 days!  ***
LibClamAV Warning: ***   Please update it as soon as possible.    ***
LibClamAV Warning: **************************************************

You can ignore this warning since clamav-freshclam will take care of that from now on. Let’s start it:

# /usr/local/etc/rc.d/clamav-freshclam start

Open /usr/local/etc/amavisd.conf and locate the @lookup_sql_dsn section. Edit it so that it looks like this:

@lookup_sql_dsn = (
     ['DBI:mysql:database=postfix;host=127.0.0.1;port=3306',
      'postfix',
      'password']);

$sql_select_policy = 'SELECT domain as name FROM domain WHERE CONCAT("@",domain) IN (%k)';

Also locate the @av_scanners section, and make sure clamav is uncommented:

### http://www.clamav.net/
['ClamAV-clamd',
   \&ask_daemon, ["CONTSCAN {}\n", "/var/run/clamav/clamd.sock"],
   qr/\bOK$/m, qr/\bFOUND$/m,
   qr/^.*?: (?!Infected Archive)(.*) FOUND$/m ],

Also tell AMaViS to let spam pass … It will be deliveret to the users inbox and then each user can set their own “Sieve” options.
Whatever you do, do not set $final_spam_destiny to D_BOUNCE. Spam is never sent from the spammers own email address, to you’ll just end up annoying (innocent) people.

$final_spam_destiny = D_PASS;
$spam_quarantine_to = undef;

Restart AMaViS:

# /usr/local/etc/rc.d/amavisd restart
Stopping amavisd.
Waiting for PIDS: 55743 55744 55745, 55743.
Starting amavisd.

Test AMaViS spam detection with this message: spamassassin.apache.org/gtube/gtube.txt
Replace my@examplemail.com with an existing mail account on your server.

# fetch http://spamassassin.apache.org/gtube/gtube.txt
# sendmail my@examplemail.com < gtube.txt

Your maillog should look something like this:

postfix/pickup[59535]: 13CF27CD: uid=0 from=<root>
postfix/cleanup[59586]: 13CF27CD: message-id=<GTUBE1.1010101@example.net>
postfix/qmgr[59173]: 13CF27CD: from=<root@examplemail.com>, size=938, nrcpt=1 (queue active)
postfix/smtpd[59590]: connect from unknown[127.0.0.1]
postfix/smtpd[59590]: 28DC17C8: client=unknown[127.0.0.1]
postfix/cleanup[59586]: 28DC17C8: message-id=<GTUBE1.1010101@example.net>
postfix/qmgr[59173]: 28DC17C8: from=<root@examplemail.com>, size=1673, nrcpt=1 (queue active)
amavis[59204]: (59204-01) Passed SPAM, <root@examplemail.com> -> <my@examplemail.com>, Message-ID: <GTUBE1.1010101@example.net>, mail_id: x1U2T7du28CF, Hits: 1005.069, size: 938, queued_as: 28DC17C8, 42885 ms
postfix/smtp[59588]: 13CF27CD: to=<my@examplemail.com>, relay=127.0.0.1[127.0.0.1]:10024, delay=50, delays=0.62/0.26/11/38, dsn=2.0.0, status=sent (250 2.0.0 from MTA([127.0.0.1]:10025): 250 2.0.0 Ok: queued as 28DC17C8)
postfix/qmgr[59173]: 13CF27CD: removed
postfix/pipe[59592]: 28DC17C8: to=<my@examplemail.com>, relay=dovecot, delay=3, delays=0.17/0.27/0/2.6, dsn=2.0.0, status=sent (delivered via dovecot service)
postfix/qmgr[59173]: 28DC17C8: removed
clamav:*:106:

5. SMTP authentication

August 1st, 2010 by jde

Postfix Tutorial – Step 5

SMTP authentication

You need to effectively prevent spammers from abusing your mail server. SASL (Simple Authentication and Security Layer) is an Internet standard method for adding authentication support to connection-based protocols such as SMTP. Dovecot includes a SASL framework that Postfix can use and therefore SMTP authentication has become very easy to setup.

Open /usr/local/etc/postfix/main.cf and set these values:

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sender_login_maps = mysql:/usr/local/etc/postfix/smtpd_sender_login_maps.cf
smtpd_sender_restrictions =
   reject_sender_login_mismatch
smtpd_recipient_restrictions =
   permit_mynetworks,
   permit_sasl_authenticated,
   reject_unauth_destination,
   reject_non_fqdn_hostname,
   reject_non_fqdn_sender,
   reject_non_fqdn_recipient,
   reject_unauth_pipelining,
   reject_invalid_hostname,
   reject_rbl_client sbl-xbl.spamhaus.org,
   reject_rbl_client korea.services.net,
   reject_rhsbl_sender dsn.rfc-ignorant.org

The first three lines enables Postfix to use dovecot SASL authentication. Line 7 (smtpd_recipient_restrictions) includes some other restrictions that will make it hard to deliver spam to local accounts. Later we will also add content checking with Spamassassin. Below you’ll find a short explanation for each of the above lines.:

  • smtpd_sasl_type
    Tell postfix to use dovecot for smtp authentication
  • smtpd_sasl_path
    Path to Dovecot SASL socket. Make sure the path is equal to ‘path’ in the ‘socket listen’->’client’ section of dovecot.conf
  • smtpd_sasl_auth_enable
    Tell postfix to enable SASL authentication
  • smtpd_sender_login_maps
    Look up allowed sender addresses in the database
  • smtpd_sender_restrictions
    Restrictions specific to the sender address received with the MAIL FROM command

    • reject_sender_login_mismatch
      Reject sender if the address in MAIL FROM command is not listed in smtpd_sender_login_maps
  • smtpd_recipient_restrictions
    Rules applied right after Postfix receives the `rcpt to` command:
    • permit_mynetworks
      Permit senders in the local network (mynetworks)
    • permit_sasl_authenticated
      Permit authenticated senders (users with a local mail account)
    • reject_unauth_destination
      Reject mail to unknown accounts. Or said in another way: Permit only mail destined to a user of a domain that is a local or virtual domain on this system (mydestination, virtual_alias_domains or virtual_mailbox_domains).
    • reject_non_fqdn_hostname:
      Reject the request when the HELO or EHLO hostname is not in fully-qualified domain form, as required by the RFC.
    • reject_non_fqdn_sender:
      Reject the request when the MAIL FROM address is not in fully-qualified domain form, as required by the RFC
    • reject_non_fqdn_recipient:
      Reject the request when the RCPT TO address is not in fully-qualified domain form, as required by the RFC
    • reject_invalid_hostname:
      Reject the request when the HELO or EHLO hostname syntax is invalid
    • reject_rbl_client sbl-xbl.spamhaus.org:
      Reject the request when the reversed client network address is listed with any A record under the specified rbl domain.
    • reject_rhsbl_sender dsn.rfc-ignorant.org:
      Reject the request when the MAIL FROM domain is listed with any A record under the specified rbl domain. Use it with care… You will most likely miss mail from Exchange servers and/or other people who misconfigured their mail setup.

Create the file /usr/local/etc/postfix/smtpd_sender_login_maps.cf with the following content:

user = postfix
password = password
hosts = localhost
dbname = postfix
query = SELECT username envelope FROM mailbox WHERE username = '%s' UNION SELECT address envelope FROM alias WHERE goto='%s'

This query creates a list of allowed sender addresses which lets the user send mail from the actual account and any address aliases pointing to that account.

Restart Postfix and let’s test to see if SMTP authentication works:

# postfix reload

# telnet localhost smtp
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.server.com ESMTP Postfix
ehlo example.com
250-mail.server.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
auth plain am5pQG5lcmRnaXJsLmRrAGpuaUBuZXJkZ2lybC5kawBqbjAzMDM3MQ==
235 2.7.0 Authentication successful
quit
221 2.0.0 Bye
Connection closed by foreign host.

For this test you need to Base64-encode the password. Let’s say the account is julia@example.com and the password is love2bicycle. The encoding is then done like this:

# perl -MMIME::Base64 -e \
'print encode_base64("julia\@example.com\0julia\@example.com\0love2bicycle")';
anVsaWFAZXhhbXBsZS5jb20AanVsaWFAZXhhbXBsZS5jb20AbG92ZTJiaWN5Y2xl

If the test above was successfull, you can test your server against mail-abuse.org:

# telnet relay-test.mail-abuse.org

A series of tests are performed and the output shows the result. At the end – if your server has successfully denied all relay attempts – you should see this message:

Tested host banner: 220 mail.example.com ESMTP Postfix
System appeared to reject relay attempts
Connection closed by foreign host.

6. Secure connections with TLS

August 1st, 2010 by jde

Postfix Tutorial – Step 6

Secure connections with TLS

It is extremely important to communicate over a secture (encrypted) connection. Especially if you plan use your mail accounts on wireless public networks (trains, libraries, restaurants, schools etc). If you don’t you are practically shouting out your password to the world.

On this page you’ll see how to setup Dovecot to offer SSL connections (imaps and pops), and TLS for SMTP connections to Postfix.

First generate a self-signed certificate that will be used for both Dovecot and Postfix:

Prepare directories for the certificates…

# cd /etc/mail/
# mkdir CA
# cd CA
# chmod 700 .
# mkdir certs crl newcerts private
# echo "01" > serial
# touch index.txt
# ln -s ../CA demoCA

Configure yourself as a CA (Certificate Authority).

# openssl req -nodes -new -days 3650 -x509 -keyout private/cakey.pem -out cacert.pem
Generating a 1024 bit RSA private key
.++++++
....++++++
writing new private key to 'private/cakey.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DK
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:Copenhagen
Organization Name (eg, company) [Internet Widgits Pty Ltd]: MeAndMy Server
Organizational Unit Name (eg, section) []:SSL Certs
Common Name (eg, YOUR name) []:mail.example.org
Email Address []:postmaster@example.org

Create a certificate:

# openssl req -nodes -new -x509 -keyout server.key -out server.ucrt
Generating a 1024 bit RSA private key
...................................................................++++++
................++++++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DK
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:Copenhagen
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MeAndMy Server
Organizational Unit Name (eg, section) []:SSL Cert
Common Name (eg, YOUR name) []:mail.example.org
Email Address []:postmaster@example.org

Create the certificate request:

# openssl x509 -x509toreq -in server.ucrt -signkey server.key -out server.csr
Getting request Private Key
Generating certificate request

Sign the request with the CA certificate:

# openssl ca -policy policy_anything -days 3650 -out server.crt -infiles server.csr
Using configuration from /etc/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
   Serial Number: 1 (0x1)
   Validity
       Not Before: Jul 31 21:56:02 2010 GMT
       Not After : Jul 28 21:56:02 2020 GMT
   Subject:
       countryName               = DK
       localityName              = Lille Skensved
       organizationName          = NerdGirl
       organizationalUnitName    = SSL Cert
       commonName                = gudinde.nerdgirl.dk
       emailAddress              = jette@nerdgirl.dk
   X509v3 extensions:
       X509v3 Basic Constraints:
           CA:FALSE
       Netscape Comment:
           OpenSSL Generated Certificate
       X509v3 Subject Key Identifier:
           F9:CA:02:AE:EA:A5:74:A9:FE:16:39:54:E7:DA:4D:B2:BC:CF:F1:89
       X509v3 Authority Key Identifier:
           keyid:64:3F:F5:19:E2:D9:00:D9:AB:C3:5B:5A:49:62:0F:39:1D:79:40:42
Certificate is to be certified until Jul 28 21:56:02 2020 GMT (3650 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
chmod o= private/cakey.pem

In /usr/local/etc/dovecot.conf find an edit these lines:

protocols = imap imaps managesieve
ssl = yes
ssl_cert_file = /etc/mail/CA/cacert.pem
ssl_key_file = /etc/mail/CA/private/cakey.pem

Restart Dovecot:

# /usr/local/etc/rc.d/dovecot restart
Stopping dovecot.
Starting dovecot.

In /usr/local/etc/postfix/main.cf add these lines:

smtpd_tls_cert_file= /etc/mail/CA/cacert.pem
smtpd_tls_key_file=/etc/mail/CA/private/cakey.pem
smtpd_tls_security_level = may

The smtpd_tls_security_level is set to ‘may’ which means that Postfix does not force it on clients. Users are offered tls as an optional feature.

# postfix reload
postfix/postfix-script: refreshing the Postfix mail system

If Postfix does not announce the TLS option some mail clients will refuse to use TLS. Therefore it is important to check that STARTTLS shows up after the ehlo greeting:

# telnet localhost smtp
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.nerdgirl.dk ESMTP Postfix
ehlo example.com
250-mail.nerdgirl.dk
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-AUTH PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
quit
221 2.0.0 Bye
Connection closed by foreign host.

Edit your mail client preferences and set it up to use TLS. You may have to restart it to make the change take effekt. You will probably be informed that the signature isn’t valid, but that’s because you signed it your self.