ns-controller: manage txt records
diff --git a/core/ns-controller/controllers/zone.go b/core/ns-controller/controllers/zone.go
new file mode 100644
index 0000000..41fb98f
--- /dev/null
+++ b/core/ns-controller/controllers/zone.go
@@ -0,0 +1,81 @@
+package controllers
+
+import (
+	"fmt"
+	"io"
+	"strings"
+	"time"
+
+	"github.com/miekg/dns"
+)
+
+type ZoneFile struct {
+	rrs []dns.RR
+}
+
+func NewZoneFile(r io.Reader) (*ZoneFile, error) {
+	rrs := make([]dns.RR, 0)
+	p := dns.NewZoneParser(r, "", "")
+	p.SetIncludeAllowed(false)
+	for {
+		if rr, ok := p.Next(); ok {
+			rrs = append(rrs, rr)
+		} else {
+			if err := p.Err(); err != nil {
+				return nil, err
+			}
+			break
+		}
+	}
+	return &ZoneFile{rrs}, nil
+}
+
+func (z *ZoneFile) DeleteTxtRecord(name, value string) {
+	for i, rr := range z.rrs {
+		if txt, ok := rr.(*dns.TXT); ok {
+			if txt.Hdr.Name == name && strings.Join(txt.Txt, "") == value {
+				z.rrs = append(z.rrs[:i], z.rrs[i+1:]...)
+			}
+		}
+	}
+}
+
+func (z *ZoneFile) CreateOrReplaceTxtRecord(name, value string) {
+	for i, rr := range z.rrs {
+		if txt, ok := rr.(*dns.TXT); ok {
+			if txt.Hdr.Name == name && strings.Join(txt.Txt, "") == value {
+				txt.Txt = []string{value}
+				z.rrs = append(z.rrs[:i], z.rrs[i+1:]...)
+				z.rrs = append(z.rrs, txt)
+				return
+			}
+		}
+	}
+	z.rrs = append(z.rrs, &dns.TXT{
+		Hdr: dns.RR_Header{
+			Name:   name,
+			Rrtype: dns.TypeTXT,
+			Class:  dns.ClassINET,
+			Ttl:    300,
+		},
+		Txt: []string{value},
+	})
+}
+
+func (z *ZoneFile) Write(w io.Writer) error {
+	for _, rr := range z.rrs {
+		if soa, ok := rr.(*dns.SOA); ok {
+			soa.Serial = NowUnix()
+		}
+		if _, err := fmt.Fprintf(w, "%s\n", rr.String()); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// TODO(gio): not going to work in 15 years?
+// TODO(gio): remove 10 *
+func NowUnix() uint32 {
+	return 10 * uint32(time.Now().Unix())
+}