Merge pull request #506 from letsencrypt/native-mx-lookup

Replace net.LookupMX use with core.LookupMX
This commit is contained in:
Jacob Hoffman-Andrews 2015-07-22 15:17:38 -07:00
commit 61394e4f2d
11 changed files with 79 additions and 26 deletions

View File

@ -6,8 +6,11 @@
package main
import (
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/cmd"
blog "github.com/letsencrypt/boulder/log"
@ -36,6 +39,9 @@ func main() {
rai := ra.NewRegistrationAuthorityImpl()
rai.AuthzBase = c.Common.BaseURL + wfe.AuthzPath
rai.MaxKeySize = c.Common.MaxKeySize
raDNSTimeout, err := time.ParseDuration(c.RA.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
rai.DNSResolver = core.NewDNSResolverImpl(raDNSTimeout, []string{c.RA.DNSResolver})
go cmd.ProfileCmd("RA", stats)

View File

@ -92,11 +92,14 @@ func main() {
cmd.FailOnError(err, "Couldn't parse issuer caching duration")
ra := ra.NewRegistrationAuthorityImpl()
raDNSTimeout, err := time.ParseDuration(c.RA.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
ra.DNSResolver = core.NewDNSResolverImpl(raDNSTimeout, []string{c.RA.DNSResolver})
va := va.NewValidationAuthorityImpl(c.CA.TestMode)
dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse DNS timeout")
va.DNSResolver = core.NewDNSResolverImpl(dnsTimeout, []string{c.VA.DNSResolver})
vaDNSTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse VA DNS timeout")
va.DNSResolver = core.NewDNSResolverImpl(vaDNSTimeout, []string{c.VA.DNSResolver})
va.UserAgent = c.VA.UserAgent
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBConnect)

View File

@ -90,6 +90,9 @@ type Config struct {
}
RA struct {
DNSResolver string
DNSTimeout string
// DebugAddr is the address to run the /debug handlers on.
DebugAddr string
}

View File

@ -168,3 +168,25 @@ func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time
}
return CAAs, rtt, nil
}
// LookupMX sends a DNS query to find a MX record associated hostname and returns the
// record target.
func (dnsResolver *DNSResolverImpl) LookupMX(hostname string) ([]string, time.Duration, error) {
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeMX)
if err != nil {
return nil, 0, err
}
if r.Rcode != dns.RcodeSuccess {
err = fmt.Errorf("DNS failure: %d-%s for MX query", r.Rcode, dns.RcodeToString[r.Rcode])
return nil, rtt, err
}
var results []string
for _, answer := range r.Answer {
if mx, ok := answer.(*dns.MX); ok {
results = append(results, mx.Mx)
}
}
return results, rtt, nil
}

View File

@ -145,4 +145,5 @@ type DNSResolver interface {
LookupHost(string) ([]net.IP, time.Duration, time.Duration, error)
LookupCNAME(string) (string, time.Duration, error)
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
LookupMX(string) ([]string, time.Duration, error)
}

View File

@ -97,3 +97,14 @@ func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error)
}
return results, 0, nil
}
// LookupMX is a mock
func (mock *MockDNS) LookupMX(domain string) ([]string, time.Duration, error) {
switch domain {
case "letsencrypt.org":
fallthrough
case "email.com":
return []string{"mail.email.com"}, 0, nil
}
return nil, 0, nil
}

View File

