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, } } const ENV_PATH string = "/usr/bin/env" 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(ENV_PATH, "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(ENV_PATH, "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(ENV_PATH, "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 }