From 7fd7334ee768535c2a4754c3a4b6e11023c56be4 Mon Sep 17 00:00:00 2001 From: puffaboo Date: Mon, 1 Aug 2022 00:19:30 +0100 Subject: [PATCH] added gandi support --- gandi.go | 64 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 +++++ go.sum | 6 +++++ main.go | 76 +++++++++++++++++++++++++++++----------------------- namecheap.go | 44 ++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 gandi.go create mode 100644 go.sum create mode 100644 namecheap.go diff --git a/gandi.go b/gandi.go new file mode 100644 index 0000000..7c9a99a --- /dev/null +++ b/gandi.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + + "github.com/go-gandi/go-gandi" + "github.com/go-gandi/go-gandi/config" + "github.com/go-gandi/go-gandi/livedns" +) + +var ( + gandiEmailRecords = []livedns.DomainRecord{ + { + RrsetType: "MX", + RrsetTTL: 10800, + RrsetName: "@", + RrsetValues: []string{"10 spool.mail.gandi.net.", "50 fb.mail.gandi.net."}, + }, + { + RrsetType: "TXT", + RrsetTTL: 10800, + RrsetName: "@", + RrsetValues: []string{`"v=spf1 include:_mailcust.gandi.net ?all"`}, + }, + } +) + +type Gandi struct{} + +func (g Gandi) IPSet(reqs []IPSetRequest) error { + if len(reqs) == 0 { + return nil + } + if err := g.EnsureAllDomainsAreSame(reqs); err != nil { + return err + } + pwd := reqs[0].Password + domain := reqs[0].Domain + records := make([]livedns.DomainRecord, len(reqs)) + for index, req := range reqs { + records[index] = livedns.DomainRecord{ + RrsetType: "A", + RrsetTTL: 300, + RrsetName: req.Host, + RrsetValues: []string{req.IP}, + } + } + records = append(records, gandiEmailRecords...) + + _, err := gandi.NewLiveDNSClient(config.Config{ + APIKey: pwd, + }).UpdateDomainRecords(domain, records) + return err +} + +func (Gandi) EnsureAllDomainsAreSame(reqs []IPSetRequest) error { + first := reqs[0].Domain + for _, dom := range reqs { + if first != dom.Domain { + return fmt.Errorf("domain mismatch: %s != %s", first, dom.Domain) + } + } + return nil +} diff --git a/go.mod b/go.mod index 845213e..0a0d5e8 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,9 @@ module ncddns go 1.18 + +require ( + github.com/go-gandi/go-gandi v0.4.0 // indirect + github.com/peterhellberg/link v1.1.0 // indirect + moul.io/http2curl v1.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..344cdd5 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/go-gandi/go-gandi v0.4.0 h1:iFVD+x3nJrI611fYvVMk6TKOlUOND/bKnvdQxuZKPSI= +github.com/go-gandi/go-gandi v0.4.0/go.mod h1:9NoYyfWCjFosClPiWjkbbRK5UViaZ4ctpT8/pKSSFlw= +github.com/peterhellberg/link v1.1.0 h1:s2+RH8EGuI/mI4QwrWGSYQCRz7uNgip9BaM04HKu5kc= +github.com/peterhellberg/link v1.1.0/go.mod h1:gtSlOT4jmkY8P47hbTc8PTgiDDWpdPbFYl75keYyBB8= +moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= +moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= diff --git a/main.go b/main.go index b5b3e0b..766a01d 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "io" "io/ioutil" "net/http" - "net/url" "os" "strings" ) @@ -15,9 +14,28 @@ const ( baseURL = "https://dynamicdns.park-your-domain.com/update?" ) +var ( + byName = map[string]IPSetter{ + "namecheap": NameCheap{}, + "gandi": Gandi{}, + } +) + +type IPSetRequest struct { + IP string + Domain string + Password string + Host string +} + +type IPSetter interface { + IPSet([]IPSetRequest) error +} + type DomainTuple struct { Domain string Password string + Setter IPSetter } func Config(path string) ([]DomainTuple, error) { @@ -28,16 +46,21 @@ func Config(path string) ([]DomainTuple, error) { tuples := []DomainTuple{} for _, line := range strings.Split(string(conf), "\n") { line := strings.TrimSpace(line) - if !strings.Contains(line, "=") { + if !strings.Contains(line, ",") { continue } - parts := strings.SplitN(line, "=", 2) - if len(parts) != 2 { + parts := strings.SplitN(line, ",", 3) + if len(parts) != 3 { continue } + setter, ok := byName[parts[0]] + if !ok { + return nil, fmt.Errorf("line [%s] has unsupported provider [%s]", line, parts[0]) + } tuples = append(tuples, DomainTuple{ - Domain: parts[0], - Password: parts[1], + Domain: parts[1], + Password: parts[2], + Setter: setter, }) } return tuples, nil @@ -60,9 +83,20 @@ func main() { fatal("config", err) for _, cfgEntry := range cfg { fmt.Println(cfgEntry.Domain) - err = Set(ip, cfgEntry.Domain, cfgEntry.Password, "*") - fatal(cfgEntry.Domain, err) - err = Set(ip, cfgEntry.Domain, cfgEntry.Password, "@") + err = cfgEntry.Setter.IPSet([]IPSetRequest{ + { + IP: ip, + Domain: cfgEntry.Domain, + Password: cfgEntry.Password, + Host: "*", + }, + { + IP: ip, + Domain: cfgEntry.Domain, + Password: cfgEntry.Password, + Host: "@", + }, + }) fatal(cfgEntry.Domain, err) } } @@ -81,27 +115,3 @@ func IP() (string, error) { return string(body), nil } - -func Set(ip, domain, password, host string) error { - params := url.Values{} - params.Add("host", host) - params.Add("domain", domain) - params.Add("password", password) - params.Add("ip", ip) - - resp, err := http.Get(fmt.Sprintf("%s%s", baseURL, params.Encode())) - if err != nil { - return fmt.Errorf("request: %w", err) - } - defer resp.Body.Close() - - data, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("read body: %w", err) - } - - if resp.StatusCode != http.StatusOK { - fmt.Fprintf(os.Stderr, "got status [%d]: %s\n", resp.StatusCode, string(data)) - } - return nil -} diff --git a/namecheap.go b/namecheap.go new file mode 100644 index 0000000..fbaf096 --- /dev/null +++ b/namecheap.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" +) + +type NameCheap struct{} + +func (nc NameCheap) IPSet(reqs []IPSetRequest) error { + for _, req := range reqs { + if err := nc.SetSingle(req); err != nil { + return fmt.Errorf("%s: %w", req.Domain, err) + } + } + return nil +} + +func (NameCheap) SetSingle(req IPSetRequest) error { + params := url.Values{} + params.Add("host", req.Host) + params.Add("domain", req.Domain) + params.Add("password", req.Password) + params.Add("ip", req.IP) + + resp, err := http.Get(fmt.Sprintf("%s%s", baseURL, params.Encode())) + if err != nil { + return fmt.Errorf("request: %w", err) + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + fmt.Fprintf(os.Stderr, "got status [%d]: %s\n", resp.StatusCode, string(data)) + } + return nil +}