152 lines
3.3 KiB
Go

package nsupdate
import (
"bytes"
"fmt"
"strings"
"os/exec"
"istheinternetonfire.app/internal/config"
)
func New(keyLabel, keyAlgorithm, keySecret, server string, port int, zone string) NsUpdateStruct {
return NsUpdateStruct{
Key: KeyStruct{
Label: keyLabel,
Algorithm: keyAlgorithm,
Secret: keySecret,
},
Server: server,
Port: port,
Zone: zone,
}
}
var (
// escape characters
specialChars = []string{
`"`,
}
)
func (c NsUpdateStruct) UpdateTXT(record, recordType, value string) error {
var (
stdout bytes.Buffer
stderr bytes.Buffer
)
for _, v := range specialChars {
value = strings.ReplaceAll(value, v, fmt.Sprintf("\\%s", v))
}
value = sanitizeTXT(value)
command := fmt.Sprintf("dig +short TXT %s", record)
cmd := exec.Command("/usr/bin/env", "sh", "-c", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
config.Cfg.Log.Error("error adding record", "error", err, "stderr", stderr.String(), "stdout", stdout.String())
return err
}
oldTXT := strings.TrimLeft(strings.TrimRight(stdout.String(), "\n\""), "\"")
if strings.ReplaceAll(oldTXT, `" "`, ``) != strings.ReplaceAll(value, `" "`, ``) {
config.Cfg.Log.Debug("deleting record", "record", record)
if err := c.Delete(record, recordType); err != nil {
return err
}
config.Cfg.Log.Debug("creating record", "record", record)
if err := c.Create(record, recordType, value); err != nil {
return err
}
return nil
}
config.Cfg.Log.Debug("no update necessary")
return nil
}
func (c NsUpdateStruct) Delete(record, recordType string) error {
var (
stdout bytes.Buffer
stderr bytes.Buffer
)
command := fmt.Sprintf(`#/usr/bin/env sh
read -r -d '' DDNS_KEY <<- EOF
key "%s" {
algorithm "%s";
secret "%s";
};
EOF
read -r -d '' COMMAND <<- EOF
server %s
update delete %s. 30 IN %s
send
EOF
nsupdate -v -k <(printf '%%s' "${DDNS_KEY}") <(printf '%%s\n\n' "${COMMAND}")
`, c.Key.Label, c.Key.Algorithm, c.Key.Secret, c.Server, record, strings.ToUpper(recordType))
cmd := exec.Command("/usr/bin/env", "sh", "-c", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
config.Cfg.Log.Error("error deleting record", "error", err, "stderr", stderr.String(), "stdout", stdout.String())
return err
}
return nil
}
func (c NsUpdateStruct) Create(record, recordType, value string) error {
var (
stdout bytes.Buffer
stderr bytes.Buffer
)
command := fmt.Sprintf(`#/usr/bin/env sh
read -r -d '' DDNS_KEY <<- EOF
key "%s" {
algorithm "%s";
secret "%s";
};
EOF
read -r -d '' COMMAND <<- EOF
server %s
update add %s. 30 IN %s "%s"
send
EOF
nsupdate -v -k <(printf '%%s' "${DDNS_KEY}") <(printf '%%s\n\n' "${COMMAND}")
`, c.Key.Label, c.Key.Algorithm, c.Key.Secret, c.Server, record, strings.ToUpper(recordType), value)
cmd := exec.Command("/usr/bin/env", "sh", "-c", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
config.Cfg.Log.Error("error adding record", "error", err, "stderr", stderr.String(), "stdout", stdout.String())
return err
}
return nil
}
func sanitizeTXT(s string) string {
// convert to rune
a := []rune(s)
// split into 255 character blocks
s = ""
for i, r := range a {
s += string(r)
if i > 0 && (i+1)%200 == 0 {
s += string(`" "`)
}
}
// convert new lines into safe string
s = strings.ReplaceAll(s, "\n", "%n")
return s
}