@ -9,7 +9,6 @@ import (
"crypto/x509"
"errors"
"fmt"
"net"
"net/mail"
"net/url"
"regexp"
@ -27,11 +26,12 @@ import (
// NOTE: All of the fields in RegistrationAuthorityImpl need to be
// populated, or there is a risk of panic.
type RegistrationAuthorityImpl struct {
CA core.CertificateAuthority
VA core.ValidationAuthority
SA core.StorageAuthority
PA core.PolicyAuthority
log *blog.AuditLogger
CA core.CertificateAuthority
VA core.ValidationAuthority
SA core.StorageAuthority
PA core.PolicyAuthority
DNSResolver core.DNSResolver
log *blog.AuditLogger
AuthzBase string
MaxKeySize int
@ -53,7 +53,7 @@ func lastPathSegment(url core.AcmeURL) string {
return allButLastPathSegment.ReplaceAllString(url.Path, "")
}
func validateEmail(address string) (err error) {
func validateEmail(address string, resolver core.DNSResolver) (err error) {
_, err = mail.ParseAddress(address)
if err != nil {
err = core.MalformedRequestError(fmt.Sprintf("%s is not a valid e-mail address", address))
@ -61,8 +61,8 @@ func validateEmail(address string) (err error) {
}
splitEmail := strings.SplitN(address, "@", -1)
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
var mx []*net.MX
mx, err = net.LookupMX(domain)
var mx []string
mx, _, err = resolver.LookupMX(domain)
if err != nil || len(mx) == 0 {
err = core.MalformedRequestError(fmt.Sprintf("No MX record for domain %s", domain))
return
@ -70,13 +70,13 @@ func validateEmail(address string) (err error) {
return
}
func validateContacts(contacts []core.AcmeURL) (err error) {
func validateContacts(contacts []core.AcmeURL, resolver core.DNSResolver) (err error) {
for _, contact := range contacts {
switch contact.Scheme {
case "tel":
continue
case "mailto":
err = validateEmail(contact.Opaque)
err = validateEmail(contact.Opaque, resolver)
if err != nil {
return
}
@ -116,7 +116,7 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (re
}
reg.MergeUpdate(init)
err = validateContacts(reg.Contact)
err = validateContacts(reg.Contact, ra.DNSResolver)
if err != nil {
return
}
@ -367,7 +367,7 @@ func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest,
func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
base.MergeUpdate(update)
err = validateContacts(base.Contact)
err = validateContacts(base.Contact, ra.DNSResolver)
if err != nil {
return
}

View File

@ -200,6 +200,7 @@ func initAuthorities(t *testing.T) (core.CertificateAuthority, *DummyValidationA
ra.PA = pa
ra.AuthzBase = "http://acme.invalid/authz/"
ra.MaxKeySize = 4096
ra.DNSResolver = &mocks.MockDNS{}
AuthzInitial.RegistrationID = Registration.ID
@ -230,38 +231,38 @@ func TestValidateContacts(t *testing.T) {
invalidEmail, _ := url.Parse("mailto:admin@example.com")
malformedEmail, _ := url.Parse("mailto:admin.com")
err := validateContacts([]core.AcmeURL{})
err := validateContacts([]core.AcmeURL{}, &mocks.MockDNS{})
test.AssertNotError(t, err, "No Contacts")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*tel)})
err = validateContacts([]core.AcmeURL{core.AcmeURL(*tel)}, &mocks.MockDNS{})
test.AssertNotError(t, err, "Simple Telephone")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*validEmail)})
err = validateContacts([]core.AcmeURL{core.AcmeURL(*validEmail)}, &mocks.MockDNS{})
test.AssertNotError(t, err, "Valid Email")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*invalidEmail)})
err = validateContacts([]core.AcmeURL{core.AcmeURL(*invalidEmail)}, &mocks.MockDNS{})
test.AssertError(t, err, "Invalid Email")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*malformedEmail)})
err = validateContacts([]core.AcmeURL{core.AcmeURL(*malformedEmail)}, &mocks.MockDNS{})
test.AssertError(t, err, "Malformed Email")
err = validateContacts([]core.AcmeURL{core.AcmeURL(*ansible)})
err = validateContacts([]core.AcmeURL{core.AcmeURL(*ansible)}, &mocks.MockDNS{})
test.AssertError(t, err, "Unknown scehme")
}
func TestValidateEmail(t *testing.T) {
err := validateEmail("an email`")
err := validateEmail("an email`", &mocks.MockDNS{})
test.AssertError(t, err, "Malformed")
err = validateEmail("a@not.a.domain")
err = validateEmail("a@not.a.domain", &mocks.MockDNS{})
test.AssertError(t, err, "Cannot resolve")
t.Logf("No Resolve: %s", err)
err = validateEmail("a@example.com")
err = validateEmail("a@example.com", &mocks.MockDNS{})
test.AssertError(t, err, "No MX Record")
t.Logf("No MX: %s", err)
err = validateEmail("a@email.com")
err = validateEmail("a@email.com", &mocks.MockDNS{})
test.AssertNotError(t, err, "Valid")
}

View File

@ -114,6 +114,8 @@
},
"ra": {
"dnsResolver": "8.8.8.8:53",
"dnsTimeout": "10s",
"debugAddr": "localhost:8002"
},

View File

@ -101,6 +101,8 @@
},
"ra": {
"dnsResolver": "8.8.8.8:53",
"dnsTimeout": "10s",
"debugAddr": "localhost:8002"
},

View File

@ -105,6 +105,8 @@
},
"ra": {
"dnsResolver": "127.0.0.1:8053",
"dnsTimeout": "10s",
"debugAddr": "localhost:8002"
},