From 29639331a22c90c8dd1f57fb3d724cd4fd499fea Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sat, 31 Mar 2012 14:50:42 +0200 Subject: Forging e-mail headers, re (cleaning) --- fripost-docs.org | 135 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 54 deletions(-) diff --git a/fripost-docs.org b/fripost-docs.org index 38e3a27..686e39b 100644 --- a/fripost-docs.org +++ b/fripost-docs.org @@ -775,81 +775,108 @@ 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 ; 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 ; +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 ; 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 ; 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 ; 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 ; 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; + (SquirrelMail authenticated user username) + by webmail.fripost.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 ; 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 ; - Thu, 22 Mar 2012 16:36:35 +0100 (CET) -Received: from client.example.org (client.machine.org [192.168.1.1]) +Received: from localhost (smtp.fripost.org [127.0.0.1]) + by fripost.org (Postfix) with ESMTP id 2D1098243D + for ; 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 ; + 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.machine.org", Issuer "machine.org" (not verified)) + (Client CN "client.example.org", Issuer "example.org" (not verified)) by machine.org (Postfix) with ESMTPS id DA22981B95 - for ; Thu, 22 Mar 2012 16:36:35 +0100 (CET) + for ; 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 ; 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 ; +Received: from localhost (smtp.fripost.org [127.0.0.1]) + by fripost.org (Postfix) with ESMTP id DBAFE816BB + for ; 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 ; Thu, 22 Mar 2012 14:48:01 +0100 (CET) -Received: from client.example.org (client.example.org [192.168.1.2]) +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: guilhem) - by guilhem.org (Postfix) with ESMTPSA id 40284804F5 - for ; Thu, 22 Mar 2012 14:48:01 +0100 (CET) -Received: by machine@example.org (Postfix, from userid 1000) + (Authenticated sender: username) + by fripost.org (Postfix) with ESMTPSA id 40284804F5 + for ; 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...). +(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) +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 main.cf, with a suitable file "smtp_header_check" in the Postfix configuration directory. - -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: - -Received: from [127.0.0.1] (localhost [127.0.0.1]) +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: guilhem) - by example.org (Postfix) with ESMTPSA id 40284804F5 - for ; Thu, 22 Mar 2012 14:48:01 +0100 (CET) + (Authenticated sender: username) + by fripost.org (Postfix) with ESMTPSA id 40284804F5 + for ; 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 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: - - /^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} - -You can try out the regexp using "postmap -h -q - regexp:smtp_header_checks < email" (email can 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 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? +(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 -- cgit v1.2.3 From b090cf1444342b20a1cfdd3ea01c602d1988f121 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sun, 1 Apr 2012 15:28:29 +0200 Subject: Use LDAP's authenticate binds with Postfix and Dovecot. --- fripost-docs.org | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/fripost-docs.org b/fripost-docs.org index 686e39b..91c8943 100644 --- a/fripost-docs.org +++ b/fripost-docs.org @@ -256,6 +256,18 @@ 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: Find a suitable LDAP schema, and drop the MySQL database] The main server will also be responsible for keeping all users in an MySQL database that will be replicated using MySQL. @@ -263,8 +275,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 +617,52 @@ 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'. + +**** 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'. + +We do not want to listen all the Internet: in `/etc/default/slapd', change +`SLAPD_SERVICES' accordingly. E.g., to only listen to localhost (non SSL) 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 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 "" + +**** Our schema + +[TODO: find something suitable to subsume the MySQL databases; we could find some +inspiration here: http://dhits.nl/download/qmail.new.schema] + +If the task is only to provide a secure way to authenticate, the "basic tree" of +http://www.rjsystems.nl/en/2100-d6-openldap-provider.php#tree is good enough. + +After adding an user, we can check that the authentication works properly: + +ldapwhoami -xD uid=myuserid,ou=account,dc=fripost,dc=org -W + *** Configuring the main IMAP server **** Install packages @@ -751,6 +813,47 @@ 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. + +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 +auth_bind = yes +auth_bind_userdn = uid=%u,ou=accounts,dc=fripost,dc=org +ldap_version = 3 +base = ou=accounts,dc=fripost,dc=org +pass_filter = (&(objectClass=posixAccount)(uid=%u)) + +(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="ou=accounts,dc=fripost,dc=org". + +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,6 +877,79 @@ emails through the tunnel. TODO: add the necessary configuration files *** Configuring the outgoing SMTP +We will offer a SMTP relay for authenticated users (via SASL). + +**** Install packages + +sudo apt-get install sasl2-bin libsasl2-modules-ldap + +(Scrictly speaking sasl2-bin is not necessary, but it offers some programs to +test our installation.) + +**** Configure saslauthd + +Customize `/etc/default/saslauthd' as follows: + +START=yes +MECHANISMS=ldap +OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd" + +(The socket has to be readable by postfix.) + +Now, in the `/etc/saslauthd.conf': + +ldap_servers: ldap://localhost +ldap_version: 3 +ldap_search_base: ou=accounts,dc=fripost,dc=org +ldap_scope: sub +ldap_filter: uid=%u +ldap_auth_method: bind + +After restarting saslauthd (`/etc/init.d/saslauthd restart'), we can test the +authentication: testsaslauthd -u userid -p password. (The password cannot be +prompted, so you may want to create a dummy user.) + +**** Configure Postfix + +If everything goes through, it is now time to modify Postfix's main.cf: +(Documentation: http://www.postfix.org/SASL_README.htm) + +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 +smtp_sasl_security_options = noanonymous, noplaintext +smtp_sasl_tls_security_options = noanonymous + +# `sasl_passwd' may be empty but Postfix complains if it doesn't exist + +And still in the main.cf, add a policy stating that authenticate users are +allowed to connect and send mail: + +smtpd_recipient_restrictions = + permit_mynetworks + permit_sasl_authenticated + [...] + +Finally, we can add the submission service to our master.cf, with customized policy: + +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 can now restart Postfix: `/etc/init.d/postfix restart'. + + **** 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 -- cgit v1.2.3 From 45768c5c537c0fa418fb45015f46fd5764022dd1 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Tue, 3 Apr 2012 02:14:31 +0200 Subject: A first schema for the table that maintains users informations. --- fripost-docs.org | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 10 deletions(-) diff --git a/fripost-docs.org b/fripost-docs.org index 91c8943..abed845 100644 --- a/fripost-docs.org +++ b/fripost-docs.org @@ -632,16 +632,26 @@ 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'. +`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 localhost (non SSL) and +`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 tunnels.) +wrapped into SSH or SSL/TLS tunnels.) We can check the configuration with @@ -651,17 +661,132 @@ and modify a .ldif file with ldapmodify -Y EXTERNAL -H ldapi:/// -f "" -**** Our schema +**** Fripost's schema + +We base our schema on qmail's: http://dhits.nl/download/qmail.new.schema . +Note: our schema is not standalone, see the section below. + +Put the code below into `/tmp/fripost.ldif', and run + ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/fripost.ldif + +dn: cn=mailAccount,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: mailAccount +olcAttributeTypes: ( 1.3.6.1.4.1.12461.1.1.1 NAME 'mailbox' + DESC 'The path to the mailbox.' + 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.2 NAME 'domain' + DESC 'The path to the mailbox.' + EQUALITY caseIgnoreIA5Match + 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 ) + + +# 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 +# doesn't matter) + + +# We will also use other attributes, coming from imported schemas: +# * uid, userid: RFC4519: user identifier. +# Usernames. Note that the case is ignored. +# * cn, commonName: RFC4519: common name(s) for which the entity is known by +# First name of the user. +# * sn, surName: RFC2256: last (family) name(s) for which the entity is known by +# +# Also, some attributes will be set automatically, see below. + + +[TODO: This is only an attempt to subsume the MySQL table `mailbox'. Do the same +for the 3 others.] + +**** Add a custom ACL + +The ACL is already properly defined (check it with `ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(|(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 + +But we may want to hide the users' name to anyone but the admin. To this end, create a file +`/tmp/bigbrother.ldif' with the following content: + +dn: olcDatabase={1}hdb,cn=config +changetype: modify +add: olcAccess +olcAccess: {1}to dn.base="o=mailAccount,dc=fripost,dc=org" attrs=cn,sn by dn="cn=admin,dc=fripost,dc=org" write by * none + +and run `ldapmodify -QY EXTERNAL -H ldapi:/// -f /tmp/bigbrother.ldif'. We can +now check `ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(|(cn=config)(olcDatabase={1}hdb))"' +the difference: + +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="o=mailAccount,dc=fripost,dc=org" attrs=cn,sn + by dn="cn=admin,dc=fripost,dc=org" write + by * none +olcAccess: {2}to dn.base="" + by * read +olcAccess: {3}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".] + +**** Add a user + +We start by creating our base tree, in `/tmp/base.ldif': + +dn: o=mailAccount,dc=fripost,dc=org +o: Mail Users +objectClass: organization + +(Run `ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /tmp/base.ldif' to attach the tree'.) + + +We are now ready to create a user. Open a file `/tmp/user.ldif', with the following content: + +dn: uid=user,o=mailAccount,dc=fripost,dc=org +objectClass: top +objectClass: mailAccount +uid: user +cn: secret +userPassword: {SSHA}ByzkkO0jNcDwx3+1wZi6FVm0WoEI5Ivo +domain: fripost.org +mailbox: /hop/ +accountActive: TRUE + +And add it with `ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /tmp/user.ldif'. (Note: this should +obviously be wrapped in a script; `ldapadd' reads the standard input, so there's no need to write +on disk.) Where the password is a the S-SHA1 hash of "hackme". + -[TODO: find something suitable to subsume the MySQL databases; we could find some -inspiration here: http://dhits.nl/download/qmail.new.schema] +To delete a user, you can run + ldapdelete -D cn=admin,dc=fripost,dc=org 'uid=user,o=mailAccount,dc=fripost,dc=org' -W -If the task is only to provide a secure way to authenticate, the "basic tree" of -http://www.rjsystems.nl/en/2100-d6-openldap-provider.php#tree is good enough. +`slapcat', run as root, dums everything in the tree, including the (hashed) passwords. However, run as a +non-authenticated user, the target's name remains hidden. -After adding an user, we can check that the authentication works properly: +We can check that the SASL binds work as excected: -ldapwhoami -xD uid=myuserid,ou=account,dc=fripost,dc=org -W + ldapwhoami -xD uid=user,ou=mailAccount,dc=fripost,dc=org -W *** Configuring the main IMAP server **** Install packages -- cgit v1.2.3 From 4ed5f860d885fd26455b657d9dac83d40a88637b Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 5 Apr 2012 00:15:19 +0200 Subject: A possible LDAP schema. --- fripost-docs.org | 560 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 354 insertions(+), 206 deletions(-) diff --git a/fripost-docs.org b/fripost-docs.org index abed845..5e7367b 100644 --- a/fripost-docs.org +++ b/fripost-docs.org @@ -267,7 +267,8 @@ 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: Find a suitable LDAP schema, and drop the MySQL database] +[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. @@ -624,6 +625,9 @@ On Debian Squeeze, OpenLDAP's configuration no longer uses `/etc/ldap/slapd.conf `/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: @@ -663,130 +667,271 @@ and modify a .ldif file with **** Fripost's schema -We base our schema on qmail's: http://dhits.nl/download/qmail.new.schema . -Note: our schema is not standalone, see the section below. - -Put the code below into `/tmp/fripost.ldif', and run - ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/fripost.ldif - -dn: cn=mailAccount,cn=schema,cn=config -objectClass: olcSchemaConfig -cn: mailAccount -olcAttributeTypes: ( 1.3.6.1.4.1.12461.1.1.1 NAME 'mailbox' - DESC 'The path to the mailbox.' - 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.2 NAME 'domain' - DESC 'The path to the mailbox.' - EQUALITY caseIgnoreIA5Match - 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 ) - - -# 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 -# doesn't matter) - +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 'mailbox' + DESC 'The path to the mailbox.' + 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 'Mail Account Object' + MUST ( mail $ userPassword $ dc $ mailbox $ isActive ) + MAY ( mailLocalAddress $ gn $ sn $ quota ) ) + -# We will also use other attributes, coming from imported schemas: -# * uid, userid: RFC4519: user identifier. -# Usernames. Note that the case is ignored. -# * cn, commonName: RFC4519: common name(s) for which the entity is known by -# First name of the user. -# * sn, surName: RFC2256: last (family) name(s) for which the entity is known by -# -# Also, some attributes will be set automatically, see below. - - -[TODO: This is only an attempt to subsume the MySQL table `mailbox'. Do the same -for the 3 others.] - -**** Add a custom ACL - -The ACL is already properly defined (check it with `ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(|(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 - -But we may want to hide the users' name to anyone but the admin. To this end, create a file -`/tmp/bigbrother.ldif' with the following content: - -dn: olcDatabase={1}hdb,cn=config -changetype: modify -add: olcAccess -olcAccess: {1}to dn.base="o=mailAccount,dc=fripost,dc=org" attrs=cn,sn by dn="cn=admin,dc=fripost,dc=org" write by * none - -and run `ldapmodify -QY EXTERNAL -H ldapi:/// -f /tmp/bigbrother.ldif'. We can -now check `ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config "(|(cn=config)(olcDatabase={1}hdb))"' -the difference: - -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="o=mailAccount,dc=fripost,dc=org" attrs=cn,sn - by dn="cn=admin,dc=fripost,dc=org" write - by * none -olcAccess: {2}to dn.base="" - by * read -olcAccess: {3}to * - by self write - by dn="cn=admin,dc=fripost,dc=org" write - by * read +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.) + +***** 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".] -**** Add a user +**** 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 + mailbox: fripost.org/user/ + isActive: TRUE + mailLocalAddress: user-alias@fripost.org + mailLocalAddress: user@example.org -We start by creating our base tree, in `/tmp/base.ldif': +ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /tmp/populate.ldif -dn: o=mailAccount,dc=fripost,dc=org -o: Mail Users -objectClass: organization -(Run `ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /tmp/base.ldif' to attach the tree'.) +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: -We are now ready to create a user. Open a file `/tmp/user.ldif', with the following content: + include /etc/ldap/schema/core.schema + include /etc/ldap/schema/cosine.schema + include /etc/ldap/schema/nis.schema + include /etc/ldap/schema/misc.schema -dn: uid=user,o=mailAccount,dc=fripost,dc=org -objectClass: top -objectClass: mailAccount -uid: user -cn: secret -userPassword: {SSHA}ByzkkO0jNcDwx3+1wZi6FVm0WoEI5Ivo -domain: fripost.org -mailbox: /hop/ -accountActive: TRUE +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 '. +[TODO: that's just ugly. Find a better way.] -And add it with `ldapadd -cxWD cn=admin,dc=fripost,dc=org -f /tmp/user.ldif'. (Note: this should -obviously be wrapped in a script; `ldapadd' reads the standard input, so there's no need to write -on disk.) Where the password is a the S-SHA1 hash of "hackme". +**** Check the SASL binds (authentication) +ldapwhoami -xD "mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org" -W -To delete a user, you can run - ldapdelete -D cn=admin,dc=fripost,dc=org 'uid=user,o=mailAccount,dc=fripost,dc=org' -W +should return the whole dn: -`slapcat', run as root, dums everything in the tree, including the (hashed) passwords. However, run as a -non-authenticated user, the target's name remains hidden. +"mail=user@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org" -We can check that the SASL binds work as excected: +**** Check the ACL - ldapwhoami -xD uid=user,ou=mailAccount,dc=fripost,dc=org -W +***** 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=fripost@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org + modifyTimestamp: 20120404215647Z + + +Doing the same thing with eg, trying to change the `mailbox', `ldapmodify' +refuses with `Insufficient access (50)'. *** Configuring the main IMAP server **** Install packages @@ -1013,22 +1158,23 @@ test our installation.) **** Configure saslauthd -Customize `/etc/default/saslauthd' as follows: - -START=yes -MECHANISMS=ldap -OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd" + :: /etc/default/saslauthd + [...] + START=yes + MECHANISMS=ldap + OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd" + [...] (The socket has to be readable by postfix.) -Now, in the `/etc/saslauthd.conf': + :: /etc/saslauthd.conf -ldap_servers: ldap://localhost -ldap_version: 3 -ldap_search_base: ou=accounts,dc=fripost,dc=org -ldap_scope: sub -ldap_filter: uid=%u -ldap_auth_method: bind + ldap_servers: ldap://localhost + ldap_version: 3 + ldap_search_base: o=mailboxes,dc=mail,dc=fripost,dc=org + ldap_scope: sub + ldap_filter: uid=%u + ldap_auth_method: bind After restarting saslauthd (`/etc/init.d/saslauthd restart'), we can test the authentication: testsaslauthd -u userid -p password. (The password cannot be @@ -1039,41 +1185,43 @@ prompted, so you may want to create a dummy user.) If everything goes through, it is now time to modify Postfix's main.cf: (Documentation: http://www.postfix.org/SASL_README.htm) -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 -smtp_sasl_security_options = noanonymous, noplaintext -smtp_sasl_tls_security_options = noanonymous - -# `sasl_passwd' may be empty but Postfix complains if it doesn't exist - -And still in the main.cf, add a policy stating that authenticate users are -allowed to connect and send mail: + :: /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 + [...] + [...] -smtpd_recipient_restrictions = - permit_mynetworks - permit_sasl_authenticated - [...] Finally, we can add the submission service to our master.cf, with customized policy: -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 can now restart Postfix: `/etc/init.d/postfix restart'. + :: /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 @@ -1082,51 +1230,51 @@ 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 ; 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 ; - 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 ; 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 ; 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 ; - 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 ; 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 ; 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 ; - 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 ; 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) + Received: from localhost (smtp.fripost.org [127.0.0.1]) + by fripost.org (Postfix) with ESMTP id C9DAB841F4 + for ; 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 ; + 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 ; 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 ; 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 ; + 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 ; 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 ; 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 ; + 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 ; 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). @@ -1150,22 +1298,22 @@ 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 ; 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) + 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 ; 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} + /^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). -- cgit v1.2.3 From 7dbac18ed15f419790bc33dec6a3f5f7b4346c8e Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 5 Apr 2012 02:45:55 +0200 Subject: LDAP: Integration with Postfix & Dovecot. --- fripost-docs.org | 105 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/fripost-docs.org b/fripost-docs.org index 5e7367b..172c1fe 100644 --- a/fripost-docs.org +++ b/fripost-docs.org @@ -676,8 +676,8 @@ Jamm's (http://jamm.sourceforge.net/howto/html/implementation.html). 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 'mailbox' - DESC 'The path to the mailbox.' + 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' @@ -696,7 +696,7 @@ Jamm's (http://jamm.sourceforge.net/howto/html/implementation.html). olcObjectclasses: ( 1.3.6.1.4.1.12461.1.2.2 NAME 'virtualMailbox' SUP top STRUCTURAL DESC 'Mail Account Object' - MUST ( mail $ userPassword $ dc $ mailbox $ isActive ) + MUST ( mail $ userPassword $ dc $ maildir $ isActive ) MAY ( mailLocalAddress $ gn $ sn $ quota ) ) @@ -851,7 +851,7 @@ To delete a leaf or a sub-tree: sn: Last Name userPassword: {SSHA}epZKWD1SiSe/dwL0to+jjnwFzxVUbFvg dc: fripost.org - mailbox: fripost.org/user/ + maildir: fripost.org/user/ isActive: TRUE mailLocalAddress: user-alias@fripost.org mailLocalAddress: user@example.org @@ -926,11 +926,11 @@ We now ensure that the leaf has been updated: [...] userPassword:: aG9w entryCSN: 20120404215647.957317Z#000000#000#000000 - modifiersName: mail=fripost@fripost.org,o=mailboxes,dc=mail,dc=fripost,dc=org + 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 `mailbox', `ldapmodify' +Doing the same thing with eg, trying to change the `maildir', `ldapmodify' refuses with `Insufficient access (50)'. *** Configuring the main IMAP server @@ -1001,11 +1001,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/ @@ -1084,7 +1133,10 @@ sudo /etc/init.d/dovecot restart 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. +**** 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 @@ -1099,26 +1151,35 @@ 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 -auth_bind = yes -auth_bind_userdn = uid=%u,ou=accounts,dc=fripost,dc=org -ldap_version = 3 -base = ou=accounts,dc=fripost,dc=org -pass_filter = (&(objectClass=posixAccount)(uid=%u)) + 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)(domain=%d)(isActive=TRUE)) + pass_attrs = mail=user + pass_filter = (&(objectClass=virtualMailbox)(mail=%u)(domain=%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="ou=accounts,dc=fripost,dc=org". +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 - } + 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 @@ -1173,7 +1234,7 @@ test our installation.) ldap_version: 3 ldap_search_base: o=mailboxes,dc=mail,dc=fripost,dc=org ldap_scope: sub - ldap_filter: uid=%u + ldap_filter: mail=%u ldap_auth_method: bind After restarting saslauthd (`/etc/init.d/saslauthd restart'), we can test the -- cgit v1.2.3 From 609d690fa33cd526c71f5e73939a2912939f73e2 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 5 Apr 2012 15:24:11 +0200 Subject: LDAP: Fixing saslauthd; Adding more ACLs and discussion. --- fripost-docs.org | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/fripost-docs.org b/fripost-docs.org index 172c1fe..79c08a4 100644 --- a/fripost-docs.org +++ b/fripost-docs.org @@ -695,11 +695,10 @@ Jamm's (http://jamm.sourceforge.net/howto/html/implementation.html). MAY ( description ) ) olcObjectclasses: ( 1.3.6.1.4.1.12461.1.2.2 NAME 'virtualMailbox' SUP top STRUCTURAL - DESC 'Mail Account Object' + 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 @@ -712,6 +711,18 @@ We can now add it to the schema list: (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 @@ -800,7 +811,7 @@ ldapmodify -QY EXTERNAL -H ldapi:/// -f /etc/ldap/local/mail.fripost.org-acl.ldi [...] 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: {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 @@ -808,6 +819,16 @@ ldapmodify -QY EXTERNAL -H ldapi:/// -f /etc/ldap/local/mail.fripost.org-acl.ldi [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 @@ -1159,9 +1180,9 @@ Copy this file in /etc/dovecot, and chmod 600 it. Uncomment the following lines: deref = never scope = subtree user_attrs = maildir=home=/home/mail/virtual/%$ - user_filter = (&(objectClass=virtualMailbox)(mail=%u)(domain=%d)(isActive=TRUE)) + user_filter = (&(objectClass=virtualMailbox)(mail=%u)(dc=%d)(isActive=TRUE)) pass_attrs = mail=user - pass_filter = (&(objectClass=virtualMailbox)(mail=%u)(domain=%d)(isActive=TRUE)) + 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". @@ -1226,20 +1247,23 @@ test our installation.) OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd" [...] -(The socket has to be readable by postfix.) +(Note: The socket has to be readable by postfix.) :: /etc/saslauthd.conf 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: mail=%u - ldap_auth_method: bind + ldap_filter: (&(objectClass=virtualMailbox)(mail=%u)(dc=%d)(isActive=TRUE)) After restarting saslauthd (`/etc/init.d/saslauthd restart'), we can test the -authentication: testsaslauthd -u userid -p password. (The password cannot be -prompted, so you may want to create a dummy user.) +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 -- cgit v1.2.3