Greylist

Greylisting is one method to defend a SMTP server against spam.

The goal is to temporarily reject emails from a sender that is not yet in registered in a database, then accepting it back if the sender retries.

To build a greylist, you need to create a database and a vSMTP service. For this tutorial, we will use the mysql database.

The database

Install MySQL

Please follow this great tutorial to install MySQL. This setup has been tested on Ubuntu 22.04, check out MySQL website for other systems.

# Install mysql.
$ sudo apt update
$ sudo apt install mysql-server
$ sudo systemctl start mysql.service

# Login as root.
$ sudo mysql

# Replace auth_socket authentication by a simple password.
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your-password';
mysql> exit

# Update root password & remove unnecessary stuff.
$ sudo mysql_secure_installation

# Connect as root with the password.
mysql -u root -p

# Reset auth to auth_socket, this way you can connect with `sudo mysql`
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH auth_socket;

Setup a user

To manage your database, you should create a new user with restricted privileges instead of relying on root.

# Here we use localhost as our host but you could also setup your database on another server.
mysql> CREATE USER 'greylist-manager'@'localhost' IDENTIFIED BY 'your-password';

Setup the database and a table

To create the database, simply put the content of this sql code into a greylist.sql file.

CREATE DATABASE greylist;

CREATE TABLE greylist.sender(
    address varchar(500) NOT null primary key,
    user varchar(500) NOT null,
    domain varchar(500) NOT null
);

And then run the mysql command as root to generate the database.

sudo mysql < greylist.sql

Grant necessary privileges to your user on the newly create table.

mysql> GRANT SELECT, INSERT ON greylist.sender TO 'greylist-manager'@'localhost';

The greylist database is now operational.

Setup vSMTP

To setup vSMTP, you first need to create a mysql service, that will enable you to query your database. You can, for example, write it in a services.vsl file where your main.vsl is located.

// -- services.vsl
service greylist db:mysql = #{
    // Change this url to the url of your database, or keep it like this if the 'greylist-manager' user is setup on localhost.
    url: "mysql://localhost/",

    // Select the user that you created when setting up your database.
    user: "greylist-manager",
    password: "your-password",
};

The query function from the db:mysql service is used to query a mysql database. Variables are passed to the query using string interpolation.

⚠️ String interpolation can lead to SQL injection if not used properly. Make sure to sanitize your inputs, set only required privileges to the mysql user, and check what kind of data you are injecting.

Create a greylist rule in your main.vsl file.

// -- main.vsl
import "services" as svc;

#{
    // The greylist is effective in the "mail" stage, because the sender
    // of the email is received at this stage.
    mail: [
        rule "greylist" || {
            let sender = mail_from();

            // if the sender is not recognized in our database,
            // we deny the transaction and write the sender into
            // the database.
            if svc::greylist.query(`SELECT * FROM greylist.sender WHERE address = '${sender}';`) == [] {
                // Writing the sender into the database.
                svc::greylist.query(`
                    INSERT INTO greylist.sender (user, domain, address)
                    values ("${sender.local_part}", "${sender.domain}", "${sender}");
                `);

                // vsl exposes a `code_greylist` code which is a "451 4.7.1" enhanced code.
                deny(code_greylist)
            } else {
                // the user is known by the server, the transaction
                // can proceed.
                accept()
            }
        }
    ]
}