penguin/webhook-freedns

freedns webhook for cert-manager

commit 289a8a57ee57d1fa4091d68a7b6a648a473456d1

author斟酌 鵬兄 <tgckpg@gmail.com>
date2022-03-13T19:03:34Z
subjectBasic Login / Select Domain / Add Record
commit 289a8a57ee57d1fa4091d68a7b6a648a473456d1
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2022-03-13T19:03:34Z

    Basic Login / Select Domain / Add Record
---
 freedns/freedns.go | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 main.go            |   5 +-
 2 files changed, 211 insertions(+), 4 deletions(-)

diff --git a/freedns/freedns.go b/freedns/freedns.go
new file mode 100755
index 0000000..fd9bdec
--- /dev/null
+++ b/freedns/freedns.go
@@ -0,0 +1,210 @@
+package freedns
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"golang.org/x/net/html"
+)
+
+type FreeDNSOperations interface {
+	Login()
+	SelectDomain()
+	AddRecord()
+}
+
+type FreeDNS struct {
+	AuthCookie *http.Cookie
+	DomainId   string
+}
+
+const URI_LOGIN string = "https://freedns.afraid.org/zc.php?step=2"
+const URI_DOMAIN string = "https://freedns.afraid.org/domain/"
+const URI_ADD_RECORD string = "https://freedns.afraid.org/subdomain/save.php?step=2"
+
+// const URI_LOGIN string = "http://127.0.0.1:1234/"
+
+func _HttpRequest(method string, url string, PostData url.Values, ExCookie *http.Cookie) (*http.Response, string, error) {
+	client := http.Client{
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse
+		},
+	}
+
+	var req *http.Request
+	var err error
+
+	if method == "GET" {
+		req, err = http.NewRequest(method, url, nil)
+	} else if method == "POST" {
+		req, err = http.NewRequest(method, url, strings.NewReader(PostData.Encode()))
+		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+	} else {
+		return nil, "", errors.New("Method + \"" + method + "\" is not supported")
+	}
+
+	if err != nil {
+		return nil, "", err
+	}
+
+	if ExCookie != nil {
+		req.AddCookie(ExCookie)
+	}
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return resp, "", err
+	}
+
+	respData, err := ioutil.ReadAll(resp.Body)
+	resp.Body.Close()
+
+	if err != nil {
+		return resp, "", err
+	}
+
+	return resp, string(respData), nil
+}
+
+func (dnsObj *FreeDNS) Login(Username string, Password string) error {
+
+	authData := url.Values{}
+	authData.Set("username", Username)
+	authData.Set("password", Password)
+	authData.Set("submit", "Login")
+	authData.Set("action", "auth")
+
+	resp, respString, err := _HttpRequest("POST", URI_LOGIN, authData, nil)
+	if err != nil {
+		return err
+	}
+
+	if strings.Contains(respString, "Invalid UserID/Pass") {
+		return errors.New("Invalid UserID/Pass")
+	}
+
+	for _, cookie := range resp.Cookies() {
+		if cookie.Name == "dns_cookie" {
+			dnsObj.AuthCookie = cookie
+		}
+	}
+
+	return nil
+}
+
+func (dnsObj *FreeDNS) SelectDomain(DomainName string) error {
+	if dnsObj.AuthCookie == nil {
+		return errors.New("Not logged in")
+	}
+
+	resp, respStr, err := _HttpRequest("GET", URI_DOMAIN, nil, dnsObj.AuthCookie)
+	if err != nil {
+		return err
+	}
+
+	if resp.StatusCode == 302 {
+		return errors.New("dns_cookie maybe expired")
+	}
+
+	htmlTokens := html.NewTokenizer(strings.NewReader(respStr))
+
+	inBold := false
+	lookForA := false
+	dnsObj.DomainId = ""
+loop:
+	for {
+		tt := htmlTokens.Next()
+		switch tt {
+		case html.ErrorToken:
+			break loop
+		case html.TextToken:
+			if inBold && strings.TrimSpace(htmlTokens.Token().Data) == DomainName {
+				fmt.Println("Found " + DomainName + ", looking for domain id")
+				lookForA = true
+			}
+		case html.StartTagToken:
+			_t, hasAttr := htmlTokens.TagName()
+			tagName := string(_t)
+			inBold = tagName == "b"
+			if lookForA && tagName == "a" && hasAttr {
+				for {
+					attrKey, attrValue, moreAttr := htmlTokens.TagAttr()
+					_href := string(attrValue)
+					if string(attrKey) == "href" && strings.Contains(_href, "/subdomain/?limit=") {
+						dnsObj.DomainId = strings.TrimLeft(_href, "/subdomain/?limit=")
+						fmt.Printf("Domain id for \"%s\" is %s\n", DomainName, dnsObj.DomainId)
+						break loop
+					}
+					if !moreAttr {
+						break
+					}
+				}
+			}
+		}
+	}
+
+	if dnsObj.DomainId == "" {
+		return errors.New(fmt.Sprintf("Unable to locate domain id for \"%s\" under /domain/ page", DomainName))
+	}
+
+	return nil
+}
+
+func (dnsObj *FreeDNS) AddRecord(RecordType string, Subdomain string, Address string, Wildcard bool, ttl string) error {
+	if dnsObj.DomainId == "" {
+		return errors.New("No domain selected")
+	}
+	recordData := url.Values{}
+	recordData.Set("type", RecordType)
+	recordData.Set("domain_id", dnsObj.DomainId)
+	recordData.Set("subdomain", Subdomain)
+	recordData.Set("address", Address)
+	recordData.Set("send", "Save!")
+	if Wildcard {
+		recordData.Set("wildcard", "1")
+	}
+
+	resp, respStr, err := _HttpRequest("POST", URI_ADD_RECORD, recordData, dnsObj.AuthCookie)
+	if err != nil {
+		return err
+	}
+
+	if resp.StatusCode != 302 {
+
+		// Record already exists, treated as success
+		if strings.Contains(respStr, "already have another already existent") {
+			fmt.Println("Record already exists")
+			return nil
+		}
+
+		htmlTokens := html.NewTokenizer(strings.NewReader(respStr))
+	loop:
+		for {
+			tt := htmlTokens.Next()
+			switch tt {
+			case html.ErrorToken:
+				break loop
+			case html.TextToken:
+			case html.StartTagToken:
+			}
+		}
+
+		return errors.New("Unknown error while submitting record")
+	}
+
+	_Location, err := resp.Location()
+	if err != nil {
+		return err
+	}
+
+	if strings.Contains(_Location.Path, "/zc.php") {
+		fmt.Println("Error on AddRecord: Cookie expired")
+		return errors.New("Cookie exipired")
+	}
+
+	return nil
+}
diff --git a/main.go b/main.go
index 52d8bf1..305aee3 100644
--- a/main.go
+++ b/main.go
@@ -93,9 +93,6 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
 		return err
 	}
 
-	// TODO: do something more useful with the decoded configuration
-	fmt.Printf("Decoded configuration %v", cfg)
-
 	secretName := cfg.SecretRef
 	secretObj, err := c.client.CoreV1().Secrets(ch.ResourceNamespace).Get(context.Background(), secretName, metav1.GetOptions{})
 	if err != nil {
@@ -105,7 +102,7 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
 	username := string(secretObj.Data["username"])
 	password := string(secretObj.Data["password"])
 
-	fmt.Printf("Domain: %s, Auth: %s %s", cfg.Domain, username, password)
+	fmt.Printf("Domain: %s, Auth: %s %s\n", cfg.Domain, username, password)
 
 	// TODO: add code that sets a record in the DNS provider's console
 	return nil