2020-09-25 04:45:31 +00:00
{ options , config , lib , pkgs , . . . }:
2020-04-24 23:36:52 +00:00
with lib ;
let
cfg = config . services . dovecot2 ;
dovecotPkg = pkgs . dovecot ;
baseDir = " / r u n / d o v e c o t 2 " ;
stateDir = " / v a r / l i b / d o v e c o t " ;
dovecotConf = concatStrings [
''
base_dir = $ { baseDir }
protocols = $ { concatStringsSep " " cfg . protocols }
sendmail_path = /run/wrappers/bin/sendmail
# defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion
mail_plugins = $ mail_plugins $ { concatStringsSep " " cfg . mailPlugins . globally . enable }
''
(
concatStringsSep " \n " (
mapAttrsToList (
protocol : plugins : ''
protocol $ { protocol } {
mail_plugins = $ mail_plugins $ { concatStringsSep " " plugins . enable }
}
''
) cfg . mailPlugins . perProtocol
)
)
(
if cfg . sslServerCert == null then ''
ssl = no
disable_plaintext_auth = no
'' e l s e ''
ssl_cert = < $ { cfg . sslServerCert }
ssl_key = < $ { cfg . sslServerKey }
$ { optionalString ( cfg . sslCACert != null ) ( " s s l _ c a = < " + cfg . sslCACert ) }
ssl_dh = < $ { config . security . dhparams . params . dovecot2 . path }
disable_plaintext_auth = yes
''
)
''
default_internal_user = $ { cfg . user }
default_internal_group = $ { cfg . group }
$ { optionalString ( cfg . mailUser != null ) " m a i l _ u i d = ${ cfg . mailUser } " }
$ { optionalString ( cfg . mailGroup != null ) " m a i l _ g i d = ${ cfg . mailGroup } " }
mail_location = $ { cfg . mailLocation }
maildir_copy_with_hardlinks = yes
pop3_uidl_format = % 0 8 Xv % 0 8 Xu
auth_mechanisms = plain login
service auth {
user = root
}
''
(
optionalString cfg . enablePAM ''
userdb {
driver = passwd
}
passdb {
driver = pam
args = $ { optionalString cfg . showPAMFailure " f a i l u r e _ s h o w _ m s g = y e s " } dovecot2
}
''
)
(
optionalString ( cfg . sieveScripts != { } ) ''
plugin {
$ { concatStringsSep " \n " ( mapAttrsToList ( to : from : " s i e v e _ ${ to } = ${ stateDir } / s i e v e / ${ to } " ) cfg . sieveScripts ) }
}
''
)
(
2020-09-25 04:45:31 +00:00
optionalString ( cfg . mailboxes != { } ) ''
2020-10-07 09:15:18 +00:00
namespace inbox {
inbox = yes
$ { concatStringsSep " \n " ( map mailboxConfig ( attrValues cfg . mailboxes ) ) }
2020-04-24 23:36:52 +00:00
}
''
)
(
optionalString cfg . enableQuota ''
service quota-status {
executable = $ { dovecotPkg } /libexec/dovecot/quota-status - p postfix
inet_listener {
port = $ { cfg . quotaPort }
}
client_limit = 1
}
plugin {
quota_rule = * : storage = $ { cfg . quotaGlobalPerUser }
quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working
quota_status_success = DUNNO
quota_status_nouser = DUNNO
quota_status_overquota = " 5 5 2 5 . 2 . 2 M a i l b o x i s f u l l "
quota_grace = 10 % %
}
''
)
cfg . extraConfig
] ;
modulesDir = pkgs . symlinkJoin {
name = " d o v e c o t - m o d u l e s " ;
paths = map ( pkg : " ${ pkg } / l i b / d o v e c o t " ) ( [ dovecotPkg ] ++ map ( module : module . override { dovecot = dovecotPkg ; } ) cfg . modules ) ;
} ;
mailboxConfig = mailbox : ''
mailbox " ${ mailbox . name } " {
auto = $ { toString mailbox . auto }
2020-06-18 07:06:33 +00:00
'' + o p t i o n a l S t r i n g ( m a i l b o x . a u t o e x p u n g e ! = n u l l ) ''
autoexpunge = $ { mailbox . autoexpunge }
2020-04-24 23:36:52 +00:00
'' + o p t i o n a l S t r i n g ( m a i l b o x . s p e c i a l U s e ! = n u l l ) ''
special_use = \ $ { toString mailbox . specialUse }
'' + " } " ;
2020-09-25 04:45:31 +00:00
mailboxes = { name , . . . }: {
2020-04-24 23:36:52 +00:00
options = {
name = mkOption {
2020-09-25 04:45:31 +00:00
type = types . strMatching '' [ ^ " ] + '' ;
2020-04-24 23:36:52 +00:00
example = " S p a m " ;
2020-09-25 04:45:31 +00:00
default = name ;
readOnly = true ;
2020-04-24 23:36:52 +00:00
description = " T h e n a m e o f t h e m a i l b o x . " ;
} ;
auto = mkOption {
type = types . enum [ " n o " " c r e a t e " " s u b s c r i b e " ] ;
default = " n o " ;
example = " s u b s c r i b e " ;
description = " W h e t h e r t o a u t o m a t i c a l l y c r e a t e o r c r e a t e a n d s u b s c r i b e t o t h e m a i l b o x o r n o t . " ;
} ;
specialUse = mkOption {
type = types . nullOr ( types . enum [ " A l l " " A r c h i v e " " D r a f t s " " F l a g g e d " " J u n k " " S e n t " " T r a s h " ] ) ;
default = null ;
example = " J u n k " ;
description = " N u l l i f n o s p e c i a l u s e f l a g i s s e t . O t h e r t h a n t h a t e v e r y u s e f l a g m e n t i o n e d i n t h e R F C i s v a l i d . " ;
} ;
2020-06-18 07:06:33 +00:00
autoexpunge = mkOption {
type = types . nullOr types . str ;
default = null ;
example = " 6 0 d " ;
description = ''
To automatically remove all email from the mailbox which is older than the
specified time .
'' ;
} ;
2020-04-24 23:36:52 +00:00
} ;
} ;
in
{
imports = [
( mkRemovedOptionModule [ " s e r v i c e s " " d o v e c o t 2 " " p a c k a g e " ] " " )
] ;
options . services . dovecot2 = {
enable = mkEnableOption " D o v e c o t 2 . x P O P 3 / I M A P s e r v e r " ;
enablePop3 = mkOption {
type = types . bool ;
default = false ;
description = " S t a r t t h e P O P 3 l i s t e n e r ( w h e n D o v e c o t i s e n a b l e d ) . " ;
} ;
enableImap = mkOption {
type = types . bool ;
default = true ;
description = " S t a r t t h e I M A P l i s t e n e r ( w h e n D o v e c o t i s e n a b l e d ) . " ;
} ;
enableLmtp = mkOption {
type = types . bool ;
default = false ;
description = " S t a r t t h e L M T P l i s t e n e r ( w h e n D o v e c o t i s e n a b l e d ) . " ;
} ;
protocols = mkOption {
type = types . listOf types . str ;
default = [ ] ;
description = " A d d i t i o n a l l i s t e n e r s t o s t a r t w h e n D o v e c o t i s e n a b l e d . " ;
} ;
user = mkOption {
type = types . str ;
default = " d o v e c o t 2 " ;
description = " D o v e c o t u s e r n a m e . " ;
} ;
group = mkOption {
type = types . str ;
default = " d o v e c o t 2 " ;
description = " D o v e c o t g r o u p n a m e . " ;
} ;
extraConfig = mkOption {
type = types . lines ;
default = " " ;
example = " m a i l _ d e b u g = y e s " ;
description = " A d d i t i o n a l e n t r i e s t o p u t v e r b a t i m i n t o D o v e c o t ' s c o n f i g f i l e . " ;
} ;
mailPlugins =
let
plugins = hint : types . submodule {
options = {
enable = mkOption {
type = types . listOf types . str ;
default = [ ] ;
description = " m a i l p l u g i n s t o e n a b l e a s a l i s t o f s t r i n g s t o a p p e n d t o t h e ${ hint } < l i t e r a l > $ m a i l _ p l u g i n s < / l i t e r a l > c o n f i g u r a t i o n v a r i a b l e " ;
} ;
} ;
} ;
in
mkOption {
type = with types ; submodule {
options = {
globally = mkOption {
description = " A d d i t i o n a l e n t r i e s t o a d d t o t h e m a i l _ p l u g i n s v a r i a b l e f o r a l l p r o t o c o l s " ;
type = plugins " t o p - l e v e l " ;
example = { enable = [ " v i r t u a l " ] ; } ;
default = { enable = [ ] ; } ;
} ;
perProtocol = mkOption {
description = " A d d i t i o n a l e n t r i e s t o a d d t o t h e m a i l _ p l u g i n s v a r i a b l e , p e r p r o t o c o l " ;
type = attrsOf ( plugins " c o r r e s p o n d i n g p e r - p r o t o c o l " ) ;
default = { } ;
example = { imap = [ " i m a p _ a c l " ] ; } ;
} ;
} ;
} ;
description = " A d d i t i o n a l e n t r i e s t o a d d t o t h e m a i l _ p l u g i n s v a r i a b l e , g l o b a l l y a n d p e r p r o t o c o l " ;
example = {
globally . enable = [ " a c l " ] ;
perProtocol . imap . enable = [ " i m a p _ a c l " ] ;
} ;
default = { globally . enable = [ ] ; perProtocol = { } ; } ;
} ;
configFile = mkOption {
type = types . nullOr types . path ;
default = null ;
description = " C o n f i g f i l e u s e d f o r t h e w h o l e d o v e c o t c o n f i g u r a t i o n . " ;
apply = v : if v != null then v else pkgs . writeText " d o v e c o t . c o n f " dovecotConf ;
} ;
mailLocation = mkOption {
type = types . str ;
default = " m a i l d i r : / v a r / s p o o l / m a i l / % u " ; /* S a m e a s i n b o x , a s p o s t f i x */
example = " m a i l d i r : ~ / m a i l : I N B O X = / v a r / s p o o l / m a i l / % u " ;
description = ''
Location that dovecot will use for mail folders . Dovecot mail_location option .
'' ;
} ;
mailUser = mkOption {
type = types . nullOr types . str ;
default = null ;
description = " D e f a u l t u s e r t o s t o r e m a i l f o r v i r t u a l u s e r s . " ;
} ;
mailGroup = mkOption {
type = types . nullOr types . str ;
default = null ;
description = " D e f a u l t g r o u p t o s t o r e m a i l f o r v i r t u a l u s e r s . " ;
} ;
createMailUser = mkOption {
type = types . bool ;
default = true ;
description = '' W h e t h e r t o a u t o m a t i c a l l y c r e a t e t h e u s e r
given in <option> services . dovecot . user < /option > and the group
given in <option> services . dovecot . group < /option > . '' ;
} ;
modules = mkOption {
type = types . listOf types . package ;
default = [ ] ;
example = literalExample " [ p k g s . d o v e c o t _ p i g e o n h o l e ] " ;
description = ''
Symlinks the contents of lib/dovecot of every given package into
/etc/dovecot/modules. This will make the given modules available
if a dovecot package with the module_dir patch applied is being used .
'' ;
} ;
sslCACert = mkOption {
type = types . nullOr types . str ;
default = null ;
description = " P a t h t o t h e s e r v e r ' s C A c e r t i f i c a t e k e y . " ;
} ;
sslServerCert = mkOption {
type = types . nullOr types . str ;
default = null ;
description = " P a t h t o t h e s e r v e r ' s p u b l i c k e y . " ;
} ;
sslServerKey = mkOption {
type = types . nullOr types . str ;
default = null ;
description = " P a t h t o t h e s e r v e r ' s p r i v a t e k e y . " ;
} ;
enablePAM = mkOption {
type = types . bool ;
default = true ;
description = " W h e t h e r t o c r e a t e a o w n D o v e c o t P A M s e r v i c e a n d c o n f i g u r e P A M u s e r l o g i n s . " ;
} ;
sieveScripts = mkOption {
type = types . attrsOf types . path ;
default = { } ;
description = " S i e v e s c r i p t s t o b e e x e c u t e d . K e y i s a s e q u e n c e , e . g . ' b e f o r e 2 ' , ' a f t e r ' e t c . " ;
} ;
showPAMFailure = mkOption {
type = types . bool ;
default = false ;
description = " S h o w t h e P A M f a i l u r e m e s s a g e o n a u t h e n t i c a t i o n e r r o r ( u s e f u l f o r O T P W ) . " ;
} ;
mailboxes = mkOption {
2020-09-25 04:45:31 +00:00
type = with types ; coercedTo
( listOf unspecified )
( list : listToAttrs ( map ( entry : { name = entry . name ; value = removeAttrs entry [ " n a m e " ] ; } ) list ) )
( attrsOf ( submodule mailboxes ) ) ;
2020-06-18 07:06:33 +00:00
default = { } ;
example = literalExample ''
{
Spam = { specialUse = " J u n k " ; auto = " c r e a t e " ; } ;
}
'' ;
2020-04-24 23:36:52 +00:00
description = " C o n f i g u r e m a i l b o x e s a n d a u t o c r e a t e o r s u b s c r i b e t h e m . " ;
} ;
enableQuota = mkOption {
type = types . bool ;
default = false ;
example = true ;
description = " W h e t h e r t o e n a b l e t h e d o v e c o t q u o t a s e r v i c e . " ;
} ;
quotaPort = mkOption {
type = types . str ;
default = " 1 2 3 4 0 " ;
description = ''
The Port the dovecot quota service binds to .
If using postfix , add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config .
'' ;
} ;
quotaGlobalPerUser = mkOption {
type = types . str ;
default = " 1 0 0 G " ;
example = " 1 0 G " ;
description = " Q u o t a l i m i t f o r t h e u s e r i n b y t e s . S u p p o r t s s u f f i x e s b , k , M , G , T a n d % . " ;
} ;
} ;
config = mkIf cfg . enable {
security . pam . services . dovecot2 = mkIf cfg . enablePAM { } ;
security . dhparams = mkIf ( cfg . sslServerCert != null ) {
enable = true ;
params . dovecot2 = { } ;
} ;
services . dovecot2 . protocols =
optional cfg . enableImap " i m a p "
++ optional cfg . enablePop3 " p o p 3 "
++ optional cfg . enableLmtp " l m t p " ;
services . dovecot2 . mailPlugins = mkIf cfg . enableQuota {
globally . enable = [ " q u o t a " ] ;
perProtocol . imap . enable = [ " i m a p _ q u o t a " ] ;
} ;
users . users = {
dovenull =
{
uid = config . ids . uids . dovenull2 ;
description = " D o v e c o t u s e r f o r u n t r u s t e d l o g i n s " ;
group = " d o v e n u l l " ;
} ;
} // optionalAttrs ( cfg . user == " d o v e c o t 2 " ) {
dovecot2 =
{
uid = config . ids . uids . dovecot2 ;
description = " D o v e c o t u s e r " ;
group = cfg . group ;
} ;
} // optionalAttrs ( cfg . createMailUser && cfg . mailUser != null ) {
$ { cfg . mailUser } =
2021-04-17 00:35:05 +00:00
{ description = " V i r t u a l M a i l U s e r " ; isSystemUser = true ; } // optionalAttrs ( cfg . mailGroup != null )
2020-04-24 23:36:52 +00:00
{ group = cfg . mailGroup ; } ;
} ;
users . groups = {
dovenull . gid = config . ids . gids . dovenull2 ;
} // optionalAttrs ( cfg . group == " d o v e c o t 2 " ) {
dovecot2 . gid = config . ids . gids . dovecot2 ;
} // optionalAttrs ( cfg . createMailUser && cfg . mailGroup != null ) {
$ { cfg . mailGroup } = { } ;
} ;
environment . etc . " d o v e c o t / m o d u l e s " . source = modulesDir ;
environment . etc . " d o v e c o t / d o v e c o t . c o n f " . source = cfg . configFile ;
systemd . services . dovecot2 = {
description = " D o v e c o t I M A P / P O P 3 s e r v e r " ;
after = [ " n e t w o r k . t a r g e t " ] ;
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
2020-05-15 21:57:56 +00:00
restartTriggers = [ cfg . configFile modulesDir ] ;
2020-04-24 23:36:52 +00:00
2020-11-03 02:18:15 +00:00
startLimitIntervalSec = 60 ; # 1 min
2020-04-24 23:36:52 +00:00
serviceConfig = {
ExecStart = " ${ dovecotPkg } / s b i n / d o v e c o t - F " ;
ExecReload = " ${ dovecotPkg } / s b i n / d o v e a d m r e l o a d " ;
Restart = " o n - f a i l u r e " ;
RestartSec = " 1 s " ;
RuntimeDirectory = [ " d o v e c o t 2 " ] ;
} ;
# When copying sieve scripts preserve the original time stamp
# (should be 0) so that the compiled sieve script is newer than
# the source file and Dovecot won't try to compile it.
preStart = ''
rm - rf $ { stateDir } /sieve
'' + o p t i o n a l S t r i n g ( c f g . s i e v e S c r i p t s ! = { } ) ''
mkdir - p $ { stateDir } /sieve
$ { concatStringsSep " \n " (
mapAttrsToList (
to : from : ''
if [ - d ' $ { from } ' ] ; then
mkdir ' $ { stateDir } /sieve / $ { to } '
cp - p " ${ from } / " * . sieve ' $ { stateDir } /sieve / $ { to } '
else
cp - p ' $ { from } ' ' $ { stateDir } /sieve / $ { to } '
fi
$ { pkgs . dovecot_pigeonhole } /bin/sievec ' $ { stateDir } /sieve / $ { to } '
''
) cfg . sieveScripts
) }
chown - R ' $ { cfg . mailUser }: $ { cfg . mailGroup } ' ' $ { stateDir } /sieve '
'' ;
} ;
environment . systemPackages = [ dovecotPkg ] ;
2020-09-25 04:45:31 +00:00
warnings = mkIf ( any isList options . services . dovecot2 . mailboxes . definitions ) [
2021-02-13 14:23:35 +00:00
" D e c l a r i n g ` s e r v i c e s . d o v e c o t 2 . m a i l b o x e s ' a s a l i s t i s d e p r e c a t e d a n d w i l l b r e a k e v a l i n 2 1 . 0 5 ! S e e t h e r e l e a s e n o t e s f o r m o r e i n f o f o r m i g r a t i o n . "
2020-09-25 04:45:31 +00:00
] ;
2020-04-24 23:36:52 +00:00
assertions = [
{
assertion = intersectLists cfg . protocols [ " p o p 3 " " i m a p " ] != [ ] ;
message = " d o v e c o t n e e d s a t l e a s t o n e o f t h e I M A P o r P O P 3 l i s t e n e r s e n a b l e d " ;
}
{
assertion = ( cfg . sslServerCert == null ) == ( cfg . sslServerKey == null )
&& ( cfg . sslCACert != null -> ! ( cfg . sslServerCert == null || cfg . sslServerKey == null ) ) ;
message = " d o v e c o t n e e d s b o t h s s l S e r v e r C e r t a n d s s l S e r v e r K e y d e f i n e d f o r w o r k i n g c r y p t o " ;
}
{
assertion = cfg . showPAMFailure -> cfg . enablePAM ;
message = " d o v e c o t i s c o n f i g u r e d w i t h s h o w P A M F a i l u r e w h i l e e n a b l e P A M i s d i s a b l e d " ;
}
{
assertion = cfg . sieveScripts != { } -> ( cfg . mailUser != null && cfg . mailGroup != null ) ;
message = " d o v e c o t r e q u i r e s m a i l U s e r a n d m a i l G r o u p t o b e s e t w h e n s i e v e S c r i p t s i s s e t " ;
}
] ;
} ;
}