date based serial numbers
complicated by OGMG-it's-2014-and-we're-still-using-unsigned-32-bit-ints
This commit is contained in:
parent
498fe2cd25
commit
8d8f68f5ab
|
@ -7,6 +7,9 @@ import qualified Propellor.Property.Service as Service
|
||||||
import Utility.Applicative
|
import Utility.Applicative
|
||||||
|
|
||||||
import Data.List
|
import Data.List
|
||||||
|
import Data.Time.Clock.POSIX
|
||||||
|
import Data.Time.Format
|
||||||
|
import Foreign.C.Types
|
||||||
|
|
||||||
namedconf :: FilePath
|
namedconf :: FilePath
|
||||||
namedconf = "/etc/bind/named.conf.local"
|
namedconf = "/etc/bind/named.conf.local"
|
||||||
|
@ -66,7 +69,11 @@ zones zs = hasContent namedconf (concatMap zoneStanza zs)
|
||||||
`onChange` Service.reloaded "bind9"
|
`onChange` Service.reloaded "bind9"
|
||||||
|
|
||||||
-- | Represents a bind 9 zone file.
|
-- | 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.
|
-- | Every domain has a SOA record, which is big and complicated.
|
||||||
data SOA = SOA
|
data SOA = SOA
|
||||||
|
@ -81,6 +88,7 @@ data SOA = SOA
|
||||||
, sRecord :: [Record]
|
, sRecord :: [Record]
|
||||||
-- ^ Records for the root of the domain. Typically NS, A, TXT
|
-- ^ Records for the root of the domain. Typically NS, A, TXT
|
||||||
}
|
}
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
-- | Types of DNS records.
|
-- | Types of DNS records.
|
||||||
--
|
--
|
||||||
|
@ -92,11 +100,13 @@ data Record
|
||||||
| MX Int BindDomain
|
| MX Int BindDomain
|
||||||
| NS BindDomain
|
| NS BindDomain
|
||||||
| TXT String
|
| TXT String
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
type Ipv4 = String
|
type Ipv4 = String
|
||||||
type Ipv6 = 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.
|
-- | 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.
|
-- The SOADomain refers to the root SOA record.
|
||||||
data BindDomain = RelDomain Domain | AbsDomain Domain | SOADomain
|
data BindDomain = RelDomain Domain | AbsDomain Domain | SOADomain
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
dValue :: BindDomain -> String
|
dValue :: BindDomain -> String
|
||||||
dValue (RelDomain d) = d
|
dValue (RelDomain d) = d
|
||||||
|
@ -127,7 +138,7 @@ rValue (MX pri d) = show pri ++ " " ++ dValue d
|
||||||
rValue (NS d) = dValue d
|
rValue (NS d) = dValue d
|
||||||
rValue (TXT s) = [q] ++ filter (/= q) s ++ [q]
|
rValue (TXT s) = [q] ++ filter (/= q) s ++ [q]
|
||||||
where
|
where
|
||||||
q = '\"'
|
q = '"'
|
||||||
|
|
||||||
-- | Adjusts the serial number of the zone to
|
-- | Adjusts the serial number of the zone to
|
||||||
--
|
--
|
||||||
|
@ -138,36 +149,59 @@ nextSerialNumber (Zone soa l) oldserial = Zone soa' l
|
||||||
where
|
where
|
||||||
soa' = soa { sSerial = succ $ max (sSerial soa) oldserial }
|
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.
|
-- | Write a Zone out to a to a file.
|
||||||
--
|
--
|
||||||
-- The serial number that is written to the file comes from larger of the
|
-- 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.
|
-- Zone's SOA serial number, and the last serial number used in the file.
|
||||||
-- This ensures that serial number always increases, while also letting
|
-- This ensures that serial number always increases, while also letting
|
||||||
-- a Zone contain an existing serial number, which may be quite large.
|
-- 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 :: Zone -> FilePath -> IO ()
|
||||||
writeZoneFile z f = do
|
writeZoneFile z f = do
|
||||||
oldserial <- nextZoneFileSerialNumber f
|
oldserial <- nextZoneFileSerialNumber f
|
||||||
let z'@(Zone soa' _) = nextSerialNumber z oldserial
|
let z' = nextSerialNumber z oldserial
|
||||||
writeFile f (genZoneFile z')
|
writeFile f (genZoneFile z')
|
||||||
writeFile (zoneSerialFile f) (show $ sSerial soa')
|
writeZonePropellorFile f z'
|
||||||
|
|
||||||
-- | Next to the zone file, is a ".serial" file, which contains
|
-- | Next to the zone file, is a ".propellor" file, which contains
|
||||||
-- the SOA Serial number of that zone. This saves the bother of parsing
|
-- the serialized Zone. This saves the bother of parsing
|
||||||
-- this horrible format.
|
-- the horrible bind zone file format.
|
||||||
zoneSerialFile :: FilePath -> FilePath
|
zonePropellorFile :: FilePath -> FilePath
|
||||||
zoneSerialFile f = f ++ ".serial"
|
zonePropellorFile f = f ++ ".serial"
|
||||||
|
|
||||||
nextZoneFileSerialNumber :: FilePath -> IO SerialNumber
|
nextZoneFileSerialNumber :: FilePath -> IO SerialNumber
|
||||||
nextZoneFileSerialNumber = maybe 1 (+1) <$$> readZoneSerialFile
|
nextZoneFileSerialNumber = maybe 1 (sSerial . zSOA . incrSerialNumber)
|
||||||
|
<$$> readZonePropellorFile
|
||||||
|
|
||||||
readZoneSerialFile :: FilePath -> IO (Maybe SerialNumber)
|
writeZonePropellorFile :: FilePath -> Zone -> IO ()
|
||||||
readZoneSerialFile f = catchDefaultIO Nothing $
|
writeZonePropellorFile f z = writeFile (zonePropellorFile f) (show z)
|
||||||
readish <$> readFile (zoneSerialFile f)
|
|
||||||
|
readZonePropellorFile :: FilePath -> IO (Maybe Zone)
|
||||||
|
readZonePropellorFile f = catchDefaultIO Nothing $
|
||||||
|
readish <$> readFile (zonePropellorFile f)
|
||||||
|
|
||||||
-- | Generating a zone file.
|
-- | Generating a zone file.
|
||||||
genZoneFile :: Zone -> String
|
genZoneFile :: Zone -> String
|
||||||
|
|
Loading…
Reference in New Issue