aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Kangas <skangas@skangas.se>2012-04-10 12:45:57 +0200
committerStefan Kangas <skangas@skangas.se>2012-04-10 12:45:57 +0200
commit96cdf431400f5d60646ba10df8751252e75fce73 (patch)
treeed20cb83c0f05fea27db1c97018115bc5641a2ab
parent3c46783e9758ad12492e7389616a1a6c9562fe08 (diff)
parent609d690fa33cd526c71f5e73939a2912939f73e2 (diff)
Merge branch 'master' of git.fripost.org:fripost-docs
-rw-r--r--fripost-docs.org701
1 files changed, 631 insertions, 70 deletions
diff --git a/fripost-docs.org b/fripost-docs.org
index 4eb2f5a..91c2122 100644
--- a/fripost-docs.org
+++ b/fripost-docs.org
@@ -256,6 +256,19 @@ This server should be referred to as the main `IMAP server'. We will have two or
more mail gateways that will relay e-mail to the main server over secure
connections. These are called `smarthosts'.
+Credentials are managed by a LDAP server. For the users to be able to
+authenticate to e.g., the IMAP server or the outgoing SMTP (via SASL), we will
+use the so called "authenticate binds": services simply forward the login
+information of the user to the LDAP server, that in turn hashes the password and
+checks wheter it maches the stored copy; if it does, the LDAP server answers back
+the query. See http://wanderingbarque.com/howtos/mailserver/big_picture.gif .
+This way, if the IMAP or SMTP server is compromised, the attacker will NOT have
+access to all credentials. Of course the LDAP server should only be listening to
+the machines hosting these services and ideally, should not be directly facing the
+internet.
+
+[TODO: Replace the MySQL database by an LDAP tree.]
+
The main server will also be responsible for keeping all users in an MySQL
database that will be replicated using MySQL.
@@ -263,8 +276,12 @@ database that will be replicated using MySQL.
IMAP server = the main storage server
+LDAP server = the server that stores users credentials and various other informations.
+
smarthost = the server receiving email from the internet (configured as MX)
+outgoing SMTP = a SMTP server that can relay mails of authenticated users (via SASL).
+
*** Configuring an SSH tunnel between two hosts
# Definitions:
@@ -601,6 +618,342 @@ mysql -u root -p
quit;
+*** Configuring the LDAP server
+
+On Debian Squeeze, OpenLDAP's configuration no longer uses `/etc/ldap/slapd.conf'
+(by default, but may completely igore it in the future), but the
+`/etc/ldap/slapd.d' directory instead. Unfortunately most of the online
+tutorials are describing methods using `/etc/ldap/slapd.conf'.
+
+[Note: This has been written by a LDAP noob. It should probably be
+rewritten/compressed in a couple of months. /Guilhem, 2012-04-03.]
+
+**** Install packages
+
+Here is a basic installation tutorial for Debian Squeeze:
+http://www.rjsystems.nl/en/2100-d6-openldap-provider.php
+
+sudo apt-get install slapd ldap-utils
+
+If it does not prompt for your domain, admin password, etc., run
+`dpkg-reconfigure -plow slapd'. Here is how we answer the questions:
+
+Omit OpenLDAP server configuration? No
+DNS domain name: fripost.org
+Organization name: Fripost
+Administrator password: *********
+Database backend to use: HDB
+Do you want the database to be removed when slapd is purged? No
+Move old database? Yes
+Allow LDAPv2 protocol? No
+
+
+We do not want to listen all the Internet: in `/etc/default/slapd', change
+`SLAPD_SERVICES' accordingly. E.g., to only listen to (non SSL) localhost and
+unix sockets, specify
+
+SLAPD_SERVICES="ldap:///127.0.0.1:389 ldapi:///%2fvar%2frun%2fopenldap%2fldapi/????x-mod=0777"
+
+(This should be enough if the connection from the IMAP/SASL services are
+wrapped into SSH or SSL/TLS tunnels.)
+
+We can check the configuration with
+
+ ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config"
+
+and modify a .ldif file with
+
+ ldapmodify -Y EXTERNAL -H ldapi:/// -f "<file.ldif>"
+
+**** Fripost's schema
+
+We base our schema on qmail's (http://dhits.nl/download/qmail.new.schema) and
+Jamm's (http://jamm.sourceforge.net/howto/html/implementation.html).
+
+
+ :: /etc/ldap/local/mail.fripost.org.ldif
+
+ dn: cn=mail.fripost.org,cn=schema,cn=config
+ objectClass: olcSchemaConfig
+ cn: mail.fripost.org
+ olcAttributeTypes: ( 1.3.6.1.4.1.12461.1.1.1 NAME 'maildir'
+ DESC 'The path to the maildir.'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+ olcAttributeTypes: ( 1.3.6.1.4.1.12461.1.1.3 NAME 'quota'
+ DESC 'The quota on a mailbox e.g., "50MB".'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
+ olcAttributetypes: ( 1.3.6.1.4.1.12461.1.1.4 NAME 'isActive'
+ DESC 'Is the leaf active?'
+ EQUALITY booleanMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
+ olcObjectclasses: ( 1.3.6.1.4.1.12461.1.2.1 NAME 'virtualDomain'
+ SUP top STRUCTURAL
+ DESC 'Virtual Domains'
+ MUST ( dc $ isActive )
+ MAY ( description ) )
+ olcObjectclasses: ( 1.3.6.1.4.1.12461.1.2.2 NAME 'virtualMailbox'
+ SUP top STRUCTURAL
+ DESC 'Virtual Mailboxes'
+ MUST ( mail $ userPassword $ dc $ maildir $ isActive )
+ MAY ( mailLocalAddress $ gn $ sn $ quota ) )
+
+Note: For the meaning of the sequences of digits above, grep the output of
+`ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config"'
+(For instance, 1.3.6.1.4.1.1466.115.121.1.26 is a IA5String, meaning the spaces
+don't matter)
+
+We can now add it to the schema list:
+
+ ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/local/mail.fripost.org.ldif
+
+(A [dirty] way to delete the schema is to remove the coresponding file in
+`/etc/ldap/slapd.d/cn=config/cn=schema/' and to restart slapd.)
+
+
+Note: Aliases have been inlined in the `virtualMailbox', and the column `alias.domain'
+of the MySQL schema has been dropped. If we want to let users manage their aliases (for
+the domains they manage), a possible solution is to add these managed virtual domains as
+childrens of their owner. A suitable ACL would then define the rights properly:
+
+ olcAccess: to dn.regex="dc=[^,]+,mail=([^,]+),o=mailboxes,dc=mail,dc=fripost,dc=org" attrs=mailLocalAddress
+ by self write
+ by dn.expand="mail=$1,o=mailboxes,dc=mail,dc=fripost,dc=org" write
+ by dn="cn=admin,dc=fripost,dc=org" write
+ by * read
+
+***** Add custom indexes
+
+The default indexes below are not enough for our purpose, since we will heavily
+be looking for e.g., the `mail' attribute.
+
+ :: ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(olcDatabase={1}hdb)"
+ [...]
+ olcDbIndex: objectClass eq
+ olcDbIndex: uid eq
+ olcDbIndex: cn eq
+ olcDbIndex: ou eq
+ olcDbIndex: dc eq
+
+
+ :: /etc/ldap/local/mail.fripost.org-index.ldif
+
+ dn: olcDatabase={1}hdb,cn=config
+ changetype: modify
+ delete: olcDbIndex
+ olcDbIndex: objectClass eq
+ -
+ add: olcDbIndex
+ olcDbIndex: objectClass pres,eq
+ -
+ delete: olcDbIndex
+ olcDbIndex: dc eq
+ -
+ add: olcDbIndex
+ olcDbIndex: dc eq,sub
+ -
+ add: olcDbIndex
+ olcDbIndex: mail eq,sub
+
+ldapmodify -QY EXTERNAL -H ldapi:/// -f /etc/ldap/local/mail.fripost.org-index.ldif
+
+
+ :: ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(olcDatabase={1}hdb)"
+ [...]
+ olcDbIndex: uid eq
+ olcDbIndex: cn eq
+ olcDbIndex: ou eq
+ olcDbIndex: objectClass pres,eq
+ olcDbIndex: dc eq,sub
+ olcDbIndex: mail eq,sub
+
+***** Restrict the access
+
+The default ACL is not restrictive enough for our purpose.
+Note: The ACLs are evaluated in order, hence the more specific rules should come
+first.
+
+ :: ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(olcDatabase={1}hdb)"
+ [...]
+ olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymous auth by dn="cn=admin,dc=fripost,dc=org" write by * none
+ olcAccess: {1}to dn.base="" by * read
+ olcAccess: {2}to * by self write by dn="cn=admin,dc=fripost,dc=org" write by * read
+ [...]
+
+
+ :: /etc/ldap/local/mail.fripost.org-acl.ldif
+
+ dn: olcDatabase={1}hdb,cn=config
+ changetype: modify
+ add: olcAccess
+ olcAccess: {0}to dn.children="dc=mail,dc=fripost,dc=org" attrs=userPassword
+ by self write
+ by dn="cn=admin,dc=fripost,dc=org" write
+ by anonymous auth
+ by * none
+ -
+ add: olcAccess
+ olcAccess: {1}to dn.children="o=mailboxes,dc=mail,dc=fripost,dc=org" attrs=gn,sn
+ by self write
+ by dn="cn=admin,dc=fripost,dc=org" write
+ by * none
+ -
+ add: olcAccess
+ olcAccess: {2}to dn.children="dc=mail,dc=fripost,dc=org"
+ by dn="cn=admin,dc=fripost,dc=org" write
+ by * read
+
+ldapmodify -QY EXTERNAL -H ldapi:/// -f /etc/ldap/local/mail.fripost.org-acl.ldif
+
+
+ :: ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(olcDatabase={1}hdb)"
+ [...]
+ olcAccess: {0}to dn.children="dc=mail,dc=fripost,dc=org" attrs=userPassword by self write by dn="cn=admin,dc=fripost,dc=org" write by anonymous auth by * none
+ olcAccess: {1}to dn.children="o=mailboxes,dc=mail,dc=fripost,dc=org" attrs=gn,sn by self write by dn="cn=admin,dc=fripost,dc=org" write by * none
+ olcAccess: {2}to dn.children="dc=mail,dc=fripost,dc=org" by dn="cn=admin,dc=fripost,dc=org" write by * read
+ olcAccess: {3}to attrs=userPassword,shadowLastChange by self write by anonymous auth by dn="cn=admin,dc=fripost,dc=org" write by * none
+ olcAccess: {4}to dn.base="" by * read
+ olcAccess: {5}to * by self write by dn="cn=admin,dc=fripost,dc=org" write by * read
+ [...]
+
+[TODO: The proper way to define admin rights would be to make a group "Admin".]
+
+Note: Attributes and entries here are world-readable (beside `userPassword' and names).
+An other solution, more restrictive, would be to bind Postfix and Dovecots requests
+to either `self' or a new dn "cn=mta,dc=mail,dc=fripost,dc=org" with a new ACL e.g.,
+
+ olcAccess: to dn.children="dc=mail,dc=fripost,dc=org"
+ by dn="cn=admin,dc=fripost,dc=org" write
+ by dn="cn=mta,dc=mail,dc=fripost,dc=org" read
+ by self read
+ by * none
+
+**** Create the base tree
+
+ :: /etc/ldap/local/mail.fripost.org-base.ldif
+
+ dn: dc=mail,dc=fripost,dc=org
+ objectClass: domain
+ dc: mail
+
+ dn: o=mailboxes,dc=mail,dc=fripost,dc=org
+ objectClass: organization
+ o: mailboxes
+ description: Virtual mailboxes
+
+ dn: o=domains,dc=mail,dc=fripost,dc=org
+ objectClass: organization
+ o: domains
+ description: Virtual domains
+
+ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /etc/ldap/local/mail.fripost.org-base.ldif
+
+
+To delete a leaf or a sub-tree:
+ ldapdelete -D cn=admin,dc=fripost,dc=org 'o=mailboxes,dc=mail,dc=fripost,dc=org' -W
+
+**** Populate the tree
+
+ :: /tmp/populate.ldif
+
+ dn: dc=fripost.org,o=domains,dc=mail,dc=fripost,dc=org
+ objectClass: top
+ objectClass: virtualDomain
+ dc: fripost.org
+ isActive: TRUE
+
+ dn: mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org
+ objectClass: top
+ objectClass: inetLocalMailRecipient
+ objectClass: virtualMailbox
+ mail: user@fripost.org
+ gn: First Name
+ sn: Last Name
+ userPassword: {SSHA}epZKWD1SiSe/dwL0to+jjnwFzxVUbFvg
+ dc: fripost.org
+ maildir: fripost.org/user/
+ isActive: TRUE
+ mailLocalAddress: user-alias@fripost.org
+ mailLocalAddress: user@example.org
+
+ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /tmp/populate.ldif
+
+
+Note: This should obviously be wrapped in a script; `ldapadd' reads the standard
+input, so there's no need to write on disk. The password here is a the S-SHA1 hash
+of "hackme", created with `slappasswd -s "{SSHA}"'.
+
+Note: If the LDIF files our schema depends on are not in loaded (in `/etc/ldap/slapd.d/cn=config/cn=schema/'),
+you may have to do it yourself. A dirty way is to create a file `/tmp/upgrade.conf' with the
+following:
+
+ include /etc/ldap/schema/core.schema
+ include /etc/ldap/schema/cosine.schema
+ include /etc/ldap/schema/nis.schema
+ include /etc/ldap/schema/misc.schema
+
+and a directory `/tmp/upgrade', then to run `slaptest -f /tmp/upgrade.conf -F /tmp/upgrade'.
+It creates a bunch of LDIF files that you need to clean (cf. https://help.ubuntu.com/10.04/serverguide/C/samba-ldap.html)
+and add with `ldapadd -Y EXTERNAL -H ldapi:/// -f <file.ldif>'.
+[TODO: that's just ugly. Find a better way.]
+
+**** Check the SASL binds (authentication)
+
+ldapwhoami -xD "mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org" -W
+
+should return the whole dn:
+
+"mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org"
+
+**** Check the ACL
+
+***** Admin
+
+`slpacat' (run as root) dumps everything in the tree, including the (hashed)
+passwords. So should
+
+ ldapsearch -xLLL -D "cn=admin,dc=fripost,dc=org" -b 'o=mailboxes,dc=mail,dc=fripost,dc=org' -W
+
+***** Anonymous user
+
+`ldapsearch -xLLL' should not return the user's name, or the (hashed) password.
+
+***** Self
+
+ldapsearch -xLLL -D "mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org" -b 'o=mailboxes,dc=mail,dc=fripost,dc=org' -W
+
+should return all the information for this very user, but not e.g., the password of the other users.
+
+
+The user should be able to change his/her password:
+
+ :: /tmp/usermod.ldif
+
+ dn: mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org
+ changetype: modify
+ replace: userPassword
+ userPassword: hop
+
+ldapmodify -D "mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org" -W -f /tmp/usermod.ldif
+
+[Note: Still that should be wrapped up in a script, and there is no need to write on
+disk since the data is read from the standard input.]
+[Note: If the task is merely to change the password, there is also `ldappasswd'.]
+
+We now ensure that the leaf has been updated:
+
+ :: slapcat -s "mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org"
+ [...]
+ userPassword:: aG9w
+ entryCSN: 20120404215647.957317Z#000000#000#000000
+ modifiersName: mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org
+ modifyTimestamp: 20120404215647Z
+
+
+Doing the same thing with eg, trying to change the `maildir', `ldapmodify'
+refuses with `Insufficient access (50)'.
+
*** Configuring the main IMAP server
**** Install packages
@@ -669,11 +1022,60 @@ sudo aptitude install dovecot-imapd
:: /etc/postfix/main.cf
+ virtual_mailbox_domains = ldap:$config_directory/ldap_virtual_mailbox_domains.cf
+ virtual_mailbox_maps = ldap:$config_directory/ldap_virtual_mailbox_maps.cf
+ virtual_alias_maps = ldap:$config_directory/ldap_virtual_alias_maps.cf
+
+ [...]
+
virtual_transport = dovecot
dovecot_destination_recipient_limit = 1
http://wiki.dovecot.org/LDA/Postfix
http://www.tehinterweb.co.uk/roundcube/#pisieverules
+
+
+:: /etc/postfix/ldap_virtual_mailbox_domains.cf
+
+ server_host = ldap://localhost/
+ version = 3
+ search_base = o=domains,dc=mail,dc=fripost,dc=org
+ bind = no
+ query_filter = (&(ObjectClass=virtualDomain)(dc=%s)(isActive=TRUE))
+ result_attribute = dc
+
+Test it:
+ postmap -q fripost.org ldap:/etc/postfix/ldap_virtual_domains_maps.cf || echo 'failed!'
+ postmap -q fake.org ldap:/etc/postfix/ldap_virtual_domains_maps.cf || echo 'failed!'
+
+
+:: /etc/postfix/ldap_virtual_mailbox_maps.cf
+
+ server_host = ldap://localhost/
+ version = 3
+ search_base = o=mailboxes,dc=mail,dc=fripost,dc=org
+ bind = no
+ query_filter = (&(ObjectClass=virtualMailbox)(mail=%s)(isActive=TRUE))
+ result_attribute = maildir
+
+Test it:
+ postmap -q user@fripost.org ldap:/etc/ldap/local/ldap_virtual_mailbox_maps.cf
+
+
+:: /etc/postfix/ldap_virtual_alias_maps.cf
+
+ server_host = ldap://localhost/
+ version = 3
+ search_base = o=mailboxes,dc=mail,dc=fripost,dc=org
+ bind = no
+ query_filter = (&(ObjectClass=virtualMailbox)(mailLocalAddress=%s)(isActive=TRUE))
+ result_attribute = mail
+
+Test it:
+ postmap -q user-alias@fripost.org ldap:/etc/postfix/ldap_virtual_alias_maps.cf
+ postmap -q user@example.org ldap:/etc/postfix/ldap_virtual_alias_maps.cf
+
+
**** Test delivery
sudo mkdir -p /home/mail/virtual/fripost.org/
@@ -751,6 +1153,59 @@ sudo /etc/init.d/dovecot restart
**** Making sure the services are not started at boot [might not be needed]
sudo update-rc.d -n dovecot stop 2 3 4 5 .
sudo update-rc.d -n postfix stop 2 3 4 5 .
+
+**** Use LDAP authenticate binds, and LDAP user queries.
+
+[TODO: The following handle the dialog the LDAP server. It should replace
+the MySQL bits above.]
+
+Instead of making a SQL query to fetch the (hashed) passwords, which implies to
+expose all credentials to Dovecot, an other approach is to forward the login
+information to our LDAP server, that will match it against the hashed copy contained
+in its database. This way if your IMAP server is compromised, the attacker will not
+have access to all the e-mails and user credentials.
+
+Documentation:
+http://wiki2.dovecot.org/HowTo/DovecotOpenLdap
+http://wiki2.dovecot.org/AuthDatabase/LDAP/AuthBinds
+
+Debian provides a squeleton configuration in /usr/share/dovecot/dovecot-ldap.conf .
+Copy this file in /etc/dovecot, and chmod 600 it. Uncomment the following lines:
+
+ hosts = localhost # Or wherever is our LDAP server
+ ldap_version = 3
+ auth_bind = yes
+ auth_bind_userdn = mail=%u,o=mailboxes,dc=mail,dc=fripost,dc=org
+ base = o=mailboxes,dc=mail,dc=fripost,dc=org
+ deref = never
+ scope = subtree
+ user_attrs = maildir=home=/home/mail/virtual/%$
+ user_filter = (&(objectClass=virtualMailbox)(mail=%u)(dc=%d)(isActive=TRUE))
+ pass_attrs = mail=user
+ pass_filter = (&(objectClass=virtualMailbox)(mail=%u)(dc=%d)(isActive=TRUE))
+
+(And the TLS-related lines in case we are not using a tunnel.) The "base" is the root
+of our tree structure, in our case dn="o=mailboxes,dc=mail,dc=fripost,dc=org".
+
+[Note: the `user_attrs' and `user_filter' are only relevant if the result of the query is
+used in the `dovecot.conf', for instance with mail_location = maildir:~. Otherwise, the
+userdb would better be dropped.]
+
+We can now amend the `dovecot.conf': Comment the "passwd sql {...}" and "userdb sql {...}"
+blocks, and uncomment
+
+ passdb ldap {
+ args = /etc/dovecot/dovecot-ldap.conf
+ }
+ # and
+ userdb ldap {
+ args = /etc/dovecot/dovecot-ldap-userdb.conf
+ }
+
+Following http://wiki2.dovecot.org/AuthDatabase/LDAP/AuthBinds, `dovecot-ldap-userdb.conf'
+can simply be a symlink to `dovecot-ldap.conf'. The names have to differ for Dovecot to send
+asynchronous request to the LDAP server.
+
*** Configuring a new smarthost to relay e-mail to the main IMAP server
**** Overview
@@ -774,82 +1229,188 @@ emails through the tunnel.
TODO: add the necessary configuration files
*** Configuring the outgoing SMTP
-**** Anonymize the senders
-If RoudCube automatically anonymize the sender (by simply shortening the trace), it's not the case (by default) for SquirrelMail, or when clients connect via ESMTP/ESMTPS/ESMTPA/ESMTPSA. Here are a couple of traces we want to obfuscate, to prevent the recicipient and/or the intermediate SMTP relays to track the sender.
-
-Received: from localhost (machine.example.org [127.0.0.1])
- by example.org (Postfix) with ESMTP id C9DAB841F4
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 16:27:56 +0100 (CET)
-Received: from example.org ([127.0.0.1])
- by localhost (machine.example.org [127.0.0.1]) (amavisd-new, port 10024)
- with ESMTP id 8onAXWOvImDh for <guilhem@fripost.org>;
- Thu, 22 Mar 2012 16:27:56 +0100 (CET)
-Received: from webmail.example.org (localhost [IPv6:::1])
- by example.org (Postfix) with ESMTP id 3ADAB8243D
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 16:27:56 +0100 (CET)
-Received: from 192.168.1.5
- (SquirrelMail authenticated user guilhem)
- by webmail.example.org with HTTP;
- Thu, 22 Mar 2012 16:27:56 +0100
-
-Received: from localhost (machine.example.org [127.0.0.1])
- by example.org (Postfix) with ESMTP id 2D1098243D
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 16:36:36 +0100 (CET)
-Received: from example.org ([127.0.0.1])
- by localhost (machine.example.org [127.0.0.1]) (amavisd-new, port 10024)
- with ESMTP id Hr2J-eRTN0jI for <guilhem@fripost.org>;
- Thu, 22 Mar 2012 16:36:35 +0100 (CET)
-Received: from client.example.org (client.machine.org [192.168.1.1])
- (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
- (Client CN "client.machine.org", Issuer "machine.org" (not verified))
- by machine.org (Postfix) with ESMTPS id DA22981B95
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 16:36:35 +0100 (CET)
-Received: (nullmailer pid 5057 invoked by uid 0);
- Thu, 22 Mar 2012 15:36:34 -0000
-
-Received: from localhost (machine.example.org [127.0.0.1])
- by example.org (Postfix) with ESMTP id DBAFE816BB
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 14:48:01 +0100 (CET)
-Received: from example.org ([127.0.0.1])
- by localhost (machine.example.org [127.0.0.1]) (amavisd-new, port 10024)
- with ESMTP id Upen4QhYpKf4 for <guilhem@fripost.org>;
- Thu, 22 Mar 2012 14:48:01 +0100 (CET)
-Received: from client.example.org (client.example.org [192.168.1.2])
- (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
- (Client CN "", Issuer "" (not verified))
- (Authenticated sender: guilhem)
- by guilhem.org (Postfix) with ESMTPSA id 40284804F5
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 14:48:01 +0100 (CET)
-Received: by machine@example.org (Postfix, from userid 1000)
- id 1D24F41747; Thu, 22 Mar 2012 14:48:00 +0100 (CET)
-
-(The first one was sent using a SquirrelMail; The second using ESMTPS; And the third using ESMTPSA).
-If we are to hide the sender, we could simply clean the trace (like RoundCube does) when the mail leaves the server. However, some aggressive mailfilters may reject the mail since the trace is incomplete (if RoundCube hides the history I guess it doesnt' happen that often, but who knows...).
-
-Another option would be to clean the trace and to simply add a fake field to pretend that the mail is sent from localhost by the user nobody:
- Received: by example.org (Postfix, from userid 65535)
- id 2C537816BB; Thu, 22 Mar 2012 14:08:45 +0100 (CET)
-This possible by adding "smtp_header_checks = regexp:$config_directory/smtp_header_checks" in the main.cf, with a suitable file "smtp_header_check" in the Postfix configuration directory.
+We will offer a SMTP relay for authenticated users (via SASL).
+
+**** Install packages
-Yetan other option is not to hide the trace, but rather forge it to pretend that the connections ESMTP/... are coming from localhost. This way we are not hiding the fast that a client has logged in using a valid certificate, and in case of an SMTP relay, the early part of the trace (before it entered our Postfix) remains unchanged. For example, the third trace would become:
+sudo apt-get install sasl2-bin libsasl2-modules-ldap
-Received: from [127.0.0.1] (localhost [127.0.0.1])
- (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
- (Client CN "", Issuer "" (not verified))
- (Authenticated sender: guilhem)
- by example.org (Postfix) with ESMTPSA id 40284804F5
- for <guilhem@fripost.org>; Thu, 22 Mar 2012 14:48:01 +0100 (CET)
-Received: by client.example.org (Postfix, from userid 1000)
- id 1D24F41747; Thu, 22 Mar 2012 14:48:00 +0100 (CET)
+(Scrictly speaking sasl2-bin is not necessary, but it offers some programs to
+test our installation.)
+
+**** Configure saslauthd
+
+ :: /etc/default/saslauthd
+ [...]
+ START=yes
+ MECHANISMS=ldap
+ OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"
+ [...]
-(the other field remaining unchanged). This is also possible using smtp_header_checks. In that case, the corresponding file would contain the following rexep, forging the header by pretending that the sender has EHLO'ed from localhost:
+(Note: The socket has to be readable by postfix.)
- /^Received:\s+from (\S+)\s+\(\S+\s+\S+\)(.*\sby example\.org \(Postfix\)\s+with E?SMTP(S|A|SA)\W.*)$/
- REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1])${2}
+ :: /etc/saslauthd.conf
-You can try out the regexp using "postmap -h -q - regexp:smtp_header_checks < email" (email can also be a bunch of traces).
+ ldap_servers: ldap://localhost
+ ldap_version: 3
+ ldap_auth_method: bind
+ ldap_search_base: o=mailboxes,dc=mail,dc=fripost,dc=org
+ ldap_scope: sub
+ ldap_filter: (&(objectClass=virtualMailbox)(mail=%u)(dc=%d)(isActive=TRUE))
-DISCLAIMER: The regexp probably needs tests (especially for multiple hops, in case of relaying SMTPs). Also, note that the hostname of the client has not been obfuscated in the above trace (and that will break the path if the client has a routable hostname that doesn't point to the SMTP server!). However, this line has been added by the client itself, so it's his/her responsability to masquerade it I suppose. In the same way, the CN and Issuer of the client's certificate may help to track him/her down. Maybe we should forge it as well?
+After restarting saslauthd (`/etc/init.d/saslauthd restart'), we can test the
+authentication: `testsaslauthd -u user@fripost.org -p password'. (The password
+cannot be prompted, so you may want to create a dummy user.)
+
+[Note: for `testsaslauthd' to work, you have to set OPTIONS="-c -m /var/run/saslauthd"
+in `/etc/default/saslauthd'.]
+
+**** Configure Postfix
+
+If everything goes through, it is now time to modify Postfix's main.cf:
+(Documentation: http://www.postfix.org/SASL_README.htm)
+
+ :: /etc/postfix/main.cf
+ [...]
+ smtpd_sasl_authenticated_header = yes
+ smtpd_sasl_auth_enable = yes
+ smtpd_sasl_local_domain =
+ smtpd_sasl_exceptions_networks = $mynetworks
+ smtpd_sasl_security_options = noanonymous, noplaintext
+ smtpd_sasl_tls_security_options = noanonymous
+ broken_sasl_auth_clients = yes
+ smtpd_sasl_type = cyrus
+ smtpd_sasl_path = smtpd
+ smtp_sasl_auth_enable = yes
+ smtp_sasl_password_maps = hash:$config_directory/sasl_passwd
+ # Note: `sasl_passwd' may be empty but Postfix complains if it doesn't exist
+ smtp_sasl_security_options = noanonymous, noplaintext
+ smtp_sasl_tls_security_options = noanonymous
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ permit_sasl_authenticated
+ [...]
+ [...]
+
+
+Finally, we can add the submission service to our master.cf, with customized policy:
+
+ :: /etc/postfix/master.cf
+ [...]
+ submission inet n - - - - smtpd
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+ [...]
+
+We now have to restart Postfix: `/etc/init.d/postfix restart'. (Maybe `postfix reload'
+is enough actually.)
+
+**** Anonymize the senders
+If RoudCube automatically anonymize the sender (by simply shortening the
+trace), it's not the case (by default) for SquirrelMail, or when clients
+connect via ESMTP/ESMTPS/ESMTPA/ESMTPSA. Here are a couple of traces we want
+to obfuscate, to prevent the recicipient and/or the intermediate SMTP relays
+to track the sender.
+
+ Received: from localhost (smtp.fripost.org [127.0.0.1])
+ by fripost.org (Postfix) with ESMTP id C9DAB841F4
+ for <recipient@example.org>; Thu, 22 Mar 2012 16:27:56 +0100 (CET)
+ Received: from fripost.org ([127.0.0.1])
+ by localhost (smtp.fripost.org [127.0.0.1]) (amavisd-new, port 10024)
+ with ESMTP id 8onAXWOvImDh for <recipient@example.org>;
+ Thu, 22 Mar 2012 16:27:56 +0100 (CET)
+ Received: from webmail.fripost.org (localhost [IPv6:::1])
+ by fripost.org (Postfix) with ESMTP id 3ADAB8243D
+ for <recipient@example.org>; Thu, 22 Mar 2012 16:27:56 +0100 (CET)
+ Received: from 192.168.1.5
+ (SquirrelMail authenticated user username)
+ by webmail.fripost.org with HTTP;
+ Thu, 22 Mar 2012 16:27:56 +0100
+
+ Received: from localhost (smtp.fripost.org [127.0.0.1])
+ by fripost.org (Postfix) with ESMTP id 2D1098243D
+ for <recipient@example.org>; Thu, 22 Mar 2012 16:36:36 +0100 (CET)
+ Received: from fripost.org ([127.0.0.1])
+ by localhost (smtp.fripost.org [127.0.0.1]) (amavisd-new, port 10024)
+ with ESMTP id Hr2J-eRTN0jI for <recipient@example.org>;
+ Thu, 22 Mar 2012 16:36:35 +0100 (CET)
+ Received: from client.example.org (client.example.org [192.168.1.1])
+ (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+ (Client CN "client.example.org", Issuer "example.org" (not verified))
+ by machine.org (Postfix) with ESMTPS id DA22981B95
+ for <recipient@example.org>; Thu, 22 Mar 2012 16:36:35 +0100 (CET)
+ Received: (nullmailer pid 5057 invoked by uid 0);
+ Thu, 22 Mar 2012 15:36:34 -0000
+
+ Received: from localhost (smtp.fripost.org [127.0.0.1])
+ by fripost.org (Postfix) with ESMTP id DBAFE816BB
+ for <recipient@example.org>; Thu, 22 Mar 2012 14:48:01 +0100 (CET)
+ Received: from fripost.org ([127.0.0.1])
+ by localhost (smtp.fripost.org [127.0.0.1]) (amavisd-new, port 10024)
+ with ESMTP id Upen4QhYpKf4 for <recipient@example.org>;
+ Thu, 22 Mar 2012 14:48:01 +0100 (CET)
+ Received: from client.example.org (client.example.org [192.168.1.5])
+ (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+ (Client CN "", Issuer "" (not verified))
+ (Authenticated sender: username)
+ by fripost.org (Postfix) with ESMTPSA id 40284804F5
+ for <recipient@example.org>; Thu, 22 Mar 2012 14:48:01 +0100 (CET)
+ Received: by client.example.org (Postfix, from userid 1000)
+ id 1D24F41747; Thu, 22 Mar 2012 14:48:00 +0100 (CET)
+
+(The first one was sent using a SquirrelMail; The second using ESMTPS;
+And the third using ESMTPSA).
+If we are to hide the sender, we could simply clean the trace (like
+RoundCube does) when the mail leaves the server. However, some aggressive
+mailfilters may reject the mail since the trace is incomplete (if RoundCube
+hides the history I guess it doesnt' happen that often, but who knows...).
+
+Another option would be to clean the trace and to simply add a fake field
+to pretend that the mail is sent from localhost by the user nobody:
+ Received: by fripost.org (Postfix, from userid 65535)
+ id 2C537816BB; Thu, 22 Mar 2012 14:08:45 +0100 (CET)
+This possible by adding "smtp_header_checks = regexp:$config_directory/smtp_header_checks"
+in the Postfix's main.cf, with a suitable file "smtp_header_check" in the Postfix
+configuration directory.
+
+Yet an other option is not to hide the trace, but rather forge it to
+pretend that the ESMTP/... connections are all coming from localhost.
+This way we are not hiding the fact that a client has logged in using a
+valid certificate, and in case of an SMTP relay, the early part of the
+trace (before it entered our Postfix sever) remains unchanged. For
+example, the early part of the third trace would become:
+
+ Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))
+ (Client CN "", Issuer "" (not verified))
+ (Authenticated sender: username)
+ by fripost.org (Postfix) with ESMTPSA id 40284804F5
+ for <recipient@example.org>; Thu, 22 Mar 2012 14:48:01 +0100 (CET)
+ Received: by client.example.org (Postfix, from userid 1000)
+ id 1D24F41747; Thu, 22 Mar 2012 14:48:00 +0100 (CET)
+
+(the other field remaining unchanged). This is also made possible by
+smtp_header_checks. In that case, the corresponding file would contain
+the following rexep, forging the header by pretending that the client
+has EHLO'ed from localhost:
+
+ /^Received:\s+from (\S+)\s+\(\S+\s+\S+\)(.*\sby fripost\.org \(Postfix\)\s+with E?SMTP(S|A|SA)\W.*)$/
+ REPLACE Received: from localhost (localhost [127.0.0.1])${2}
+
+You can try out the regexp using "postmap -h -q - regexp:smtp_header_checks < email"
+(where `email' may also be a bunch of traces).
+
+DISCLAIMER: The regexp probably needs tests (especially for multiple hops,
+in case of relaying SMTPs). Also, note that the hostname of the client has
+NOT been obfuscated in the above trace (and that will break the relaying path
+if the client has a routable hostname that doesn't point to the SMTP server!).
+However, this line has been added by the client itself, so it's his/her
+responsability to masquerade it I suppose. In the same way, the CN and Issuer
+of the client's certificate may help to track him/her down. Maybe we should
+forge these as well?
** Configuring the webserver