WIP: DKIM.
This commit is contained in:
parent
6178d60faa
commit
ca34673de4
@ -8,6 +8,13 @@ import Data.Codec.Argonaut (JsonCodec)
|
|||||||
import Data.Codec.Argonaut as CA
|
import Data.Codec.Argonaut as CA
|
||||||
import Data.Codec.Argonaut.Record as CAR
|
import Data.Codec.Argonaut.Record as CAR
|
||||||
|
|
||||||
|
type PubKey = String
|
||||||
|
type CryptoHash = String
|
||||||
|
type Signature = String
|
||||||
|
type Algorithm = String
|
||||||
|
type Selector = String
|
||||||
|
type Time = Int
|
||||||
|
|
||||||
type ResourceRecord
|
type ResourceRecord
|
||||||
= { rrtype :: String
|
= { rrtype :: String
|
||||||
, rrid :: Int
|
, rrid :: Int
|
||||||
@ -39,10 +46,35 @@ type ResourceRecord
|
|||||||
, modifiers :: Maybe (Array Modifier)
|
, modifiers :: Maybe (Array Modifier)
|
||||||
, q :: Maybe Qualifier -- Qualifier for default mechanism (`all`).
|
, q :: Maybe Qualifier -- Qualifier for default mechanism (`all`).
|
||||||
|
|
||||||
-- TODO: DKIM specific entries.
|
-- DKIM is so complex, it deserves its own type.
|
||||||
|
--, dkim :: Maybe DKIM
|
||||||
|
|
||||||
-- TODO: DMARC specific entries.
|
-- TODO: DMARC specific entries.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- DKIM specific entries.
|
||||||
|
type DKIM
|
||||||
|
= { v :: Maybe Int -- Default: 1
|
||||||
|
, a :: Maybe Algorithm -- TODO: (required), signing algorithm (example: `rsa-sha256`)
|
||||||
|
, d :: Maybe String -- TODO: (required), Signing Domain Identifier (SDID) (example: `netlib.re`)
|
||||||
|
, s :: Maybe Selector -- TODO: (required), selector (name of the DNS TXT entry for DKIM, such as `baguette` for `_baguette._dkim.netlib.re`)
|
||||||
|
, c :: Maybe Algorithm -- TODO: (optional), canonicalization algorithm(s) for header and body (ex: "relaxed/simple")
|
||||||
|
, q :: Maybe String -- TODO: (optional), default query method (example: `dns/txt`)
|
||||||
|
, i :: Maybe String -- TODO: (optional), Agent or User Identifier (AUID) (in practice, an email address)
|
||||||
|
, t :: Maybe Time -- TODO: (recommended), signature timestamp (time = number, such as `1117574938`)
|
||||||
|
, x :: Maybe Time -- TODO: (recommended), expire time (time = number, such as `1117574938`)
|
||||||
|
, l :: Maybe Int -- TODO: (optional), body length (such as `200`)
|
||||||
|
, h :: Maybe String -- TODO: (required), header fields - list of those that have been signed
|
||||||
|
, z :: Maybe String -- TODO: (optional), header fields - copy of selected header fields and values
|
||||||
|
, bh :: Maybe CryptoHash -- TODO: (required), body hash
|
||||||
|
, b :: Maybe Signature -- TODO: (required), signature of headers and body
|
||||||
|
}
|
||||||
|
-- h=from:to:subject:date:keywords:keywords;
|
||||||
|
-- z=From:foo@eng.example.net|To:joe@example.com|
|
||||||
|
-- Subject:demo=20run|Date:July=205,=202005=203:44:08=20PM=20-0700;
|
||||||
|
-- bh=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=;
|
||||||
|
-- b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZVoG4ZHRNiYzR
|
||||||
|
|
||||||
codec :: JsonCodec ResourceRecord
|
codec :: JsonCodec ResourceRecord
|
||||||
codec = CA.object "ResourceRecord"
|
codec = CA.object "ResourceRecord"
|
||||||
(CAR.record
|
(CAR.record
|
||||||
@ -75,6 +107,7 @@ codec = CA.object "ResourceRecord"
|
|||||||
, mechanisms: CAR.optional (CA.array codecMechanism)
|
, mechanisms: CAR.optional (CA.array codecMechanism)
|
||||||
, modifiers: CAR.optional (CA.array codecModifier)
|
, modifiers: CAR.optional (CA.array codecModifier)
|
||||||
, q: CAR.optional codecQualifier
|
, q: CAR.optional codecQualifier
|
||||||
|
--, dkim: CAR.optional codecDKIM
|
||||||
})
|
})
|
||||||
|
|
||||||
type Mechanism
|
type Mechanism
|
||||||
@ -125,7 +158,7 @@ show_mechanism m =
|
|||||||
show_qualifier_char :: Qualifier -> String
|
show_qualifier_char :: Qualifier -> String
|
||||||
show_qualifier_char = case _ of
|
show_qualifier_char = case _ of
|
||||||
Pass -> "+"
|
Pass -> "+"
|
||||||
None -> "?"
|
Neutral -> "?"
|
||||||
SoftFail -> "~"
|
SoftFail -> "~"
|
||||||
HardFail -> "-"
|
HardFail -> "-"
|
||||||
|
|
||||||
@ -212,13 +245,15 @@ emptyRR
|
|||||||
, mechanisms: Nothing
|
, mechanisms: Nothing
|
||||||
, modifiers: Nothing
|
, modifiers: Nothing
|
||||||
, q: Nothing
|
, q: Nothing
|
||||||
|
|
||||||
|
--, dkim: Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
data Qualifier = Pass | None | SoftFail | HardFail
|
data Qualifier = Pass | Neutral | SoftFail | HardFail
|
||||||
all_qualifiers :: Array Qualifier
|
all_qualifiers :: Array Qualifier
|
||||||
all_qualifiers = [Pass, None, SoftFail, HardFail]
|
all_qualifiers = [Pass, Neutral, SoftFail, HardFail]
|
||||||
qualifier_types :: Array String
|
qualifier_types :: Array String
|
||||||
qualifier_types = ["pass", "none", "soft_fail", "hard_fail"]
|
qualifier_types = ["pass", "neutral", "soft_fail", "hard_fail"]
|
||||||
|
|
||||||
-- | Codec for just encoding a single value of type `Qualifier`.
|
-- | Codec for just encoding a single value of type `Qualifier`.
|
||||||
codecQualifier :: CA.JsonCodec Qualifier
|
codecQualifier :: CA.JsonCodec Qualifier
|
||||||
@ -227,7 +262,7 @@ codecQualifier = CA.prismaticCodec "Qualifier" str_to_qualifier show_qualifier C
|
|||||||
str_to_qualifier :: String -> Maybe Qualifier
|
str_to_qualifier :: String -> Maybe Qualifier
|
||||||
str_to_qualifier = case _ of
|
str_to_qualifier = case _ of
|
||||||
"pass" -> Just Pass -- +
|
"pass" -> Just Pass -- +
|
||||||
"none" -> Just None -- ?
|
"neutral" -> Just Neutral -- ?
|
||||||
"soft_fail" -> Just SoftFail -- ~
|
"soft_fail" -> Just SoftFail -- ~
|
||||||
"hard_fail" -> Just HardFail -- -
|
"hard_fail" -> Just HardFail -- -
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
@ -235,6 +270,6 @@ str_to_qualifier = case _ of
|
|||||||
show_qualifier :: Qualifier -> String
|
show_qualifier :: Qualifier -> String
|
||||||
show_qualifier = case _ of
|
show_qualifier = case _ of
|
||||||
Pass -> "pass"
|
Pass -> "pass"
|
||||||
None -> "none"
|
Neutral -> "neutral"
|
||||||
SoftFail -> "soft_fail"
|
SoftFail -> "soft_fail"
|
||||||
HardFail -> "hard_fail"
|
HardFail -> "hard_fail"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module App.Text.Explanations where
|
module App.Text.Explanations where
|
||||||
import Halogen.HTML as HH
|
import Halogen.HTML as HH
|
||||||
|
import Bulma as Bulma
|
||||||
|
|
||||||
spf_introduction :: forall w i. Array (HH.HTML w i)
|
spf_introduction :: forall w i. Array (HH.HTML w i)
|
||||||
spf_introduction =
|
spf_introduction =
|
||||||
@ -29,8 +30,14 @@ spf_introduction =
|
|||||||
]
|
]
|
||||||
|
|
||||||
spf_default_behavior :: forall w i. Array (HH.HTML w i)
|
spf_default_behavior :: forall w i. Array (HH.HTML w i)
|
||||||
spf_default_behavior = [HH.text """
|
spf_default_behavior = [Bulma.p """
|
||||||
What should someone do when receiving a mail with your email address but not from a listed domain or IP address?
|
What should someone do when receiving a mail with your email address but not from a listed domain or IP address?
|
||||||
|
"""
|
||||||
By default, let's be neutral ("none", meaning no policy).
|
, HH.text """
|
||||||
|
By default, let's advise to drop the mail (a
|
||||||
|
"""
|
||||||
|
, HH.u_ [HH.text "hard fail"]
|
||||||
|
, HH.text """).
|
||||||
|
The only way for DKIM to be really meaningful is to block any mail not coming from the intended email servers.
|
||||||
|
Otherwise, it's just a statu quo, and the spamming will continue.
|
||||||
"""]
|
"""]
|
@ -234,6 +234,18 @@ validationSPF form = ado
|
|||||||
, v = form.v, mechanisms = Just mechanisms
|
, v = form.v, mechanisms = Just mechanisms
|
||||||
, modifiers = form.modifiers, q = form.q }
|
, modifiers = form.modifiers, q = form.q }
|
||||||
|
|
||||||
|
--validationDKIM :: ResourceRecord -> V (Array Error) ResourceRecord
|
||||||
|
--validationDKIM form = ado
|
||||||
|
-- name <- parse DomainParser.sub_eof form.name VEName
|
||||||
|
-- ttl <- is_between min_ttl max_ttl form.ttl VETTL
|
||||||
|
-- mechanisms <- verification_loop validate_DKIM_mechanism (maybe [] id form.mechanisms)
|
||||||
|
-- -- No need to validate the target, actually, it will be completely discarded.
|
||||||
|
-- -- The different specific entries replace `target` completely.
|
||||||
|
-- in emptyRR { rrid = form.rrid, readonly = form.readonly, rrtype = "DKIM"
|
||||||
|
-- , name = name, ttl = ttl, target = "" -- `target` is discarded!
|
||||||
|
-- , v = form.v, mechanisms = Just mechanisms
|
||||||
|
-- , modifiers = form.modifiers, q = form.q }
|
||||||
|
|
||||||
validation :: ResourceRecord -> Either (Array Error) ResourceRecord
|
validation :: ResourceRecord -> Either (Array Error) ResourceRecord
|
||||||
validation entry = case entry.rrtype of
|
validation entry = case entry.rrtype of
|
||||||
"A" -> toEither $ validationA entry
|
"A" -> toEither $ validationA entry
|
||||||
@ -244,6 +256,7 @@ validation entry = case entry.rrtype of
|
|||||||
"MX" -> toEither $ validationMX entry
|
"MX" -> toEither $ validationMX entry
|
||||||
"SRV" -> toEither $ validationSRV entry
|
"SRV" -> toEither $ validationSRV entry
|
||||||
"SPF" -> toEither $ validationSPF entry
|
"SPF" -> toEither $ validationSPF entry
|
||||||
|
--"DKIM" -> toEither $ validationDKIM entry
|
||||||
_ -> toEither $ invalid [UNKNOWN]
|
_ -> toEither $ invalid [UNKNOWN]
|
||||||
|
|
||||||
id :: forall a. a -> a
|
id :: forall a. a -> a
|
||||||
|
@ -184,6 +184,7 @@ show_accepted_type = case _ of
|
|||||||
MX -> "MX"
|
MX -> "MX"
|
||||||
SRV -> "SRV"
|
SRV -> "SRV"
|
||||||
SPF -> "SPF"
|
SPF -> "SPF"
|
||||||
|
--DKIM -> "DKIM"
|
||||||
|
|
||||||
string_to_acceptedtype :: String -> Maybe AcceptedRRTypes
|
string_to_acceptedtype :: String -> Maybe AcceptedRRTypes
|
||||||
string_to_acceptedtype str = case str of
|
string_to_acceptedtype str = case str of
|
||||||
@ -195,6 +196,7 @@ string_to_acceptedtype str = case str of
|
|||||||
"MX" -> Just MX
|
"MX" -> Just MX
|
||||||
"SRV" -> Just SRV
|
"SRV" -> Just SRV
|
||||||
"SPF" -> Just SPF
|
"SPF" -> Just SPF
|
||||||
|
--"DKIM" -> Just DKIM
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
type State =
|
type State =
|
||||||
@ -315,6 +317,7 @@ render state
|
|||||||
"MX" -> template modal_content_mx (foot_content MX)
|
"MX" -> template modal_content_mx (foot_content MX)
|
||||||
"SRV" -> template modal_content_srv (foot_content SRV)
|
"SRV" -> template modal_content_srv (foot_content SRV)
|
||||||
"SPF" -> template modal_content_spf (foot_content SPF)
|
"SPF" -> template modal_content_spf (foot_content SPF)
|
||||||
|
--"DKIM" -> template modal_content_dkim (foot_content DKIM)
|
||||||
_ -> Bulma.p $ "Invalid type: " <> state._currentRR.rrtype
|
_ -> Bulma.p $ "Invalid type: " <> state._currentRR.rrtype
|
||||||
where
|
where
|
||||||
-- DRY
|
-- DRY
|
||||||
@ -436,7 +439,7 @@ render state
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
default_qualifier_str = "none" :: String
|
default_qualifier_str = "hard_fail" :: String
|
||||||
|
|
||||||
display_domain_side = (if state._currentRR.name == (state._domain <> ".") then "" else "." <> state._domain)
|
display_domain_side = (if state._currentRR.name == (state._domain <> ".") then "" else "." <> state._domain)
|
||||||
should_be_disabled = (if true then (HP.enabled true) else (HP.disabled true))
|
should_be_disabled = (if true then (HP.enabled true) else (HP.disabled true))
|
||||||
@ -487,8 +490,9 @@ handleAction = case _ of
|
|||||||
default_rr_SRV = emptyRR { rrtype = "SRV", name = "_sip._tcp", target = "www"
|
default_rr_SRV = emptyRR { rrtype = "SRV", name = "_sip._tcp", target = "www"
|
||||||
, port = Just 5061, weight = Just 100, priority = Just 10, protocol = Just "tcp" }
|
, port = Just 5061, weight = Just 100, priority = Just 10, protocol = Just "tcp" }
|
||||||
default_mechanisms = maybe [] (\x -> [x]) $ to_mechanism "pass" "mx" ""
|
default_mechanisms = maybe [] (\x -> [x]) $ to_mechanism "pass" "mx" ""
|
||||||
default_rr_SPF = emptyRR { rrtype = "SPF", name = "", target = "www"
|
default_rr_SPF = emptyRR { rrtype = "SPF", name = "", target = ""
|
||||||
, mechanisms = Just default_mechanisms }
|
, mechanisms = Just default_mechanisms }
|
||||||
|
--default_rr_DKIM = emptyRR { rrtype = "DKIM", name = "_default._dkim", target = "" }
|
||||||
|
|
||||||
case t of
|
case t of
|
||||||
A -> H.modify_ _ { _currentRR = default_rr_A }
|
A -> H.modify_ _ { _currentRR = default_rr_A }
|
||||||
@ -499,6 +503,7 @@ handleAction = case _ of
|
|||||||
MX -> H.modify_ _ { _currentRR = default_rr_MX }
|
MX -> H.modify_ _ { _currentRR = default_rr_MX }
|
||||||
SRV -> H.modify_ _ { _currentRR = default_rr_SRV }
|
SRV -> H.modify_ _ { _currentRR = default_rr_SRV }
|
||||||
SPF -> H.modify_ _ { _currentRR = default_rr_SPF }
|
SPF -> H.modify_ _ { _currentRR = default_rr_SPF }
|
||||||
|
--DKIM -> H.modify_ _ { _currentRR = default_rr_DKIM }
|
||||||
|
|
||||||
-- | Initialize the ZoneInterface component: ask for the domain zone to `dnsmanagerd`.
|
-- | Initialize the ZoneInterface component: ask for the domain zone to `dnsmanagerd`.
|
||||||
Initialize -> do
|
Initialize -> do
|
||||||
@ -866,7 +871,7 @@ render_new_records _
|
|||||||
-- use "level" to get horizontal buttons next to each other (probably vertical on mobile)
|
-- use "level" to get horizontal buttons next to each other (probably vertical on mobile)
|
||||||
, Bulma.level [
|
, Bulma.level [
|
||||||
Bulma.btn "SPF" (CreateNewRRModal SPF)
|
Bulma.btn "SPF" (CreateNewRRModal SPF)
|
||||||
, Bulma.btn_ro (C.is_small <> C.is_warning) "DKIM"
|
--Bulma.btn "DKIM" (CreateNewRRModal DKIM)
|
||||||
, Bulma.btn_ro (C.is_small <> C.is_warning) "DMARC"
|
, Bulma.btn_ro (C.is_small <> C.is_warning) "DMARC"
|
||||||
] []
|
] []
|
||||||
, Bulma.hr
|
, Bulma.hr
|
||||||
|
Loading…
Reference in New Issue
Block a user