package main /* * https://github.com/joeig/go-powerdns * https://github.com/xlzd/gotp * https://github.com/dspinhirne/netaddr-go => https://pkg.go.dev/github.com/dspinhirne/netaddr-go * https://github.com/go-yaml/yaml/tree/v2.4.0 => */ import ( "context" "fmt" "github.com/dspinhirne/netaddr-go" "github.com/joeig/go-powerdns/v3" "github.com/xlzd/gotp" "gopkg.in/yaml.v3" "log" "net" "net/http" "os" "strings" ) type conf struct { TotpToken string `yaml:"totp_secret"` PdnsApiToken string `yaml:"pdns_api_token"` Zone string `yaml:"zone"` Records []string `yaml:"records"` } func (c *conf) getConf() *conf { yamlFile, err := os.ReadFile("config.yaml") if err != nil { log.Printf("yamlFile.Get err #%v ", err) } err = yaml.Unmarshal(yamlFile, c) if err != nil { log.Fatalf("Unmarshal: %v", err) } return c } func main() { f, err := os.OpenFile("/tmp/testlogfile", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening file: %v", err) } defer f.Close() log.SetOutput(f) var c conf c.getConf() ctx := context.Background() pdns := powerdns.New("http://localhost:8081", "localhost", powerdns.WithAPIKey(c.PdnsApiToken)) http.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) { token := r.URL.Query().Get("token") if token == gotp.NewDefaultTOTP(c.TotpToken).Now() { ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { log.Fatalf("Err %v ", err) } userIP := net.ParseIP(ip) if userIP == nil { log.Fatalf("Err %v ", err) } //fmt.Fprintf(w, "IP: %q \n", ip) //fmt.Fprintf(w, "IP: %q \n", ip) log.Println(fmt.Sprintf("TOTP : %q", gotp.NewDefaultTOTP(c.TotpToken).Now())) log.Println(fmt.Sprintf("Token: %q", token)) ipv6, _ := netaddr.ParseIPv6(ip) new_prefix := ipv6.NetId() updated := false for _, record_to_update := range c.Records { records, err := pdns.Records.Get(ctx, c.Zone, record_to_update, powerdns.RRTypePtr(powerdns.RRTypeAAAA)) if err != nil { log.Fatalf("%v", err) } for _, r := range records { if strings.Index(*r.Name, record_to_update) == 0 { ipv6, err := netaddr.ParseIPv6(*r.Records[0].Content) if err != nil { log.Fatalf("IPv6 parse err %v ", err) } hostId := ipv6.HostId() new_ipv6 := netaddr.NewIPv6(new_prefix, hostId) log.Println(fmt.Sprintf("Host: %q -> %q needs update: %t", ipv6, new_ipv6, ipv6.Long() != new_ipv6.Long())) if ipv6.Long() != new_ipv6.Long() { fmt.Fprintf(w, "Update Host %q to %q\n", *r.Name, ip) updated = true err = pdns.Records.Change(ctx, c.Zone, record_to_update+"."+c.Zone, powerdns.RRTypeAAAA, 30, []string{new_ipv6.Long()}) if err != nil { log.Fatalf("Update err %v ", err) } } else { fmt.Fprintf(w, "No update for host %q\n", *r.Name) } } } } if updated { // increase serial zone, err := pdns.Zones.Get(ctx, c.Zone) if err != nil { log.Fatalf("%v", err) } serial := *zone.Serial zoneChangeSet := &powerdns.Zone{ Serial: powerdns.Uint32(serial + 1), } if err := pdns.Zones.Change(ctx, c.Zone, zoneChangeSet); err != nil { log.Fatalf("%v", err) } pdns.Zones.Notify(ctx, c.Zone) log.Println("Updated...") } } }) log.Println("Started...") http.ListenAndServe(":8080", nil) }