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