summaryrefslogtreecommitdiff
path: root/Propellor/Property
diff options
context:
space:
mode:
authorJoey Hess2014-04-18 16:33:06 -0400
committerJoey Hess2014-04-18 16:33:06 -0400
commit8d8f68f5abdfc6980697c160307751aa3b18f9b8 (patch)
tree60ab7fe6520d7947e8290934cae7e0e1f17a131c /Propellor/Property
parent498fe2cd2551f3a4fdbcbd2b99fdfdbefa0879d0 (diff)
date based serial numbers
complicated by OGMG-it's-2014-and-we're-still-using-unsigned-32-bit-ints
Diffstat (limited to 'Propellor/Property')
-rw-r--r--Propellor/Property/Dns.hs72
1 files changed, 53 insertions, 19 deletions
diff --git a/Propellor/Property/Dns.hs b/Propellor/Property/Dns.hs
index 260f3aa6..4c59aacd 100644
--- a/Propellor/Property/Dns.hs
+++ b/Propellor/Property/Dns.hs
@@ -7,6 +7,9 @@ import qualified Propellor.Property.Service as Service
import Utility.Applicative
import Data.List
+import Data.Time.Clock.POSIX
+import Data.Time.Format
+import Foreign.C.Types
namedconf :: FilePath
namedconf = "/etc/bind/named.conf.local"
@@ -66,7 +69,11 @@ zones zs = hasContent namedconf (concatMap zoneStanza zs)
`onChange` Service.reloaded "bind9"
-- | Represents a bind 9 zone file.
-data Zone = Zone SOA [(HostName, Record)]
+data Zone = Zone
+ { zSOA :: SOA
+ , zHosts :: [(HostName, Record)]
+ }
+ deriving (Read, Show, Eq)
-- | Every domain has a SOA record, which is big and complicated.
data SOA = SOA
@@ -81,6 +88,7 @@ data SOA = SOA
, sRecord :: [Record]
-- ^ Records for the root of the domain. Typically NS, A, TXT
}
+ deriving (Read, Show, Eq)
-- | Types of DNS records.
--
@@ -92,11 +100,13 @@ data Record
| MX Int BindDomain
| NS BindDomain
| TXT String
+ deriving (Read, Show, Eq)
type Ipv4 = String
type Ipv6 = String
-type SerialNumber = Integer
+-- | Bind serial numbers are unsigned, 32 bit integers.
+type SerialNumber = CInt
-- | Domains in the zone file must end with a period if they are absolute.
--
@@ -105,6 +115,7 @@ type SerialNumber = Integer
--
-- The SOADomain refers to the root SOA record.
data BindDomain = RelDomain Domain | AbsDomain Domain | SOADomain
+ deriving (Read, Show, Eq)
dValue :: BindDomain -> String
dValue (RelDomain d) = d
@@ -127,7 +138,7 @@ rValue (MX pri d) = show pri ++ " " ++ dValue d
rValue (NS d) = dValue d
rValue (TXT s) = [q] ++ filter (/= q) s ++ [q]
where
- q = '\"'
+ q = '"'
-- | Adjusts the serial number of the zone to
--
@@ -138,36 +149,59 @@ nextSerialNumber (Zone soa l) oldserial = Zone soa' l
where
soa' = soa { sSerial = succ $ max (sSerial soa) oldserial }
+incrSerialNumber :: Zone -> Zone
+incrSerialNumber (Zone soa l) = Zone soa' l
+ where
+ soa' = soa { sSerial = succ (sSerial soa) }
+
+-- | Propellor uses a serial number derived from the current date and time.
+--
+-- This ensures that, even if zone files are being generated on
+-- multiple hosts, the serial numbers will not get out of sync between
+-- them.
+--
+-- Since serial numbers are limited to 32 bits, the number of seconds
+-- since the epoch is divided by 5. This will work until the year 2650,
+-- at which point this stupid limit had better have been increased to
+-- 128 bits. If we didn't divide by 5, it would only work up to 2106!
+--
+-- Dividing by 5 means that this number only changes once every 5 seconds.
+-- If propellor is running more often than once every 5 seconds, you're
+-- doing something wrong.
+currentSerialNumber :: IO SerialNumber
+currentSerialNumber = calc <$> getPOSIXTime
+ where
+ calc t = floor (t / 5)
+
-- | Write a Zone out to a to a file.
--
-- The serial number that is written to the file comes from larger of the
-- Zone's SOA serial number, and the last serial number used in the file.
-- This ensures that serial number always increases, while also letting
-- a Zone contain an existing serial number, which may be quite large.
---
--- TODO: This increases the serial number when propellor is running on the
--- same host and generating its zone there, but what if the DNS host is
--- changed? We'd then want to remember the actual serial number and
--- propigate it to the new DNS host.
writeZoneFile :: Zone -> FilePath -> IO ()
writeZoneFile z f = do
oldserial <- nextZoneFileSerialNumber f
- let z'@(Zone soa' _) = nextSerialNumber z oldserial
+ let z' = nextSerialNumber z oldserial
writeFile f (genZoneFile z')
- writeFile (zoneSerialFile f) (show $ sSerial soa')
+ writeZonePropellorFile f z'
--- | Next to the zone file, is a ".serial" file, which contains
--- the SOA Serial number of that zone. This saves the bother of parsing
--- this horrible format.
-zoneSerialFile :: FilePath -> FilePath
-zoneSerialFile f = f ++ ".serial"
+-- | Next to the zone file, is a ".propellor" file, which contains
+-- the serialized Zone. This saves the bother of parsing
+-- the horrible bind zone file format.
+zonePropellorFile :: FilePath -> FilePath
+zonePropellorFile f = f ++ ".serial"
nextZoneFileSerialNumber :: FilePath -> IO SerialNumber
-nextZoneFileSerialNumber = maybe 1 (+1) <$$> readZoneSerialFile
+nextZoneFileSerialNumber = maybe 1 (sSerial . zSOA . incrSerialNumber)
+ <$$> readZonePropellorFile
+
+writeZonePropellorFile :: FilePath -> Zone -> IO ()
+writeZonePropellorFile f z = writeFile (zonePropellorFile f) (show z)
-readZoneSerialFile :: FilePath -> IO (Maybe SerialNumber)
-readZoneSerialFile f = catchDefaultIO Nothing $
- readish <$> readFile (zoneSerialFile f)
+readZonePropellorFile :: FilePath -> IO (Maybe Zone)
+readZonePropellorFile f = catchDefaultIO Nothing $
+ readish <$> readFile (zonePropellorFile f)
-- | Generating a zone file.
genZoneFile :: Zone -> String