Jump to content

Dovecot: Difference between revisions

From NixOS Wiki
Tie-ling (talk | contribs)
Setup auth via passwd-file: add missing double quotes
this took me *three fucking hours* to figure out and nearly made me cry
Line 176: Line 176:
You also need to set proper file permissions on the cert directory and key files.
You also need to set proper file permissions on the cert directory and key files.
See [[ACME#Integration_with_service_modules]].
See [[ACME#Integration_with_service_modules]].
= Using sieve plugins =
The following is required to use Sieve plugins (<code>imap_sieve<code>, ManageSieve, etc):
<syntaxhighlight lang="nix">
environment.systemPackages = with pkgs; [
  dovecot_pigeonhole
];
</syntaxhighlight>


= mail_crypt plugin (encryption at rest) =
= mail_crypt plugin (encryption at rest) =

Revision as of 17:23, 30 September 2025

Dovecot is a mail storage server. It handles the storage of e-mail messages and their retrieval by a e-mail client (mail user agent) via authenticated IMAP.

This article describes a basic Dovecot setup with Postfix and virtual users, i.e., e-mail users are configured separately in Dovecot passdb, and are independent from system users.

SSL Certificate with ACME

This article assumes a working ACME configuration for certificate renewal.

{
  config,
  ...
}:
let
  sslCertDir = config.security.acme.certs."example.org".directory;
  domainName = "example.org";
in
{
  # further dovecot configuration goes here
  ...
}

Dovecot passwd database

Create a plaintext virtual user password database in the following format

  # authenticate via Passwd-file
  #
  # bill@example.org:{PLAIN}your_plain_password
  # alice@example.org:{PLAIN-MD5}1a1dc91c907325c69271ddf0c944bc72::::::
  # This file need to have correct permissions.

  age.secrets.dovecot = {
    file = "./dovecot-secret.age";
    # -rw-------
    mode = "600";
    owner = "dovecot2";
    group = "dovecot2";
  };

Open firewall port

Open firewall port for IMAPS clients.

  networking.firewall.allowedTCPPorts = [ 993 ]; # dovecot imaps

Setup auth via passwd-file

  services.dovecot2 = {
    enable = true;

    # use my own passwd file for auth, see below
    enablePAM = false;

    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_dovecot_lmtp/
    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_and_dovecot_sasl/
    extraConfig = ''
      # force to use full user name plus domain name
      # for disambiguation
      auth_username_format = %Lu

      # Authentication configuration:
      auth_mechanisms = plain
      passdb {
        driver = passwd-file
        args = ${config.age.secrets.dovecot.path}
      }
    ''
  };

Setup virtual users

  # /var/spool/mail/vmail needs to be created and owned by vmail
  users.users."vmail" = {
    createHome = true;
    home = "/var/spool/mail/vmail";
  };

  services.dovecot2 = {
    # configure virtual mail user and group
    createMailUser = true;
    mailUser = "vmail";
    mailGroup = "vmail";

    # implement virtual users
    # https://doc.dovecot.org/2.3/configuration_manual/howto/simple_virtual_install/
    # store virtual mail under
    # /var/spool/mail/vmail/<DOMAIN>/<USER>/Maildir/
    mailLocation = "maildir:~/Maildir";

    extraConfig = ''
      userdb {
        driver = static
        # the full e-mail address inside passwd-file is the username (%u)
        # user@example.com
        # %d for domain_name %n for user_name
        args = uid=vmail gid=vmail username_format=%u home=/var/spool/mail/vmail/%d/%n
      }
    '';
  };

Connect to postfix via lmtp

See Postfix for further setup on postfix side.

  services.dovecot2 = {
    # connection to postfix
    enableLmtp = true;

    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_dovecot_lmtp/
    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_and_dovecot_sasl/
    extraConfig = ''
      # connection to postfix via lmtp
      service lmtp {
       unix_listener /var/spool/postfix/dovecot-lmtp {
         mode = 0600
         user = postfix
         group = postfix
        }
      }
      service auth {
        unix_listener /var/spool/postfix/auth {
          mode = 0600
          user = postfix
          group = postfix
        }
      }
    '';
  };

  # postfix connection to dovecot
  services.postfix.config = {
    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_dovecot_lmtp/
    mailbox_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";
    virtual_transport = "lmtp:unix:/var/spool/postfix/dovecot-lmtp";

    # https://doc.dovecot.org/2.3/configuration_manual/howto/postfix_and_dovecot_sasl/
    smtpd_sasl_type = "dovecot";
    smtpd_sasl_path = "/var/spool/postfix/auth";
    smtpd_sasl_auth_enable = "yes";
    smtpd_recipient_restrictions = "permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination";
  };

  # create path for dovecot socket
  users.users."postfix" = {
    createHome = true;
    home = "/var/spool/postfix";
  };

Configure SSL Certificates

  services.dovecot2 = {
    sslServerCert = "${sslCertDir}/fullchain.pem";
    sslServerKey = "${sslCertDir}/key.pem";
    sslCACert = "${sslCertDir}/chain.pem";
  };

You also need to set proper file permissions on the cert directory and key files. See ACME#Integration_with_service_modules.

Using sieve plugins

The following is required to use Sieve plugins (imap_sieve, ManageSieve, etc):

environment.systemPackages = with pkgs; [
  dovecot_pigeonhole
];

mail_crypt plugin (encryption at rest)

The following seems to make mail_crypt work in its per-user/per-folder mode (note that this mode is still described as 'not production quality' in the dovecot docs):

security.pam.services.dovecot2 = { }; # needed as we disable PAM below

services.dovecot2 = {
  enable = true;
  enablePAM = false; # need to disable this as we redefine passdb
  mailPlugins.globally.enable = [ "mail_crypt" ]; 
  pluginSettings = {
    mail_crypt_curve = "secp521r1";
    mail_crypt_save_version = "2";
    mail_crypt_require_encrypted_user_key = "yes";
  }; 
  extraConfig = '' 
    mail_attribute_dict = file:%h/.attributes
    userdb {
      driver = passwd
    }  
    passdb {
      driver = pam
      override_fields = userdb_mail_crypt_private_password=%{sha256:password} userdb_mail_crypt_save_version=2
      args = failure_show_msg=yes dovecot2
    }  
  '';
}; 

Troubleshooting

sievec fails to compile basic sieve scripts

Sieve commands such as fileinto need to be enabled explicitly with:

services.dovecot2.sieve.globalExtensions = ["fileinto"];

Otherwise, the sievec command will fail to compile sieve scripts with fileinto statements and as a result the Dovecot service itself will fail to start if the configuration contains services.dovecot2.sieve.scripts.