Implement Source abstraction

This commit is contained in:
nemunaire 2020-04-22 13:12:27 +02:00
parent fa2ed5133f
commit 679bd9b9fa
9 changed files with 314 additions and 7 deletions

View File

@ -13,6 +13,8 @@ import (
"git.happydns.org/happydns/config"
"git.happydns.org/happydns/storage"
leveldb "git.happydns.org/happydns/storage/leveldb"
_ "git.happydns.org/happydns/sources/ddns"
)
type ResponseWriterPrefix struct {

View File

@ -5,9 +5,9 @@ import (
)
type Domain struct {
Id int64 `json:"id"`
IdUser int64
IdSource int64
Id int64 `json:"id"`
IdUser int64 `json:"id_owner"`
IdSource int64 `json:"id_source"`
DomainName string `json:"domain"`
}
@ -21,10 +21,10 @@ func (d *Domain) NormalizedNSServer() string {
}
}
func NewDomain(u *User, s Source, dn string) (d *Domain) {
func NewDomain(u *User, st *SourceType, dn string) (d *Domain) {
d = &Domain{
IdUser: u.Id,
//IdSource: s.GetId(),
IdUser: u.Id,
IdSource: st.Id,
DomainName: dn,
}

24
model/source.go Normal file
View File

@ -0,0 +1,24 @@
package happydns
import (
"github.com/miekg/dns"
)
type Source interface {
Validate() error
ImportZone(*Domain) ([]dns.RR, error)
AddRR(*Domain, dns.RR) error
DeleteRR(*Domain, dns.RR) error
}
type SourceType struct {
Type string `json:"_srctype"`
Id int64 `json:"_id"`
OwnerId int64 `json:"_ownerid"`
Comment string `json:"_comment,omitempty"`
}
type SourceCombined struct {
Source
SourceType
}

115
sources/ddns/source.go Normal file
View File

@ -0,0 +1,115 @@
package ddns // import "happydns.org/sources/ddns"
import (
"encoding/base64"
"net"
"time"
"github.com/miekg/dns"
"git.happydns.org/happydns/model"
"git.happydns.org/happydns/sources"
)
type DDNSServer struct {
Server string `json:"server,omitempty"`
KeyName string `json:"keyname,omitempty"`
KeyAlgo string `json:"algorithm,omitempty"`
KeyBlob []byte `json:"keyblob,omitempty"`
}
func (s *DDNSServer) base64KeyBlob() string {
return base64.StdEncoding.EncodeToString(s.KeyBlob)
}
func (s *DDNSServer) Validate() error {
d := net.Dialer{}
con, err := d.Dial("tcp", s.Server)
if err != nil {
return err
}
defer con.Close()
return nil
}
func (s *DDNSServer) ImportZone(dn *happydns.Domain) (rrs []dns.RR, err error) {
d := net.Dialer{}
con, errr := d.Dial("tcp", s.Server)
if errr != nil {
err = errr
return
}
defer con.Close()
m := new(dns.Msg)
m.SetEdns0(4096, true)
m.SetAxfr(dn.DomainName)
m.SetTsig(s.KeyName, s.KeyAlgo, 300, time.Now().Unix())
dnscon := &dns.Conn{Conn: con}
transfer := &dns.Transfer{Conn: dnscon, TsigSecret: map[string]string{s.KeyName: s.base64KeyBlob()}}
c, errr := transfer.In(m, s.Server)
if errr != nil {
err = errr
return
}
for {
response, ok := <-c
if !ok {
break
}
for _, rr := range response.RR {
rrs = append(rrs, rr)
}
}
if len(rrs) > 0 {
rrs = rrs[0 : len(rrs)-1]
}
return
}
func (s *DDNSServer) AddRR(domain *happydns.Domain, rr dns.RR) error {
m := new(dns.Msg)
m.Id = dns.Id()
m.Opcode = dns.OpcodeUpdate
m.Question = make([]dns.Question, 1)
m.Question[0] = dns.Question{domain.DomainName, dns.TypeSOA, dns.ClassINET}
m.Insert([]dns.RR{rr})
c := new(dns.Client)
c.TsigSecret = map[string]string{s.KeyName: s.base64KeyBlob()}
m.SetTsig(s.KeyName, s.KeyAlgo, 300, time.Now().Unix())
_, _, err := c.Exchange(m, s.Server)
return err
}
func (s *DDNSServer) DeleteRR(domain *happydns.Domain, rr dns.RR) error {
m := new(dns.Msg)
m.Id = dns.Id()
m.Opcode = dns.OpcodeUpdate
m.Question = make([]dns.Question, 1)
m.Question[0] = dns.Question{domain.DomainName, dns.TypeSOA, dns.ClassINET}
m.Remove([]dns.RR{rr})
c := new(dns.Client)
c.TsigSecret = map[string]string{s.KeyName: s.base64KeyBlob()}
m.SetTsig(s.KeyName, s.KeyAlgo, 300, time.Now().Unix())
_, _, err := c.Exchange(m, s.Server)
return err
}
func init() {
sources.RegisterSource("git.happydns.org/happydns/sources/ddns/DDNSServer", func() happydns.Source {
return &DDNSServer{}
})
}

26
sources/decoder.go Normal file
View File

@ -0,0 +1,26 @@
package sources // import "happydns.org/sources"
import (
"fmt"
"log"
"git.happydns.org/happydns/model"
)
type SourceCreator func() happydns.Source
var sources map[string]SourceCreator = map[string]SourceCreator{}
func RegisterSource(name string, creator SourceCreator) {
log.Println("Registering new source:", name)
sources[name] = creator
}
func FindSource(name string) (happydns.Source, error) {
src, ok := sources[name]
if !ok {
return nil, fmt.Errorf("Unable to find corresponding source for `%s`.", name)
}
return src(), nil
}

5
storage/config.go Normal file
View File

@ -0,0 +1,5 @@
package storage
import ()
type DBConfig interface{}

View File

@ -24,6 +24,14 @@ type Storage interface {
DeleteSession(session *happydns.Session) error
ClearSessions() error
GetSourceTypes(u *happydns.User) ([]happydns.SourceType, error)
GetSource(u *happydns.User, id int64) (*happydns.SourceCombined, error)
CreateSource(u *happydns.User, s happydns.Source, comment string) (*happydns.SourceCombined, error)
UpdateSource(s *happydns.SourceCombined) error
UpdateSourceOwner(s *happydns.SourceCombined, newOwner *happydns.User) error
DeleteSource(s *happydns.SourceType) error
ClearSources() error
GetUsers() (happydns.Users, error)
GetUser(id int) (*happydns.User, error)
GetUserByEmail(email string) (*happydns.User, error)

View File

@ -57,7 +57,8 @@ func (s *LevelDBStorage) put(key string, v interface{}) error {
func (s *LevelDBStorage) findInt63Key(prefix string) (key string, id int64, err error) {
found := true
for found {
id = mrand.Int63()
// max random id is 2^53 to fit on float64 without loosing precision (JSON limitation)
id = mrand.Int63n(1 << 53)
key = fmt.Sprintf("%s%d", prefix, id)
found, err = s.db.Has([]byte(key), nil)

126
storage/leveldb/source.go Normal file
View File

@ -0,0 +1,126 @@
package database
import (
"fmt"
"reflect"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
"git.happydns.org/happydns/model"
"git.happydns.org/happydns/sources"
)
func (s *LevelDBStorage) GetSourceTypes(u *happydns.User) (srcs []happydns.SourceType, err error) {
iter := s.search("source-")
defer iter.Release()
for iter.Next() {
var srcType happydns.SourceType
err = decodeData(iter.Value(), &srcType)
if err != nil {
return
}
if srcType.OwnerId != u.Id {
continue
}
srcs = append(srcs, srcType)
}
return
}
func (s *LevelDBStorage) GetSource(u *happydns.User, id int64) (src *happydns.SourceCombined, err error) {
var v []byte
v, err = s.db.Get([]byte(fmt.Sprintf("source-%d", id)), nil)
if err != nil {
return
}
var srcType happydns.SourceType
err = decodeData(v, &srcType)
if err != nil {
return
}
if srcType.OwnerId != u.Id {
src = nil
err = leveldb.ErrNotFound
}
var tsrc happydns.Source
tsrc, err = sources.FindSource(srcType.Type)
src = &happydns.SourceCombined{
tsrc,
srcType,
}
err = decodeData(v, src)
if err != nil {
return
}
return
}
func (s *LevelDBStorage) CreateSource(u *happydns.User, src happydns.Source, comment string) (*happydns.SourceCombined, error) {
key, id, err := s.findInt63Key("source-")
if err != nil {
return nil, err
}
sType := reflect.Indirect(reflect.ValueOf(src)).Type()
st := &happydns.SourceCombined{
src,
happydns.SourceType{
Type: sType.PkgPath() + "/" + sType.Name(),
Id: id,
OwnerId: u.Id,
Comment: comment,
},
}
return st, s.put(key, st)
}
func (s *LevelDBStorage) UpdateSource(src *happydns.SourceCombined) error {
return s.put(fmt.Sprintf("source-%d", src.Id), src)
}
func (s *LevelDBStorage) UpdateSourceOwner(src *happydns.SourceCombined, newOwner *happydns.User) error {
src.OwnerId = newOwner.Id
return s.UpdateSource(src)
}
func (s *LevelDBStorage) DeleteSource(src *happydns.SourceType) error {
return s.delete(fmt.Sprintf("source-%d", src.Id))
}
func (s *LevelDBStorage) ClearSources() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("source-")), nil)
defer iter.Release()
for iter.Next() {
err = tx.Delete(iter.Key(), nil)
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}