diff --git a/modules/dns_proxy/dns_proxy.go b/modules/dns_proxy/dns_proxy.go index e737504b..f27cde2c 100644 --- a/modules/dns_proxy/dns_proxy.go +++ b/modules/dns_proxy/dns_proxy.go @@ -2,6 +2,10 @@ package dns_proxy import ( "github.com/bettercap/bettercap/v2/session" + "github.com/bettercap/bettercap/v2/tls" + + "github.com/evilsocket/islazy/fs" + "github.com/evilsocket/islazy/str" ) type DnsProxy struct { @@ -22,6 +26,10 @@ func (mod *DnsProxy) Configure() error { var netProtocol string var proxyPort int var scriptPath string + var certFile string + var keyFile string + var whitelist string + var blacklist string if mod.Running() { return session.ErrAlreadyStarted(mod.Name()) @@ -29,21 +37,56 @@ func (mod *DnsProxy) Configure() error { return err } else if err, address = mod.StringParam("dns.proxy.address"); err != nil { return err + } else if err, certFile = mod.StringParam("dns.proxy.certificate"); err != nil { + return err + } else if certFile, err = fs.Expand(certFile); err != nil { + return err + } else if err, keyFile = mod.StringParam("dns.proxy.key"); err != nil { + return err + } else if keyFile, err = fs.Expand(keyFile); err != nil { + return err } else if err, nameserver = mod.StringParam("dns.proxy.nameserver"); err != nil { return err - } else if err, netProtocol = mod.StringParam("dns.proxy.networkprotocol"); err != nil { - return err } else if err, proxyPort = mod.IntParam("dns.proxy.port"); err != nil { return err + } else if err, netProtocol = mod.StringParam("dns.proxy.protocol"); err != nil { + return err } else if err, doRedirect = mod.BoolParam("dns.proxy.redirect"); err != nil { return err } else if err, scriptPath = mod.StringParam("dns.proxy.script"); err != nil { return err + } else if err, blacklist = mod.StringParam("dns.proxy.blacklist"); err != nil { + return err + } else if err, whitelist = mod.StringParam("dns.proxy.whitelist"); err != nil { + return err } - error := mod.proxy.Configure(address, dnsPort, doRedirect, nameserver, netProtocol, proxyPort, scriptPath) + mod.proxy.Blacklist = str.Comma(blacklist) + mod.proxy.Whitelist = str.Comma(whitelist) - return error + if netProtocol == "tcp-tls" { + if !fs.Exists(certFile) || !fs.Exists(keyFile) { + cfg, err := tls.CertConfigFromModule("dns.proxy", mod.SessionModule) + if err != nil { + return err + } + + mod.Debug("%+v", cfg) + mod.Info("generating proxy certification authority TLS key to %s", keyFile) + mod.Info("generating proxy certification authority TLS certificate to %s", certFile) + if err := tls.Generate(cfg, certFile, keyFile, true); err != nil { + return err + } + } else { + mod.Info("loading proxy certification authority TLS key from %s", keyFile) + mod.Info("loading proxy certification authority TLS certificate from %s", certFile) + } + } + + err = mod.proxy.Configure(address, dnsPort, doRedirect, nameserver, netProtocol, + proxyPort, scriptPath, certFile, keyFile) + + return err } func (mod *DnsProxy) Description() string { @@ -69,24 +112,42 @@ func NewDnsProxy(s *session.Session) *DnsProxy { session.IPv4Validator, "Address to bind the DNS proxy to.")) + mod.AddParam(session.NewStringParameter("dns.proxy.blacklist", "", "", + "Comma separated list of hostnames to skip while proxying (wildcard expressions can be used).")) + + mod.AddParam(session.NewStringParameter("dns.proxy.whitelist", "", "", + "Comma separated list of hostnames to proxy if the blacklist is used (wildcard expressions can be used).")) + mod.AddParam(session.NewStringParameter("dns.proxy.nameserver", "1.1.1.1", session.IPv4Validator, "DNS resolver address.")) - mod.AddParam(session.NewStringParameter("dns.proxy.networkprotocol", - "udp", - "^(udp|tcp|tcp-tls)$", - "Network protocol for the DNS proxy server to use. Accepted values: udp, tcp, tcp-tls")) - mod.AddParam(session.NewIntParameter("dns.proxy.port", "8053", "Port to bind the DNS proxy to.")) + mod.AddParam(session.NewStringParameter("dns.proxy.protocol", + "udp", + "^(udp|tcp|tcp-tls)$", + "Network protocol for the DNS proxy server to use. Accepted values: udp, tcp, tcp-tls")) + mod.AddParam(session.NewBoolParameter("dns.proxy.redirect", "true", "Enable or disable port redirection with iptables.")) + mod.AddParam(session.NewStringParameter("dns.proxy.certificate", + "~/.bettercap-ca.cert.pem", + "", + "DNS proxy certification authority TLS certificate file.")) + + mod.AddParam(session.NewStringParameter("dns.proxy.key", + "~/.bettercap-ca.key.pem", + "", + "DNS proxy certification authority TLS key file.")) + + tls.CertConfigToModule("dns.proxy", &mod.SessionModule, tls.DefaultCloudflareDNSConfig) + mod.AddParam(session.NewStringParameter("dns.proxy.script", "", "", diff --git a/modules/dns_proxy/dns_proxy_base.go b/modules/dns_proxy/dns_proxy_base.go index 1c619e95..b9652237 100644 --- a/modules/dns_proxy/dns_proxy_base.go +++ b/modules/dns_proxy/dns_proxy_base.go @@ -2,7 +2,10 @@ package dns_proxy import ( "context" + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "strings" "time" @@ -20,13 +23,17 @@ const ( ) type DNSProxy struct { - Address string Name string + Address string + Server *dns.Server + Redirection *firewall.Redirection Nameserver string NetProtocol string - Redirection *firewall.Redirection Script *DnsProxyScript - Server *dns.Server + CertFile string + KeyFile string + Blacklist []string + Whitelist []string Sess *session.Session doRedirect bool @@ -34,11 +41,13 @@ type DNSProxy struct { tag string } -func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, nameserver string, netProtocol string, proxyPort int, scriptPath string) error { +func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, nameserver string, netProtocol string, proxyPort int, scriptPath string, certFile string, keyFile string) error { var err error p.Address = address p.doRedirect = doRedirect + p.CertFile = certFile + p.KeyFile = keyFile if scriptPath != "" { if err, p.Script = LoadDnsProxyScript(scriptPath, p.Sess); err != nil { @@ -77,9 +86,10 @@ func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, names } res = p.onResponseFilter(req, res, clientIP) if res == nil { - p.Error("response filter returned nil") + p.Debug("response is nil") m.SetRcode(req, dns.RcodeServerFailure) w.WriteMsg(m) + return } else { if err := w.WriteMsg(res); err != nil { p.Error("Error writing response: %s", err) @@ -98,14 +108,35 @@ func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, names Handler: handler, } + if netProtocol == "tcp-tls" && p.CertFile != "" && p.KeyFile != "" { + rawCert, _ := ioutil.ReadFile(p.CertFile) + rawKey, _ := ioutil.ReadFile(p.KeyFile) + ourCa, err := tls.X509KeyPair(rawCert, rawKey) + if err != nil { + return err + } + + if ourCa.Leaf, err = x509.ParseCertificate(ourCa.Certificate[0]); err != nil { + return err + } + + p.Server.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{ourCa}, + } + } + if p.doRedirect { if !p.Sess.Firewall.IsForwardingEnabled() { p.Info("enabling forwarding.") p.Sess.Firewall.EnableForwarding(true) } + redirectProtocol := netProtocol + if redirectProtocol == "tcp-tls" { + redirectProtocol = "tcp" + } p.Redirection = firewall.NewRedirection(p.Sess.Interface.Name(), - netProtocol, + redirectProtocol, dnsPort, p.Address, proxyPort) diff --git a/modules/dns_proxy/dns_proxy_base_filters.go b/modules/dns_proxy/dns_proxy_base_filters.go index 5255b608..ad12db76 100644 --- a/modules/dns_proxy/dns_proxy_base_filters.go +++ b/modules/dns_proxy/dns_proxy_base_filters.go @@ -6,43 +6,60 @@ import ( "github.com/miekg/dns" ) -func shortenResourceRecords(records []string) []string { - shorterRecords := []string{} - for _, record := range records { - shorterRecord := strings.ReplaceAll(record, "\t", " ") - shorterRecords = append(shorterRecords, shorterRecord) +func questionsToStrings(qs []dns.Question) []string { + questions := []string{} + for _, q := range qs { + questions = append(questions, tabsToSpaces(q.String())) } - return shorterRecords + return questions } -func (p *DNSProxy) logRequestAction(j *JSQuery) { +func recordsToStrings(rrs []dns.RR) []string { + records := []string{} + for _, rr := range rrs { + records = append(records, tabsToSpaces(rr.String())) + } + return records +} + +func tabsToSpaces(s string) string { + return strings.ReplaceAll(s, "\t", " ") +} + +func (p *DNSProxy) logRequestAction(m *dns.Msg, clientIP string) { + var questions []string + for _, q := range m.Question { + questions = append(questions, tabsToSpaces(q.String())) + } p.Sess.Events.Add(p.Name+".spoofed-request", struct { Client string Questions []string }{ - j.Client["IP"], - shortenResourceRecords(j.Questions), + clientIP, + questions, }) } -func (p *DNSProxy) logResponseAction(j *JSQuery) { +func (p *DNSProxy) logResponseAction(m *dns.Msg, clientIP string) { p.Sess.Events.Add(p.Name+".spoofed-response", struct { client string - Extras []string Answers []string + Extras []string Nameservers []string Questions []string }{ - j.Client["IP"], - shortenResourceRecords(j.Extras), - shortenResourceRecords(j.Answers), - shortenResourceRecords(j.Nameservers), - shortenResourceRecords(j.Questions), + clientIP, + recordsToStrings(m.Answer), + recordsToStrings(m.Extra), + recordsToStrings(m.Ns), + questionsToStrings(m.Question), }) } func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *dns.Msg) { - p.Debug("< %s %s", clientIP, query.Question) + p.Debug("< %s %s", + clientIP, + strings.Join(questionsToStrings(query.Question), ",")) // do we have a proxy script? if p.Script == nil { @@ -53,12 +70,14 @@ func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *d jsreq, jsres := p.Script.OnRequest(query, clientIP) if jsreq != nil { // the request has been changed by the script - p.logRequestAction(jsreq) - return jsreq.ToQuery(), nil + req := jsreq.ToQuery() + p.logRequestAction(req, clientIP) + return req, nil } else if jsres != nil { // a fake response has been returned by the script - p.logResponseAction(jsres) - return query, jsres.ToQuery() + res := jsres.ToQuery() + p.logResponseAction(res, clientIP) + return query, res } return query, nil @@ -70,15 +89,21 @@ func (p *DNSProxy) onResponseFilter(req, res *dns.Msg, clientIP string) *dns.Msg return nil } - p.Debug("> %s %s", clientIP, res.Answer) + p.Debug("> %s %s [%s] [%s] [%s]", + clientIP, + strings.Join(questionsToStrings(res.Question), ","), + strings.Join(recordsToStrings(res.Answer), ","), + strings.Join(recordsToStrings(res.Extra), ","), + strings.Join(recordsToStrings(res.Ns), ",")) // do we have a proxy script? if p.Script != nil { _, jsres := p.Script.OnResponse(req, res, clientIP) if jsres != nil { // the response has been changed by the script - p.logResponseAction(jsres) - return jsres.ToQuery() + res := jsres.ToQuery() + p.logResponseAction(res, clientIP) + return res } } diff --git a/modules/dns_proxy/dns_proxy_js_query.go b/modules/dns_proxy/dns_proxy_js_query.go index a13a6ef6..7db5118a 100644 --- a/modules/dns_proxy/dns_proxy_js_query.go +++ b/modules/dns_proxy/dns_proxy_js_query.go @@ -1,9 +1,8 @@ package dns_proxy import ( + "encoding/json" "fmt" - "regexp" - "strings" "github.com/bettercap/bettercap/v2/log" "github.com/bettercap/bettercap/v2/session" @@ -11,17 +10,14 @@ import ( "github.com/miekg/dns" ) -var whiteSpaceRegexp = regexp.MustCompile(`\s+`) -var stripWhiteSpaceRegexp = regexp.MustCompile(`^\s*(.*?)\s*$`) - type JSQuery struct { - Answers []string + Answers []map[string]interface{} Client map[string]string - Compress bool `json:"-"` - Extras []string - Header *JSQueryHeader - Nameservers []string - Questions []string + Compress bool + Extras []map[string]interface{} + Header JSQueryHeader + Nameservers []map[string]interface{} + Questions []map[string]interface{} refHash string } @@ -41,6 +37,11 @@ type JSQueryHeader struct { } func (j *JSQuery) NewHash() string { + answers, _ := json.Marshal(j.Answers) + extras, _ := json.Marshal(j.Extras) + nameservers, _ := json.Marshal(j.Nameservers) + questions, _ := json.Marshal(j.Questions) + headerHash := fmt.Sprintf("%t.%t.%t.%d.%d.%d.%t.%t.%t.%t.%t", j.Header.AuthenticatedData, j.Header.Authoritative, @@ -53,50 +54,58 @@ func (j *JSQuery) NewHash() string { j.Header.Response, j.Header.Truncated, j.Header.Zero) + hash := fmt.Sprintf("%s.%s.%t.%s.%s.%s.%s", - strings.Join(j.Answers, ""), + answers, j.Client["IP"], j.Compress, - strings.Join(j.Extras, ""), + extras, headerHash, - strings.Join(j.Nameservers, ""), - strings.Join(j.Questions, "")) + nameservers, + questions) + return hash } -func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery { - answers := []string{} - extras := []string{} - nameservers := []string{} - questions := []string{} +func NewJSQuery(query *dns.Msg, clientIP string) (jsQuery *JSQuery) { + answers := make([]map[string]interface{}, len(query.Answer)) + extras := make([]map[string]interface{}, len(query.Extra)) + nameservers := make([]map[string]interface{}, len(query.Ns)) + questions := make([]map[string]interface{}, len(query.Question)) - header := &JSQueryHeader{ - AuthenticatedData: query.MsgHdr.AuthenticatedData, - Authoritative: query.MsgHdr.Authoritative, - CheckingDisabled: query.MsgHdr.CheckingDisabled, - Id: query.MsgHdr.Id, - Opcode: query.MsgHdr.Opcode, - Rcode: query.MsgHdr.Rcode, - RecursionAvailable: query.MsgHdr.RecursionAvailable, - RecursionDesired: query.MsgHdr.RecursionDesired, - Response: query.MsgHdr.Response, - Truncated: query.MsgHdr.Truncated, - Zero: query.MsgHdr.Zero, + for i, rr := range query.Answer { + jsRecord, err := NewJSResourceRecord(rr) + if err != nil { + log.Error(err.Error()) + continue + } + answers[i] = jsRecord } - for _, rr := range query.Answer { - answers = append(answers, rr.String()) + for i, rr := range query.Extra { + jsRecord, err := NewJSResourceRecord(rr) + if err != nil { + log.Error(err.Error()) + continue + } + extras[i] = jsRecord } - for _, rr := range query.Extra { - extras = append(extras, rr.String()) + + for i, rr := range query.Ns { + jsRecord, err := NewJSResourceRecord(rr) + if err != nil { + log.Error(err.Error()) + continue + } + nameservers[i] = jsRecord } - for _, rr := range query.Ns { - nameservers = append(nameservers, rr.String()) - } - for _, q := range query.Question { - qType := dns.Type(q.Qtype).String() - qClass := dns.Class(q.Qclass).String() - questions = append(questions, fmt.Sprintf("%s\t%s\t%s", q.Name, qClass, qType)) + + for i, question := range query.Question { + questions[i] = map[string]interface{}{ + "Name": question.Name, + "Qtype": question.Qtype, + "Qclass": question.Qclass, + } } clientMAC := "" @@ -108,11 +117,23 @@ func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery { client := map[string]string{"IP": clientIP, "MAC": clientMAC, "Alias": clientAlias} jsquery := &JSQuery{ - Answers: answers, - Client: client, - Compress: query.Compress, - Extras: extras, - Header: header, + Answers: answers, + Client: client, + Compress: query.Compress, + Extras: extras, + Header: JSQueryHeader{ + AuthenticatedData: query.MsgHdr.AuthenticatedData, + Authoritative: query.MsgHdr.Authoritative, + CheckingDisabled: query.MsgHdr.CheckingDisabled, + Id: query.MsgHdr.Id, + Opcode: query.MsgHdr.Opcode, + Rcode: query.MsgHdr.Rcode, + RecursionAvailable: query.MsgHdr.RecursionAvailable, + RecursionDesired: query.MsgHdr.RecursionDesired, + Response: query.MsgHdr.Response, + Truncated: query.MsgHdr.Truncated, + Zero: query.MsgHdr.Zero, + }, Nameservers: nameservers, Questions: questions, } @@ -121,80 +142,43 @@ func NewJSQuery(query *dns.Msg, clientIP string) *JSQuery { return jsquery } -func stringToClass(s string) (uint16, error) { - for i, dnsClass := range dns.ClassToString { - if s == dnsClass { - return i, nil - } - } - return 0, fmt.Errorf("unkown DNS class (got %s)", s) -} - -func stringToType(s string) (uint16, error) { - for i, dnsType := range dns.TypeToString { - if s == dnsType { - return i, nil - } - } - return 0, fmt.Errorf("unkown DNS type (got %s)", s) -} - func (j *JSQuery) ToQuery() *dns.Msg { var answers []dns.RR var extras []dns.RR var nameservers []dns.RR var questions []dns.Question - for _, s := range j.Answers { - rr, err := dns.NewRR(s) + for _, jsRR := range j.Answers { + rr, err := ToRR(jsRR) if err != nil { - log.Error("error parsing DNS answer resource record: %s", err.Error()) - return nil - } else { - answers = append(answers, rr) + log.Error(err.Error()) + continue } + answers = append(answers, rr) } - for _, s := range j.Extras { - rr, err := dns.NewRR(s) + for _, jsRR := range j.Extras { + rr, err := ToRR(jsRR) if err != nil { - log.Error("error parsing DNS extra resource record: %s", err.Error()) - return nil - } else { - extras = append(extras, rr) + log.Error(err.Error()) + continue } + extras = append(extras, rr) } - for _, s := range j.Nameservers { - rr, err := dns.NewRR(s) + for _, jsRR := range j.Nameservers { + rr, err := ToRR(jsRR) if err != nil { - log.Error("error parsing DNS nameserver resource record: %s", err.Error()) - return nil - } else { - nameservers = append(nameservers, rr) + log.Error(err.Error()) + continue } + nameservers = append(nameservers, rr) } - for _, s := range j.Questions { - qStripped := stripWhiteSpaceRegexp.FindStringSubmatch(s) - qParts := whiteSpaceRegexp.Split(qStripped[1], -1) - - if len(qParts) != 3 { - log.Error("invalid DNS question format: (got %s)", s) - return nil - } - - qName := dns.Fqdn(qParts[0]) - qClass, err := stringToClass(qParts[1]) - if err != nil { - log.Error("error parsing DNS question class: %s", err.Error()) - return nil - } - qType, err := stringToType(qParts[2]) - if err != nil { - log.Error("error parsing DNS question type: %s", err.Error()) - return nil - } - - questions = append(questions, dns.Question{qName, qType, qClass}) + for _, jsQ := range j.Questions { + questions = append(questions, dns.Question{ + Name: jsPropToString(jsQ, "Name"), + Qtype: jsPropToUint16(jsQ, "Qtype"), + Qclass: jsPropToUint16(jsQ, "Qclass"), + }) } query := &dns.Msg{ diff --git a/modules/dns_proxy/dns_proxy_js_record.go b/modules/dns_proxy/dns_proxy_js_record.go new file mode 100644 index 00000000..647267e2 --- /dev/null +++ b/modules/dns_proxy/dns_proxy_js_record.go @@ -0,0 +1,1014 @@ +package dns_proxy + +import ( + "fmt" + "net" + + "github.com/bettercap/bettercap/v2/log" + "github.com/miekg/dns" +) + +func jsPropToMap(obj map[string]interface{}, key string) map[string]interface{} { + if v, ok := obj[key].(map[string]interface{}); ok { + return v + } + return map[string]interface{}{} +} + +func jsPropToMapArray(obj map[string]interface{}, key string) []map[string]interface{} { + if v, ok := obj[key].([]map[string]interface{}); ok { + return v + } + return []map[string]interface{}{} +} + +func jsPropToString(obj map[string]interface{}, key string) string { + if v, ok := obj[key].(string); ok { + return v + } + return "" +} + +func jsPropToStringArray(obj map[string]interface{}, key string) []string { + if v, ok := obj[key].([]string); ok { + return v + } + return []string{} +} + +func jsPropToUint8(obj map[string]interface{}, key string) uint8 { + if v, ok := obj[key].(uint8); ok { + return v + } + return 0 +} + +func jsPropToUint8Array(obj map[string]interface{}, key string) []uint8 { + if v, ok := obj[key].([]uint8); ok { + return v + } + return []uint8{} +} + +func jsPropToUint16(obj map[string]interface{}, key string) uint16 { + if v, ok := obj[key].(uint16); ok { + return v + } + return 0 +} + +func jsPropToUint16Array(obj map[string]interface{}, key string) []uint16 { + if v, ok := obj[key].([]uint16); ok { + return v + } + return []uint16{} +} + +func jsPropToUint32(obj map[string]interface{}, key string) uint32 { + if v, ok := obj[key].(uint32); ok { + return v + } + return 0 +} + +func jsPropToUint64(obj map[string]interface{}, key string) uint64 { + if v, ok := obj[key].(uint64); ok { + return v + } + return 0 +} + +func NewJSResourceRecord(rr dns.RR) (jsRecord map[string]interface{}, err error) { + header := rr.Header() + + jsRecord = map[string]interface{}{ + "Header": map[string]interface{}{ + "Class": header.Class, + "Name": header.Name, + "Rrtype": header.Rrtype, + "Ttl": header.Ttl, + }, + } + + switch rr := rr.(type) { + case *dns.A: + jsRecord["A"] = rr.A.String() + case *dns.AAAA: + jsRecord["AAAA"] = rr.AAAA.String() + case *dns.APL: + jsPrefixes := make([]map[string]interface{}, len(rr.Prefixes)) + for i, v := range rr.Prefixes { + jsPrefixes[i] = map[string]interface{}{ + "Negation": v.Negation, + "Network": v.Network.String(), + } + } + jsRecord["Prefixes"] = jsPrefixes + case *dns.CNAME: + jsRecord["Target"] = rr.Target + case *dns.MB: + jsRecord["Mb"] = rr.Mb + case *dns.MD: + jsRecord["Md"] = rr.Md + case *dns.MF: + jsRecord["Mf"] = rr.Mf + case *dns.MG: + jsRecord["Mg"] = rr.Mg + case *dns.MR: + jsRecord["Mr"] = rr.Mr + case *dns.MX: + jsRecord["Mx"] = rr.Mx + jsRecord["Preference"] = rr.Preference + case *dns.NULL: + jsRecord["Data"] = rr.Data + case *dns.SOA: + jsRecord["Expire"] = rr.Expire + jsRecord["Minttl"] = rr.Minttl + jsRecord["Ns"] = rr.Ns + jsRecord["Refresh"] = rr.Refresh + jsRecord["Retry"] = rr.Retry + jsRecord["Mbox"] = rr.Mbox + jsRecord["Serial"] = rr.Serial + case *dns.TXT: + jsRecord["Txt"] = rr.Txt + case *dns.SRV: + jsRecord["Port"] = rr.Port + jsRecord["Priority"] = rr.Priority + jsRecord["Target"] = rr.Target + jsRecord["Weight"] = rr.Weight + case *dns.PTR: + jsRecord["Ptr"] = rr.Ptr + case *dns.NS: + jsRecord["Ns"] = rr.Ns + case *dns.DNAME: + jsRecord["Target"] = rr.Target + case *dns.AFSDB: + jsRecord["Subtype"] = rr.Subtype + jsRecord["Hostname"] = rr.Hostname + case *dns.CAA: + jsRecord["Flag"] = rr.Flag + jsRecord["Tag"] = rr.Tag + jsRecord["Value"] = rr.Value + case *dns.HINFO: + jsRecord["Cpu"] = rr.Cpu + jsRecord["Os"] = rr.Os + case *dns.MINFO: + jsRecord["Email"] = rr.Email + jsRecord["Rmail"] = rr.Rmail + case *dns.ISDN: + jsRecord["Address"] = rr.Address + jsRecord["SubAddress"] = rr.SubAddress + case *dns.KX: + jsRecord["Exchanger"] = rr.Exchanger + jsRecord["Preference"] = rr.Preference + case *dns.LOC: + jsRecord["Altitude"] = rr.Altitude + jsRecord["HorizPre"] = rr.HorizPre + jsRecord["Latitude"] = rr.Latitude + jsRecord["Longitude"] = rr.Longitude + jsRecord["Size"] = rr.Size + jsRecord["Version"] = rr.Version + jsRecord["VertPre"] = rr.VertPre + case *dns.SSHFP: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["FingerPrint"] = rr.FingerPrint + jsRecord["Type"] = rr.Type + case *dns.TLSA: + jsRecord["Certificate"] = rr.Certificate + jsRecord["MatchingType"] = rr.MatchingType + jsRecord["Selector"] = rr.Selector + jsRecord["Usage"] = rr.Usage + case *dns.CERT: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Certificate"] = rr.Certificate + jsRecord["KeyTag"] = rr.KeyTag + jsRecord["Type"] = rr.Type + case *dns.DS: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Digest"] = rr.Digest + jsRecord["DigestType"] = rr.DigestType + jsRecord["KeyTag"] = rr.KeyTag + case *dns.NAPTR: + jsRecord["Order"] = rr.Order + jsRecord["Preference"] = rr.Preference + jsRecord["Flags"] = rr.Flags + jsRecord["Service"] = rr.Service + jsRecord["Regexp"] = rr.Regexp + jsRecord["Replacement"] = rr.Replacement + case *dns.RRSIG: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Expiration"] = rr.Expiration + jsRecord["Inception"] = rr.Inception + jsRecord["KeyTag"] = rr.KeyTag + jsRecord["Labels"] = rr.Labels + jsRecord["OrigTtl"] = rr.OrigTtl + jsRecord["Signature"] = rr.Signature + jsRecord["SignerName"] = rr.SignerName + jsRecord["TypeCovered"] = rr.TypeCovered + case *dns.NSEC: + jsRecord["NextDomain"] = rr.NextDomain + jsRecord["TypeBitMap"] = rr.TypeBitMap + case *dns.NSEC3: + jsRecord["Flags"] = rr.Flags + jsRecord["Hash"] = rr.Hash + jsRecord["HashLength"] = rr.HashLength + jsRecord["Iterations"] = rr.Iterations + jsRecord["NextDomain"] = rr.NextDomain + jsRecord["Salt"] = rr.Salt + jsRecord["SaltLength"] = rr.SaltLength + jsRecord["TypeBitMap"] = rr.TypeBitMap + case *dns.NSEC3PARAM: + jsRecord["Flags"] = rr.Flags + jsRecord["Hash"] = rr.Hash + jsRecord["Iterations"] = rr.Iterations + jsRecord["Salt"] = rr.Salt + jsRecord["SaltLength"] = rr.SaltLength + case *dns.TKEY: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Error"] = rr.Error + jsRecord["Expiration"] = rr.Expiration + jsRecord["Inception"] = rr.Inception + jsRecord["Key"] = rr.Key + jsRecord["KeySize"] = rr.KeySize + jsRecord["Mode"] = rr.Mode + jsRecord["OtherData"] = rr.OtherData + jsRecord["OtherLen"] = rr.OtherLen + case *dns.TSIG: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Error"] = rr.Error + jsRecord["Fudge"] = rr.Fudge + jsRecord["MACSize"] = rr.MACSize + jsRecord["MAC"] = rr.MAC + jsRecord["OrigId"] = rr.OrigId + jsRecord["OtherData"] = rr.OtherData + jsRecord["OtherLen"] = rr.OtherLen + jsRecord["TimeSigned"] = rr.TimeSigned + case *dns.IPSECKEY: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["GatewayAddr"] = rr.GatewayAddr.String() + jsRecord["GatewayHost"] = rr.GatewayHost + jsRecord["GatewayType"] = rr.GatewayType + jsRecord["Precedence"] = rr.Precedence + jsRecord["PublicKey"] = rr.PublicKey + case *dns.KEY: + jsRecord["Flags"] = rr.Flags + jsRecord["Protocol"] = rr.Protocol + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["PublicKey"] = rr.PublicKey + case *dns.CDS: + jsRecord["KeyTag"] = rr.KeyTag + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["DigestType"] = rr.DigestType + jsRecord["Digest"] = rr.Digest + case *dns.CDNSKEY: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Flags"] = rr.Flags + jsRecord["Protocol"] = rr.Protocol + jsRecord["PublicKey"] = rr.PublicKey + case *dns.NID: + jsRecord["NodeID"] = rr.NodeID + jsRecord["Preference"] = rr.Preference + case *dns.L32: + jsRecord["Locator32"] = rr.Locator32.String() + jsRecord["Preference"] = rr.Preference + case *dns.L64: + jsRecord["Locator64"] = rr.Locator64 + jsRecord["Preference"] = rr.Preference + case *dns.LP: + jsRecord["Fqdn"] = rr.Fqdn + jsRecord["Preference"] = rr.Preference + case *dns.GPOS: + jsRecord["Altitude"] = rr.Altitude + jsRecord["Latitude"] = rr.Latitude + jsRecord["Longitude"] = rr.Longitude + case *dns.RP: + jsRecord["Mbox"] = rr.Mbox + jsRecord["Txt"] = rr.Txt + case *dns.RKEY: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Flags"] = rr.Flags + jsRecord["Protocol"] = rr.Protocol + jsRecord["PublicKey"] = rr.PublicKey + case *dns.SMIMEA: + jsRecord["Certificate"] = rr.Certificate + jsRecord["MatchingType"] = rr.MatchingType + jsRecord["Selector"] = rr.Selector + jsRecord["Usage"] = rr.Usage + case *dns.AMTRELAY: + jsRecord["GatewayAddr"] = rr.GatewayAddr.String() + jsRecord["GatewayHost"] = rr.GatewayHost + jsRecord["GatewayType"] = rr.GatewayType + jsRecord["Precedence"] = rr.Precedence + case *dns.AVC: + jsRecord["Txt"] = rr.Txt + case *dns.URI: + jsRecord["Priority"] = rr.Priority + jsRecord["Weight"] = rr.Weight + jsRecord["Target"] = rr.Target + case *dns.EUI48: + jsRecord["Address"] = rr.Address + case *dns.EUI64: + jsRecord["Address"] = rr.Address + case *dns.GID: + jsRecord["Gid"] = rr.Gid + case *dns.UID: + jsRecord["Uid"] = rr.Uid + case *dns.UINFO: + jsRecord["Uinfo"] = rr.Uinfo + case *dns.SPF: + jsRecord["Txt"] = rr.Txt + case *dns.HTTPS: + jsRecord["Priority"] = rr.Priority + jsRecord["Target"] = rr.Target + kvs := rr.Value + var jsKvs []map[string]interface{} + for _, kv := range kvs { + jsKv, err := NewJSSVCBKeyValue(kv) + if err != nil { + log.Error(err.Error()) + continue + } + jsKvs = append(jsKvs, jsKv) + } + jsRecord["Value"] = jsKvs + case *dns.SVCB: + jsRecord["Priority"] = rr.Priority + jsRecord["Target"] = rr.Target + kvs := rr.Value + jsKvs := make([]map[string]interface{}, len(kvs)) + for i, kv := range kvs { + jsKv, err := NewJSSVCBKeyValue(kv) + if err != nil { + log.Error(err.Error()) + continue + } + jsKvs[i] = jsKv + } + jsRecord["Value"] = jsKvs + case *dns.ZONEMD: + jsRecord["Digest"] = rr.Digest + jsRecord["Hash"] = rr.Hash + jsRecord["Scheme"] = rr.Scheme + jsRecord["Serial"] = rr.Serial + case *dns.CSYNC: + jsRecord["Flags"] = rr.Flags + jsRecord["Serial"] = rr.Serial + jsRecord["TypeBitMap"] = rr.TypeBitMap + case *dns.OPENPGPKEY: + jsRecord["PublicKey"] = rr.PublicKey + case *dns.TALINK: + jsRecord["NextName"] = rr.NextName + jsRecord["PreviousName"] = rr.PreviousName + case *dns.NINFO: + jsRecord["ZSData"] = rr.ZSData + case *dns.DHCID: + jsRecord["Digest"] = rr.Digest + case *dns.DNSKEY: + jsRecord["Flags"] = rr.Flags + jsRecord["Protocol"] = rr.Protocol + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["PublicKey"] = rr.PublicKey + case *dns.HIP: + jsRecord["Hit"] = rr.Hit + jsRecord["HitLength"] = rr.HitLength + jsRecord["PublicKey"] = rr.PublicKey + jsRecord["PublicKeyAlgorithm"] = rr.PublicKeyAlgorithm + jsRecord["PublicKeyLength"] = rr.PublicKeyLength + jsRecord["RendezvousServers"] = rr.RendezvousServers + case *dns.OPT: + jsRecord["Option"] = rr.Option + case *dns.NIMLOC: + jsRecord["Locator"] = rr.Locator + case *dns.EID: + jsRecord["Endpoint"] = rr.Endpoint + case *dns.NXT: + jsRecord["NextDomain"] = rr.NextDomain + jsRecord["TypeBitMap"] = rr.TypeBitMap + case *dns.PX: + jsRecord["Mapx400"] = rr.Mapx400 + jsRecord["Map822"] = rr.Map822 + jsRecord["Preference"] = rr.Preference + case *dns.SIG: + jsRecord["Algorithm"] = rr.Algorithm + jsRecord["Expiration"] = rr.Expiration + jsRecord["Inception"] = rr.Inception + jsRecord["KeyTag"] = rr.KeyTag + jsRecord["Labels"] = rr.Labels + jsRecord["OrigTtl"] = rr.OrigTtl + jsRecord["Signature"] = rr.Signature + jsRecord["SignerName"] = rr.SignerName + jsRecord["TypeCovered"] = rr.TypeCovered + case *dns.RT: + jsRecord["Host"] = rr.Host + jsRecord["Preference"] = rr.Preference + case *dns.NSAPPTR: + jsRecord["Ptr"] = rr.Ptr + case *dns.X25: + jsRecord["PSDNAddress"] = rr.PSDNAddress + case *dns.RFC3597: + jsRecord["Rdata"] = rr.Rdata + // case *dns.ATMA: + // case *dns.WKS: + // case *dns.DOA: + // case *dns.SINK: + default: + if header.Rrtype == dns.TypeNone { + break + } + return nil, fmt.Errorf("error creating JSResourceRecord: unknown type: %d", header.Rrtype) + } + + return jsRecord, nil +} + +func ToRR(jsRecord map[string]interface{}) (rr dns.RR, err error) { + jsHeader := jsPropToMap(jsRecord, "Header") + + header := dns.RR_Header{ + Class: jsPropToUint16(jsHeader, "Class"), + Name: jsPropToString(jsHeader, "Name"), + Rrtype: jsPropToUint16(jsHeader, "Rrtype"), + Ttl: jsPropToUint32(jsHeader, "Ttl"), + } + + switch header.Rrtype { + case dns.TypeNone: + break + case dns.TypeA: + rr = &dns.A{ + Hdr: header, + A: net.ParseIP(jsPropToString(jsRecord, "A")), + } + case dns.TypeAAAA: + rr = &dns.AAAA{ + Hdr: header, + AAAA: net.ParseIP(jsPropToString(jsRecord, "AAAA")), + } + case dns.TypeAPL: + jsPrefixes := jsRecord["Prefixes"].([]map[string]interface{}) + prefixes := make([]dns.APLPrefix, len(jsPrefixes)) + for i, jsPrefix := range jsPrefixes { + jsNetwork := jsPrefix["Network"].(string) + _, network, err := net.ParseCIDR(jsNetwork) + if err != nil { + log.Error("error parsing CIDR: %s", jsNetwork) + continue + } + prefixes[i] = dns.APLPrefix{ + Negation: jsPrefix["Negation"].(bool), + Network: *network, + } + } + rr = &dns.APL{ + Hdr: header, + Prefixes: prefixes, + } + case dns.TypeCNAME: + rr = &dns.CNAME{ + Hdr: header, + Target: jsPropToString(jsRecord, "Target"), + } + case dns.TypeMB: + rr = &dns.MB{ + Hdr: header, + Mb: jsPropToString(jsRecord, "Mb"), + } + case dns.TypeMD: + rr = &dns.MD{ + Hdr: header, + Md: jsPropToString(jsRecord, "Md"), + } + case dns.TypeMF: + rr = &dns.MF{ + Hdr: header, + Mf: jsPropToString(jsRecord, "Mf"), + } + case dns.TypeMG: + rr = &dns.MG{ + Hdr: header, + Mg: jsPropToString(jsRecord, "Mg"), + } + case dns.TypeMR: + rr = &dns.MR{ + Hdr: header, + Mr: jsPropToString(jsRecord, "Mr"), + } + case dns.TypeMX: + rr = &dns.MX{ + Hdr: header, + Mx: jsPropToString(jsRecord, "Mx"), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeNULL: + rr = &dns.NULL{ + Hdr: header, + Data: jsPropToString(jsRecord, "Data"), + } + case dns.TypeSOA: + rr = &dns.SOA{ + Hdr: header, + Expire: jsPropToUint32(jsRecord, "Expire"), + Mbox: jsPropToString(jsRecord, "Mbox"), + Minttl: jsPropToUint32(jsRecord, "Minttl"), + Ns: jsPropToString(jsRecord, "Ns"), + Refresh: jsPropToUint32(jsRecord, "Refresh"), + Retry: jsPropToUint32(jsRecord, "Retry"), + Serial: jsPropToUint32(jsRecord, "Serial"), + } + case dns.TypeTXT: + rr = &dns.TXT{ + Hdr: header, + Txt: jsPropToStringArray(jsRecord, "Txt"), + } + case dns.TypeSRV: + rr = &dns.SRV{ + Hdr: header, + Port: jsPropToUint16(jsRecord, "Port"), + Priority: jsPropToUint16(jsRecord, "Priority"), + Target: jsPropToString(jsRecord, "Target"), + Weight: jsPropToUint16(jsRecord, "Weight"), + } + case dns.TypePTR: + rr = &dns.PTR{ + Hdr: header, + Ptr: jsPropToString(jsRecord, "Ptr"), + } + case dns.TypeNS: + rr = &dns.NS{ + Hdr: header, + Ns: jsPropToString(jsRecord, "Ns"), + } + case dns.TypeDNAME: + rr = &dns.DNAME{ + Hdr: header, + Target: jsPropToString(jsRecord, "Target"), + } + case dns.TypeAFSDB: + rr = &dns.AFSDB{ + Hdr: header, + Hostname: jsPropToString(jsRecord, "Hostname"), + Subtype: jsPropToUint16(jsRecord, "Subtype"), + } + case dns.TypeCAA: + rr = &dns.CAA{ + Hdr: header, + Flag: jsPropToUint8(jsRecord, "Flag"), + Tag: jsPropToString(jsRecord, "Tag"), + Value: jsPropToString(jsRecord, "Value"), + } + case dns.TypeHINFO: + rr = &dns.HINFO{ + Hdr: header, + Cpu: jsPropToString(jsRecord, "Cpu"), + Os: jsPropToString(jsRecord, "Os"), + } + case dns.TypeMINFO: + rr = &dns.MINFO{ + Hdr: header, + Email: jsPropToString(jsRecord, "Email"), + Rmail: jsPropToString(jsRecord, "Rmail"), + } + case dns.TypeISDN: + rr = &dns.ISDN{ + Hdr: header, + Address: jsPropToString(jsRecord, "Address"), + SubAddress: jsPropToString(jsRecord, "SubAddress"), + } + case dns.TypeKX: + rr = &dns.KX{ + Hdr: header, + Preference: jsPropToUint16(jsRecord, "Preference"), + Exchanger: jsPropToString(jsRecord, "Exchanger"), + } + case dns.TypeLOC: + rr = &dns.LOC{ + Hdr: header, + Version: jsPropToUint8(jsRecord, "Version"), + Size: jsPropToUint8(jsRecord, "Size"), + HorizPre: jsPropToUint8(jsRecord, "HorizPre"), + VertPre: jsPropToUint8(jsRecord, "VertPre"), + Latitude: jsPropToUint32(jsRecord, "Latitude"), + Longitude: jsPropToUint32(jsRecord, "Longitude"), + Altitude: jsPropToUint32(jsRecord, "Altitude"), + } + case dns.TypeSSHFP: + rr = &dns.SSHFP{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + FingerPrint: jsPropToString(jsRecord, "FingerPrint"), + Type: jsPropToUint8(jsRecord, "Type"), + } + case dns.TypeTLSA: + rr = &dns.TLSA{ + Hdr: header, + Certificate: jsPropToString(jsRecord, "Certificate"), + MatchingType: jsPropToUint8(jsRecord, "MatchingType"), + Selector: jsPropToUint8(jsRecord, "Selector"), + Usage: jsPropToUint8(jsRecord, "Usage"), + } + case dns.TypeCERT: + rr = &dns.CERT{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Certificate: jsPropToString(jsRecord, "Certificate"), + KeyTag: jsPropToUint16(jsRecord, "KeyTag"), + Type: jsPropToUint16(jsRecord, "Type"), + } + case dns.TypeDS: + rr = &dns.DS{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Digest: jsPropToString(jsRecord, "Digest"), + DigestType: jsPropToUint8(jsRecord, "DigestType"), + KeyTag: jsPropToUint16(jsRecord, "KeyTag"), + } + case dns.TypeNAPTR: + rr = &dns.NAPTR{ + Hdr: header, + Flags: jsPropToString(jsRecord, "Flags"), + Order: jsPropToUint16(jsRecord, "Order"), + Preference: jsPropToUint16(jsRecord, "Preference"), + Regexp: jsPropToString(jsRecord, "Regexp"), + Replacement: jsPropToString(jsRecord, "Replacement"), + Service: jsPropToString(jsRecord, "Service"), + } + case dns.TypeRRSIG: + rr = &dns.RRSIG{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Expiration: jsPropToUint32(jsRecord, "Expiration"), + Inception: jsPropToUint32(jsRecord, "Inception"), + KeyTag: jsPropToUint16(jsRecord, "KeyTag"), + Labels: jsPropToUint8(jsRecord, "Labels"), + OrigTtl: jsPropToUint32(jsRecord, "OrigTtl"), + Signature: jsPropToString(jsRecord, "Signature"), + SignerName: jsPropToString(jsRecord, "SignerName"), + TypeCovered: jsPropToUint16(jsRecord, "TypeCovered"), + } + case dns.TypeNSEC: + rr = &dns.NSEC{ + Hdr: header, + NextDomain: jsPropToString(jsRecord, "NextDomain"), + TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"), + } + case dns.TypeNSEC3: + rr = &dns.NSEC3{ + Hdr: header, + Flags: jsPropToUint8(jsRecord, "Flags"), + Hash: jsPropToUint8(jsRecord, "Hash"), + HashLength: jsPropToUint8(jsRecord, "HashLength"), + Iterations: jsPropToUint16(jsRecord, "Iterations"), + NextDomain: jsPropToString(jsRecord, "NextDomain"), + Salt: jsPropToString(jsRecord, "Salt"), + SaltLength: jsPropToUint8(jsRecord, "SaltLength"), + TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"), + } + case dns.TypeNSEC3PARAM: + rr = &dns.NSEC3PARAM{ + Hdr: header, + Flags: jsPropToUint8(jsRecord, "Flags"), + Hash: jsPropToUint8(jsRecord, "Hash"), + Iterations: jsPropToUint16(jsRecord, "Iterations"), + Salt: jsPropToString(jsRecord, "Salt"), + SaltLength: jsPropToUint8(jsRecord, "SaltLength"), + } + case dns.TypeTKEY: + rr = &dns.TKEY{ + Hdr: header, + Algorithm: jsPropToString(jsRecord, "Algorithm"), + Error: jsPropToUint16(jsRecord, "Error"), + Expiration: jsPropToUint32(jsRecord, "Expiration"), + Inception: jsPropToUint32(jsRecord, "Inception"), + Key: jsPropToString(jsRecord, "Key"), + KeySize: jsPropToUint16(jsRecord, "KeySize"), + Mode: jsPropToUint16(jsRecord, "Mode"), + OtherData: jsPropToString(jsRecord, "OtherData"), + OtherLen: jsPropToUint16(jsRecord, "OtherLen"), + } + case dns.TypeTSIG: + rr = &dns.TSIG{ + Hdr: header, + Algorithm: jsPropToString(jsRecord, "Algorithm"), + Error: jsPropToUint16(jsRecord, "Error"), + Fudge: jsPropToUint16(jsRecord, "Fudge"), + MACSize: jsPropToUint16(jsRecord, "MACSize"), + MAC: jsPropToString(jsRecord, "MAC"), + OrigId: jsPropToUint16(jsRecord, "OrigId"), + OtherData: jsPropToString(jsRecord, "OtherData"), + OtherLen: jsPropToUint16(jsRecord, "OtherLen"), + TimeSigned: jsPropToUint64(jsRecord, "TimeSigned"), + } + case dns.TypeIPSECKEY: + rr = &dns.IPSECKEY{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + GatewayAddr: net.IP(jsPropToString(jsRecord, "GatewayAddr")), + GatewayHost: jsPropToString(jsRecord, "GatewayHost"), + GatewayType: jsPropToUint8(jsRecord, "GatewayType"), + Precedence: jsPropToUint8(jsRecord, "Precedence"), + PublicKey: jsPropToString(jsRecord, "PublicKey"), + } + case dns.TypeKEY: + rr = &dns.KEY{ + DNSKEY: dns.DNSKEY{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Flags: jsPropToUint16(jsRecord, "Flags"), + Protocol: jsPropToUint8(jsRecord, "Protocol"), + PublicKey: jsPropToString(jsRecord, "PublicKey"), + }, + } + case dns.TypeCDS: + rr = &dns.CDS{ + DS: dns.DS{ + Hdr: header, + KeyTag: jsPropToUint16(jsRecord, "KeyTag"), + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + DigestType: jsPropToUint8(jsRecord, "DigestType"), + Digest: jsPropToString(jsRecord, "Digest"), + }, + } + case dns.TypeCDNSKEY: + rr = &dns.CDNSKEY{ + DNSKEY: dns.DNSKEY{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Flags: jsPropToUint16(jsRecord, "Flags"), + Protocol: jsPropToUint8(jsRecord, "Protocol"), + PublicKey: jsPropToString(jsRecord, "PublicKey"), + }, + } + case dns.TypeNID: + rr = &dns.NID{ + Hdr: header, + NodeID: jsPropToUint64(jsRecord, "NodeID"), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeL32: + rr = &dns.L32{ + Hdr: header, + Locator32: net.IP(jsPropToString(jsRecord, "Locator32")), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeL64: + rr = &dns.L64{ + Hdr: header, + Locator64: jsPropToUint64(jsRecord, "Locator64"), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeLP: + rr = &dns.LP{ + Hdr: header, + Fqdn: jsPropToString(jsRecord, "Fqdn"), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeGPOS: + rr = &dns.GPOS{ + Hdr: header, + Altitude: jsPropToString(jsRecord, "Altitude"), + Latitude: jsPropToString(jsRecord, "Latitude"), + Longitude: jsPropToString(jsRecord, "Longitude"), + } + case dns.TypeRP: + rr = &dns.RP{ + Hdr: header, + Mbox: jsPropToString(jsRecord, "Mbox"), + Txt: jsPropToString(jsRecord, "Txt"), + } + case dns.TypeRKEY: + rr = &dns.RKEY{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Flags: jsPropToUint16(jsRecord, "Flags"), + Protocol: jsPropToUint8(jsRecord, "Protocol"), + PublicKey: jsPropToString(jsRecord, "PublicKey"), + } + case dns.TypeSMIMEA: + rr = &dns.SMIMEA{ + Hdr: header, + Certificate: jsPropToString(jsRecord, "Certificate"), + MatchingType: jsPropToUint8(jsRecord, "MatchingType"), + Selector: jsPropToUint8(jsRecord, "Selector"), + Usage: jsPropToUint8(jsRecord, "Usage"), + } + case dns.TypeAMTRELAY: + rr = &dns.AMTRELAY{ + Hdr: header, + GatewayAddr: net.IP(jsPropToString(jsRecord, "GatewayAddr")), + GatewayHost: jsPropToString(jsRecord, "GatewayHost"), + GatewayType: jsPropToUint8(jsRecord, "GatewayType"), + Precedence: jsPropToUint8(jsRecord, "Precedence"), + } + case dns.TypeAVC: + rr = &dns.AVC{ + Hdr: header, + Txt: jsPropToStringArray(jsRecord, "Txt"), + } + case dns.TypeURI: + rr = &dns.URI{ + Hdr: header, + Priority: jsPropToUint16(jsRecord, "Priority"), + Weight: jsPropToUint16(jsRecord, "Weight"), + Target: jsPropToString(jsRecord, "Target"), + } + case dns.TypeEUI48: + rr = &dns.EUI48{ + Hdr: header, + Address: jsPropToUint64(jsRecord, "Address"), + } + case dns.TypeEUI64: + rr = &dns.EUI64{ + Hdr: header, + Address: jsPropToUint64(jsRecord, "Address"), + } + case dns.TypeGID: + rr = &dns.GID{ + Hdr: header, + Gid: jsPropToUint32(jsRecord, "Gid"), + } + case dns.TypeUID: + rr = &dns.UID{ + Hdr: header, + Uid: jsPropToUint32(jsRecord, "Uid"), + } + case dns.TypeUINFO: + rr = &dns.UINFO{ + Hdr: header, + Uinfo: jsPropToString(jsRecord, "Uinfo"), + } + case dns.TypeSPF: + rr = &dns.SPF{ + Hdr: header, + Txt: jsPropToStringArray(jsRecord, "Txt"), + } + case dns.TypeHTTPS: + jsKvs := jsPropToMapArray(jsRecord, "Value") + var kvs []dns.SVCBKeyValue + for _, jsKv := range jsKvs { + kv, err := ToSVCBKeyValue(jsKv) + if err != nil { + log.Error(err.Error()) + continue + } + kvs = append(kvs, kv) + } + rr = &dns.HTTPS{ + SVCB: dns.SVCB{ + Hdr: header, + Priority: jsPropToUint16(jsRecord, "Priority"), + Target: jsPropToString(jsRecord, "Target"), + Value: kvs, + }, + } + case dns.TypeSVCB: + jsKvs := jsPropToMapArray(jsRecord, "Value") + var kvs []dns.SVCBKeyValue + for _, jsKv := range jsKvs { + kv, err := ToSVCBKeyValue(jsKv) + if err != nil { + log.Error(err.Error()) + continue + } + kvs = append(kvs, kv) + } + rr = &dns.SVCB{ + Hdr: header, + Priority: jsPropToUint16(jsRecord, "Priority"), + Target: jsPropToString(jsRecord, "Target"), + Value: kvs, + } + case dns.TypeZONEMD: + rr = &dns.ZONEMD{ + Hdr: header, + Digest: jsPropToString(jsRecord, "Digest"), + Hash: jsPropToUint8(jsRecord, "Hash"), + Scheme: jsPropToUint8(jsRecord, "Scheme"), + Serial: jsPropToUint32(jsRecord, "Serial"), + } + case dns.TypeCSYNC: + rr = &dns.CSYNC{ + Hdr: header, + Flags: jsPropToUint16(jsRecord, "Flags"), + Serial: jsPropToUint32(jsRecord, "Serial"), + TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"), + } + case dns.TypeOPENPGPKEY: + rr = &dns.OPENPGPKEY{ + Hdr: header, + PublicKey: jsPropToString(jsRecord, "PublicKey"), + } + case dns.TypeTALINK: + rr = &dns.TALINK{ + Hdr: header, + NextName: jsPropToString(jsRecord, "NextName"), + PreviousName: jsPropToString(jsRecord, "PreviousName"), + } + case dns.TypeNINFO: + rr = &dns.NINFO{ + Hdr: header, + ZSData: jsPropToStringArray(jsRecord, "ZSData"), + } + case dns.TypeDHCID: + rr = &dns.DHCID{ + Hdr: header, + Digest: jsPropToString(jsRecord, "Digest"), + } + case dns.TypeDNSKEY: + rr = &dns.DNSKEY{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Flags: jsPropToUint16(jsRecord, "Flags"), + Protocol: jsPropToUint8(jsRecord, "Protocol"), + PublicKey: jsPropToString(jsRecord, "PublicKey"), + } + case dns.TypeHIP: + rr = &dns.HIP{ + Hdr: header, + Hit: jsPropToString(jsRecord, "Hit"), + HitLength: jsPropToUint8(jsRecord, "HitLength"), + PublicKey: jsPropToString(jsRecord, "PublicKey"), + PublicKeyAlgorithm: jsPropToUint8(jsRecord, "PublicKeyAlgorithm"), + PublicKeyLength: jsPropToUint16(jsRecord, "PublicKeyLength"), + RendezvousServers: jsPropToStringArray(jsRecord, "RendezvousServers"), + } + case dns.TypeOPT: + jsOptions := jsPropToMapArray(jsRecord, "Option") + var options []dns.EDNS0 + for _, jsOption := range jsOptions { + option, err := ToEDNS0(jsOption) + if err != nil { + log.Error(err.Error()) + continue + } + options = append(options, option) + } + rr = &dns.OPT{ + Hdr: header, + Option: options, + } + case dns.TypeNIMLOC: + rr = &dns.NIMLOC{ + Hdr: header, + Locator: jsPropToString(jsRecord, "Locator"), + } + case dns.TypeEID: + rr = &dns.EID{ + Hdr: header, + Endpoint: jsPropToString(jsRecord, "Endpoint"), + } + case dns.TypeNXT: + rr = &dns.NXT{ + NSEC: dns.NSEC{ + Hdr: header, + NextDomain: jsPropToString(jsRecord, "NextDomain"), + TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"), + }, + } + case dns.TypePX: + rr = &dns.PX{ + Hdr: header, + Mapx400: jsPropToString(jsRecord, "Mapx400"), + Map822: jsPropToString(jsRecord, "Map822"), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeSIG: + rr = &dns.SIG{ + RRSIG: dns.RRSIG{ + Hdr: header, + Algorithm: jsPropToUint8(jsRecord, "Algorithm"), + Expiration: jsPropToUint32(jsRecord, "Expiration"), + Inception: jsPropToUint32(jsRecord, "Inception"), + KeyTag: jsPropToUint16(jsRecord, "KeyTag"), + Labels: jsPropToUint8(jsRecord, "Labels"), + OrigTtl: jsPropToUint32(jsRecord, "OrigTtl"), + Signature: jsPropToString(jsRecord, "Signature"), + SignerName: jsPropToString(jsRecord, "SignerName"), + TypeCovered: jsPropToUint16(jsRecord, "TypeCovered"), + }, + } + case dns.TypeRT: + rr = &dns.RT{ + Hdr: header, + Host: jsPropToString(jsRecord, "Host"), + Preference: jsPropToUint16(jsRecord, "Preference"), + } + case dns.TypeNSAPPTR: + rr = &dns.NSAPPTR{ + Hdr: header, + Ptr: jsPropToString(jsRecord, "Ptr"), + } + case dns.TypeX25: + rr = &dns.X25{ + Hdr: header, + PSDNAddress: jsPropToString(jsRecord, "PSDNAddress"), + } + // case dns.TypeATMA: + // case dns.TypeWKS: + // case dns.TypeDOA: + // case dns.TypeSINK: + default: + return nil, fmt.Errorf("error converting to dns.RR: unknown type: %d", header.Rrtype) + } + + return rr, nil +} diff --git a/modules/dns_proxy/dns_proxy_js_record_edns0.go b/modules/dns_proxy/dns_proxy_js_record_edns0.go new file mode 100644 index 00000000..297c9957 --- /dev/null +++ b/modules/dns_proxy/dns_proxy_js_record_edns0.go @@ -0,0 +1,208 @@ +package dns_proxy + +import ( + "fmt" + "net" + + "github.com/bettercap/bettercap/v2/log" + "github.com/miekg/dns" +) + +func NewJSEDNS0(e dns.EDNS0) (jsEDNS0 map[string]interface{}, err error) { + option := e.Option() + + jsEDNS0 = map[string]interface{}{ + "Option": option, + } + + var jsVal map[string]interface{} + + switch opt := e.(type) { + case *dns.EDNS0_LLQ: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Error": opt.Error, + "Id": opt.Id, + "LeaseLife": opt.LeaseLife, + "Opcode": opt.Opcode, + "Version": opt.Version, + } + case *dns.EDNS0_UL: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Lease": opt.Lease, + "KeyLease": opt.KeyLease, + } + case *dns.EDNS0_NSID: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Nsid": opt.Nsid, + } + case *dns.EDNS0_ESU: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Uri": opt.Uri, + } + case *dns.EDNS0_DAU: + jsVal = map[string]interface{}{ + "AlgCode": opt.AlgCode, + "Code": opt.Code, + } + case *dns.EDNS0_DHU: + jsVal = map[string]interface{}{ + "AlgCode": opt.AlgCode, + "Code": opt.Code, + } + case *dns.EDNS0_N3U: + jsVal = map[string]interface{}{ + "AlgCode": opt.AlgCode, + "Code": opt.Code, + } + case *dns.EDNS0_SUBNET: + jsVal = map[string]interface{}{ + "Address": opt.Address.String(), + "Code": opt.Code, + "Family": opt.Family, + "SourceNetmask": opt.SourceNetmask, + "SourceScope": opt.SourceScope, + } + case *dns.EDNS0_EXPIRE: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Empty": opt.Empty, + "Expire": opt.Expire, + } + case *dns.EDNS0_COOKIE: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Cookie": opt.Cookie, + } + case *dns.EDNS0_TCP_KEEPALIVE: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Length": opt.Length, + "Timeout": opt.Timeout, + } + case *dns.EDNS0_PADDING: + jsVal = map[string]interface{}{ + "Padding": string(opt.Padding), + } + case *dns.EDNS0_EDE: + jsVal = map[string]interface{}{ + "ExtraText": opt.ExtraText, + "InfoCode": opt.InfoCode, + } + case *dns.EDNS0_LOCAL: + jsVal = map[string]interface{}{ + "Code": opt.Code, + "Data": string(opt.Data), + } + default: + return nil, fmt.Errorf("unsupported EDNS0 option: %d", option) + } + + jsEDNS0["Value"] = jsVal + + return jsEDNS0, nil +} + +func ToEDNS0(jsEDNS0 map[string]interface{}) (e dns.EDNS0, err error) { + option := jsPropToUint16(jsEDNS0, "Option") + + jsVal := jsPropToMap(jsEDNS0, "Value") + + switch option { + case dns.EDNS0LLQ: + e = &dns.EDNS0_LLQ{ + Code: jsPropToUint16(jsVal, "Code"), + Error: jsPropToUint16(jsVal, "Error"), + Id: jsPropToUint64(jsVal, "Id"), + LeaseLife: jsPropToUint32(jsVal, "LeaseLife"), + Opcode: jsPropToUint16(jsVal, "Opcode"), + Version: jsPropToUint16(jsVal, "Version"), + } + case dns.EDNS0UL: + e = &dns.EDNS0_UL{ + Code: jsPropToUint16(jsVal, "Code"), + Lease: jsPropToUint32(jsVal, "Lease"), + KeyLease: jsPropToUint32(jsVal, "KeyLease"), + } + case dns.EDNS0NSID: + e = &dns.EDNS0_NSID{ + Code: jsPropToUint16(jsVal, "Code"), + Nsid: jsPropToString(jsVal, "Nsid"), + } + case dns.EDNS0ESU: + e = &dns.EDNS0_ESU{ + Code: jsPropToUint16(jsVal, "Code"), + Uri: jsPropToString(jsVal, "Uri"), + } + case dns.EDNS0DAU: + e = &dns.EDNS0_DAU{ + AlgCode: jsPropToUint8Array(jsVal, "AlgCode"), + Code: jsPropToUint16(jsVal, "Code"), + } + case dns.EDNS0DHU: + e = &dns.EDNS0_DHU{ + AlgCode: jsPropToUint8Array(jsVal, "AlgCode"), + Code: jsPropToUint16(jsVal, "Code"), + } + case dns.EDNS0N3U: + e = &dns.EDNS0_N3U{ + AlgCode: jsPropToUint8Array(jsVal, "AlgCode"), + Code: jsPropToUint16(jsVal, "Code"), + } + case dns.EDNS0SUBNET: + e = &dns.EDNS0_SUBNET{ + Address: net.ParseIP(jsPropToString(jsVal, "Address")), + Code: jsPropToUint16(jsVal, "Code"), + Family: jsPropToUint16(jsVal, "Family"), + SourceNetmask: jsPropToUint8(jsVal, "SourceNetmask"), + SourceScope: jsPropToUint8(jsVal, "SourceScope"), + } + case dns.EDNS0EXPIRE: + if empty, ok := jsVal["Empty"].(bool); !ok { + log.Error("invalid or missing EDNS0_EXPIRE.Empty bool value, skipping field.") + e = &dns.EDNS0_EXPIRE{ + Code: jsPropToUint16(jsVal, "Code"), + Expire: jsPropToUint32(jsVal, "Expire"), + } + } else { + e = &dns.EDNS0_EXPIRE{ + Code: jsPropToUint16(jsVal, "Code"), + Expire: jsPropToUint32(jsVal, "Expire"), + Empty: empty, + } + } + case dns.EDNS0COOKIE: + e = &dns.EDNS0_COOKIE{ + Code: jsPropToUint16(jsVal, "Code"), + Cookie: jsPropToString(jsVal, "Cookie"), + } + case dns.EDNS0TCPKEEPALIVE: + e = &dns.EDNS0_TCP_KEEPALIVE{ + Code: jsPropToUint16(jsVal, "Code"), + Length: jsPropToUint16(jsVal, "Length"), + Timeout: jsPropToUint16(jsVal, "Timeout"), + } + case dns.EDNS0PADDING: + e = &dns.EDNS0_PADDING{ + Padding: []byte(jsPropToString(jsVal, "Padding")), + } + case dns.EDNS0EDE: + e = &dns.EDNS0_EDE{ + ExtraText: jsPropToString(jsVal, "ExtraText"), + InfoCode: jsPropToUint16(jsVal, "InfoCode"), + } + case dns.EDNS0LOCALSTART, dns.EDNS0LOCALEND, 0x8000: + // _DO = 0x8000 + e = &dns.EDNS0_LOCAL{ + Code: jsPropToUint16(jsVal, "Code"), + Data: []byte(jsPropToString(jsVal, "Data")), + } + default: + return nil, fmt.Errorf("unsupported EDNS0 option: %d", option) + } + + return e, nil +} diff --git a/modules/dns_proxy/dns_proxy_js_record_svcb.go b/modules/dns_proxy/dns_proxy_js_record_svcb.go new file mode 100644 index 00000000..f0f0242d --- /dev/null +++ b/modules/dns_proxy/dns_proxy_js_record_svcb.go @@ -0,0 +1,127 @@ +package dns_proxy + +import ( + "fmt" + "net" + + "github.com/bettercap/bettercap/v2/log" + "github.com/miekg/dns" +) + +func NewJSSVCBKeyValue(kv dns.SVCBKeyValue) (map[string]interface{}, error) { + key := kv.Key() + + jsKv := map[string]interface{}{ + "Key": uint16(key), + } + + switch v := kv.(type) { + case *dns.SVCBAlpn: + jsKv["Alpn"] = v.Alpn + case *dns.SVCBNoDefaultAlpn: + break + case *dns.SVCBECHConfig: + jsKv["ECH"] = string(v.ECH) + case *dns.SVCBPort: + jsKv["Port"] = v.Port + case *dns.SVCBIPv4Hint: + ips := v.Hint + jsIps := make([]string, len(ips)) + for i, ip := range ips { + jsIps[i] = ip.String() + } + jsKv["Hint"] = jsIps + case *dns.SVCBIPv6Hint: + ips := v.Hint + jsIps := make([]string, len(ips)) + for i, ip := range ips { + jsIps[i] = ip.String() + } + jsKv["Hint"] = jsIps + case *dns.SVCBDoHPath: + jsKv["Template"] = v.Template + case *dns.SVCBOhttp: + break + case *dns.SVCBMandatory: + keys := v.Code + jsKeys := make([]uint16, len(keys)) + for i, _key := range keys { + jsKeys[i] = uint16(_key) + } + jsKv["Code"] = jsKeys + default: + return nil, fmt.Errorf("error creating JSSVCBKeyValue: unknown key: %d", key) + } + + return jsKv, nil +} + +func ToSVCBKeyValue(jsKv map[string]interface{}) (dns.SVCBKeyValue, error) { + var kv dns.SVCBKeyValue + + key := dns.SVCBKey(jsPropToUint16(jsKv, "Key")) + + switch key { + case dns.SVCB_ALPN: + kv = &dns.SVCBAlpn{ + Alpn: jsPropToStringArray(jsKv, "Value"), + } + case dns.SVCB_NO_DEFAULT_ALPN: + kv = &dns.SVCBNoDefaultAlpn{} + case dns.SVCB_ECHCONFIG: + kv = &dns.SVCBECHConfig{ + ECH: []byte(jsPropToString(jsKv, "Value")), + } + case dns.SVCB_PORT: + kv = &dns.SVCBPort{ + Port: jsPropToUint16(jsKv, "Value"), + } + case dns.SVCB_IPV4HINT: + jsIps := jsPropToStringArray(jsKv, "Value") + var ips []net.IP + for _, jsIp := range jsIps { + ip := net.ParseIP(jsIp) + if ip == nil { + log.Error("error converting to SVCBKeyValue: invalid IPv4Hint IP: %s", jsIp) + continue + } + ips = append(ips, ip) + } + kv = &dns.SVCBIPv4Hint{ + Hint: ips, + } + case dns.SVCB_IPV6HINT: + jsIps := jsPropToStringArray(jsKv, "Value") + var ips []net.IP + for _, jsIp := range jsIps { + ip := net.ParseIP(jsIp) + if ip == nil { + log.Error("error converting to SVCBKeyValue: invalid IPv6Hint IP: %s", jsIp) + continue + } + ips = append(ips, ip) + } + kv = &dns.SVCBIPv6Hint{ + Hint: ips, + } + case dns.SVCB_DOHPATH: + kv = &dns.SVCBDoHPath{ + Template: jsPropToString(jsKv, "Value"), + } + case dns.SVCB_OHTTP: + kv = &dns.SVCBOhttp{} + case dns.SVCB_MANDATORY: + v := jsPropToUint16Array(jsKv, "Value") + keys := make([]dns.SVCBKey, len(v)) + for i, jsKey := range v { + keys[i] = dns.SVCBKey(jsKey) + } + kv = &dns.SVCBMandatory{ + Code: keys, + } + default: + return nil, fmt.Errorf("error converting to dns.SVCBKeyValue: unknown key: %d", key) + } + + return kv, nil +} diff --git a/tls/cert.go b/tls/cert.go index 099c9b7a..aa804611 100644 --- a/tls/cert.go +++ b/tls/cert.go @@ -40,6 +40,14 @@ var ( OrganizationalUnit: "https://certs.godaddy.com/repository/", CommonName: "Go Daddy Secure Certificate Authority - G2", } + DefaultCloudflareDNSConfig = CertConfig{ + Bits: 4096, + Country: "US", + Locality: "San Francisco", + Organization: "Cloudflare, Inc.", + OrganizationalUnit: "", + CommonName: "cloudflare-dns.com", + } ) func CertConfigToModule(prefix string, m *session.SessionModule, defaults CertConfig) {