Installing James 3.0 With SPF Verification

Thibaut SAUTEREAU
Linagora Engineering
12 min readOct 4, 2017

In this article, we will cover the installation of James Server 3.0 with Docker.
We will use the cassandra-guice flavor, which ships a James server storing mailboxes in a Cassandra database and indexing them into an Elasticsearch service.

The main purpose of this guide is not to replace the documentation but to show people how easily and quickly James can be installed, up and running.

Installation With Docker

We will directly use a Docker image from Linagora’s repository. Here is the Docker Compose file we use for this setup:

docker-compose.yml

We use Docker links so that James can communicate with the Cassandra and Elasticsearch containers. We also use Docker DNS helpers to ensure our container can use our DNS server, which is running on the host machine and whose configuration will be detailed in the next section. We also mount volumes in which we will place James configuration files and potential extensions jars. Traditional SMTP and IMAP ports are mapped to unprivileged and unused ports on the host.

Before using docker-compose up -d to launch our James Server, we have some prior configuration work to do.

How to Configure DNS Records to Use James

As we are going to configure a server, we need a domain name. Moreover, as it is a mail server and because we want it to be reachable by other mail servers and by mail clients, we need DNS resolution.

In particular, we need A records in order to translate domain names into IP addresses as well as an MX record, which will be used by other mail servers to reach our James instance when they have emails to relay to our users. We also need reverse DNS resolutions for our infrastructure to be trusted on the Internet. One way to manage all this is by using the BIND9 nameserver.

If you do not know how to set up Bind9, please take a look at the Debian documentation. We need to add a new zone thus we will create a /etc/bind/db.test-domain.com zone configuration file to create a test-domain.com domain. The fact that this domain is only “local” (we did not register it) and already exists will require us to be even more careful during our setup. In the following snippets, we use A.B.C.D as the IP address of our host server.

/etc/bind/db.test-domain.com

The above configuration is rather traditional. Apart from the A and MX records we have already mentioned, we add the classical NS record to identify our nameserver. However, this was only for forward resolution. For reverse resolution, we create another zone configuration file:

As usual with reverse DNS resolution, the IP address in the PTR record needs to be written in reverse byte order.

Then we need to add both zone files to our DNS configuration file which tells our DNS server that it should control and serve requests for these new domain zones. For that, add the following to /etc/bind/named.conf.local:

/etc/bind/named.conf.local

Then, restart the bind9 daemon in order to reload the new configuration files. Also, be sure that Bind listens on your public interface (and not just on localhost for instance) and that it forwards queries it cannot answer, for instance to Google’s revolvers.

Configuring James

Entering our James container to modify it is not a good practice and that is not how one should use Docker. We use bind mounts instead. All James configuration files reside in the /root/conf directory of the container. With our bind mounts, we thus have to add our configuration files in $PWD/conf when we want to override the default configuration from Linagora.

We will now detail a minimalist configuration of James to have it up and running. For each configuration file we need to edit, we will point out the changes made from the default configuration files you can find here.

Edit domainlist.xml to change domain

The domainname entry identifies the DNS namespace served by this instance of James. These domain names are used for both matcher/mailet processing and SMTP authentication to determine when an email is intended for local delivery. We thus replace james.linagora.com with james-thibaut.test-domain.com.

We also remove the following lines which are causing interferences with existing deployments on our test server, especially automatic DNS resolutions of some IP addresses.

<autodetect>true</autodetect>
<autodetectIP>true</autodetectIP>

Edit dnsservice.xml to add our DNS server

We replace 62.210.16.6 by the IP address of our DNS server and remove the second entry with 8.8.8.8. Indeed, this entry is not needed since our nameserver is supposed to forward DNS queries it is unable to answer to upper-level nameservers. This also ensures that our DNS answers queries related to our test-domain.com domain name as it is only a local and non-registered one.

Edit mailetcontainer.xml to change postmaster

For now, we just need to edit the postmaster mail address.

However, the true purpose of this file is to configure mailets. A mailet is an email processing agent. Actually, this file describes a pipeline arranged into processors, which in turn are lists of mailet/matcher pairs allowing better logical organisation. A matcher is used to implement a condition for executing a mailet.

The default configuration is sufficient to obtain a basic working setup of James. We will edit it later when we configure SPF.

Edit imapserver.xml, smtpserver.xml, pop3server.xml and jmap.properties

We need to provide the keys we will use for TLS, through the use of a keystore. Basically, a keystore is a repository of security certificates, that can hold your keys and certificates and encrypt them all with a password. James expects a keystore to be composed of a single-element certificate chain and the corresponding private key. The password protecting the keystore is used in our imapserver.xml, smtpserver.xml, pop3server.xml and jmap.properties configuration files. In the default configuration, the password is set to “james72laBalle” and no default keystore file is shipped for obvious security reasons.

