239 lines
5.2 KiB
Go
239 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"github.com/julienschmidt/httprouter"
|
|
"github.com/hoisie/redis"
|
|
"encoding/json"
|
|
"github.com/weppos/publicsuffix-go/publicsuffix"
|
|
"strings"
|
|
"github.com/asaskevich/govalidator"
|
|
"flag"
|
|
"os"
|
|
"log"
|
|
)
|
|
|
|
var c *redis.Client
|
|
|
|
type infoResponse struct {
|
|
RedisServer string `json:"redis"`
|
|
}
|
|
|
|
var (
|
|
flagKey = flag.String("key", "godns:hosts", "Redis key for hash set")
|
|
flagListen = flag.String("listen", ":8080", "listen address")
|
|
flagServer = flag.String("server", "localhost:6379", "redis host")
|
|
filePath = flag.String("filepath", "/var/lib/joker/dist", "file path")
|
|
)
|
|
|
|
var (
|
|
key string
|
|
server string
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if key = os.Getenv("REDIS_KEY"); key == "" {
|
|
key = *flagKey
|
|
}
|
|
|
|
if server = os.Getenv("REDIS_SERVER"); server == "" {
|
|
server = *flagServer
|
|
}
|
|
|
|
c = &redis.Client{
|
|
Addr: server,
|
|
}
|
|
|
|
router := httprouter.New()
|
|
|
|
fs := http.FileServer(http.Dir(*filePath))
|
|
|
|
serveFiles := func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
fs.ServeHTTP(w, r)
|
|
}
|
|
|
|
router.GET("/", serveFiles)
|
|
router.GET("/js/*filepath", serveFiles)
|
|
router.GET("/css/*filepath", serveFiles)
|
|
|
|
router.GET("/info", getInfo)
|
|
|
|
router.GET("/records", getRecords)
|
|
router.GET("/records/:zone", getRecords)
|
|
|
|
router.POST("/update", updateRecord)
|
|
router.POST("/remove", removeRecord)
|
|
|
|
log.Println("Starting server on " + *flagListen)
|
|
|
|
http.ListenAndServe(*flagListen, router)
|
|
}
|
|
|
|
func getInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
json.NewEncoder(w).Encode(&infoResponse{
|
|
RedisServer: server,
|
|
})
|
|
}
|
|
|
|
type tableRow struct {
|
|
Domain string `json:"domain"`
|
|
IP string `json:"ip"`
|
|
ItemCount int `json:"itemCount"`
|
|
}
|
|
|
|
type response struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func getRecords(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
zone := p.ByName("zone")
|
|
|
|
hosts := make(map[string]string)
|
|
|
|
if err := c.Hgetall(key, hosts); err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(&response{Success: false, Message: "Unable to retrieve records: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
groups := make(map[string]map[string]string)
|
|
|
|
for host, addr := range hosts {
|
|
domain, err := publicsuffix.Domain(host)
|
|
|
|
if err != nil {
|
|
domain = host
|
|
}
|
|
|
|
if zone != "" && !strings.HasSuffix(host, zone) {
|
|
continue
|
|
}
|
|
|
|
if group, ok := groups[domain]; ok {
|
|
group[host] = addr
|
|
} else {
|
|
groups[domain] = map[string]string{host:addr}
|
|
}
|
|
}
|
|
|
|
out := make([]*tableRow, 0)
|
|
|
|
if zone == "" {
|
|
for domain, children := range groups {
|
|
ip := children[domain]
|
|
|
|
if len(children) > 1 {
|
|
ip = "-"
|
|
} else {
|
|
domain = mapFirst(children)
|
|
ip = children[domain]
|
|
}
|
|
|
|
out = append(out, &tableRow{
|
|
Domain: domain,
|
|
IP: ip,
|
|
ItemCount: len(children),
|
|
})
|
|
}
|
|
} else {
|
|
for domain, ip := range groups[zone] {
|
|
out = append(out, &tableRow{
|
|
Domain: domain,
|
|
IP: ip,
|
|
ItemCount: 1,
|
|
})
|
|
}
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(out)
|
|
}
|
|
|
|
func mapFirst(m map[string]string) string {
|
|
for k := range m {
|
|
return k
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
type domainRequest struct {
|
|
Domain string `json:"domain"`
|
|
IP string `json:"ip"`
|
|
Group bool `json:"group"`
|
|
}
|
|
|
|
func updateRecord(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
var req domainRequest
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(&response{Success: false, Message: "Unable to decode body: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
req.Domain = strings.ToLower(req.Domain)
|
|
|
|
if !govalidator.IsDNSName(req.Domain) || !govalidator.IsIP(req.IP) {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(&response{Success: false, Message: "Invalid domain or IP"})
|
|
return
|
|
}
|
|
|
|
if _, err := c.Hset(key, req.Domain, []byte(req.IP)); err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(&response{Success: false, Message: "Unable to save record: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
c.Publish("godns:update_record", []byte(strings.ToLower(req.Domain)))
|
|
|
|
json.NewEncoder(w).Encode(&response{Success: true})
|
|
}
|
|
|
|
func removeRecord(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
var req domainRequest
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
req.Domain = strings.ToLower(req.Domain)
|
|
|
|
if !govalidator.IsDNSName(strings.Replace(req.Domain, "*", "a", -1)) {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.Group {
|
|
hosts := make(map[string]string)
|
|
|
|
if err := c.Hgetall(key, hosts); err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(&response{Success: false, Message: "Unable to fetch records: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
for host, _ := range hosts {
|
|
host = strings.ToLower(host)
|
|
|
|
if !strings.HasSuffix(host, req.Domain) {
|
|
continue
|
|
}
|
|
|
|
c.Hdel(key, host)
|
|
|
|
c.Publish("godns:remove_record", []byte(host))
|
|
}
|
|
} else {
|
|
if _, err := c.Hdel(key, req.Domain); err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
json.NewEncoder(w).Encode(&response{Success: false, Message: "Unable to delete record: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
c.Publish("godns:remove_record", []byte(strings.ToLower(req.Domain)))
|
|
}
|
|
} |