dnsupdater/main.go
2019-07-09 15:13:44 +04:00

266 lines
6.3 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/matryer/try"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
"gopkg.in/gomail.v2"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
"time"
)
type Configuration struct {
Username string `json:"name"`
Email string `json:"email"`
GandiKey string `json:"gandikey"`
Domains []string `json:"domains"`
RouterLogin string `json:"router_login"`
RouterPassword string `json:"router_password"`
RouterStatusUrl string `json:"router_status_url"`
WanIPRegex string `json:"wan_ip_regex"`
MailSettings *struct {
Sender string `json:"sender"`
To []string `json:"to"`
SmtpHost string `json:"smtp_host"`
SmtpLogin string `json:"smtp_login"`
SmtpPassword string `json:"smtp_password"`
SmtpPort int `json:"smtp_port"`
} `json:"mail_settings"`
}
type DnsResponse struct {
RecordType string `json:"rrset_type"`
Ttl int `json:"rrset_ttl"`
Name string `json:"rrset_name"`
Values []string `json:"rrset_values"`
}
var (
configuration *Configuration
configPath string
)
const (
GandiARecordUrl = "https://dns.api.gandi.net/api/v5/domains/%s/records/@/A"
)
func main() {
app := cli.NewApp()
app.Name = "dnsupdater"
app.Usage = "Automatically update dns"
app.Before = before
app.Action = start
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "config, C",
Usage: "path to config file",
Value: "/config",
EnvVar: "CONFIG_PATH",
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func before(c *cli.Context) error {
log.SetLevel(log.InfoLevel)
configPath = path.Join(c.String("config"), "conf.json")
file, err := os.Open(configPath)
if err != nil {
return fmt.Errorf("error while opening config file: %v", err)
}
defer file.Close()
decoder := json.NewDecoder(file)
configuration = &Configuration{}
err = decoder.Decode(configuration)
if err != nil {
return err
}
return nil
}
func start(c *cli.Context) error {
if err := update(); err != nil {
SendStatusMail(fmt.Sprintf("Error during update process : %s", err))
return err
}
return nil
}
func update() error {
newIp, err := GetOutboundIP()
if err != nil {
return err
}
updatedDomains := ""
for _, domain := range configuration.Domains {
currentIp, err := GetCurrentIp(*configuration, domain)
if err != nil {
return err
}
if currentIp == newIp {
continue
}
log.Infoln(fmt.Sprintf("%s -> Ip has changed %s -> %s", domain, currentIp, newIp))
err = SetCurrentIp(newIp, domain, *configuration)
if err != nil {
return err
}
updatedDomains += fmt.Sprintf("\t- %s\n", domain)
}
if updatedDomains != "" {
SendStatusMail(fmt.Sprintf(`Home IP has changed : %s
Dns updated successfully for domains
%s`, newIp, updatedDomains))
}
return nil
}
func SendStatusMail(messageText string) {
settings := configuration.MailSettings
if settings == nil {
log.Warnln("no smtp settings defined > status mail not sent")
return
}
d := gomail.NewDialer(settings.SmtpHost, settings.SmtpPort, settings.SmtpLogin, settings.SmtpPassword)
s, err := d.Dial()
if err != nil {
log.Errorln(fmt.Sprintf("error while dialing smtp server : %v", err))
return
}
m := gomail.NewMessage()
for _, r := range settings.To {
m.SetHeader("From", settings.Sender)
m.SetAddressHeader("To", r, "")
m.SetHeader("Subject", "DnsUpdater Status")
m.SetBody("text/plain", messageText)
if err := gomail.Send(s, m); err != nil {
log.Warnln(fmt.Sprintf("could not send email to %q: %v", r, err))
}
m.Reset()
}
}
func GetCurrentIp(configuration Configuration, domain string) (string, error) {
var value string
err := try.Do(func(attempt int) (bool, error) {
var err error
value, err = func(configuration Configuration, domain string) (string, error) {
url := fmt.Sprintf(GandiARecordUrl, domain)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("X-Api-Key", configuration.GandiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
dnsresponse := DnsResponse{}
err = decoder.Decode(&dnsresponse)
if err != nil {
return "", err
}
return dnsresponse.Values[0], nil
}(configuration, domain)
if err != nil {
log.Warnln(fmt.Sprintf("Error while getting current ip from gandi : %v", err))
time.Sleep(30 * time.Second)
}
return attempt < 5, err
})
if err != nil {
return "", err
}
return value, nil
}
func SetCurrentIp(newip string, domain string, configuration Configuration) error {
url := fmt.Sprintf(GandiARecordUrl, domain)
log.Infoln("URL:>", url)
var str = fmt.Sprintf("{\"rrset_ttl\": %d,\"rrset_values\": [\"%s\"]}", 600, newip)
log.Infoln("json:", str)
var jsonStr = []byte(str)
req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonStr))
if err != nil {
return err
}
req.Header.Set("X-Api-Key", configuration.GandiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
log.Debugln("response Status:", resp.Status)
log.Debugln("response Headers:", resp.Header)
body, _ := ioutil.ReadAll(resp.Body)
log.Debugln("response Body:", string(body))
return nil
}
func GetOutboundIP() (string, error) {
url := configuration.RouterStatusUrl
if url == "" {
url = "http://192.168.1.1/Status_Internet.live.asp"
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
if configuration.RouterLogin != "" && configuration.RouterPassword != "" {
req.SetBasicAuth(configuration.RouterLogin, configuration.RouterPassword)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
log.Debugln(string(body))
wanipregex := configuration.WanIPRegex
if wanipregex == "" {
wanipregex = "wan_ipaddr::([0-9.]*)}"
}
r := regexp.MustCompile(wanipregex)
matches := r.FindStringSubmatch(string(body))
if len(matches) < 2 {
return "", fmt.Errorf("unable to find WAN IP with regex %s in %s", wanipregex, string(body))
}
return matches[1], nil
}