{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE RankNTypes #-} module Network.Cloudflare.DNS.Record where import Control.Lens hiding ((.=)) import Data.Aeson import Data.Aeson.Types import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import Data.Int import Data.Maybe import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Data.Time import Data.Word import Network.Cloudflare.Types import Network.Wreq import Network.Wreq.Types (Auth (OAuth2Bearer)) -- | Create a new DNS record for a zone createDNSRecord :: DNSRecord record => CloudflareAuth -> Text -- ^ Zone Identifier -> CreateDNSRecord record -> IO (Either String (ResultResponse (DNSRecordEntry record))) createDNSRecord authInfo zone create = do let opts = defaults & header "Authorization" .~ ["Bearer " <> Text.encodeUtf8 (cloudflareAuthToken authInfo)] url = "https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zone <> "/dns_records" r <- postWith opts url $ toJSON create pure $ r ^. responseBody . to eitherDecode -- | List DNS records for a zone listDNSRecords :: CloudflareAuth -> Text -- ^ Zone Identifier -> IO (Either String (ResultWithInfo [DNSEntry])) listDNSRecords authInfo zId = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) r <- getWith opts $ "https://api.cloudflare.com/client/v4/zones/" ++ Text.unpack zId ++ "/dns_records" pure $ r ^. responseBody . to eitherDecode -- | Export a zone's DNS records in BIND format exportDNSRecords :: CloudflareAuth -> Text -- ^ Zone identifier -> IO Text -- ^ Exported BIND zone file exportDNSRecords authInfo zoneIdentifier = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) resp <- getWith opts $ "https://api.cloudflare.com/client/v4/zones/" <> (Text.unpack zoneIdentifier) <> "/dns_records/export" pure . Text.decodeUtf8 . BSL.toStrict $ resp ^. responseBody -- | Import DNS records in BIND format importDNSRecords :: CloudflareAuth -> Text -- ^ Zone identifier -> Text -- ^ Bind config -> Bool -- ^ If true, proxy the DNS record through Cloudflare -> IO (Either String (ResultResponse ImportResponse)) importDNSRecords authInfo zoneIdentifier bindConfig proxied = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) resp <- postWith opts ("https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zoneIdentifier <> "/dns_records/import") form pure $ resp ^. responseBody . to eitherDecode where form :: [Part] form = [ -- TODO should this be utf8 encoded? partBS "file" $ Text.encodeUtf8 bindConfig , partBS "proxied" $ proxiedStr ] proxiedStr = case proxied of True -> "true" False -> "false" -- | Scan DNS records for a zone scanDNSRecords :: CloudflareAuth -> Text -- ^ Zone identifier -> IO (Either String (ResultResponse ImportResponse)) scanDNSRecords authInfo zoneIdentifier = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) response <- postWith opts ("https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zoneIdentifier <> "/dns_records/scan") (mempty :: BS.ByteString) pure $ response ^. responseBody . to eitherDecode -- | Delete a DNS record deleteDNSRecord :: CloudflareAuth -> Text -- ^ Zone identifier -> Text -- ^ DNS identifier -> IO (Either String (ResultOnly DNSIdentifier)) deleteDNSRecord authInfo zoneIdentifier dnsIdentifier = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) response <- deleteWith opts ("https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zoneIdentifier <> "/dns_records/" <> Text.unpack dnsIdentifier) pure $ response ^. responseBody . to eitherDecode -- | Get a single DNS record for dnsRecordDetails :: CloudflareAuth -> Text -- ^ Zone identifier -> Text -- ^ DNS identifier -> IO (Response (ResultResponse DNSEntry)) dnsRecordDetails authInfo zoneIdentifier dnsIdentifier = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) asJSON =<< getWith opts ("https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zoneIdentifier <> "/dns_records/" <> Text.unpack dnsIdentifier) -- | Patch a DNS record patchDNSRecord :: DNSRecord record => CloudflareAuth -> Text -- ^ Zone identifier -> Text -- ^ DNS identifier -> CreateDNSRecord record -> IO (Either String (ResultResponse DNSEntry)) patchDNSRecord authInfo zoneIdentifier dnsIdentifier create = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) response <- patchWith opts ("https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zoneIdentifier <> "/dns_records/" <> Text.unpack dnsIdentifier) (toJSON create) pure $ response ^. responseBody . to eitherDecode -- | Update a DNS record updateDNSRecord :: DNSRecord record => CloudflareAuth -> Text -- ^ Zone identifier -> Text -- ^ DNS identifier -> CreateDNSRecord record -> IO (Either String (ResultResponse DNSEntry)) updateDNSRecord authInfo zoneIdentifier dnsIdentifier create = do let opts = defaults & auth .~ Just (OAuth2Bearer (Text.encodeUtf8 (cloudflareAuthToken authInfo))) response <- putWith opts ("https://api.cloudflare.com/client/v4/zones/" <> Text.unpack zoneIdentifier <> "/dns_records/" <> Text.unpack dnsIdentifier) (toJSON create) pure $ response ^. responseBody . to eitherDecode -- TODO add timing from response data ImportResponse = ImportResponse { importResponseRecsAdded :: Int -- ^ Number of DNS records added , importResponseTotalRecordsParsed :: Int -- ^ Total number of DNS records parsed } deriving (Eq, Ord, Show) instance FromJSON ImportResponse where parseJSON = withObject "ImportResponse" $ \o -> do added <- o .: "recs_added" parsed <- o .: "total_records_parsed" pure $ ImportResponse added parsed newtype DNSIdentifier = DNSIdentifier { dnsIdentifierId :: Text } deriving (Eq, Ord, Show) instance FromJSON DNSIdentifier where parseJSON = withObject "DNSIdentifier" $ \o -> do id' <- o .: "id" pure $ DNSIdentifier id' data DNSEntry = ARecordEntry (DNSRecordEntry ARecord) | AAAARecordEntry (DNSRecordEntry AAAARecord) | CAARecordEntry (DNSRecordEntry CAARecord) | CERTRecordEntry (DNSRecordEntry CERTRecord) | CNAMERecordEntry (DNSRecordEntry CNAMERecord) | DNSKEYRecordEntry (DNSRecordEntry DNSKEYRecord) | DSRecordEntry (DNSRecordEntry DSRecord) | HTTPSRecordEntry (DNSRecordEntry HTTPSRecord) | LOCRecordEntry (DNSRecordEntry LOCRecord) | MXRecordEntry (DNSRecordEntry MXRecord) | NAPTRRecordEntry (DNSRecordEntry NAPTRRecord) | NSRecordEntry (DNSRecordEntry NSRecord) | PTRRecordEntry (DNSRecordEntry PTRRecord) | SMIMEARecordEntry (DNSRecordEntry SMIMEARecord) | SRVRecordEntry (DNSRecordEntry SRVRecord) | SSHFPRecordEntry (DNSRecordEntry SSHFPRecord) | SVCBRecordEntry (DNSRecordEntry SVCBRecord) | TLSARecordEntry (DNSRecordEntry TLSARecord) | TXTRecordEntry (DNSRecordEntry TXTRecord) | URIRecordEntry (DNSRecordEntry URIRecord) deriving (Eq, Ord, Show) withDNSEntry :: (forall record. DNSRecord record => DNSRecordEntry record -> a) -> DNSEntry -> a withDNSEntry f entry = case entry of ARecordEntry e -> f e AAAARecordEntry e -> f e CAARecordEntry e -> f e CERTRecordEntry e -> f e CNAMERecordEntry e -> f e DNSKEYRecordEntry e -> f e DSRecordEntry e -> f e HTTPSRecordEntry e -> f e LOCRecordEntry e -> f e MXRecordEntry e -> f e NAPTRRecordEntry e -> f e NSRecordEntry e -> f e PTRRecordEntry e -> f e SMIMEARecordEntry e -> f e SRVRecordEntry e -> f e SSHFPRecordEntry e -> f e SVCBRecordEntry e -> f e TLSARecordEntry e -> f e TXTRecordEntry e -> f e URIRecordEntry e -> f e instance FromJSON DNSEntry where parseJSON = withObject "DNSEntry" $ \o -> do type' :: Text <- o .: "type" case type' of "A" -> ARecordEntry <$> parseJSON (Object o) "AAAA" -> AAAARecordEntry <$> parseJSON (Object o) "CAA" -> CAARecordEntry <$> parseJSON (Object o) "CERT" -> CERTRecordEntry <$> parseJSON (Object o) "CNAME" -> CNAMERecordEntry <$> parseJSON (Object o) "DNSKEY" -> DNSKEYRecordEntry <$> parseJSON (Object o) "DS" -> DSRecordEntry <$> parseJSON (Object o) "HTTPS" -> HTTPSRecordEntry <$> parseJSON (Object o) "LOC" -> LOCRecordEntry <$> parseJSON (Object o) "MX" -> MXRecordEntry <$> parseJSON (Object o) "NAPTR" -> NAPTRRecordEntry <$> parseJSON (Object o) "NS" -> NSRecordEntry <$> parseJSON (Object o) "PTR" -> PTRRecordEntry <$> parseJSON (Object o) "SMIMEA" -> SMIMEARecordEntry <$> parseJSON (Object o) "SRV" -> SRVRecordEntry <$> parseJSON (Object o) "SSHFP" -> SSHFPRecordEntry <$> parseJSON (Object o) "SVCB" -> SVCBRecordEntry <$> parseJSON (Object o) "TLSA" -> TLSARecordEntry <$> parseJSON (Object o) "TXT" -> TXTRecordEntry <$> parseJSON (Object o) "URI" -> URIRecordEntry <$> parseJSON (Object o) _ -> fail $ "Unknown DNS record type: " ++ Text.unpack type' class DNSRecord record where dnsRecordToJSON :: record -> [Pair] -- ^ Type of the record and a list the fields of the record dnsRecordType :: record -> Text -- ^ DNS record type dnsRecordParse :: Object -> Parser record -- ^ Parse the fields of the record -- | DNS entries returned by the Cloudflare API data DNSRecordEntry record = DNSRecordEntry { dnsRecordEntryData :: record , dnsRecordEntryType :: Text -- ^ DNS record type , dnsRecordEntryContent :: Text -- ^ DNS record content , dnsRecordEntryComment :: Maybe Text -- ^ Comments or notes about the DNS record. This field has no effect on DNS responses , dnsRecordEntryCreatedOn :: UTCTime , dnsRecordEntryId :: Text , dnsRecordEntryLocked :: Bool , dnsRecordEntryMeta :: Maybe Object -- TODO maybe make this a specific type , dnsRecordEntryModifiedOn :: UTCTime , dnsRecordEntryProxiable :: Bool , dnsRecordEntryTags :: [Text] -- ^ Custom tags for the DNS record. This field has no effect on DNS responses. , dnsRecordEntryTTL :: Word32 -- ^ Time To Live (TTL) of the DNS record in seconds. Setting to 1 means 'automatic'. Value must be between 60 and 86400, with the minimum reduced to 30 for Enterprise zones. , dnsRecordEntryZoneId :: Text -- ^ Zone identifier , dnsRecordEntryZoneName :: Text -- ^ Zone name } deriving (Eq, Ord, Show) instance DNSRecord record => FromJSON (DNSRecordEntry record) where parseJSON = withObject "DNSRecordEntry" $ \o -> do data' <- dnsRecordParse o content <- o .: "content" type' <- o .: "type" comment <- o .:? "comment" createdOn <- o .: "created_on" id' <- o .: "id" locked <- o .: "locked" meta <- o .:? "meta" modifiedOn <- o .: "modified_on" proxiable <- o .: "proxiable" tags <- o .: "tags" ttl <- o .: "ttl" zId <- o .: "zone_id" zName <- o .: "zone_name" pure $ DNSRecordEntry data' type' content comment createdOn id' locked meta modifiedOn proxiable tags ttl zId zName -- | Create a new DNS record for a zone data CreateDNSRecord record = CreateDNSRecord { createDNSRecordData :: record -- ^ The data for the DNS record , createDNSRecordName :: Text -- ^ DNS record name (or @ for the zone apex) in Punycode , createDNSRecordProxied :: Bool -- ^ Use Cloudflare's proxying for this record , createDNSRecordComment :: Maybe Text -- ^ Comments or notes about the DNS record. This field has no effect on DNS responses , createDNSRecordTags :: [Text] -- ^ Custom tags for the DNS record. This field has no effect on DNS responses. Not available in the free plan , createDNSRecordTTL :: Word32 -- ^ Time To Live (TTL) of the DNS record in seconds. Setting to 1 means 'automatic'. Value must be between 60 and 86400, with the minimum reduced to 30 for Enterprise zones. } deriving (Eq, Ord, Show) instance DNSRecord record => ToJSON (CreateDNSRecord record) where toJSON (CreateDNSRecord record name proxied comment tags ttl) = object $ mconcat [ dnsRecordToJSON record , [ "type" .= dnsRecordType record , "name" .= name , "tags" .= tags , "ttl" .= ttl , "proxied" .= proxied ] , catMaybes [ ("comment" .=) <$> comment ] ] -- | RFC 1035 A record data ARecord = ARecord { aRecordContent :: Text -- ^ Valid IPv4 address. } deriving (Eq, Ord, Show) instance DNSRecord ARecord where dnsRecordType _ = "A" dnsRecordToJSON (ARecord content) = [ "content" .= content ] dnsRecordParse o = do content <- o .: "content" pure $ ARecord content -- | RFC 3596 AAAA record data AAAARecord = AAAARecord { aaaaRecordContent :: Text -- ^ Valid IPv6 address. } deriving (Eq, Ord, Show) instance DNSRecord AAAARecord where dnsRecordType _ = "AAAA" dnsRecordToJSON (AAAARecord content) = [ "content" .= content ] dnsRecordParse o = do content <- o .: "content" pure $ AAAARecord content -- | RFC 6844 CAA record data CAARecord = CAARecord { caaRecordFlags :: Word8 -- ^ Flags for the CAA record see RFC 6844 section 1 , caaRecordTag :: Text -- ^ Name of the property controlled by this record (e.g.: issue, issuewild, iodef). TODO make a specific type , caaRecordValue :: Text -- ^ Value of the record. This field's semantics depend on the chosen tag. } deriving (Eq, Ord, Show) instance DNSRecord CAARecord where dnsRecordType _ = "CAA" dnsRecordToJSON (CAARecord flags tag value) = [ ("data", object [ "flags" .= flags , "tag" .= tag , "value" .= value ]) ] dnsRecordParse o = do dataObj <- o .: "data" flags <- dataObj .: "flags" tag <- dataObj .: "tag" value <- dataObj .: "value" pure $ CAARecord flags tag value -- | RFC4398 CERT record data CERTRecord = CERTRecord { certRecordAlgorithm :: Int -- ^ Algorithm, defined in 2.1.3 of RFC4034 , certRecordCertificate :: Text -- ^ Certificate , certRecordKeyTag :: Word16 -- ^ Key tag, defined in appendix B of RFC4034 , certRecordType :: Int -- ^ Type, section 2.1 of RFC4398 1 for X.509 } deriving (Eq, Ord, Show) instance DNSRecord CERTRecord where dnsRecordType _ = "CERT" dnsRecordToJSON (CERTRecord algorithm certificate keyTag type') = [ ("data", object [ "algorithm" .= algorithm , "certificate" .= certificate , "key_tag" .= keyTag , "type" .= type' ]) ] dnsRecordParse o = do dataObj <- o .: "data" algorithm <- dataObj .: "algorithm" certificate <- dataObj .: "certificate" keyTag <- dataObj .: "key_tag" type' <- dataObj .: "type" pure $ CERTRecord algorithm certificate keyTag type' -- | RFC 1035 CNAME record, section 3.3.1 data CNAMERecord = CNAMERecord { cnameRecordContent :: Text -- ^ Domain name to which the record points to } deriving (Eq, Ord, Show) instance DNSRecord CNAMERecord where dnsRecordType _ = "CNAME" dnsRecordToJSON (CNAMERecord content) = [ "content" .= content ] dnsRecordParse o = do content <- o .: "content" pure $ CNAMERecord content -- | RFC4034 DNSKEY record, defined in section 2 data DNSKEYRecord = DNSKEYRecord { dnskeyRecordAlgorithm :: Word8 -- ^ Algorithm, defined in 2.1.3 of RFC4034 , dnskeyRecordFlags :: Word16 -- ^ Flags for the DNSKEY record, defined in 2.1.1 of RFC4034 , dnskeyRecordProtocol :: Word8 -- ^ Protocol for the DNSKEY record, defined in 2.1.2 of RFC4034 , dnskeyRecordPublicKey :: Text -- ^ Public key for the DNSKEY record, depends on the algorithm used, see appendix section A.1 of RFC4034 } deriving (Eq, Ord, Show) instance DNSRecord DNSKEYRecord where dnsRecordType _ = "DNSKEY" dnsRecordToJSON (DNSKEYRecord algorithm flags protocol publicKey) = [ ("data", object [ "algorithm" .= algorithm , "flags" .= flags , "protocol" .= protocol , "public_key" .= publicKey ]) ] dnsRecordParse o = do dataObj <- o .: "data" algorithm <- dataObj .: "algorithm" flags <- dataObj .: "flags" protocol <- dataObj .: "protocol" publicKey <- dataObj .: "public_key" pure $ DNSKEYRecord algorithm flags protocol publicKey -- | RFC4034 DS record, defined in section 5.1 data DSRecord = DSRecord { dsRecordAlgorithm :: Word8 -- ^ Algorithm, defined in 5.1.2 of RFC4034 , dsRecordDigest :: Text -- ^ Digest, defined in 5.1.4 of RFC4034 , dsRecordDigestType :: Word8 -- ^ Digest type, defined in 5.1.3 of RFC4034 , dsRecordKeyTag :: Word16 -- ^ Key tag, defined in 5.1.1 of RFC4034 } deriving (Eq, Ord, Show) instance DNSRecord DSRecord where dnsRecordType _ = "DS" dnsRecordToJSON (DSRecord algorithm digest digestType keyTag) = [ ("data", object [ "algorithm" .= algorithm , "digest" .= digest , "digest_type" .= digestType , "key_tag" .= keyTag ]) ] dnsRecordParse o = do dataObj <- o .: "data" algorithm <- dataObj .: "algorithm" digest <- dataObj .: "digest" digestType <- dataObj .: "digest_type" keyTag <- dataObj .: "key_tag" pure $ DSRecord algorithm digest digestType keyTag -- | Defined in section 1.2 of draft-ietf-dnsop-svcb-https-12, it shares the same structure as the SVCB record data HTTPSRecord = HTTPSRecord { httpsRecordPriority :: Word16 -- ^ priority , httpsRecordTarget :: Text -- ^ target , httpsRecordValue :: Text -- ^ value TODO this could be made into a [(Text, Text))] } deriving (Eq, Ord, Show) instance DNSRecord HTTPSRecord where dnsRecordType _ = "HTTPS" dnsRecordToJSON (HTTPSRecord priority target value) = [ ("data", object [ "priority" .= priority , "target" .= target , "value" .= value ]) ] dnsRecordParse o = do dataObj <- o .: "data" priority <- dataObj .: "priority" target <- dataObj .: "target" value <- dataObj .: "value" pure $ HTTPSRecord priority target value -- | RFC 1876 LOC record, defined in section 2 data LOCRecord = LOCRecord { locRecordAltitude :: Int32 -- ^ Altitude of location in meters from 0e0 to 9e9, from a base of 100,000 meters below the WGS 84 reference spheroid used by GPS , locRecordLatDegrees :: Int -- ^ Degrees of latitude , locRecordLatDirection :: Text -- ^ Direction of latitude (N or S) TODO make this a specific type , locRecordLatMinutes :: Int -- ^ Minutes of latitude , locRecordLatSeconds :: Double -- ^ Seconds of latitude rounded to 3 decimal places , locRecordLongDegrees :: Int -- ^ Degrees of longitude , locRecordLongDirection :: Text -- ^ Direction of longitude (E or W) TODO make this a specific type , locRecordLongMinutes :: Int -- ^ Minutes of longitude , locRecordLongSeconds :: Double -- ^ Seconds of longitude rounded to 3 decimal places , locRecordPrecisionHoriz :: Int -- ^ Horizontal precision of location in meters , locRecordPrecisionVert :: Int -- ^ Vertical precision of location in meters from 0e0 to 9e9 , locRecordSize :: Int -- ^ Size of location in meters from 0e0 to 9e9 } deriving (Eq, Ord, Show) instance DNSRecord LOCRecord where dnsRecordType _ = "LOC" dnsRecordToJSON (LOCRecord altitude latDegrees latDirection latMinutes latSeconds longDegrees longDirection longMinutes longSeconds precisionHoriz precisionVert size) = [ ("data", object [ "altitude" .= altitude , "lat_degrees" .= latDegrees , "lat_direction" .= latDirection , "lat_minutes" .= latMinutes , "lat_seconds" .= latSeconds , "long_degrees" .= longDegrees , "long_direction" .= longDirection , "long_minutes" .= longMinutes , "long_seconds" .= longSeconds , "precision_horz" .= precisionHoriz , "precision_vert" .= precisionVert , "size" .= size ]) ] dnsRecordParse o = do dataObj <- o .: "data" altitude <- dataObj .: "altitude" latDegrees <- dataObj .: "lat_degrees" latDirection <- dataObj .: "lat_direction" latMinutes <- dataObj .: "lat_minutes" latSeconds <- dataObj .: "lat_seconds" longDegrees <- dataObj .: "long_degrees" longDirection <- dataObj .: "long_direction" longMinutes <- dataObj .: "long_minutes" longSeconds <- dataObj .: "long_seconds" precisionHoriz <- dataObj .: "precision_horz" precisionVert <- dataObj .: "precision_vert" size <- dataObj .: "size" pure $ LOCRecord altitude latDegrees latDirection latMinutes latSeconds longDegrees longDirection longMinutes longSeconds precisionHoriz precisionVert size -- | RFC 1035 MX record, defined in section 3.3.9 data MXRecord = MXRecord { mxRecordContent :: Text -- ^ A domain name to act as a mail exchange , mxRecordPriority :: Word16 -- ^ Priority of the target host, client MUST attempt to contact the target host with the lowest priority it can reach } deriving (Eq, Ord, Show) instance DNSRecord MXRecord where dnsRecordType _ = "MX" dnsRecordToJSON (MXRecord content priority) = [ "content" .= content , "priority" .= priority ] dnsRecordParse o = do content <- o .: "content" priority <- o .: "priority" pure $ MXRecord content priority -- | RFC 3403 NAPTR, Name Authority Pointer record data NAPTRRecord = NAPTRRecord { naptrRecordFlags :: Text -- ^ Flags to control the rewriting and interpretation of the fields in the record, characters from A-Z and 0-9, defined in section 4.1 , naptrRecordOrder :: Word16 -- ^ Order that the records MUST be processed in, defined in section 4.1 , naptrRecordPreference :: Word16 -- ^ Preference, equivalent to priority in DDDS, defined in section 4.1 , naptrRecordRegexp :: Text -- ^ Regexp, substitution expression that is applied to the original string, defined in section 4.1 , naptrRecordReplacement :: Text -- ^ Replacement, MUST be a fully qualified domain name, defined in section 4.1 , naptrRecordService :: Text -- ^ Service parameters applicable to this delegation path, defined in section 4.1 } deriving (Eq, Ord, Show) instance DNSRecord NAPTRRecord where dnsRecordType _ = "NAPTR" dnsRecordToJSON (NAPTRRecord flags order preference regexp replacement service) = [ ("data", object [ "flags" .= flags , "order" .= order , "preference" .= preference , "regexp" .= regexp , "replacement" .= replacement , "service" .= service ]) ] dnsRecordParse o = do dataObj <- o .: "data" flags <- dataObj .: "flags" order <- dataObj .: "order" preference <- dataObj .: "preference" regexp <- dataObj .: "regexp" replacement <- dataObj .: "replacement" service <- dataObj .: "service" pure $ NAPTRRecord flags order preference regexp replacement service -- | RFC 1035 NS, name server record defined in section 3.3.11 data NSRecord = NSRecord { nsRecordContent :: Text -- ^ A domain name to act as a name server } deriving (Eq, Ord, Show) instance DNSRecord NSRecord where dnsRecordType _ = "NS" dnsRecordToJSON (NSRecord content) = [ "content" .= content ] dnsRecordParse o = do content <- o .: "content" pure $ NSRecord content -- | RFC 1035 PTR, pointer record defined in section 3.3.12 data PTRRecord = PTRRecord { ptrRecordContent :: Text -- ^ A valid PTR target host name } deriving (Eq, Ord, Show) instance DNSRecord PTRRecord where dnsRecordType _ = "PTR" dnsRecordToJSON (PTRRecord content) = [ "content" .= content ] dnsRecordParse o = do content <- o .: "content" pure $ PTRRecord content -- | RFC 8162 SMIMEA record data SMIMEARecord = SMIMEARecord { smimeaRecordCertificate :: Text -- ^ Certificate , smimeaMathingType :: Int -- ^ Matching type , smimeaRecordSelector :: Int -- ^ Selector , smimeaRecordUsage :: Int -- ^ Usage } deriving (Eq, Ord, Show) instance DNSRecord SMIMEARecord where dnsRecordType _ = "SMIMEA" dnsRecordToJSON (SMIMEARecord certificate matchingType selector usage) = [ ("data", object [ "certificate" .= certificate , "matching_type" .= matchingType , "selector" .= selector , "usage" .= usage ]) ] dnsRecordParse o = do dataObj <- o .: "data" certificate <- dataObj .: "certificate" matchingType <- dataObj .: "matching_type" selector <- dataObj .: "selector" usage <- dataObj .: "usage" pure $ SMIMEARecord certificate matchingType selector usage -- | RFC 2782 SRV, service record data SRVRecord = SRVRecord { srvRecordName :: Text -- ^ Domain name of the service , srvRecordPort :: Word16 -- ^ The port of the service , srvRecordPriority :: Word16 -- ^ The priority of the target host, client MUST attempt to contact the target host with the lowest priority it can reach , srvRecordProto :: Text -- ^ Name of the protocol for the service, prepended with an underscore ('_TCP', '_UDP') , srvRecordService :: Text -- ^ A service type, prefixed with an underscore (Example '_SIP'), defined in RFC 1700 or locally , srvRecordTarget :: Text -- ^ Domain name of the target host, there can not be an alias , srvRecordWeight :: Word16 -- ^ Relative weight for records with the same priority, higher value means higher chance of being selected } deriving (Eq, Ord, Show) instance DNSRecord SRVRecord where dnsRecordType _ = "SRV" dnsRecordToJSON (SRVRecord name port priority proto service target weight) = [ ("data", object [ "name" .= name , "port" .= port , "priority" .= priority , "proto" .= proto , "service" .= service , "target" .= target , "weight" .= weight ]) ] dnsRecordParse o = do dataObj <- o .: "data" name <- dataObj .: "name" port <- dataObj .: "port" priority <- dataObj .: "priority" proto <- dataObj .: "proto" service <- dataObj .: "service" target <- dataObj .: "target" weight <- dataObj .: "weight" pure $ SRVRecord name port priority proto service target weight -- | RFC 4255 SSHFP record data SSHFPRecord = SSHFPRecord { sshfpRecordAlgorithm :: Int -- ^ Algorithm , sshfpRecordFingerprint :: Text -- ^ Fingerprint , sshfpRecordType :: Int -- ^ Type } deriving (Eq, Ord, Show) instance DNSRecord SSHFPRecord where dnsRecordType _ = "SSHFP" dnsRecordToJSON (SSHFPRecord algorithm fingerprint type') = [ ("data", object [ "algorithm" .= algorithm , "fingerprint" .= fingerprint , "type" .= type' ]) ] dnsRecordParse o = do dataObj <- o .: "data" algorithm <- dataObj .: "algorithm" fingerprint <- dataObj .: "fingerprint" type' <- dataObj .: "type" pure $ SSHFPRecord algorithm fingerprint type' -- | draft-ietf-dnsop-svcb-https-12 SVCB record data SVCBRecord = SVCBRecord { svcBRecordPriority :: Int -- ^ Priority , svcBRecordTarget :: Text -- ^ Target , svcBRecordValue :: Text -- ^ Value (Example 'alpn="h3,h2" ipv4hint="127.0.0.1" ipv6hint="::1"') } deriving (Eq, Ord, Show) instance DNSRecord SVCBRecord where dnsRecordType _ = "SVCB" dnsRecordToJSON (SVCBRecord priority target value) = [ ("data", object [ "priority" .= priority , "target" .= target , "value" .= value ]) ] dnsRecordParse o = do dataObj <- o .: "data" priority <- dataObj .: "priority" target <- dataObj .: "target" value <- dataObj .: "value" pure $ SVCBRecord priority target value -- | RFC 6698 TLSA record, defined in section 2 data TLSARecord = TLSARecord { tlsaRecordCertificate :: Text -- ^ Certificate, either raw data (full certificate or -- SubjectPublicKeyInfo based on the selector) for matching -- type 0, or the hash of the raw certificate data for types 1 -- and 2. The hash is in binary form, not hex-encoded. -- Defined in section 2.1.4 , tlsaRecordMatchingType :: Word8 -- ^ Matching type specifies how the certificate association is presented, defined in section 2.1.3 , tlsaRecordSelector :: Word8 -- ^ Selector the part of the TLS certificate to match against, defined in section 2.1.2 , tlsaRecordUsage :: Word8 -- ^ Usage of the certificate, defined in section 2.1.1 } deriving (Eq, Ord, Show) instance DNSRecord TLSARecord where dnsRecordType _ = "TLSA" dnsRecordToJSON (TLSARecord certificate matchingType selector usage) = [ ("data", object [ "certificate" .= certificate , "matching_type" .= matchingType , "selector" .= selector , "usage" .= usage ]) ] dnsRecordParse o = do dataObj <- o .: "data" certificate <- dataObj .: "certificate" matchingType <- dataObj .: "matching_type" selector <- dataObj .: "selector" usage <- dataObj .: "usage" pure $ TLSARecord certificate matchingType selector usage -- | RFC 1035 TXT record, defined in section 3.3.14 data TXTRecord = TXTRecord { txtRecordContent :: Text -- ^ Text content of the record } deriving (Eq, Ord, Show) instance DNSRecord TXTRecord where dnsRecordType _ = "TXT" dnsRecordToJSON (TXTRecord content) = [ "content" .= content ] dnsRecordParse o = do content <- o .: "content" pure $ TXTRecord content -- | RFC 7553 URI record data URIRecord = URIRecord { uriRecordContent :: Text -- ^ URI content, defined in section 2 , uriRecordPriority :: Word16 -- ^ Priority, lower value means higher chance of being selected } deriving (Eq, Ord, Show) instance DNSRecord URIRecord where dnsRecordType _ = "URI" dnsRecordToJSON (URIRecord content priority) = [ ("data", object [ "content" .= content , "priority" .= priority ]) ] dnsRecordParse o = do dataObj <- o .: "data" content <- dataObj .: "content" priority <- dataObj .: "priority" pure $ URIRecord content priority