1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#
# SPDX-FileCopyrightText: 2020 Kirill Elagin <https://kir.elagin.me/>
#
# SPDX-License-Identifier: MPL-2.0 or MIT
#
# This is a “fake” record type, not actually part of DNS.
# It gets compiled down to a TXT record.
# RFC 7208
{ lib }:
let
inherit (lib) dns mkOption types;
in
rec {
rtype = "TXT";
options = {
adkim = mkOption {
type = types.enum ["relaxed" "strict"];
default = "relaxed";
example = "strict";
description = "DKIM Identifier Alignment mode";
apply = builtins.substring 0 1;
};
aspf = mkOption {
type = types.enum ["relaxed" "strict"];
default = "relaxed";
example = "strict";
description = "SPF Identifier Alignment mode";
apply = builtins.substring 0 1;
};
fo = mkOption {
type = types.listOf (types.enum ["0" "1" "d" "s"]);
default = ["0"];
example = ["0" "1" "s"];
description = "Failure reporting options";
apply = lib.concatStringsSep ":";
};
p = mkOption {
type = types.enum ["none" "quarantine" "reject"];
example = "quarantine";
description = "Requested Mail Receiver policy";
};
pct = mkOption {
type = types.ints.between 0 100;
default = 100;
example = 30;
description = "Percentage of messages to which the DMARC policy is to be applied";
apply = builtins.toString;
};
rf = mkOption {
type = types.listOf (types.enum ["afrf"]);
default = ["afrf"];
example = ["afrf"];
description = "Format to be used for message-specific failure reports";
apply = lib.concatStringsSep ":";
};
ri = mkOption {
type = types.ints.unsigned; # FIXME: u32
default = 86400;
example = 12345;
description = "Interval requested between aggregate reports";
apply = builtins.toString;
};
rua = mkOption {
type = types.oneOf [types.str (types.listOf types.str)];
default = [];
example = "mailto:dmarc+rua@example.com";
description = "Addresses to which aggregate feedback is to be sent";
apply = val: # FIXME: need to encode commas in URIs
if builtins.isList val
then lib.concatStringsSep "," val
else val;
};
ruf = mkOption {
type = types.listOf types.str;
default = [];
example = ["mailto:dmarc+ruf@example.com" "mailto:another+ruf@example.com"];
description = "Addresses to which message-specific failure information is to be reported";
apply = val: # FIXME: need to encode commas in URIs
if builtins.isList val
then lib.concatStringsSep "," val
else val;
};
sp = mkOption {
type = types.nullOr (types.enum ["none" "quarantine" "reject"]);
default = null;
example = "quarantine";
description = "Requested Mail Receiver policy for all subdomains";
};
};
dataToString = data:
let
# The specification could be more clear on this, but `v` and `p` MUST
# be the first two tags in the record.
items = ["v=DMARC1; p=${data.p}"] ++ lib.pipe data [
(builtins.intersectAttrs options) # remove garbage list `_module`
(lib.filterAttrs (k: v: v != null && v != "" && k != "p"))
(lib.mapAttrsToList (k: v: "${k}=${v}"))
];
result = lib.concatStringsSep "; " items + ";";
in dns.util.writeCharacterString result;
nameFixup = name: _self:
"_dmarc.${name}";
}