package main import ( "encoding/json" "errors" "fmt" "log" "net" "strings" "time" "github.com/julienschmidt/httprouter" "github.com/miekg/dns" "git.nemunai.re/srs/adlin/libadlin" ) var ( ControlSocket = "[2a01:e0a:518:830::5]:53" tsigName = "ddns." tsigSecret = "" ) func init() { router.GET("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.GetAssociatedDomains(), nil })) router.POST("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { ue := &struct { Domain string `json:"domain"` A string `json:"a"` AAAA string `json:"aaaa"` CNAME string `json:"cname,omitempty"` }{} if err := json.Unmarshal(body, &ue); err != nil { return nil, err } if ue.Domain != "" && ue.A == "" && ue.AAAA == "" && ue.CNAME == "" { student.AssociatedDomain = nil if _, err := student.Update(); err != nil { return nil, err } return true, nil } else if ue.CNAME != "" { cname := dns.Fqdn(ue.CNAME) // Ensure delegation and association differs if student.DelegatedDomain != nil && *student.DelegatedDomain == cname { return nil, fmt.Errorf("Le domaine pour l'association CNAME doit être différent du domaine délégué.") } student.AssociatedDomain = &cname if _, err := student.Update(); err != nil { return nil, err } return true, nil } else { var aaaa net.IP if ue != nil && len(ue.AAAA) > 0 { aaaa = net.ParseIP(ue.AAAA) } return true, AddAssociatedDomains(student, aaaa) } })) router.GET("/api/adomains/:dn", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return GetAssociatedDomain(student, ps.ByName("dn")) })) router.GET("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return []string{student.MyDelegatedDomain()}, nil })) router.POST("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { ue := &struct { NS string `json:"ns"` }{} if err := json.Unmarshal(body, &ue); err != nil { return nil, err } if ue.NS == "" { student.DelegatedDomain = nil if _, err := student.Update(); err != nil { return nil, err } return true, nil } else { ns := dns.Fqdn(ue.NS) // Ensure delegation and association differs if student.AssociatedDomain != nil && *student.AssociatedDomain == ns { return nil, fmt.Errorf("Le domaine pour la délégation doit être différent du CNAME associé précédemment.") } // Ensure ns doesn't belong to one of our domain for _, ddomain := range adlin.DelegatedDomainSuffixes { if strings.HasSuffix(ns, ddomain) { return nil, fmt.Errorf("Vous ne pouvez pas vous créer une délégation vers ce sous-domaine: interdit par l'administrateur.") } } student.DelegatedDomain = &ns if _, err := student.Update(); err != nil { return nil, err } return true, nil } })) router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "") })) router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "NS") })) router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, "")) })) router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "NS", strings.Join(ue.Values, " ")) })) router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "AAAA") })) router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) })) router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) })) router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " ")) })) router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "DS") })) router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddDSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "DS", strings.Join(ue.Values, " ")) })) } type Entry struct { Domain string `json:"domain"` TTL uint32 `json:"ttl"` RR string `json:"rr"` ValuesFrom string `json:"valuesfrom,omitempty"` Values []string `json:"values"` } func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) { t := new(dns.Transfer) m := new(dns.Msg) t.TsigSecret = map[string]string{tsigName: tsigSecret} m.SetAxfr(globalDomain) m.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) c, err := t.In(m, ControlSocket) if err != nil { log.Println(globalDomain, err) return nil, err } for { response, ok := <-c if !ok { break } for _, r := range response.RR { if strings.HasSuffix(r.Header().Name, domain) { z := []string{} if v, ok := r.(*dns.A); ok { z = append(z, fmt.Sprintf("%s", v.A)) } else if v, ok := r.(*dns.AAAA); ok { z = append(z, fmt.Sprintf("%s", v.AAAA)) } else if v, ok := r.(*dns.NS); ok { z = append(z, fmt.Sprintf("%s", v.Ns)) } else if v, ok := r.(*dns.DS); ok { z = append(z, fmt.Sprintf("%d", v.KeyTag), fmt.Sprintf("%d", v.Algorithm), fmt.Sprintf("%d", v.DigestType), fmt.Sprintf("%s", v.Digest)) } rr = append(rr, Entry{r.Header().Name, r.Header().Ttl, dns.TypeToString[r.Header().Rrtype], "", z}) } } } return } func GetAssociatedDomain(student *adlin.Student, dn string) (rrs []Entry, err error) { domains := student.GetAssociatedDomains() found := false for _, d := range domains { if d == dn { found = true } } if !found { err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn)) } if entries, errr := parseZoneRead(student.MyAssociatedDomainSuffix(), dn); err != nil { return nil, errr } else { for _, e := range entries { if e.RR != "RRSIG" && e.RR != "NSEC" && e.RR != "NSEC3" { rrs = append(rrs, e) } } } return } func delAssociatedDomains(student *adlin.Student, dn string) (err error) { var adomains []Entry adomains, err = GetAssociatedDomain(student, dn) if err != nil { return } m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyAssociatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var rrs []dns.RR for _, domain := range adomains { rr, errr := dns.NewRR(fmt.Sprintf("%s %s %s", domain.Domain, domain.RR, strings.Join(domain.Values, " "))) if errr != nil { return errr } rrs = append(rrs, rr) } m1.Remove(rrs) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) if err != nil { return } return } func AddAssociatedDomains(student *adlin.Student, aaaa net.IP) (err error) { err = delAssociatedDomains(student, student.DefaultAssociatedDomain()) if err != nil { return } if aaaa == nil { aaaa = net.ParseIP(adlin.StudentIP(student.Id, 0).String() + "1") } else if !adlin.StudentNet(student.Id, 0).Contains(aaaa) { return errors.New("The associated IP has to be in your IP range.") } m2 := new(dns.Msg) m2.Id = dns.Id() m2.Opcode = dns.OpcodeUpdate m2.Question = make([]dns.Question, 1) m2.Question[0] = dns.Question{Name: student.MyAssociatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrA := new(dns.A) rrA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600} rrA.A = net.IPv4(82, 64, 151, 41) m2.Insert([]dns.RR{rrA}) rrAAAA := new(dns.AAAA) rrAAAA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} rrAAAA.AAAA = aaaa m2.Insert([]dns.RR{rrAAAA}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m2.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m2, ControlSocket) return } func getRRDelegatedDomain(student *adlin.Student, dn string, rr string) (rrs []Entry, err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { if d == dn { found = true } } if !found { err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn)) } if entries, errr := parseZoneRead(student.MyDelegatedDomainSuffix(), dn); err != nil { return nil, errr } else { for _, e := range entries { if e.RR == rr && strings.HasSuffix(strings.TrimSuffix(e.Domain, "."), strings.TrimSuffix(dn, ".")) { rrs = append(rrs, e) } } } return } func AddNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyDelegatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrNS := new(dns.NS) rrNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: ttl} rrNS.Ns = ns m1.Insert([]dns.RR{rrNS}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) } return } func UpdateNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldns string, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyDelegatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrOldNS := new(dns.NS) rrOldNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET} rrOldNS.Ns = oldns m1.Remove([]dns.RR{rrOldNS}) rrNS := new(dns.NS) rrNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: ttl} rrNS.Ns = ns m1.Insert([]dns.RR{rrNS}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) } return } func AddGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, aaaa string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { if strings.HasSuffix(dn, d) { found = true } } if !found { err = errors.New(fmt.Sprintf("Unable to find domain %q in your whitelist.", dn)) return } m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyDelegatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var rr dns.RR rr, err = dns.NewRR(fmt.Sprintf("%s %d IN AAAA %s", dn, ttl, aaaa)) if err != nil { return } m1.Insert([]dns.RR{rr}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) return } func UpdateGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldaaaa string, aaaa string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { if strings.HasSuffix(dn, d) { found = true } } if !found { err = errors.New(fmt.Sprintf("Unable to find domain %q in your whitelist.", dn)) return } m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyDelegatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var rr dns.RR rr, err = dns.NewRR(fmt.Sprintf("%s IN AAAA %s", dn, oldaaaa)) if err != nil { return } m1.Remove([]dns.RR{rr}) rr, err = dns.NewRR(fmt.Sprintf("%s %d IN AAAA %s", dn, ttl, aaaa)) if err != nil { return } m1.Insert([]dns.RR{rr}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) return } func AddDSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, rdata string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { if dn == d { found = true } } if !found { err = errors.New(fmt.Sprintf("Unable to find domain %q in your whitelist.", dn)) return } var rr dns.RR rr, err = dns.NewRR(fmt.Sprintf("%s IN DNSKEY %s", dn, rdata)) if err != nil { return } var dnskey dns.DNSKEY if v, ok := rr.(*dns.DNSKEY); ok { dnskey = *v } else { err = errors.New(fmt.Sprintf("This is not a valid DNSKEY record.")) return } m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyDelegatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} var ds *dns.DS ds = dnskey.ToDS(dns.SHA256) if ds == nil { err = errors.New(fmt.Sprintf("Unable to generate corresponding DS record, please check given data.")) return } m1.Insert([]dns.RR{ds}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) return } func DeleteRRDelegatedDomain(student *adlin.Student, dn string, rr string, values ...string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { if strings.HasSuffix(dn, d) { found = true } } if !found { err = errors.New(fmt.Sprintf("Unable to find domain %q in your whitelist.", dn)) return } m1 := new(dns.Msg) m1.Id = dns.Id() m1.Opcode = dns.OpcodeUpdate m1.Question = make([]dns.Question, 1) m1.Question[0] = dns.Question{Name: student.MyDelegatedDomainSuffix(), Qtype: dns.TypeSOA, Qclass: dns.ClassINET} rrr, errr := dns.NewRR(fmt.Sprintf("%s %s %s", dn, rr, strings.Join(values, " "))) if errr != nil { return errr } m1.Remove([]dns.RR{rrr}) c := new(dns.Client) c.TsigSecret = map[string]string{tsigName: tsigSecret} m1.SetTsig(tsigName, dns.HmacSHA256, 300, time.Now().Unix()) _, _, err = c.Exchange(m1, ControlSocket) return }