Repository: xenolf/lego (go)

Issue:
DuckDNS sub domains do not work
When trying to get a certificate for a subdomain it does not work. It works for wild cards, but not individual subdomains.
It is because of a flawed assumption. See https://plus.google.com/105377443134795862842/posts/YPt1GmJ2gxL for more info.
One way around this would be to strip off the subdomain when generating the url for presenting the challenge.

Pull request:
duckdns: fix subsubdomain
fix issue #626 by always converting url to subdomain.duckdns.org

Fixes #626

Patch:
diff --git a/providers/dns/duckdns/duckdns.go b/providers/dns/duckdns/duckdns.go
index 6bbf7635..2ac53f03 100644
--- a/providers/dns/duckdns/duckdns.go
+++ b/providers/dns/duckdns/duckdns.go
@@ -7,8 +7,12 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
 	"time"
 
+	"github.com/miekg/dns"
 	"github.com/xenolf/lego/acme"
 	"github.com/xenolf/lego/platform/config/env"
 )
@@ -96,9 +100,16 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
 // To update the TXT record we just need to make one simple get request.
 // In DuckDNS you only have one TXT record shared with the domain and all sub domains.
 func updateTxtRecord(domain, token, txt string, clear bool) error {
-	u := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s&clear=%t&txt=%s", domain, token, clear, txt)
+	u, _ := url.Parse("https://www.duckdns.org/update")
 
-	response, err := acme.HTTPClient.Get(u)
+	query := u.Query()
+	query.Set("domains", getMainDomain(domain))
+	query.Set("token", token)
+	query.Set("clear", strconv.FormatBool(clear))
+	query.Set("txt", txt)
+	u.RawQuery = query.Encode()
+
+	response, err := acme.HTTPClient.Get(u.String())
 	if err != nil {
 		return err
 	}
@@ -115,3 +126,23 @@ func updateTxtRecord(domain, token, txt string, clear bool) error {
 	}
 	return nil
 }
+
+// DuckDNS only lets you write to your subdomain
+// so it must be in format subdomain.duckdns.org
+// not in format subsubdomain.subdomain.duckdns.org
+// so strip off everything that is not top 3 levels
+func getMainDomain(domain string) string {
+	domain = acme.UnFqdn(domain)
+
+	split := dns.Split(domain)
+	if strings.HasSuffix(strings.ToLower(domain), "duckdns.org") {
+		if len(split) < 3 {
+			return ""
+		}
+
+		firstSubDomainIndex := split[len(split)-3]
+		return domain[firstSubDomainIndex:]
+	}
+
+	return domain[split[len(split)-1]:]
+}


Test patch:
diff --git a/providers/dns/duckdns/duckdns_test.go b/providers/dns/duckdns/duckdns_test.go
index 25c2c686..40bf5eac 100644
--- a/providers/dns/duckdns/duckdns_test.go
+++ b/providers/dns/duckdns/duckdns_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"github.com/xenolf/lego/platform/tester"
 )
@@ -86,6 +87,65 @@ func TestNewDNSProviderConfig(t *testing.T) {
 	}
 }
 
+func Test_getMainDomain(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		domain   string
+		expected string
+	}{
+		{
+			desc:     "empty",
+			domain:   "",
+			expected: "",
+		},
+		{
+			desc:     "missing sub domain",
+			domain:   "duckdns.org",
+			expected: "",
+		},
+		{
+			desc:     "explicit domain: sub domain",
+			domain:   "sub.duckdns.org",
+			expected: "sub.duckdns.org",
+		},
+		{
+			desc:     "explicit domain: subsub domain",
+			domain:   "my.sub.duckdns.org",
+			expected: "sub.duckdns.org",
+		},
+		{
+			desc:     "explicit domain: subsubsub domain",
+			domain:   "my.sub.sub.duckdns.org",
+			expected: "sub.duckdns.org",
+		},
+		{
+			desc:     "only subname: sub domain",
+			domain:   "sub",
+			expected: "sub",
+		},
+		{
+			desc:     "only subname: subsub domain",
+			domain:   "my.sub",
+			expected: "sub",
+		},
+		{
+			desc:     "only subname: subsubsub domain",
+			domain:   "my.sub.sub",
+			expected: "sub",
+		},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(test.desc, func(t *testing.T) {
+			t.Parallel()
+
+			wDomain := getMainDomain(test.domain)
+			assert.Equal(t, test.expected, wDomain)
+		})
+	}
+}
+
 func TestLivePresent(t *testing.T) {
 	if !envTest.IsLiveTest() {
 		t.Skip("skipping live test")
@@ -108,7 +168,7 @@ func TestLiveCleanUp(t *testing.T) {
 	provider, err := NewDNSProvider()
 	require.NoError(t, err)
 
-	time.Sleep(10 * time.Second)
+	time.Sleep(1 * time.Second)
 
 	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
 	require.NoError(t, err)