package main import ( "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "log" "os" "os/exec" "strconv" "strings" "github.com/julienschmidt/httprouter" ) const ( AssociatedDomainSuffix = "adlin2020.p0m.fr" DelegatedDomainSuffix = "srs.p0m.fr" ) func init() { router.GET("/api/adomains/", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.GetAssociatedDomains(), nil })) router.POST("/api/adomains/", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return true, student.AddAssociatedDomains() })) router.GET("/api/adomains/:dn", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.GetAssociatedDomain(ps.ByName("dn")) })) router.GET("/api/ddomains/", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return []string{student.MyDelegatedDomain()}, nil })) router.GET("/api/ddomains/:dn/", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.getRRDelegatedDomain(ps.ByName("dn"), "") })) router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.getRRDelegatedDomain(ps.ByName("dn"), "NS") })) router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.AddNSDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.Values[0]) })) router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.UpdateNSDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom[0], ue.Values[0]) })) router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "NS", ue.Values...) })) router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.getRRDelegatedDomain(ps.ByName("dn"), "AAAA") })) router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.AddGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.Values[0]) })) router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.UpdateGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom[0], ue.Values[0]) })) router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.UpdateGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom[0], ue.Values[0]) })) router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "AAAA", ue.Values...) })) router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.getRRDelegatedDomain(ps.ByName("dn"), "DS") })) router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.AddDSDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.Values) })) router.PATCH("/api/ddomains/:dn/DS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.UpdateDSDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, ue.Values) })) router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "DS", ue.Values...) })) } type Entry struct { Domain string `json:"domain"` TTL uint64 `json:"ttl"` RR string `json:"rr"` ValuesFrom []string `json:"valuesfrom,omitempty"` Values []string `json:"values"` } func runKnotc(args ...string) (out []byte, err error) { cmd := exec.Command("knotc", args...) cmd.Env = append(os.Environ(), "LD_PRELOAD=/usr/lib/gcc/armv5tel-softfloat-linux-gnueabi/8.2.0/libgcc_s.so.1", ) return cmd.CombinedOutput() } func parseKnotZoneRead(args ...string) (rr []Entry, err error) { var out []byte args = append([]string{"zone-read"}, args...) out, err = runKnotc(args...) for _, line := range strings.Split(string(out), "\n") { cols := strings.Fields(line) if len(cols) >= 5 { var ttl uint64 ttl, err = strconv.ParseUint(cols[2], 10, 64) if err != nil { return } rr = append(rr, Entry{cols[1], ttl, cols[3], nil, cols[4:]}) } } return } func (student Student) myAssociatedDomain() (string) { return fmt.Sprintf("%s.%s.", strings.Replace(student.Login, "_", "-", -1), AssociatedDomainSuffix) } func (student Student) GetAssociatedDomains() (ds []string) { studentDomain := student.myAssociatedDomain() if _, err := parseKnotZoneRead(AssociatedDomainSuffix, studentDomain); err == nil { ds = append(ds, studentDomain) } return } func (student Student) GetAssociatedDomain(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 := parseKnotZoneRead(AssociatedDomainSuffix, 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 (student Student) AddAssociatedDomains() (err error) { for _, d := range []string{student.myAssociatedDomain()} { for _, cmd := range [][]string{ []string{"zone-begin", AssociatedDomainSuffix}, []string{"zone-set", AssociatedDomainSuffix, d, "900", "A", "82.64.31.248"}, []string{"zone-set", AssociatedDomainSuffix, d, "900", "AAAA", studentIP(student.Id) + "1"}, []string{"zone-commit", AssociatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", AssociatedDomainSuffix) return } } } return } func (student Student) MyDelegatedDomain() (string) { return fmt.Sprintf("%s.%s.", strings.Replace(student.Login, "_", "-", -1), DelegatedDomainSuffix) } func (student Student) getRRDelegatedDomain(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 := parseKnotZoneRead(DelegatedDomainSuffix); 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 (student Student) AddNSDelegatedDomain(dn string, ttl uint64, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, []string{"zone-set", DelegatedDomainSuffix, d, fmt.Sprintf("%d", ttl), "NS", ns}, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil && cmd[0] != "zone-unset" { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } } return } func (student Student) UpdateNSDelegatedDomain(dn string, ttl uint64, oldns string, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, []string{"zone-unset", DelegatedDomainSuffix, d, "NS", oldns}, []string{"zone-set", DelegatedDomainSuffix, d, fmt.Sprintf("%d", ttl), "NS", ns}, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil && cmd[0] != "zone-unset" { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } } return } func (student Student) AddGLUEDelegatedDomain(dn string, ttl uint64, 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 } for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, []string{"zone-set", DelegatedDomainSuffix, dn, fmt.Sprintf("%d", ttl), "AAAA", aaaa}, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil && cmd[0] != "zone-unset" { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } return } func (student Student) UpdateGLUEDelegatedDomain(dn string, ttl uint64, 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 } for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, []string{"zone-unset", DelegatedDomainSuffix, student.MyDelegatedDomain(), "AAAA", oldaaaa}, []string{"zone-unset", DelegatedDomainSuffix, dn, "AAAA", oldaaaa}, []string{"zone-set", DelegatedDomainSuffix, dn, fmt.Sprintf("%d", ttl), "AAAA", aaaa}, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil && cmd[0] != "zone-unset" { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } return } func (student Student) AddDSDelegatedDomain(dn string, ttl uint64, dnskey []string) (err error) { if len(dnskey) != 4 { return errors.New("Wrong number of value for this record") } dshash := sha256.New() dshash.Write([]byte("nemunai.re")) var flag uint64 if flag, err = strconv.ParseUint(dnskey[1], 10, 16); err != nil { return } binary.Write(dshash, binary.BigEndian, flag) var proto uint8 = 3 dshash.Write([]byte{proto}) var alg uint64 if alg, err = strconv.ParseUint(dnskey[2], 10, 8); err != nil { return } dshash.Write([]byte{uint8(alg)}) var pubkey []byte if pubkey, err = base64.StdEncoding.DecodeString(strings.Replace(dnskey[3], " ", "", -1)); err != nil { return } dshash.Write(pubkey) for _, d := range []string{student.MyDelegatedDomain()} { for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, []string{"zone-set", DelegatedDomainSuffix, d, fmt.Sprintf("%d", ttl), "DS", dnskey[0], dnskey[2], hex.EncodeToString(dshash.Sum(nil))}, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil && cmd[0] != "zone-unset" { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } } return } func (student Student) UpdateDSDelegatedDomain(dn string, ttl uint64, oldds []string, ds []string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, []string{"zone-unset", DelegatedDomainSuffix, d, "DS", strings.Join(oldds, " ")}, []string{"zone-set", DelegatedDomainSuffix, d, fmt.Sprintf("%d", ttl), "DS", strings.Join(ds, " ")}, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil && cmd[0] != "zone-unset" { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } } return } func (student Student) DeleteRRDelegatedDomain(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 } zu := []string{"zone-unset", DelegatedDomainSuffix, dn, rr} zu = append(zu, values...) for _, cmd := range [][]string{ []string{"zone-begin", DelegatedDomainSuffix}, zu, []string{"zone-commit", DelegatedDomainSuffix}, } { var out []byte out, err = runKnotc(cmd...) if err != nil { log.Printf("An error occurs on command '%s': %s", strings.Join(cmd, " "), err.Error()) err = errors.New(string(out)) runKnotc("zone-abort", DelegatedDomainSuffix) return } } return }