github.com/pmoroney/dnscontrol@v0.2.4-0.20171024134423-fad98f73f44a/providers/bind/bindProvider.go (about) 1 package bind 2 3 /* 4 5 bind - 6 Generate zonefiles suitiable for BIND. 7 8 The zonefiles are read and written to the directory -bind_dir 9 10 If the old zonefiles are readable, we read them to determine 11 if an update is actually needed. The old zonefile is also used 12 as the basis for generating the new SOA serial number. 13 14 */ 15 16 import ( 17 "bytes" 18 "encoding/json" 19 "fmt" 20 "log" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/miekg/dns" 26 "github.com/miekg/dns/dnsutil" 27 28 "github.com/StackExchange/dnscontrol/models" 29 "github.com/StackExchange/dnscontrol/providers" 30 "github.com/StackExchange/dnscontrol/providers/diff" 31 ) 32 33 var docNotes = providers.DocumentationNotes{ 34 providers.DocDualHost: providers.Can(), 35 providers.DocCreateDomains: providers.Can("Driver just maintains list of zone files. It should automatically add missing ones."), 36 providers.DocOfficiallySupported: providers.Can(), 37 } 38 39 func initBind(config map[string]string, providermeta json.RawMessage) (providers.DNSServiceProvider, error) { 40 // config -- the key/values from creds.json 41 // meta -- the json blob from NewReq('name', 'TYPE', meta) 42 api := &Bind{ 43 directory: config["directory"], 44 } 45 if api.directory == "" { 46 api.directory = "zones" 47 } 48 if len(providermeta) != 0 { 49 err := json.Unmarshal(providermeta, api) 50 if err != nil { 51 return nil, err 52 } 53 } 54 api.nameservers = models.StringsToNameservers(api.DefaultNS) 55 return api, nil 56 } 57 58 func init() { 59 providers.RegisterDomainServiceProviderType("BIND", initBind, providers.CanUsePTR, 60 providers.CanUseSRV, providers.CanUseCAA, providers.CanUseTLSA, providers.CantUseNOPURGE, docNotes) 61 } 62 63 type SoaInfo struct { 64 Ns string `json:"master"` 65 Mbox string `json:"mbox"` 66 Serial uint32 `json:"serial"` 67 Refresh uint32 `json:"refresh"` 68 Retry uint32 `json:"retry"` 69 Expire uint32 `json:"expire"` 70 Minttl uint32 `json:"minttl"` 71 } 72 73 func (s SoaInfo) String() string { 74 return fmt.Sprintf("%s %s %d %d %d %d %d", s.Ns, s.Mbox, s.Serial, s.Refresh, s.Retry, s.Expire, s.Minttl) 75 } 76 77 type Bind struct { 78 DefaultNS []string `json:"default_ns"` 79 DefaultSoa SoaInfo `json:"default_soa"` 80 nameservers []*models.Nameserver 81 directory string 82 } 83 84 //var bindSkeletin = flag.String("bind_skeletin", "skeletin/master/var/named/chroot/var/named/master", "") 85 86 func rrToRecord(rr dns.RR, origin string, replaceSerial uint32) (models.RecordConfig, uint32) { 87 // Convert's dns.RR into our native data type (models.RecordConfig). 88 // Records are translated directly with no changes. 89 // If it is an SOA for the apex domain and 90 // replaceSerial != 0, change the serial to replaceSerial. 91 // WARNING(tlim): This assumes SOAs do not have serial=0. 92 // If one is found, we replace it with serial=1. 93 var old_serial, new_serial uint32 94 header := rr.Header() 95 rc := models.RecordConfig{} 96 rc.Type = dns.TypeToString[header.Rrtype] 97 rc.NameFQDN = strings.ToLower(strings.TrimSuffix(header.Name, ".")) 98 rc.Name = strings.ToLower(dnsutil.TrimDomainName(header.Name, origin)) 99 rc.TTL = header.Ttl 100 switch v := rr.(type) { // #rtype_variations 101 case *dns.A: 102 rc.Target = v.A.String() 103 case *dns.AAAA: 104 rc.Target = v.AAAA.String() 105 case *dns.CAA: 106 rc.CaaTag = v.Tag 107 rc.CaaFlag = v.Flag 108 rc.Target = v.Value 109 case *dns.CNAME: 110 rc.Target = v.Target 111 case *dns.MX: 112 rc.Target = v.Mx 113 rc.MxPreference = v.Preference 114 case *dns.NS: 115 rc.Target = v.Ns 116 case *dns.PTR: 117 rc.Target = v.Ptr 118 case *dns.SOA: 119 old_serial = v.Serial 120 if old_serial == 0 { 121 // For SOA records, we never return a 0 serial number. 122 old_serial = 1 123 } 124 new_serial = v.Serial 125 if rc.Name == "@" && replaceSerial != 0 { 126 new_serial = replaceSerial 127 } 128 rc.Target = fmt.Sprintf("%v %v %v %v %v %v %v", 129 v.Ns, v.Mbox, new_serial, v.Refresh, v.Retry, v.Expire, v.Minttl) 130 case *dns.SRV: 131 rc.Target = v.Target 132 rc.SrvPort = v.Port 133 rc.SrvWeight = v.Weight 134 rc.SrvPriority = v.Priority 135 case *dns.TLSA: 136 rc.TlsaUsage = v.Usage 137 rc.TlsaSelector = v.Selector 138 rc.TlsaMatchingType = v.MatchingType 139 rc.Target = v.Certificate 140 case *dns.TXT: 141 rc.Target = strings.Join(v.Txt, " ") 142 default: 143 log.Fatalf("rrToRecord: Unimplemented zone record type=%s (%v)\n", rc.Type, rr) 144 } 145 return rc, old_serial 146 } 147 148 func makeDefaultSOA(info SoaInfo, origin string) *models.RecordConfig { 149 // Make a default SOA record in case one isn't found: 150 soaRec := models.RecordConfig{ 151 Type: "SOA", 152 Name: "@", 153 } 154 soaRec.NameFQDN = dnsutil.AddOrigin(soaRec.Name, origin) 155 if len(info.Ns) == 0 { 156 info.Ns = "DEFAULT_NOT_SET." 157 } 158 if len(info.Mbox) == 0 { 159 info.Mbox = "DEFAULT_NOT_SET." 160 } 161 if info.Serial == 0 { 162 info.Serial = 1 163 } 164 if info.Refresh == 0 { 165 info.Refresh = 3600 166 } 167 if info.Retry == 0 { 168 info.Retry = 600 169 } 170 if info.Expire == 0 { 171 info.Expire = 604800 172 } 173 if info.Minttl == 0 { 174 info.Minttl = 1440 175 } 176 soaRec.Target = info.String() 177 178 return &soaRec 179 } 180 181 func (c *Bind) GetNameservers(string) ([]*models.Nameserver, error) { 182 return c.nameservers, nil 183 } 184 185 func (c *Bind) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 186 dc.Punycode() 187 // Phase 1: Copy everything to []*models.RecordConfig: 188 // expectedRecords < dc.Records[i] 189 // foundRecords < zonefile 190 // 191 // Phase 2: Do any manipulations: 192 // add NS 193 // manipulate SOA 194 // 195 // Phase 3: Convert to []diff.Records and compare: 196 // expectedDiffRecords < expectedRecords 197 // foundDiffRecords < foundRecords 198 // diff.Inc...(foundDiffRecords, expectedDiffRecords ) 199 200 // Default SOA record. If we see one in the zone, this will be replaced. 201 soaRec := makeDefaultSOA(c.DefaultSoa, dc.Name) 202 203 // Read foundRecords: 204 foundRecords := make([]*models.RecordConfig, 0) 205 var oldSerial, newSerial uint32 206 zonefile := filepath.Join(c.directory, strings.Replace(strings.ToLower(dc.Name), "/", "_", -1)+".zone") 207 foundFH, err := os.Open(zonefile) 208 zoneFileFound := err == nil 209 if err != nil && !os.IsNotExist(os.ErrNotExist) { 210 // Don't whine if the file doesn't exist. However all other 211 // errors will be reported. 212 fmt.Printf("Could not read zonefile: %v\n", err) 213 } else { 214 for x := range dns.ParseZone(foundFH, dc.Name, zonefile) { 215 if x.Error != nil { 216 log.Println("Error in zonefile:", x.Error) 217 } else { 218 rec, serial := rrToRecord(x.RR, dc.Name, oldSerial) 219 if serial != 0 && oldSerial != 0 { 220 log.Fatalf("Multiple SOA records in zonefile: %v\n", zonefile) 221 } 222 if serial != 0 { 223 // This was an SOA record. Update the serial. 224 oldSerial = serial 225 newSerial = generate_serial(oldSerial) 226 // Regenerate with new serial: 227 *soaRec, _ = rrToRecord(x.RR, dc.Name, newSerial) 228 rec = *soaRec 229 } 230 foundRecords = append(foundRecords, &rec) 231 } 232 } 233 } 234 235 // Add SOA record to expected set: 236 if !dc.HasRecordTypeName("SOA", "@") { 237 dc.Records = append(dc.Records, soaRec) 238 } 239 240 differ := diff.New(dc) 241 _, create, del, mod := differ.IncrementalDiff(foundRecords) 242 243 buf := &bytes.Buffer{} 244 // Print a list of changes. Generate an actual change that is the zone 245 changes := false 246 for _, i := range create { 247 changes = true 248 if zoneFileFound { 249 fmt.Fprintln(buf, i) 250 } 251 } 252 for _, i := range del { 253 changes = true 254 if zoneFileFound { 255 fmt.Fprintln(buf, i) 256 } 257 } 258 for _, i := range mod { 259 changes = true 260 if zoneFileFound { 261 fmt.Fprintln(buf, i) 262 } 263 } 264 msg := fmt.Sprintf("GENERATE_ZONEFILE: %s\n", dc.Name) 265 if !zoneFileFound { 266 msg = msg + fmt.Sprintf(" (%d records)\n", len(create)) 267 } 268 msg += buf.String() 269 corrections := []*models.Correction{} 270 if changes { 271 corrections = append(corrections, 272 &models.Correction{ 273 Msg: msg, 274 F: func() error { 275 fmt.Printf("CREATING ZONEFILE: %v\n", zonefile) 276 zf, err := os.Create(zonefile) 277 if err != nil { 278 log.Fatalf("Could not create zonefile: %v", err) 279 } 280 zonefilerecords := make([]dns.RR, 0, len(dc.Records)) 281 for _, r := range dc.Records { 282 zonefilerecords = append(zonefilerecords, r.ToRR()) 283 } 284 err = WriteZoneFile(zf, zonefilerecords, dc.Name) 285 286 if err != nil { 287 log.Fatalf("WriteZoneFile error: %v\n", err) 288 } 289 err = zf.Close() 290 if err != nil { 291 log.Fatalf("Closing: %v", err) 292 } 293 return nil 294 }, 295 }) 296 } 297 298 return corrections, nil 299 }