In this guide, we will use a self-signed certificate but you can also install one provided by a Certificate Authority (see here for more information on how to do this). You can generate your keystore using the following command:

# keytool -genkeypair -alias james -keyalg RSA -keystore $PWD/conf/keystore

Warning: Do not set a different alias, i.e. keep “james” as this is hardcoded in James code.

After executing this command, you will first be prompted for the keystore password. Next you will be prompted for general information about this Certificate, such as company, contact name and so on. This information may be displayed to users when importing into the certificate store of the client, therefore make sure that the information provided here matches what they will expect. Moreover, it is important that you set the Common Name (CN) to the DNS name of your James server (the one you will use to access it from your mail client, i.e. james-thibaut.test-domain.com in our case).

Finally, you will be prompted for the key password, which is the password to specifically protect this newly generated private key (as opposed to any other Certificates stored in the same keystore file). Please directly type “Enter” to automatically use the same password as for the keystore, because that is what James is expecting.

If everything was successful, you now have a keystore file with a Certificate that can be used by your server.

Warning: You MUST have only one certificate in the keystore file used by James.

You can finally replace “james72laBalle” by the password you entered for the keystore and you might also need to change the path to the keystore depending on what you did. The affected configuration files are imapserver.xml, smtpserver.xml, pop3server.xml and jmap.properties.

In jmap.properties, we will also comment out the following line:

 jwt.publickeypem.url=file://conf/jwt_publickey

Indeed, this is only required if you wish to use OAuth-like authentication (JWT stands for JSON Web Tokens) like we do with OpenPaaS. Obviously, if you need this, you should generate your own JWT public key. Do not use the one from our repository!

Import elasticsearch.properties and cassandra.properties

Those files are needed in order for James to be able to communicate with our Elasticsearch and Cassandra instances. There is no need to edit them thanks to the Docker links we used in our Compose file.

Import mailrepositorystore.xml

This file declares the file:// protocol used for storing anything other than emails. We need it because this protocol is for instance the one that was used above to define the path to our keystore.

Import jmx.properties

This file is needed for management of the James Server (see below).

Miscellaneous

  • logback.xml is a place to configure the logging based on your needs. We do not deal with that in this article.
  • tika: Apache Tika is a powerful content-analysis toolkit which James leverages to handle attachment text extraction before indexing in Elasticsearch. This makes attachments searchable. We do not deal with that in this article.
  • By default, POP3 is disabled in the pop3server.xml configuration file. You can of course enable it by editing the enabled attribute of the pop3server element. One should also notice that contrarily to the SMTP and IMAP configuration files, only one socket is declared for POP3. You can declare other ones. By default, the present one has TLS disabled. You can change this behavior to obtain a TLS or STARTTLS socket by editing the socketTLS and startTLS attributes of the TLS element. Obviously, you cannot have both on the same socket thus you should instantiate other sockets if you want to offer your users with different choices. Take the IMAP or SMTP configuration files as examples to do so.

It is now time to launch our James Server instance:

# pwd && ls
/home/tsautereau/james-root-docker
conf docker-compose.yml extensions-jars
# docker-compose up -d

If you edit some configuration files after having already run the Docker instance of James, you can run docker-compose restart james and Docker Compose will restart your James Server instance. This way, you will not lose users, mailboxes or anything else stored and indexed in your Cassandra and Elasticsearch instances. Nevertheless, if you really need to start fresh and get rid of all data, you can use docker-compose down to stop and remove the three containers.

Finally, if you really want to enter your James container out of curiosity, the following command can be used:

# docker exec -i -t james-thibaut /bin/bash

Managing James Server

James Server is managed using the james-cli command-line client. The -p option in the following commands corresponds to the JMX agent port number and this is why we needed to import the jmx.properties file before. Let’s now create a James domain corresponding to the one we previously set up in our nameserver as well as a user:

# alias james-thibaut-cli="docker exec james-thibaut \
> java -jar /root/james-cli.jar \
> -h 127.0.0.1 -p 9999"
# james-thibaut-cli AddDomain test-domain.com
AddDomain command executed successfully in 159 ms.
# james-thibaut-cli AddUser tsautereau@test-domain.com tototo
AddUser command executed successfully in 160 ms.

Consequently, the username to use in our email client will be tsautereau@test-domain.com and the password will be tototo.

A comprehensive list of all available management commands is available here.

Verifying a TLS-enabled James Server

Ensuring that our TLS setup is working is obviously a good practice in terms of security. Both of the following commands should establish a connection (meaning that you should obtain a prompt with the James server greeting) because they use non-TLS ports:

# telnet 127.0.0.1 3025# telnet 127.0.0.1 3587

However, as port 465 (mapped to port 3465 on our host) corresponds to SMTPS and TLS is thus expected, telnet 127.0.0.1 3465 should hang and eventually timeout. We can verify that TLS is working fine using the following command, which should print our certificate information and establish a connection:

