NIX_PATH+=":nixsys=$PWD/lib/nixsys"
NIX_PATH+=":sys=$PWD"
#NIX_PATH+=":nixpkgs-overlays=$PWD/lib/nixsys/build/overlays.nix"
+#NIX_PATH+=":nixpkgs-overlays=$PWD/install/overlays.nix"
export NIX_PATH
# Use NixOps as Disnix's provisioning backend
};
};
config = {
+ nixpkgs.overlays = import ../overlays.nix;
networking = {
baseName = "commonsoft";
domain = "${config.networking.baseName}.coop";
#sssd
docker
#nss_ldap
- nss_pam_ldapd
+ #nss_pam_ldapd
socat
];
};
{pkgs, lib, config, system, ...}:
-let inherit (builtins) toString toFile attrNames;
+let inherit (builtins) toString toFile;
inherit (lib) types;
- inherit (config.services) dovecot2 postfix x509;
+ inherit (config.services) dovecot2 postfix x509 openldap;
unlines = lib.concatStringsSep "\n";
when = x: y: if x == null then "" else y;
extSep = postfix.recipientDelimiter;
|| "0"<=c && c<="9"
|| c=="-"
then c else "_");
+ etc_dovecot = [
+ { target = "dovecot/${config.networking.domain}/dovecot-ldap.conf";
+ source = pkgs.writeText "dovecot-ldap.conf" ''
+ ${lib.optionalString dovecot2.debug ''
+ debug_level = 1
+ ''}
+
+ # LDAP database
+ uris = ldapi://
+ base = ou=posix,${openldap.domainSuffix}
+ scope = subtree
+ deref = never
+ blocking = no
+ # NOTE: sufficient for small systems and uses less resources.
+
+ # LDAP auth
+ sasl_bind = yes
+ sasl_mech = EXTERNAL
+ dn = cn=admin,${openldap.domainSuffix}
+ #dnpass = useless with sasl_mech=EXTERNAL
+ auth_bind = no
+ #auth_bind_userdn = cn=%n,ou=accounts,ou=posix,dc=${openldap.domainSuffix}
+
+ # dovecot passdb query
+ pass_filter = (&(objectClass=posixAccount)(uid=%n))
+ pass_attrs = userPassword=password,\
+ uidNumber=userdb_uid,\
+ gidNumber=userdb_gid,\
+ mailGroupMember=mail_access_groups,\
+ =user=%n@%d
+ #homeDirectory=userdb_home
+ #quotaBytes=userdb_quota_rule=*:bytes=%{ldap:quotaBytes}
+ # TODO: userdb_quota_rule=*:storage=
+ # TODO: userdb_mail_access_groups
+ # DOC: http://wiki2.dovecot.org/PasswordDatabase/ExtraFields
+ default_pass_scheme = CRYPT
+
+ # dovecot userdb query
+ user_filter = (&(objectClass=posixAccount)(uid=%n))
+ #user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(mailEnabled=TRUE))
+ # # DOC: http://wiki2.dovecot.org/Variables
+ #user_attrs = homeDirectory=home,uidNumber=uid,gidNumber=gid
+ #user_attrs = mailHomeDirectory=home,\
+ # mailStorageDirectory=mail,\
+ # mailUidNumber=uid,\
+ # mailGidNumber=gid,\
+ # mailQuota=quota_rule=*:bytes=%$
+ # # DOC: http://wiki2.dovecot.org/UserDatabase/ExtraFields
+
+ # doveadm user query
+ iterate_attrs = =user=%{ldap:uid}@${config.networking.domain}
+ iterate_filter = (objectClass=posixAccount)
+ '';
+ }
+ ];
in
{
+imports = [
+ dovecot/autoconfig.nix
+];
config = {
- services.nginx = {
- virtualHosts."autoconfig" =
- let servers = lib.concatMapStringsSep " "
- (dom: "autoconfig.${dom}")
- (attrNames dovecot2.domains);
- autoconfigSite = pkgs.writeTextFile {
- name = "autoconfig";
- destination = "/mail/config-v1.1.xml";
- text = ''
- <?xml version="1.0"?>
- <clientConfig version="1.1">
- <emailProvider id="%EMAILDOMAIN%">
- <!-- <displayName></displayName> -->
- <!-- <displayShortName></displayShortName> -->
- <domain>%EMAILDOMAIN%</domain>
- <incomingServer type="imap">
- <hostname>mail.%EMAILDOMAIN%</hostname>
- <port>993</port>
- <socketType>SSL</socketType>
- <username>%EMAILADDRESS%</username>
- <authentication>password-cleartext</authentication>
- </incomingServer>
- <incomingServer type="pop3">
- <hostname>mail.%EMAILDOMAIN%</hostname>
- <port>995</port>
- <socketType>SSL</socketType>
- <username>%EMAILADDRESS%</username>
- <authentication>password-cleartext</authentication>
- <pop3>
- <leaveMessagesOnServer>false</leaveMessagesOnServer>
- <downloadOnBiff>true</downloadOnBiff>
- </pop3>
- </incomingServer>
- <outgoingServer type="smtp">
- <hostname>mail.%EMAILDOMAIN%</hostname>
- <port>465</port>
- <socketType>SSL</socketType> <!-- see above -->
- <username>%EMAILADDRESS%</username> <!-- if smtp-auth -->
- <authentication>password-cleartext</authentication>
- <!-- <restriction>client-IP-address</restriction> -->
- <addThisServer>true</addThisServer>
- <useGlobalPreferredServer>false</useGlobalPreferredServer>
- </outgoingServer>
- </emailProvider>
- <!-- <clientConfigUpdate url="https://www.example.com/config/mozilla.xml" /> -->
- </clientConfig>
- '';
- };
- in
- {
- serverName = "autoconfig.${config.networking.domain}";
- root = autoconfigSite;
- #addSSL = true;
- extraConfig = ''
- access_log off;
- log_not_found off;
- '';
- };
- };
+ systemd.services.dovecot2.after = [ "postfix.service" ];
+ systemd.services.dovecot2.restartTriggers =
+ map (f: f.source) etc_dovecot;
+ environment.etc = etc_dovecot;
#services.postfix.mapFiles."transport-dovecot" =
# toFile "transport-dovecot"
# (unlines
# (lib.mapAttrsToList
# (dom: {...}: "${transportSubDomain}.${dom} lmtp:unix:private/dovecot-lmtp")
# dovecot2.domains));
- systemd.services.dovecot2.after = [ "postfix.service" ];
#users.extraUsers = [
# { name = "dovecot";
# uid = config.ids.uids.dovecot2;
# group = dovecot2.group;
# }
#];
- users.extraGroups = lib.mapAttrs
- (domain: {...}:
- { name = escapeGroup "${dovecot2.mailGroup}-${domain}";
- })
- dovecot2.domains;
+ users.extraGroups =
+ lib.mapAttrs
+ (domain: {...}:
+ { name = escapeGroup "${dovecot2.mailGroup}-${domain}";
+ })
+ dovecot2.domains;
systemd.services.dovecot2.preStart =
let sieveList =
pkgs.writeText "list.sieve" ''
-g ${authGroup} \
${libDir}/auth \
${libDir}/auth/${domain}
- dir_passwd=${libDir}/auth/${domain}
+ dir_passwd=${authDir}/${domain}
old_passwd=$dir_passwd/passwd
new_passwd=$(TMPDIR= mktemp --tmpdir=$dir_passwd -t passwd.XXXXXXXX.tmp)
) dovecot2.domains);
services.dovecot2 = {
enable = true;
+ debug = true;
mailUser = "dovemail";
mailGroup = "dovemail";
modules = [
#pkgs.dovecot_antispam
pkgs.dovecot_pigeonhole
];
- # ${lib.concatMapStringsSep "\n"
- # (dom: ''
- # local_name imap.${dom} {
- # #ssl_ca = <''${caPath}
- # ssl_cert = <${x509.cert dom}
- # ssl_key = <${x509.key dom}
- # }
- # local_name pop.${dom} {
- # #ssl_ca = <''${caPath}
- # ssl_cert = <${x509.cert dom}
- # ssl_key = <${x509.key dom}
- # }
- # '')
- # dovecot2.domains
- # }
-
configFile = toString (pkgs.writeText "dovecot.conf" ''
passdb {
- driver = passwd-file
- args = scheme=crypt username_format=%n ${authDir}/%d/passwd
- }
+ driver = ldap
+ args = /etc/dovecot/${config.networking.domain}/dovecot-ldap.conf
+ default_fields =
+ override_fields =
+ }
userdb {
driver = prefetch
- }
+ }
userdb {
# NOTE: this userdb is only used by lda.
- driver = passwd-file
- args = username_format=%n ${authDir}/%d/passwd
- #default_fields = home=${mailDir}/%d/%n
- }
+ driver = ldap
+ args = /etc/dovecot/${config.networking.domain}/dovecot-ldap.conf
+ default_fields =
+ override_fields =
+ skip = found
+ }
+ #passdb {
+ # driver = passwd-file
+ # args = scheme=crypt username_format=%n ${authDir}/%d/passwd
+ #}
+ #userdb {
+ # # NOTE: this userdb is only used by lda.
+ # driver = passwd-file
+ # args = username_format=%n ${authDir}/%d/passwd
+ # #default_fields = home=${mailDir}/%d/%n
+ # skip = found
+ #}
mail_home = ${mailDir}/%d/%n
+ # NOTE: if needed, may be overrided by userdb_mail
+ mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n
+ # NOTE: if needed, may be overrided by userdb_mail
+ # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
+ # SEE: http://wiki2.dovecot.org/Quota/FS
auth_mechanisms = plain login
- # postfix does not supply a client cert.
auth_ssl_require_client_cert = no
+ # NOTE: postfix does not supply a client cert.
auth_ssl_username_from_cert = yes
auth_verbose = yes
+ auth_username_format = %Lu
+ # NOTE: lowercase the username, help with LDAP?
${lib.optionalString dovecot2.debug ''
auth_debug = yes
mail_debug = yes
lda_mailbox_autosubscribe = yes
listen = *
log_timestamp = "%Y-%m-%d %H:%M:%S "
- # NOTE: INDEX and CONTROL are on a partition without quota, as explain in the doc.
- # SEE: http://wiki2.dovecot.org/Quota/FS
- mail_location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n:CONTROL=${libDir}/control/%d/%n
+ #maildir_copy_with_hardlinks = yes
namespace inbox {
# NOTE: here because protocol sieve {namespace inbox{}} does not seem to work.
inbox = yes
list = yes
prefix =
separator = ${dirSep}
- }
+ }
namespace {
#list = children
list = yes
- location = maildir:${mailDir}/%%d/%%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%%n:CONTROL=${libDir}/control/%d/%n/Shared/%%n
+ location = maildir:${mailDir}/%d/%n/Maildir:LAYOUT=fs:INDEX=${libDir}/index/%d/%n/Shared/%n:CONTROL=${libDir}/control/%d/%n/Shared/%n
+ # FIXME: %d not working
prefix = Partages+%%n+
separator = ${dirSep}
subscriptions = yes
type = shared
- }
+ }
mail_plugins = $mail_plugins acl quota virtual
#mail_uid = ${dovecot2.mailUser}
#mail_gid = ${dovecot2.mailGroup}
+ # NOTE: each user has a dedicated (uid,gid) pair
#mail_privileged_group = mail
#mail_access_groups =
plugin {
acl = vfile:/etc/dovecot/acl/global.d
acl_anyone = allow
acl_shared_dict = file:${mailDir}/%d/acl.db
+ # NOTE: to let users LIST mailboxes shared by other users,
+ # Dovecot needs a shared mailbox dictionary.
+ # FIXME: %d not working with userdb ldap
##antispam_allow_append_to_spam = yes
# # NOTE: pour offlineimap
#antispam_backend = pipe
quota = maildir:User quota
quota_rule = *:storage=256M
quota_rule2 = Trash:storage=+64M
+ quota_max_mail_size = 20M
+ #quota_exceeded_message = </path/to/quota_exceeded_message.txt
+ quota_warning = storage=95%% quota-warning 95 %u
+ quota_warning2 = storage=80%% quota-warning 80 %u
+ quota_warning3 = -storage=100%% quota-warning below %u
+ # NOTE: user is no longer over quota
recipient_delimiter = ${extSep}
sieve = file:${mailDir}/%d/%n/sieve;active=${mailDir}/%d/%n/sieve/main.sieve
#sieve_default = file:${mailDir}/%u/default.sieve
sieve_spamtest_status_header = X-Spam-Score
sieve_spamtest_status_type = strlen
sieve_user_log = /var/log/dovecot/%d/sieve.%n.log
+ }
+ service quota-warning {
+ executable = script ${
+ pkgs.writeScript "quota-warning" ''
+ #!/bin/sh -eu
+ PERCENT=$1
+ USER=$2
+ cat << EOF | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -d $USER -o
+ "plugin/quota=maildir:User quota:noenforcing"
+ From: postmaster@${config.networking.domain}
+ Subject: [WARNING] your mailbox is now $PERCENT% full.
+
+ Please remove some mails to make room for new ones.
+ EOF
+ ''
+ }
+ # use some unprivileged user for executing the quota warnings
+ user = ${dovecot2.user}
+ unix_listener quota-warning {
}
+ }
protocol imap {
#mail_max_userip_connections = 10
mail_plugins = $mail_plugins imap_acl imap_quota # antispam
list = yes
mailbox Drafts {
special_use = \Drafts
- }
+ }
mailbox Junk {
special_use = \Junk
- }
+ }
mailbox Sent {
special_use = \Sent
- }
+ }
mailbox "Sent Messages" {
special_use = \Sent
- }
+ }
mailbox Trash {
special_use = \Trash
- }
+ }
prefix =
separator = ${dirSep}
- }
}
+ }
protocol lda {
auth_socket_path = /var/run/dovecot/auth-userdb
hostname = ${config.networking.domain}
list = yes
prefix =
separator = ${dirSep}
- }
+ }
postmaster_address = postmaster${extSep}dovecot${extSep}lda@${config.networking.domain}
syslog_facility = mail
- }
+ }
protocol lmtp {
#info_log_path = /tmp/dovecot-lmtp.log
mail_plugins = $mail_plugins sieve
list = yes
prefix =
separator = ${dirSep}
- }
- postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${config.networking.domain}
}
+ postmaster_address = postmaster${extSep}dovecot${extSep}lmtp@${config.networking.domain}
+ }
protocol pop3 {
#mail_max_userip_connections = 10
- # Used by ${libDir}/pop3/INBOX/dovecot-virtual
namespace all {
+ # NOTE: used by ${libDir}/pop3/INBOX/dovecot-virtual
hidden = yes
list = no
location =
prefix = all+
separator = ${dirSep}
- }
+ }
# Virtual namespace for the virtual INBOX.
# Use a global directory for dovecot-virtual files.
namespace inbox {
location = virtual:${libDir}/pop3:INDEX=${libDir}/index/%d/%n/POP3:LAYOUT=fs
prefix = pop3+
separator = ${dirSep}
- }
+ }
pop3_client_workarounds =
pop3_fast_size_lookups = yes
pop3_lock_session = yes
pop3_no_flag_updates = yes
# Use GUIDs to avoid accidental POP3 UIDL changes instead of IMAP UIDs.
pop3_uidl_format = %g
- }
+ }
protocol sieve {
#mail_max_userip_connections = 10
#managesieve_implementation_string = Dovecot Pigeonhole
#managesieve_max_line_length = 65536
#managesieve_notify_capability = mailto
#managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date ihave
- }
+ }
protocols = imap lmtp pop3 sieve
service lmtp {
#executable = lmtp -L
user = ${postfix.user}
group = ${postfix.group}
mode = 0600
- }
- #user = mail
}
+ #user = mail
+ }
service auth {
user = root
+ # FIXME: may be user=dovecot-auth with LDAP?
unix_listener auth-userdb {
user = ${dovecot2.user}
group = ${dovecot2.group}
mode = 0660
- }
+ }
unix_listener /var/lib/postfix/queue/private/auth {
user = ${postfix.user}
group = ${postfix.group}
mode = 0660
- }
}
+ }
service imap {
# Most of the memory goes to mmap()ing files.
# You may need to increase this limit if you have huge mailboxes.
#vsz_limit =
process_limit = 1024
- }
+ }
service imap-login {
#inet_listener imap {
# address = 127.0.0.1
# port = 143
# ssl = no
- # }
+ #}
inet_listener imaps {
port = 993
ssl = yes
- }
}
+ }
service pop3 {
process_limit = 1024
- }
+ }
service pop3-login {
inet_listener pop3s {
port = 995
ssl = yes
- }
}
+ }
ssl = required
- #ssl_ca = <''${caPath}
+ ssl_dh = <${x509.dir}/dh.pem
+ ssl_cipher_list = HIGH:!LOW:!SSLv2:!EXP:!aNULL
ssl_cert = <${x509.cert}
- ssl_dh = <${x509.dir}/dh.pem
- # gOTE: only with dovecot >= 2.3
- ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL
ssl_key = <${x509.key}
+ #ssl_ca = <''${caPath}
#ssl_verify_client_cert = yes
'');
+ #${lib.concatMapStringsSep "\n"
+ # (dom: ''
+ # local_name mail.${dom} {
+ # #ssl_ca = <''${caPath}
+ # ssl_cert = <${x509.cert dom}
+ # ssl_key = <${x509.key dom}
+ # }
+ # '')
+ # dovecot2.domains
+ #}
};
};
}
--- /dev/null
+{pkgs, lib, config, ...}:
+let inherit (builtins) attrNames;
+ inherit (config.services) dovecot2;
+in
+{
+config = {
+ services.nginx = {
+ virtualHosts."autoconfig" =
+ let servers = lib.concatMapStringsSep " "
+ (dom: "autoconfig.${dom}")
+ (attrNames dovecot2.domains);
+ in
+ {
+ serverName = "autoconfig.${config.networking.domain}";
+ #addSSL = true;
+ extraConfig = ''
+ access_log off;
+ log_not_found off;
+ '';
+ root = pkgs.writeTextFile {
+ name = "autoconfig";
+ destination = "/mail/config-v1.1.xml";
+ text = ''
+ <?xml version="1.0"?>
+ <clientConfig version="1.1">
+ <emailProvider id="%EMAILDOMAIN%">
+ <!-- <displayName></displayName> -->
+ <!-- <displayShortName></displayShortName> -->
+ <domain>%EMAILDOMAIN%</domain>
+ <incomingServer type="imap">
+ <hostname>mail.%EMAILDOMAIN%</hostname>
+ <port>993</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ </incomingServer>
+ <incomingServer type="pop3">
+ <hostname>mail.%EMAILDOMAIN%</hostname>
+ <port>995</port>
+ <socketType>SSL</socketType>
+ <username>%EMAILADDRESS%</username>
+ <authentication>password-cleartext</authentication>
+ <pop3>
+ <leaveMessagesOnServer>false</leaveMessagesOnServer>
+ <downloadOnBiff>true</downloadOnBiff>
+ </pop3>
+ </incomingServer>
+ <outgoingServer type="smtp">
+ <hostname>mail.%EMAILDOMAIN%</hostname>
+ <port>465</port>
+ <socketType>SSL</socketType> <!-- see above -->
+ <username>%EMAILADDRESS%</username> <!-- if smtp-auth -->
+ <authentication>password-cleartext</authentication>
+ <!-- <restriction>client-IP-address</restriction> -->
+ <addThisServer>true</addThisServer>
+ <useGlobalPreferredServer>false</useGlobalPreferredServer>
+ </outgoingServer>
+ </emailProvider>
+ <!-- <clientConfigUpdate url="https://www.example.com/config/mozilla.xml" /> -->
+ </clientConfig>
+ '';
+ };
+ };
+ };
+};
+}
{pkgs, lib, config, ...}:
-let inherit (config.services) openldap;
+let inherit (builtins) baseNameOf readFile;
+ inherit (config.services) openldap;
inherit (config.users) ldap;
- cnConfigLDIF = pkgs.writeText "cn=config.ldif" ''
+ copyFile = file: pkgs.writeText (baseNameOf file) (readFile file);
+ configLDIF = pkgs.writeText "cn=config.ldif" ''
dn: cn=config
- cn: config
objectClass: olcGlobal
#olcPidFile: /run/slapd/slapd.pid
# List of arguments that were passed to the server
#olcArgsFile: /run/slapd/slapd.args
# Read slapd-config(5) for possible values
olcLogLevel: none
- # The tool-threads parameter sets the actual amount of cpu's that is used
- # for indexing.
+ # The tool-threads parameter sets the actual amount of cpu's
+ # that is used for indexing.
olcToolThreads: 1
-
+
dn: olcDatabase={-1}frontend,cn=config
- olcDatabase: {-1}frontend
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
# The maximum number of entries that is returned for a search operation
by * read
olcAccess: to dn.base="cn=Subschema"
by * read
-
+
dn: olcDatabase=config,cn=config
- olcDatabase: config
objectClass: olcDatabaseConfig
olcRootDN: cn=admin,cn=config
# Access to cn=config, system root can be manager
olcAccess: to *
by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
by * break
-
+
dn: cn=schema,cn=config
- cn: schema
objectClass: olcSchemaConfig
-
+
include: file://${pkgs.openldap}/etc/schema/core.ldif
include: file://${pkgs.openldap}/etc/schema/cosine.ldif
include: file://${pkgs.openldap}/etc/schema/nis.ldif
include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
-
+ include: file://${copyFile openldap/postfix-book.ldif}
+ include: file://${copyFile openldap/postfix2.ldif}
+
dn: cn=module{0},cn=config
- cn: module{0}
objectClass: olcModuleList
# Where the dynamically loaded modules are stored
#olcModulePath: /usr/lib/ldap
olcModuleLoad: back_mdb
-
+
dn: olcBackend={1}mdb,cn=config
- olcBackend: {1}mdb
objectClass: olcBackendConfig
- include: file://${mdb1Config}
+ include: file://${domainConfigLDIF openldap.domainSuffix}
'';
- mdb1Suffix = "dc=${config.networking.baseName}";
- mdb1Config = pkgs.writeText "${mdb1Suffix}.config.ldif" ''
- # sudo ldapsearch -LLL -D cn=admin,cn=config -Y EXTERNAL -b 'olcDatabase={1}mdb,cn=config' -s sub
+ domainConfigLDIF = dbSuffix: pkgs.writeText "config.ldif" ''
+ # sudo ldapsearch -LLL -H ldapi:// -D cn=admin,cn=config -Y EXTERNAL -b 'olcDatabase={1}mdb,cn=config' -s sub
dn: olcDatabase={1}mdb,cn=config
- olcDatabase: {1}mdb
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
- # Checkpoint the database periodically in case of system
- # failure and to speed slapd shutdown.
+ # NOTE: checkpoint the database periodically in case of system failure
+ # and to speed slapd shutdown.
olcDbCheckpoint: 512 30
# Database max size is 1G
olcDbMaxSize: 1073741824
olcLastMod: TRUE
- olcSuffix: ${mdb1Suffix}
+ olcSuffix: ${dbSuffix}
olcDbDirectory: ${openldap.dataDir}
- # Database superuser. Needed for syncrepl.
- olcRootDN: cn=admin,${mdb1Suffix}
- # superuser password, generated with slappasswd -s SECRET
- # olcRootPW: {SSHA}VUlLVeNl3IKltfX50f/PokMRnlhRsSDI
+ # NOTE: database superuser. Needed for syncrepl.
+ olcRootDN: cn=admin,${dbSuffix}
+ # NOTE: superuser password, generated with slappasswd -s SECRET
+ # FIXME: remove when dovecot2 compiled with SASL
+ olcRootPW: {SSHA}NONVwwKnKsCBmFxkMqTCFekdu3SJQHc9
olcDbIndex: objectClass eq
olcDbIndex: cn,uid eq
olcDbIndex: uidNumber,gidNumber eq
olcDbIndex: member,memberUid eq
+ olcDbIndex: mail eq
+ olcDbIndex: mailEnabled eq
olcAccess: to attrs=userPassword
by self write
by anonymous auth
+ by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
by * none
olcAccess: to attrs=shadowLastChange
by self write
by * none
- olcAccess: to dn.sub="ou=posix,${mdb1Suffix}"
- by dn="gidNumber=${toString config.users.groups.nslcd.gid}+uidNumber=${toString config.users.users.nslcd.uid},cn=peercred,cn=external,cn=auth" manage
+ olcAccess: to dn.sub="ou=posix,${dbSuffix}"
+ by dn="gidNumber=${toString config.users.groups.nslcd.gid}+uidNumber=${toString config.users.users.nslcd.uid},cn=peercred,cn=external,cn=auth" read
+ by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
olcAccess: to *
by self read
by * none
'';
- mdb1LDIF = pkgs.writeText "${mdb1Suffix}.ldif" ''
- dn: ${mdb1Suffix}
- dc: ${config.networking.baseName}
+ domainDataLDIF = dbSuffix: pkgs.writeText "data.ldif" ''
+ dn: ${dbSuffix}
objectClass: top
objectClass: dcObject
objectClass: organization
- o: Commonsoft
-
- dn: cn=admin,${mdb1Suffix}
- cn: admin
+ o: ${config.networking.baseName}
+
+ dn: cn=admin,${dbSuffix}
objectClass: simpleSecurityObject
objectClass: organizationalRole
description: ${config.networking.baseName} LDAP administrator
- roleOccupant: ${mdb1Suffix}
- userPassword:
-
- dn: ou=posix,${mdb1Suffix}
- ou: posix
+ roleOccupant: ${dbSuffix}
+ userPassword:
+ #userPassword: {SSHA}NONVwwKnKsCBmFxkMqTCFekdu3SJQHc9
+
+ dn: ou=posix,${dbSuffix}
objectClass: top
objectClass: organizationalUnit
-
- dn: ou=accounts,ou=posix,${mdb1Suffix}
- ou: accounts
+
+ dn: ou=accounts,ou=posix,${dbSuffix}
objectClass: top
objectClass: organizationalUnit
-
- dn: ou=groups,ou=posix,${mdb1Suffix}
- ou: groups
+
+ dn: ou=groups,ou=posix,${dbSuffix}
objectClass: top
objectClass: organizationalUnit
-
- dn: cn=users,ou=groups,ou=posix,${mdb1Suffix}
- cn: users
+
+ dn: cn=users,ou=groups,ou=posix,${dbSuffix}
objectclass: top
objectclass: posixGroup
gidnumber: 10000
- memberuid: julm
+ memberuid: ju
memberuid: sevy
-
- dn: uid=julm,ou=accounts,ou=posix,${mdb1Suffix}
- uid: julm
- objectClass: account
+
+ #dn: cn=dovemail,ou=groups,ou=posix,${dbSuffix}
+ #objectclass: top
+ #objectclass: posixGroup
+ #gidnumber: 497
+ # # FIXME: do not hardcode this gid
+ #memberuid: ju
+ #memberuid: sevy
+
+ dn: uid=ju,ou=accounts,ou=posix,${dbSuffix}
+ #objectClass: account
+ objectclass: person
objectClass: posixAccount
+ objectclass: postfixUser
+ objectclass: PostfixBookMailAccount
+ objectclass: PostfixBookMailForward
cn: Julien M.
+ sn: julm
+ mail: ju@commonsoft.coop
+ mailAlias: julien.moutinho@commonsoft.coop
uidNumber: 10000
- gidNumber: 10000
- homeDirectory: /home/julm
+ gidNumber: 497
+ homeDirectory: /home/ju
loginShell: /run/current-system/sw/bin/bash
userPassword: {SSHA}144Rfau9KJ14U0U4KdLNB7OrtpiEc3E3
-
- dn: uid=sevy,ou=accounts,ou=posix,${mdb1Suffix}
- uid: sevy
- objectClass: account
+
+ dn: uid=sevy,ou=accounts,ou=posix,${dbSuffix}
+ #objectClass: account
+ objectclass: person
objectClass: posixAccount
+ objectclass: postfixUser
+ objectclass: PostfixBookMailAccount
+ objectclass: PostfixBookMailForward
cn: Séverine P.
+ sn: sévy
+ mail: sevy@commonsoft.coop
+ mailAlias: severine.popek@commonsoft.coop
uidNumber: 10001
gidNumber: 10000
homeDirectory: /home/sevy
'';
in
{
+ options.services.openldap.domainSuffix = lib.mkOption {
+ type = lib.types.str;
+ default = "dc=${lib.concatStringsSep ",dc=" (lib.splitString "." config.networking.domain)}";
+ description = ''
+ LDAP suffix for the first database.
+ '';
+ };
config = {
users.ldap = {
enable = true;
'';
};
server = "ldapi:///";
- base = "ou=posix,${mdb1Suffix}";
+ base = "ou=posix,${openldap.domainSuffix}";
bind = {
- #distinguishedName = "cn=admin,${mdb1Suffix}";
+ #distinguishedName = "cn=admin,${openldap.domainSuffix}";
};
};
services.openldap = {
};
systemd.services.openldap = {
preStart = ''
- # NOTE: the config is always re-initialized.
+ # NOTE: slapd's config is always re-initialized.
rm -rf "${openldap.configDir}"/cn=config \
"${openldap.configDir}"/cn=config.ldif
umask 0077
- install -D -d -m 0700 \
- -o "${openldap.user}" \
- -g "${openldap.group}" \
- "${openldap.dataDir}" \
- "${openldap.configDir}"
-
- # NOTE: slapd is stopped in preStart, slap* commands can therefore be used.
- ${pkgs.openldap}/bin/slapadd -n 0 -F "${openldap.configDir}" -l ${cnConfigLDIF}
+ install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
+
+ # NOTE: slapd is supposed to be stopped while in preStart,
+ # hence slap* commands can safely be used.
+ ${pkgs.openldap}/bin/slapadd -n 0 -F "${openldap.configDir}" -l ${configLDIF}
# NOTE: slapadd(8): To populate the config database slapd-config(5),
# use -n 0 as it is always the first database.
# It must physically exist on the filesystem prior to this, however.
-
+
# NOTE: the data are only initialized, never re-initialized.
if test ! -e "${openldap.dataDir}"/data.mdb
then
- ${pkgs.openldap}/bin/slapadd -F "${openldap.configDir}" -l ${mdb1LDIF}
+ install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.dataDir}"
+ ${pkgs.openldap}/bin/slapadd -F "${openldap.configDir}" -l ${domainDataLDIF openldap.domainSuffix}
fi
chown -R "${openldap.user}:${openldap.group}" \
"${openldap.dataDir}" \
--- /dev/null
+# SOURCE: https://github.com/variablenix/ldap-mail-schema/
+dn: cn=postfix-book,cn=schema,cn=config
+objectClass: olcSchemaConfig
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.1
+ NAME 'mailHomeDirectory'
+ DESC 'The absolute path to the mail user home directory'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.2
+ NAME 'mailAlias'
+ DESC 'RFC822 Mailbox - mail alias'
+ EQUALITY caseIgnoreIA5Match
+ SUBSTR caseIgnoreIA5SubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.3
+ NAME 'mailUidNumber'
+ DESC 'UID required to access the mailbox'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.4
+ NAME 'mailGidNumber'
+ DESC 'GID required to access the mailbox'
+ EQUALITY integerMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+ SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.5
+ NAME 'mailEnabled'
+ DESC 'TRUE to enable, FALSE to disable account'
+ EQUALITY booleanMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+ SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.6
+ NAME 'mailGroupMember'
+ DESC 'Name of a mail distribution list'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.7
+ NAME 'mailQuota'
+ DESC 'Mail quota limit in kilobytes'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.8
+ NAME 'mailStorageDirectory'
+ DESC 'The absolute path to the mail users mailbox'
+ EQUALITY caseExactIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ SINGLE-VALUE )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.9
+ NAME 'mailSieveRuleSource'
+ DESC 'Sun ONE Messaging Server defined attribute'
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
+ X-ORIGIN 'Sun ONE Messaging Server' )
+olcAttributeTypes: ( 1.3.6.1.4.1.29426.1.10.10
+ NAME 'mailForwardingAddress'
+ DESC 'Address(es) to forward all incoming messages to.'
+ EQUALITY caseIgnoreIA5Match
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{320} )
+olcObjectClasses: ( 1.3.6.1.4.1.29426.1.2.2.1
+ NAME 'PostfixBookMailAccount'
+ DESC 'Mail account used in Postfix Book'
+ SUP top AUXILIARY
+ MUST mail
+ MAY ( mailHomeDirectory $ mailAlias $ mailGroupMember $
+ mailUidNumber $ mailGidNumber $ mailEnabled $
+ mailQuota $ mailStorageDirectory $ mailSieveRuleSource ) )
+olcObjectClasses: ( 1.3.6.1.4.1.29426.1.2.2.2
+ NAME 'PostfixBookMailForward'
+ DESC 'Mail forward used in Postfix Book'
+ SUP top AUXILIARY
+ MUST ( mail $ mailAlias )
+ MAY mailForwardingAddress )
--- /dev/null
+# SOURCE: https://github.com/variablenix/ldap-mail-schema/
+# DOC: http://www.postfix.org/LDAP_README.html
+dn: cn=postfix2,cn=schema,cn=config
+objectClass: olcSchemaConfig
+olcAttributeTypes: ( 1.3.6.1.4.1.4203.666.1.200
+ NAME 'mailacceptinggeneralid'
+ DESC 'Postfix mail local address alias attribute'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )
+olcAttributeTypes: ( 1.3.6.1.4.1.4203.666.1.201
+ NAME 'maildrop'
+ DESC 'Postfix mail final destination attribute'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )
+olcObjectClasses: ( 1.3.6.1.4.1.4203.666.1.100
+ NAME 'postfixUser'
+ DESC 'Postfix mail user class'
+ SUP top AUXILIARY
+ MAY ( mailacceptinggeneralid $ maildrop ) )
else [];
services.postfix = {
enable = true;
- hostname = "${config.networking.domain}";
+ #hostname = config.networking.domain;
+ #domain = "localdomain";
networksStyle = "host";
#mapFiles."valias" = toFile "valias" (unlines (all_valiases_postfix ++ catchAllPostfix));
# See https://blog.grimneko.de/2011/12/24/a-bunch-of-tips-for-improving-your-postfix-setup/
sslKey = x509.key;
#enableSubmission = true;
#enableSmtp = true;
- destination = [ "localhost" ];
+ destination = [
+ "localhost"
+ "localhost.localdomain"
+ config.networking.hostName
+ "${config.networking.hostName}.localdomain"
+ ];
networks = [ "127.0.0.0/8" "[::1]/128" ];
recipientDelimiter = "+";
config = {
#header_checks = "regexp:/var/lib/postfix/conf/header_checks";
#inet_interfaces = "all";
line_length_limit = "2048";
+
# Let $fallback_transport check existence of recipients
local_recipient_maps = "";
+ #mail_spool_directory = "/var/spool/mail";
+ # NOTE: nixpkgs's default
#local_header_rewrite_clients = "";
- mailbox_command = ''
- ${pkgs.procmail}/bin/procmail -t -a "$SENDER" -a "$RECIPIENT" -a "$USER" -a "$EXTENSION" -a "$DOMAIN" -a "$ORIGINAL_RECIPIENT" "$HOME/.procmailrc"
- '';
- mailbox_size_limit = "0";
+ #home_mailbox = "Maildir/";
+ #mailbox_command = ''
+ # ${pkgs.procmail}/bin/procmail -t -a "$SENDER" -a "$RECIPIENT" -a "$USER" -a "$EXTENSION" -a "$DOMAIN" -a "$ORIGINAL_RECIPIENT" "$HOME/.procmailrc"
+ #'';
+ mailbox_size_limit = "204800000";
+
masquerade_classes = [ "envelope_sender" "header_sender" "header_recipient" ];
masquerade_domains = "";
masquerade_exceptions = "root";
#smtp_tls_verify_cert_match = "hostname";
# Useful to test restrictions
smtpd_authorized_xclient_hosts = "127.0.0.1";
- smtpd_banner = "$myhostname ESMTP $mail_name (NixOS)";
+ smtpd_banner = "${config.networking.fqdn} ESMTP $mail_name (NixOS)";
smtpd_client_connection_count_limit = "50";
smtpd_client_connection_rate_limit = "0";
smtpd_client_event_limit_exceptions = "$mynetworks";
"reject_unauth_destination"
];
#smtpd_restriction_classes = "";
+ broken_sasl_auth_clients = false;
#smtpd_sasl_auth_enable = true;
#smtpd_sasl_path = "private/auth";
#smtpd_sasl_security_options = "noanonymous";
# Use a non blocking source of randomness
tls_random_source = "dev:/dev/urandom";
transport_maps = [
+ #"ldap:transport"
#"hash:/etc/postfix/transport-dovecot"
#"hash:/etc/postfix/$mydomain/transport"
#"hash:/etc/dovecot/transport"
virtual_alias_domains = [];
virtual_alias_maps = [
"hash:/etc/postfix/virtual_alias_maps"
+ #"ldap:aliases"
#"hash:/etc/postfix/virtual_alias-dovecot"
#"hash:/var/lib/postfix/conf/valias"
#"regexp:/etc/sympa/virtual_alias"
# cleanup_service_name = "submission-header-cleanup";
#};
extraMasterConf = ''
- spfcheck unix - n n - 0 spawn
- user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl
+ #spfcheck unix - n n - 0 spawn
+ # user=policyd-spf argv=/usr/sbin/postfix-policyd-spf-perl
465 inet n - - - - smtpd
-o milter_macro_daemon_name=ORIGINATING
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
-o smtpd_sasl_auth_enable=yes
+ -o smtpd_sasl_local_domain=$myhostname
+ -o smtpd_sasl_path=private/auth
+ -o smtpd_sasl_security_options=noanonymous
+ -o smtpd_sasl_type=dovecot
-o smtpd_tls_ask_ccert=no
-o smtpd_tls_auth_only=yes
-o smtpd_tls_ccert_verifydepth=0
-o smtpd_tls_req_ccert=no
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_wrappermode=yes
- -o smtpd_recipient_restrictions=reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject
- -o smtpd_sasl_local_domain=$myhostname
- -o smtpd_sasl_security_options=noanonymous
- -o smtpd_sasl_type=dovecot
- -o smtpd_sasl_path=private/auth
# -o smtpd_sender_restrictions=reject_sender_login_mismatch
# -o smtpd_sender_login_maps=hash:/etc/postfix/vaccounts
# -o cleanup_service_name=submission-header-cleanup
'';
in
{
-options.services.dkim = lib.mkOption {
- default = {};
- type = types.submodule {
- options = {
- keyDir = lib.mkOption {
- type = types.path;
- default = "/var/lib/dkim";
- description = ''
- '';
- };
- selector = lib.mkOption {
- type = types.str;
- default = "mail";
- description = ''
- '';
+ options.services.dkim = lib.mkOption {
+ default = {};
+ type = types.submodule {
+ options = {
+ keyDir = lib.mkOption {
+ type = types.path;
+ default = "/var/lib/dkim";
+ description = ''
+ '';
+ };
+ selector = lib.mkOption {
+ type = types.str;
+ default = "mail";
+ description = ''
+ '';
+ };
};
};
};
-};
-config = {
- services.rspamd = {
- enable = true;
- };
- services.rmilter = {
- enable = true;
- #debug = true;
- postfix = {
+ config = {
+ services.rspamd = {
enable = true;
};
- rspamd = {
- enable = true;
- extraConfig = "extended_spam_headers = yes;";
- };
- extraConfig = ''
- use_redis = true;
- max_size = 20M;
- #clamav {
- # servers = /var/run/clamav/clamd.ctl;
- #};
- # NOTE: domain = "*"; causes rmilter to try to search key in the key path
- # as keypath/domain.selector.key for any domain.
- dkim {
- domain {
- domain = "*";
- key = "${dkim.keyDir}";
- selector = "${dkim.selector}";
- };
- sign_alg = sha256;
- auth_only = yes;
+ services.rmilter = {
+ enable = true;
+ #debug = true;
+ postfix = {
+ enable = true;
};
- '';
- bindSocket.type = "unix";
- bindSocket.path = "/run/rmilter.sock";
- # NOTE: fix default which is in wiped out directory /run/rmilter/rmilter.sock
- };
- #systemd.sockets.rmilter.socketConfig.Accept = false;
- systemd.services.rmilter = {
- requires = [ "rmilter.socket" ];
- after = [ "rmilter.socket" ];
- preStart = ''
- install -D -d -o rmilter -g rmilter ${dkim.keyDir}
- ${lib.concatStringsSep "\n" (map createDomainDkimCert (attrNames dovecot2.domains))}
- chown -R rmilter:rmilter "${dkim.keyDir}"
- '';
+ rspamd = {
+ enable = true;
+ extraConfig = "extended_spam_headers = yes;";
+ };
+ extraConfig = ''
+ use_redis = true;
+ max_size = 20M;
+ #clamav {
+ # servers = /var/run/clamav/clamd.ctl;
+ #};
+ # NOTE: domain = "*"; causes rmilter to try to search key in the key path
+ # as keypath/domain.selector.key for any domain.
+ dkim {
+ domain {
+ domain = "*";
+ key = "${dkim.keyDir}";
+ selector = "${dkim.selector}";
+ };
+ sign_alg = sha256;
+ auth_only = yes;
+ };
+ '';
+ bindSocket.type = "unix";
+ bindSocket.path = "/run/rmilter.sock";
+ # NOTE: fix default which is in wiped out directory /run/rmilter/rmilter.sock
+ };
+ #systemd.sockets.rmilter.socketConfig.Accept = false;
+ systemd.services.rmilter = {
+ requires = [ "rmilter.socket" ];
+ after = [ "rmilter.socket" ];
+ preStart = ''
+ install -D -d -o rmilter -g rmilter ${dkim.keyDir}
+ ${lib.concatStringsSep "\n" (map createDomainDkimCert (attrNames dovecot2.domains))}
+ chown -R rmilter:rmilter "${dkim.keyDir}"
+ '';
+ };
};
-};
}
--- /dev/null
+map import
+[ #overlays/servers/mail/dovecot.nix
+]
{
-friot = {pkgs, config, ...}: {
- deployment.targetHost = "1.2.3.4";
- networking.zones = {
- net = {
- iface = null;
- ipv4 = null;
+ friot = {pkgs, config, ...}: {
+ deployment.targetHost = "1.2.3.4";
+ deployment.autoLuks = {
+ # NOTE: not working on virtualbox deployment
+ secretdisk = {
+ device = "/dev/sda";
+ passphrase = "foobar";
+ autoFormat = true;
+ cipher = "aes-cbc-essiv:sha256";
+ };
};
- lan = {
- iface = null;
- ipv4 = null;
+ networking.zones = {
+ net = {
+ iface = null;
+ ipv4 = null;
+ };
+ lan = {
+ iface = null;
+ ipv4 = null;
+ };
};
};
-};
}
{
-network.rollBack = false;
-friot = {pkgs, lib, config, options, ...}:
-let ipv4 = if options.networking.privateIPv4.isDefined
- then config.networking.privateIPv4
- else "X.X.X.X";
-in
-{
- config = {
- deployment.targetEnv = "virtualbox";
- deployment.virtualbox.headless = true;
- deployment.virtualbox.memorySize = 1024;
- deployment.virtualbox.vcpu = 2;
- deployment.virtualbox.disks.disk1.baseImage = <sys/var/virtualbox/nixops.vmdk>;
- #deployment.virtualbox.disks.disk1.size = 6024;
- # NOTE: resize not yet supported.
+ network.rollBack = false;
+ friot = {pkgs, lib, config, options, ...}:
+ let ipv4 = if options.networking.privateIPv4.isDefined
+ then config.networking.privateIPv4
+ else "X.X.X.X";
+ in {
+ config = {
+ deployment.targetEnv = "virtualbox";
+ deployment.virtualbox.headless = true;
+ deployment.virtualbox.memorySize = 1024;
+ deployment.virtualbox.vcpu = 2;
+ deployment.virtualbox.disks.disk1.baseImage = <sys/var/virtualbox/nixops.vmdk>;
+ #deployment.virtualbox.disks.disk1.size = 6024;
+ # NOTE: resize not yet supported.
- deployment.storeKeysOnMachine = true;
- deployment.autoLuks = {
- # NOTE: not working on virtualbox deployment
- secretdisk = {
- device = "/dev/sda";
- passphrase = "foobar";
- autoFormat = true;
- cipher = "aes-cbc-essiv:sha256";
- };
- };
- networking = {
- interfaces."enp0s8" = {
- #macAddress = "00:11:22:33:44:55";
- #ipv4.addresses = [ { address = ipv4; prefixLength = 32; } ];
- ipv6.addresses = [ { address = "fe80::1"; prefixLength = 10; } ];
- };
- zones = {
- net = {
- iface = "enp0s3";
- ipv4 = ipv4;
+ deployment.storeKeysOnMachine = true;
+ networking = {
+ interfaces."enp0s8" = {
+ #macAddress = "00:11:22:33:44:55";
+ #ipv4.addresses = [ { address = ipv4; prefixLength = 32; } ];
+ ipv6.addresses = [ { address = "fe80::1"; prefixLength = 10; } ];
};
- lan = {
- iface = "enp0s8";
- ipv4 = ipv4;
- #ipv6 = "fe80::1";
+ zones = {
+ net = {
+ iface = "enp0s3";
+ ipv4 = ipv4;
+ };
+ lan = {
+ iface = "enp0s8";
+ ipv4 = ipv4;
+ #ipv6 = "fe80::1";
+ };
};
};
};
};
-};
}
#{