Compare commits
10 Commits
bc8b6e89c6
...
0c0557bd12
Author | SHA1 | Date | |
---|---|---|---|
0c0557bd12 | |||
ec952b50fa | |||
597bdcbcff | |||
fd30989e3a | |||
e38baf825e | |||
70a189163e | |||
8062973169 | |||
d69e1992d8 | |||
56054b23e3 | |||
0fd9b26266 |
1
main.go
1
main.go
@ -38,6 +38,7 @@ import (
|
||||
"git.happydns.org/happyDomain/internal/app"
|
||||
"git.happydns.org/happyDomain/storage"
|
||||
|
||||
_ "git.happydns.org/happyDomain/services/abstract"
|
||||
_ "git.happydns.org/happyDomain/services/providers/google"
|
||||
|
||||
_ "git.happydns.org/happyDomain/storage/leveldb"
|
||||
|
@ -75,7 +75,7 @@ func init() {
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"temporary",
|
||||
"tls",
|
||||
"verification",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -126,7 +126,7 @@ func init() {
|
||||
Description: "Delegate this subdomain to another name server",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"internal",
|
||||
"domain name",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeNS,
|
||||
|
@ -23,15 +23,12 @@ package abstract
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/services"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type EMail struct {
|
||||
@ -48,38 +45,9 @@ func (s *EMail) GetNbResources() int {
|
||||
}
|
||||
|
||||
func (s *EMail) GenComment(origin string) string {
|
||||
poolMX := map[string]int{}
|
||||
|
||||
for _, mx := range s.MX {
|
||||
labels := dns.SplitDomainName(mx.Target)
|
||||
nbLabel := len(labels)
|
||||
|
||||
var dn string
|
||||
if nbLabel <= 2 {
|
||||
dn = mx.Target
|
||||
} else if len(labels[nbLabel-2]) < 4 {
|
||||
dn = strings.Join(labels[nbLabel-3:], ".") + "."
|
||||
} else {
|
||||
dn = strings.Join(labels[nbLabel-2:], ".") + "."
|
||||
}
|
||||
|
||||
poolMX[dn] += 1
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
first := true
|
||||
|
||||
for dn, nb := range poolMX {
|
||||
if !first {
|
||||
buffer.WriteString("; ")
|
||||
} else {
|
||||
first = !first
|
||||
}
|
||||
buffer.WriteString(strings.TrimSuffix(dn, "."+origin))
|
||||
if nb > 1 {
|
||||
buffer.WriteString(fmt.Sprintf(" ×%d", nb))
|
||||
}
|
||||
}
|
||||
buffer.WriteString((&svcs.MXs{MX: s.MX}).GenComment(origin))
|
||||
|
||||
if s.SPF != nil {
|
||||
buffer.WriteString(" + SPF")
|
||||
@ -106,200 +74,40 @@ func (s *EMail) GenComment(origin string) string {
|
||||
|
||||
func (s *EMail) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
if len(s.MX) > 0 {
|
||||
for _, mx := range s.MX {
|
||||
rc := utils.NewRecordConfig(domain, "MX", ttl, origin)
|
||||
rc.MxPreference = mx.Preference
|
||||
rc.SetTarget(utils.DomainFQDN(mx.Target, origin))
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
}
|
||||
rrs = append(rrs, (&svcs.MXs{MX: s.MX}).GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
|
||||
if s.SPF != nil {
|
||||
rc := utils.NewRecordConfig(domain, "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(s.SPF.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
rrs = append(rrs, s.SPF.GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
|
||||
for selector, d := range s.DKIM {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin(selector+"._domainkey", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(d.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
rrs = append(rrs, (&svcs.DKIMRecord{
|
||||
DKIM: *d,
|
||||
Selector: selector,
|
||||
}).GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
|
||||
if s.DMARC != nil {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin("_dmarc", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(s.DMARC.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
rrs = append(rrs, s.DMARC.GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
|
||||
if s.MTA_STS != nil {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin("_mta-sts", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(s.MTA_STS.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
rrs = append(rrs, s.MTA_STS.GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
|
||||
if s.TLS_RPT != nil {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin("_smtp._tls", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(s.TLS_RPT.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
rrs = append(rrs, s.TLS_RPT.GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func email_analyze(a *svcs.Analyzer) (err error) {
|
||||
services := map[string]*EMail{}
|
||||
|
||||
// Handle only MX records
|
||||
for _, record := range a.SearchRR(svcs.AnalyzerRecordFilter{Type: dns.TypeMX}) {
|
||||
if record.Type == "MX" {
|
||||
dn := record.NameFQDN
|
||||
|
||||
if _, ok := services[dn]; !ok {
|
||||
services[dn] = &EMail{}
|
||||
}
|
||||
|
||||
services[dn].MX = append(
|
||||
services[dn].MX,
|
||||
svcs.MX{
|
||||
Target: record.GetTargetField(),
|
||||
Preference: record.MxPreference,
|
||||
},
|
||||
)
|
||||
|
||||
err = a.UseRR(
|
||||
record,
|
||||
dn,
|
||||
services[dn],
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for domain, service := range services {
|
||||
// Is there SPF record?
|
||||
for _, record := range a.SearchRR(svcs.AnalyzerRecordFilter{Type: dns.TypeTXT, Domain: domain, Contains: "v=spf1"}) {
|
||||
if record.Type == "TXT" || record.Type == "SPF" {
|
||||
if service.SPF == nil {
|
||||
service.SPF = &svcs.SPF{}
|
||||
}
|
||||
|
||||
err = service.SPF.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = a.UseRR(record, domain, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
service.DKIM = map[string]*svcs.DKIM{}
|
||||
// Is there DKIM record?
|
||||
for _, record := range a.SearchRR(svcs.AnalyzerRecordFilter{Type: dns.TypeTXT, SubdomainsOf: "_domainkey." + domain}) {
|
||||
selector := strings.TrimSuffix(record.NameFQDN, "._domainkey."+domain)
|
||||
|
||||
if _, ok := service.DKIM[selector]; !ok {
|
||||
service.DKIM[selector] = &svcs.DKIM{}
|
||||
}
|
||||
|
||||
if record.Type == "TXT" {
|
||||
err = service.DKIM[selector].Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = a.UseRR(record, domain, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Is there DMARC record?
|
||||
for _, record := range a.SearchRR(svcs.AnalyzerRecordFilter{Type: dns.TypeTXT, Domain: "_dmarc." + domain}) {
|
||||
if service.DMARC == nil {
|
||||
service.DMARC = &svcs.DMARC{}
|
||||
}
|
||||
|
||||
if record.Type == "TXT" {
|
||||
err = service.DMARC.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = a.UseRR(record, domain, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Is there MTA-STS record?
|
||||
for _, record := range a.SearchRR(svcs.AnalyzerRecordFilter{Type: dns.TypeTXT, Domain: "_mta-sts." + domain}) {
|
||||
if service.MTA_STS == nil {
|
||||
service.MTA_STS = &svcs.MTA_STS{}
|
||||
}
|
||||
|
||||
if record.Type == "TXT" {
|
||||
err = service.MTA_STS.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = a.UseRR(record, domain, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Is there TLS-RPT record?
|
||||
for _, record := range a.SearchRR(svcs.AnalyzerRecordFilter{Type: dns.TypeTXT, Domain: "_smtp._tls." + domain}) {
|
||||
// rfc8460: 3. records that do not begin with "v=TLSRPTv1;" are discarded
|
||||
if !strings.HasPrefix(record.GetTargetTXTJoined(), "v=TLSRPT") {
|
||||
continue
|
||||
}
|
||||
|
||||
if service.TLS_RPT == nil {
|
||||
service.TLS_RPT = &svcs.TLS_RPT{}
|
||||
} else {
|
||||
// rfc8460: 3. If the number of resulting records is not one, senders MUST assume the recipient domain does not implement TLSRPT.
|
||||
continue
|
||||
}
|
||||
|
||||
if record.Type == "TXT" {
|
||||
err = service.TLS_RPT.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = a.UseRR(record, domain, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
svcs.RegisterService(
|
||||
func() happydns.Service {
|
||||
return &EMail{}
|
||||
},
|
||||
email_analyze,
|
||||
nil,
|
||||
svcs.ServiceInfos{
|
||||
Name: "E-Mail",
|
||||
Description: "Sends and receives e-mail with this domain.",
|
||||
@ -309,7 +117,6 @@ func init() {
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeMX,
|
||||
dns.TypeSPF,
|
||||
},
|
||||
Tabs: true,
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
|
@ -83,7 +83,7 @@ func init() {
|
||||
Description: "Temporary record to prove that you control the domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"temporary",
|
||||
"verification",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -80,7 +80,7 @@ func init() {
|
||||
Description: "Temporary record to prove that you control the domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"temporary",
|
||||
"verification",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -75,7 +75,7 @@ func init() {
|
||||
Description: "Temporary record to prove that you control the domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"temporary",
|
||||
"verification",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -74,7 +74,7 @@ func init() {
|
||||
Description: "Temporary record to prove that you control the domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"temporary",
|
||||
"verification",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -122,7 +122,7 @@ func init() {
|
||||
Description: "Communicate on Matrix using your domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"im",
|
||||
"service",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -176,7 +176,7 @@ func init() {
|
||||
Description: "This is the root of your domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"internal",
|
||||
"domain name",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeSOA,
|
||||
@ -202,7 +202,7 @@ func init() {
|
||||
Description: "This is the root of your domain.",
|
||||
Family: svcs.Hidden,
|
||||
Categories: []string{
|
||||
"internal",
|
||||
"domain name",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeNS,
|
||||
|
@ -74,7 +74,7 @@ func init() {
|
||||
Description: "Temporary record to prove that you control the domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"temporary",
|
||||
"verification",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -41,7 +41,17 @@ type Server struct {
|
||||
}
|
||||
|
||||
func (s *Server) GetNbResources() int {
|
||||
return 1
|
||||
i := 0
|
||||
|
||||
if s.A != nil {
|
||||
i += 1
|
||||
}
|
||||
|
||||
if s.AAAA != nil {
|
||||
i += 1
|
||||
}
|
||||
|
||||
return i + len(s.SSHFP)
|
||||
}
|
||||
|
||||
func (s *Server) GenComment(origin string) string {
|
||||
@ -78,13 +88,8 @@ func (s *Server) GenRRs(domain string, ttl uint32, origin string) (rrs models.Re
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
}
|
||||
for _, sshfp := range s.SSHFP {
|
||||
rc := utils.NewRecordConfig(domain, "SSHFP", ttl, origin)
|
||||
rc.SshfpAlgorithm = sshfp.Algorithm
|
||||
rc.SshfpFingerprint = sshfp.Type
|
||||
rc.SetTarget(sshfp.FingerPrint)
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
if len(s.SSHFP) > 0 {
|
||||
rrs = append(rrs, (&svcs.SSHFPs{SSHFP: s.SSHFP}).GenRRs(domain, ttl, origin)...)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -148,7 +148,7 @@ func init() {
|
||||
Description: "Communicate over XMPP with your domain.",
|
||||
Family: svcs.Abstract,
|
||||
Categories: []string{
|
||||
"im",
|
||||
"service",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
|
@ -298,7 +298,7 @@ func init() {
|
||||
Name: "Certification Authority Authorization",
|
||||
Description: "Indicate to certificate authorities whether they are authorized to issue digital certificates for a particular domain name.",
|
||||
Categories: []string{
|
||||
"tls",
|
||||
"security",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeCAA,
|
||||
|
@ -108,7 +108,7 @@ func init() {
|
||||
Name: "SubAlias",
|
||||
Description: "A service alias to another domain/service.",
|
||||
Categories: []string{
|
||||
"internal",
|
||||
"alias",
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
@ -128,7 +128,7 @@ func init() {
|
||||
Name: "Alias",
|
||||
Description: "Maps an alias to another (canonical) domain.",
|
||||
Categories: []string{
|
||||
"internal",
|
||||
"alias",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeCNAME,
|
||||
|
@ -26,6 +26,12 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type DKIM struct {
|
||||
@ -111,3 +117,76 @@ func (t *DKIM) String() string {
|
||||
|
||||
return strings.Join(fields, ";")
|
||||
}
|
||||
|
||||
type DKIMRecord struct {
|
||||
Selector string `json:"selector" happydomain:"label=Selector,placeholder=reykjavik,required,description=Name of the key"`
|
||||
DKIM
|
||||
}
|
||||
|
||||
func (s *DKIMRecord) GetNbResources() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (s *DKIMRecord) GenComment(origin string) string {
|
||||
return s.Selector
|
||||
}
|
||||
|
||||
func (s *DKIMRecord) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin(s.Selector+"._domainkey", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(s.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func dkim_analyze(a *Analyzer) (err error) {
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeTXT}) {
|
||||
dkidx := strings.Index(record.NameFQDN, "._domainkey.")
|
||||
if dkidx <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
service := &DKIMRecord{
|
||||
Selector: record.NameFQDN[:dkidx],
|
||||
}
|
||||
|
||||
err = service.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.UseRR(record, record.NameFQDN[dkidx+12:], service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &DKIMRecord{}
|
||||
},
|
||||
dkim_analyze,
|
||||
ServiceInfos{
|
||||
Name: "DKIM",
|
||||
Description: "DomainKeys Identified Mail, authenticate outgoing emails.",
|
||||
Categories: []string{
|
||||
"email",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@ -26,7 +26,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/services/common"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type DMARC struct {
|
||||
@ -43,6 +48,45 @@ type DMARC struct {
|
||||
Percent uint8 `json:"pct" happydomain:"label=Policy applies on,description=Percentage of messages to which the DMARC policy is to be applied.,unit=%"`
|
||||
}
|
||||
|
||||
func (t *DMARC) GetNbResources() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *DMARC) GenComment(origin string) string {
|
||||
var b strings.Builder
|
||||
|
||||
if t.ADKIM && t.ASPF {
|
||||
b.WriteString("strict ")
|
||||
} else if t.ADKIM {
|
||||
b.WriteString("SPF relaxed ")
|
||||
} else if t.ASPF {
|
||||
b.WriteString("DKIM relaxed ")
|
||||
} else {
|
||||
b.WriteString("relaxed ")
|
||||
}
|
||||
|
||||
if t.Request != "" {
|
||||
b.WriteString(t.Request)
|
||||
b.WriteString(" ")
|
||||
}
|
||||
|
||||
if t.Percent < 100 {
|
||||
b.WriteString(strconv.Itoa(int(t.Percent)))
|
||||
b.WriteString(" %")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t *DMARC) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin("_dmarc", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(t.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func analyseFields(txt string) map[string]string {
|
||||
ret := map[string]string{}
|
||||
|
||||
@ -167,3 +211,47 @@ func (t *DMARC) String() string {
|
||||
|
||||
return strings.Join(fields, ";")
|
||||
}
|
||||
|
||||
func dmarc_analyze(a *Analyzer) (err error) {
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeTXT, Prefix: "_dmarc"}) {
|
||||
service := &DMARC{}
|
||||
|
||||
err = service.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.UseRR(record, strings.TrimPrefix(record.NameFQDN, "_dmarc."), service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &DMARC{}
|
||||
},
|
||||
dmarc_analyze,
|
||||
ServiceInfos{
|
||||
Name: "DMARC",
|
||||
Description: "Domain-based Message Authentication, Reporting and Conformance.",
|
||||
Categories: []string{
|
||||
"email",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@ -25,6 +25,12 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type MTA_STS struct {
|
||||
@ -32,6 +38,22 @@ type MTA_STS struct {
|
||||
Id string `json:"id" happydomain:"label=Policy Identifier,placeholder=,description=A short string used to track policy updates."`
|
||||
}
|
||||
|
||||
func (t *MTA_STS) GetNbResources() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *MTA_STS) GenComment(origin string) string {
|
||||
return t.Id
|
||||
}
|
||||
|
||||
func (t *MTA_STS) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin("_mta-sts", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(t.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *MTA_STS) Analyze(txt string) error {
|
||||
fields := strings.Split(txt, ";")
|
||||
|
||||
@ -68,3 +90,52 @@ func (t *MTA_STS) Analyze(txt string) error {
|
||||
func (t *MTA_STS) String() string {
|
||||
return fmt.Sprintf("v=STSv%d; id=%s", t.Version, t.Id)
|
||||
}
|
||||
|
||||
func mtasts_analyze(a *Analyzer) (err error) {
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeTXT, Prefix: "_mta-sts."}) {
|
||||
// rfc8461: 3.1 records that do not begin with "v=STSv1;" are discarded
|
||||
if !strings.HasPrefix(record.GetTargetTXTJoined(), "v=STS") {
|
||||
continue
|
||||
}
|
||||
|
||||
service := &MTA_STS{}
|
||||
|
||||
err = service.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.UseRR(record, strings.TrimPrefix(record.NameFQDN, "_mta-sts."), service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &MTA_STS{}
|
||||
},
|
||||
mtasts_analyze,
|
||||
ServiceInfos{
|
||||
Name: "MTA-STS",
|
||||
Description: "SMTP MTA Strict Transport Security.",
|
||||
Categories: []string{
|
||||
"email",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
123
services/mx.go
123
services/mx.go
@ -21,9 +21,130 @@
|
||||
|
||||
package svcs
|
||||
|
||||
import ()
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type MX struct {
|
||||
Target string `json:"target"`
|
||||
Preference uint16 `json:"preference,omitempty"`
|
||||
}
|
||||
|
||||
type MXs struct {
|
||||
MX []MX `json:"mx" happydomain:"label=EMail Servers,required"`
|
||||
}
|
||||
|
||||
func (s *MXs) GetNbResources() int {
|
||||
return len(s.MX)
|
||||
}
|
||||
|
||||
func (s *MXs) GenComment(origin string) string {
|
||||
poolMX := map[string]int{}
|
||||
|
||||
for _, mx := range s.MX {
|
||||
labels := dns.SplitDomainName(mx.Target)
|
||||
nbLabel := len(labels)
|
||||
|
||||
var dn string
|
||||
if nbLabel <= 2 {
|
||||
dn = mx.Target
|
||||
} else if len(labels[nbLabel-2]) < 4 {
|
||||
dn = strings.Join(labels[nbLabel-3:], ".") + "."
|
||||
} else {
|
||||
dn = strings.Join(labels[nbLabel-2:], ".") + "."
|
||||
}
|
||||
|
||||
poolMX[dn] += 1
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
first := true
|
||||
|
||||
for dn, nb := range poolMX {
|
||||
if !first {
|
||||
buffer.WriteString("; ")
|
||||
} else {
|
||||
first = !first
|
||||
}
|
||||
buffer.WriteString(strings.TrimSuffix(dn, "."+origin))
|
||||
if nb > 1 {
|
||||
buffer.WriteString(fmt.Sprintf(" ×%d", nb))
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (s *MXs) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
for _, mx := range s.MX {
|
||||
rc := utils.NewRecordConfig(domain, "MX", ttl, origin)
|
||||
rc.MxPreference = mx.Preference
|
||||
rc.SetTarget(utils.DomainFQDN(mx.Target, origin))
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mx_analyze(a *Analyzer) (err error) {
|
||||
service := &MXs{}
|
||||
|
||||
// Handle only MX records
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeMX}) {
|
||||
dn := record.NameFQDN
|
||||
|
||||
service.MX = append(
|
||||
service.MX,
|
||||
MX{
|
||||
Target: record.GetTargetField(),
|
||||
Preference: record.MxPreference,
|
||||
},
|
||||
)
|
||||
|
||||
err = a.UseRR(
|
||||
record,
|
||||
dn,
|
||||
service,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &MXs{}
|
||||
},
|
||||
mx_analyze,
|
||||
ServiceInfos{
|
||||
Name: "E-Mail servers",
|
||||
Description: "Receives e-mail with this domain.",
|
||||
Categories: []string{
|
||||
"email",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeMX,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
Single: true,
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeMX,
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@ -85,6 +85,9 @@ func init() {
|
||||
naptr_analyze,
|
||||
ServiceInfos{
|
||||
Name: "Naming Authority Pointer",
|
||||
Categories: []string{
|
||||
"telephony",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeNAPTR,
|
||||
},
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/services"
|
||||
"git.happydns.org/happyDomain/services/abstract"
|
||||
)
|
||||
|
||||
type GSuite struct {
|
||||
@ -37,7 +36,7 @@ type GSuite struct {
|
||||
}
|
||||
|
||||
func (s *GSuite) GenKnownSvcs() []happydns.Service {
|
||||
knownSvc := &abstract.EMail{
|
||||
knownSvc := &svcs.MXs{
|
||||
MX: []svcs.MX{
|
||||
svcs.MX{Target: "aspmx.l.google.com.", Preference: 1},
|
||||
svcs.MX{Target: "alt1.aspmx.l.google.com.", Preference: 5},
|
||||
@ -45,9 +44,6 @@ func (s *GSuite) GenKnownSvcs() []happydns.Service {
|
||||
svcs.MX{Target: "alt3.aspmx.l.google.com.", Preference: 10},
|
||||
svcs.MX{Target: "alt4.aspmx.l.google.com.", Preference: 10},
|
||||
},
|
||||
SPF: &svcs.SPF{
|
||||
Directives: []string{"include:_spf.google.com", "~all"},
|
||||
},
|
||||
}
|
||||
|
||||
if len(s.ValidationCode) > 0 {
|
||||
@ -57,7 +53,10 @@ func (s *GSuite) GenKnownSvcs() []happydns.Service {
|
||||
})
|
||||
}
|
||||
|
||||
return []happydns.Service{knownSvc}
|
||||
return []happydns.Service{knownSvc, &svcs.SPF{
|
||||
Version: 1,
|
||||
Directives: []string{"include:_spf.google.com", "~all"},
|
||||
}}
|
||||
}
|
||||
|
||||
func (s *GSuite) GetNbResources() int {
|
||||
@ -149,7 +148,6 @@ func init() {
|
||||
Description: "The suite of cloud computing, productivity and collaboration tools by Google.",
|
||||
Family: svcs.Provider,
|
||||
Categories: []string{
|
||||
"cloud",
|
||||
"email",
|
||||
},
|
||||
Restrictions: svcs.ServiceRestrictions{
|
||||
|
@ -73,7 +73,7 @@ func init() {
|
||||
Name: "Pointer",
|
||||
Description: "A pointer to another domain.",
|
||||
Categories: []string{
|
||||
"internal",
|
||||
"domain name",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypePTR,
|
||||
|
@ -25,7 +25,13 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/pkg/spflib"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type SPF struct {
|
||||
@ -33,6 +39,23 @@ type SPF struct {
|
||||
Directives []string `json:"directives" happydomain:"label=Directives,placeholder=ip4:203.0.113.12"`
|
||||
}
|
||||
|
||||
func (s *SPF) GetNbResources() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (s *SPF) GenComment(origin string) string {
|
||||
return fmt.Sprintf("%d directives", len(s.Directives))
|
||||
}
|
||||
|
||||
func (s *SPF) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
rc := utils.NewRecordConfig(domain, "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(s.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *SPF) Analyze(txt string) error {
|
||||
_, err := spflib.Parse(txt, nil)
|
||||
if err != nil {
|
||||
@ -61,7 +84,65 @@ func (t *SPF) Analyze(txt string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SPF) String() string {
|
||||
directives := append([]string{fmt.Sprintf("v=spf%d", t.Version)}, t.Directives...)
|
||||
func spf_analyze(a *Analyzer) (err error) {
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeTXT, Contains: "v=spf1"}) {
|
||||
service := &SPF{}
|
||||
|
||||
err = service.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.UseRR(record, record.NameFQDN, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeSPF, Contains: "v=spf1"}) {
|
||||
service := &SPF{}
|
||||
|
||||
err = service.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.UseRR(record, record.NameFQDN, service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SPF) String() string {
|
||||
directives := append([]string{fmt.Sprintf("v=spf%d", s.Version)}, s.Directives...)
|
||||
return strings.Join(directives, " ")
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &SPF{}
|
||||
},
|
||||
spf_analyze,
|
||||
ServiceInfos{
|
||||
Name: "SPF",
|
||||
Description: "Sender Policy Framework, to authenticate domain name on email sending.",
|
||||
Categories: []string{
|
||||
"email",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
dns.TypeSPF,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@ -21,10 +21,96 @@
|
||||
|
||||
package svcs
|
||||
|
||||
import ()
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type SSHFP struct {
|
||||
Algorithm uint8 `json:"algorithm"`
|
||||
Type uint8 `json:"type"`
|
||||
FingerPrint string `json:"fingerprint"`
|
||||
}
|
||||
|
||||
type SSHFPs struct {
|
||||
SSHFP []*SSHFP `json:"SSHFP,omitempty" happydomain:"label=SSH Fingerprint,description=Server's SSH fingerprint"`
|
||||
}
|
||||
|
||||
func (s *SSHFPs) GetNbResources() int {
|
||||
return len(s.SSHFP)
|
||||
}
|
||||
|
||||
func (s *SSHFPs) GenComment(origin string) string {
|
||||
return fmt.Sprintf("%d fingerprints", len(s.SSHFP))
|
||||
}
|
||||
|
||||
func (s *SSHFPs) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
for _, sshfp := range s.SSHFP {
|
||||
rc := utils.NewRecordConfig(domain, "SSHFP", ttl, origin)
|
||||
rc.SshfpAlgorithm = sshfp.Algorithm
|
||||
rc.SshfpFingerprint = sshfp.Type
|
||||
rc.SetTarget(sshfp.FingerPrint)
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func sshfp_analyze(a *Analyzer) error {
|
||||
pool := map[string]models.Records{}
|
||||
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeSSHFP}) {
|
||||
domain := record.NameFQDN
|
||||
|
||||
pool[domain] = append(pool[domain], record)
|
||||
}
|
||||
|
||||
for dn, rrs := range pool {
|
||||
s := &SSHFPs{}
|
||||
|
||||
for _, rr := range rrs {
|
||||
if rr.Type == "SSHFP" {
|
||||
s.SSHFP = append(s.SSHFP, &SSHFP{
|
||||
Algorithm: rr.SshfpAlgorithm,
|
||||
Type: rr.SshfpFingerprint,
|
||||
FingerPrint: rr.GetTargetField(),
|
||||
})
|
||||
|
||||
a.UseRR(rr, dn, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &SSHFPs{}
|
||||
},
|
||||
sshfp_analyze,
|
||||
ServiceInfos{
|
||||
Name: "SSHFP",
|
||||
Description: "Store SSH key fingerprints in DNS.",
|
||||
Categories: []string{
|
||||
"security",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeSSHFP,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeSSHFP,
|
||||
},
|
||||
},
|
||||
},
|
||||
1000,
|
||||
)
|
||||
}
|
||||
|
@ -25,6 +25,12 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v4/models"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/utils"
|
||||
)
|
||||
|
||||
type TLS_RPT struct {
|
||||
@ -32,6 +38,23 @@ type TLS_RPT struct {
|
||||
Rua []string `json:"rua" happydomain:"label=Aggregate Report URI,placeholder=https://example.com/path|mailto:name@example.com"`
|
||||
}
|
||||
|
||||
func (t *TLS_RPT) GetNbResources() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *TLS_RPT) GenComment(origin string) string {
|
||||
return strings.Join(t.Rua, ", ")
|
||||
}
|
||||
|
||||
func (t *TLS_RPT) GenRRs(domain string, ttl uint32, origin string) (rrs models.Records) {
|
||||
rc := utils.NewRecordConfig(utils.DomainJoin("_smtp._tls", domain), "TXT", ttl, origin)
|
||||
rc.SetTargetTXT(t.String())
|
||||
|
||||
rrs = append(rrs, rc)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TLS_RPT) Analyze(txt string) error {
|
||||
fields := strings.Split(txt, ";")
|
||||
|
||||
@ -72,3 +95,52 @@ func (t *TLS_RPT) Analyze(txt string) error {
|
||||
func (t *TLS_RPT) String() string {
|
||||
return fmt.Sprintf("v=TLSRPTv%d; rua=%s", t.Version, strings.Join(t.Rua, ","))
|
||||
}
|
||||
|
||||
func tlsrpt_analyze(a *Analyzer) (err error) {
|
||||
for _, record := range a.SearchRR(AnalyzerRecordFilter{Type: dns.TypeTXT, Prefix: "_smtp._tls."}) {
|
||||
// rfc8460: 3. records that do not begin with "v=TLSRPTv1;" are discarded
|
||||
if !strings.HasPrefix(record.GetTargetTXTJoined(), "v=TLSRPT") {
|
||||
continue
|
||||
}
|
||||
|
||||
service := &TLS_RPT{}
|
||||
|
||||
err = service.Analyze(record.GetTargetTXTJoined())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.UseRR(record, strings.TrimPrefix(record.NameFQDN, "_smtp._tls."), service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterService(
|
||||
func() happydns.Service {
|
||||
return &TLS_RPT{}
|
||||
},
|
||||
tlsrpt_analyze,
|
||||
ServiceInfos{
|
||||
Name: "TLS-RPT",
|
||||
Description: "SMTP TLS Reporting.",
|
||||
Categories: []string{
|
||||
"email",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
Restrictions: ServiceRestrictions{
|
||||
NearAlone: true,
|
||||
NeedTypes: []uint16{
|
||||
dns.TypeTXT,
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ func init() {
|
||||
Name: "TLSA records",
|
||||
Description: "Publish TLS certificates exposed by your services.",
|
||||
Categories: []string{
|
||||
"tls",
|
||||
"security",
|
||||
},
|
||||
RecordTypes: []uint16{
|
||||
dns.TypeTLSA,
|
||||
|
Loading…
x
Reference in New Issue
Block a user