# openssl s_client -quiet -connect localhost:3465

Finally, in the case of STARTTLS on port 3587, you can check with the following command:

# openssl s_client -quiet -connect localhost:3587 -starttls smtp

Configuration of the Mail User Agent (Thunderbird)

First, you should add your nameserver’s IP address in the /etc/resolv.conf file of your test machine (the one you will launch Thunderbird on), before any other nameserver directive to ensure correct DNS resolution.

In this guide, we use Thunderbird 52.3.0. After launching it, go to Accounts -> Create a new account: Email -> Skip this and use my existing email.

Enter your name (it will be used as the From: header in emails you send), email address (tsautereau@test-domain.com in our case) and password (tototo in our case) and then click on Continue. Thunderbird usually detects other parameters automatically, especially IMAP and SMTP port numbers. However, since we used different ones due to our container setup, we need to click on Manual config.

For instance, to use STARTTLS, set the IMAP port to 3143 and the SMTP port to 3587.

As Thunderbird does not know about the self-signed certificate you generated from your email server, you will be asked to add a security exception. Thunderbird will then try to authenticate and connect to your James instance.

You can try creating another user and send emails between these two accounts. Thunderbird will probably ask you for a second Security Exception when connecting to the SMTP server to send the first email. The sending will fail before you have the time to click to validate the Security Exception. But just do it then and retry sending the email, it should work.

You can try creating folders (i.e. mailboxes) from within Thunderbird and check that they were created on the James server (and inversely):

# james-thibaut-cli ListUserMailboxes tsautereau@test-domain.com
INBOX
INBOX.Test
Sent
Trash
ListUserMailboxes command executed successfully in 144 ms.

How To Set Up The Sender Policy Framework (SPF)

The Sender Policy Framework (SPF) is an open standard specifying a technical method to prevent sender address forgery. More precisely, SPF protects the envelope sender address, which is used for the delivery of messages. It allows the owner of a domain to specify their mail sending policy, e.g. which mail servers they use to send emails from their domain.

To correctly configure SPF for your domain, you need to answer the following questions:

  • From what server or servers will email from my domain originate? In our case, we only want our James Server to be able to send emails from our domain.
  • How do you want illegitimate email to be handled? As a reminder, -all is an SPF fail and usually means dropping such emails, whereas ~all is an SPF softfail and traditionally means accepting but marking them.

Therefore, we add the following DNS records to our db.test-domain.com zone file:

@ IN TXT “v=spf1 +a:james-thibaut.test-domain.com -all”
@ IN SPF “v=spf1 +a:james-thibaut.test-domain.com -all”

In James, we use jSPF. It is a Java implementation of the SPF standard. This only requires setting up a new mailet in the mailetcontainer.xml configuration file because jSPF is already included in the James Server project.

However, we are also going to create a new processor called SPFProcessor. It will handle emails after the root processor but before the transport processor. Moreover, we do not need to perform an SPF check or take a decision if the sender is authenticated or is a local user, because we already trust him.

In all other cases, we add a SPF header using the SPF mailet. Then we need to take a decision about incoming emails. We use the HasMailAttributeWithValue matcher which has seven possible values to handle in the case of SPF: permerror, temperror, none, pass, neutral, fail and softfail. What action you choose for each of these values depends on what you want to do. In our case, we redirect SPF errors and fails to the error processor, whereas all other cases lead directly to the transport processor for further normal processing. We are rather tolerant since we authorize softfails.

Here is the resulting processor:

Extract from mailetcontainer.xml

Notice that we edited the root processor to have it redirect to the SPFProcessor processor instead of the transport processor.

Although every single email should match one of the seven cases, the SPF mailet could change some day and for instance introduce new attributes. Therefore, we also added a matcher at the end of the SPFProcessor which will catch unmatching emails and use the LogMessage mailet to log the fact that something went wrong with our SPF configuration.

You can now send e-mails between your local email accounts and check that no SPF header has been added since you are both an authenticated and local user. You can also check that no Unknown SPF result log was generated in the output of the docker-compose logs james command.

Conclusion

In this article, we explained how to obtain a basic but fully operational installation of James Server 3.0. We managed to send and receive emails between local users and we set up SPF verification.

The next step could be to setup DKIM using the jDKIM Java library, which is also part of the James Project but in a different way, making it a bit harder to use. You can also dive more into the mailetcontainer.xml configuration file to customize your processors, mailets and matchers, depending on your objectives such as whether you want to use James as a Mail Transfer Agent (i.e. an MX server) or as a Mail Delivery Agent (e.g. an IMAP server).

Finally, now that you are running your own James Server, you can learn more about the James Project on our website. Also, feel free to join our community on GitHub and start contributing!

--

--