2020-05-04 14:58:02 +00:00
// Copyright or © or Copr. happyDNS (2020)
//
2022-01-10 13:06:19 +00:00
// contact@happydomain.org
2020-05-04 14:58:02 +00:00
//
// This software is a computer program whose purpose is to provide a modern
// interface to interact with DNS systems.
//
// This software is governed by the CeCILL license under French law and abiding
// by the rules of distribution of free software. You can use, modify and/or
// redistribute the software under the terms of the CeCILL license as
// circulated by CEA, CNRS and INRIA at the following URL
// "http://www.cecill.info".
//
// As a counterpart to the access to the source code and rights to copy, modify
// and redistribute granted by the license, users are provided only with a
// limited warranty and the software's author, the holder of the economic
// rights, and the successive licensors have only limited liability.
//
// In this respect, the user's attention is drawn to the risks associated with
// loading, using, modifying and/or developing or reproducing the software by
// the user in light of its specific status of free software, that may mean
// that it is complicated to manipulate, and that also therefore means that it
// is reserved for developers and experienced professionals having in-depth
// computer knowledge. Users are therefore encouraged to load and test the
// software's suitability as regards their requirements in conditions enabling
// the security of their systems and/or data to be ensured and, more generally,
// to use and operate it in the same conditions as regards security.
//
// The fact that you are presently reading this means that you have had
// knowledge of the CeCILL license and that you accept its terms.
2020-04-28 10:57:03 +00:00
package api
import (
2021-01-12 15:32:42 +00:00
"fmt"
2021-05-05 01:48:16 +00:00
"log"
2020-05-07 11:55:57 +00:00
"math/rand"
2021-01-12 15:32:42 +00:00
"net/http"
2020-05-07 20:17:17 +00:00
"strings"
2020-04-28 10:57:03 +00:00
"time"
2021-05-05 01:48:16 +00:00
"github.com/gin-gonic/gin"
2020-04-28 10:57:03 +00:00
"github.com/miekg/dns"
)
2021-01-12 14:59:56 +00:00
var (
RRToAskForANY = [ ] uint16 { dns . TypeSOA , dns . TypeA , dns . TypeAAAA , dns . TypeNS , dns . TypeMX , dns . TypeTXT }
)
2021-05-05 01:48:16 +00:00
func declareResolverRoutes ( router * gin . RouterGroup ) {
router . POST ( "/resolver" , runResolver )
2020-04-28 10:57:03 +00:00
}
type resolverRequest struct {
Resolver string ` json:"resolver" `
2020-12-30 15:25:18 +00:00
Custom string ` json:"custom,omitempty" `
2020-04-28 10:57:03 +00:00
DomainName string ` json:"domain" `
Type string ` json:"type" `
}
2021-01-12 14:59:56 +00:00
func resolverANYQuestion ( client dns . Client , resolver string , dn string ) ( r * dns . Msg , err error ) {
var response * dns . Msg
for _ , rrType := range RRToAskForANY {
m := new ( dns . Msg )
m . Question = append ( m . Question , dns . Question {
Name : dn ,
Qtype : rrType ,
Qclass : dns . ClassINET ,
} )
m . RecursionDesired = true
m . SetEdns0 ( 4096 , true )
response , _ , err = client . Exchange ( m , resolver )
if err != nil {
return
}
if len ( response . Answer ) > 0 {
if r == nil {
r = response
r . Question [ 0 ] . Qtype = dns . TypeANY
} else {
r . Answer = append ( r . Answer , response . Answer ... )
}
}
}
if r == nil {
r = response
r . Question [ 0 ] . Qtype = dns . TypeANY
}
return
}
func resolverQuestion ( client dns . Client , resolver string , dn string , rrType uint16 ) ( * dns . Msg , error ) {
m := new ( dns . Msg )
m . SetQuestion ( dn , rrType )
m . RecursionDesired = true
m . SetEdns0 ( 4096 , true )
r , _ , err := client . Exchange ( m , resolver )
return r , err
}
2021-05-05 01:48:16 +00:00
func runResolver ( c * gin . Context ) {
2020-04-28 10:57:03 +00:00
var urr resolverRequest
2021-05-05 01:48:16 +00:00
if err := c . ShouldBindJSON ( & urr ) ; err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s sends invalid ResolverRequest JSON: %s" , c . ClientIP ( ) , err . Error ( ) )
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : fmt . Sprintf ( "Something is wrong in received data: %s" , err . Error ( ) ) } )
2021-05-05 01:48:16 +00:00
return
2020-04-28 10:57:03 +00:00
}
2020-12-30 15:25:18 +00:00
urr . DomainName = dns . Fqdn ( urr . DomainName )
if urr . Resolver == "custom" {
urr . Resolver = urr . Custom
} else if urr . Resolver == "local" {
2020-05-07 11:55:57 +00:00
cConf , err := dns . ClientConfigFromFile ( "/etc/resolv.conf" )
if err != nil {
2021-07-30 09:49:11 +00:00
log . Printf ( "%s unable to load ClientConfigFromFile: %s" , c . ClientIP ( ) , err . Error ( ) )
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Sorry, we are currently unable to perform the request. Please try again later." } )
return
2020-05-07 11:55:57 +00:00
}
urr . Resolver = cConf . Servers [ rand . Intn ( len ( cConf . Servers ) ) ]
}
2020-05-07 20:17:17 +00:00
if strings . Count ( urr . Resolver , ":" ) > 0 && urr . Resolver [ 0 ] != '[' {
urr . Resolver = "[" + urr . Resolver + "]"
}
2020-04-28 10:57:03 +00:00
client := dns . Client { Timeout : time . Second * 5 }
2021-01-12 14:59:56 +00:00
var r * dns . Msg
2021-05-05 01:48:16 +00:00
var err error
2021-01-12 14:59:56 +00:00
rrType := dns . StringToType [ urr . Type ]
if rrType == dns . TypeANY {
r , err = resolverANYQuestion ( client , urr . Resolver + ":53" , urr . DomainName )
} else {
r , err = resolverQuestion ( client , urr . Resolver + ":53" , urr . DomainName , rrType )
}
2020-04-28 10:57:03 +00:00
if err != nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : err . Error ( ) } )
return
2020-04-28 10:57:03 +00:00
}
if r == nil {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusNoContent , gin . H { "errmsg" : "No response to display." } )
return
2021-01-12 15:32:42 +00:00
} else if r . Rcode == dns . RcodeFormatError {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusBadRequest , gin . H { "errmsg" : "DNS request mal formated." } )
return
2021-01-12 15:32:42 +00:00
} else if r . Rcode == dns . RcodeServerFailure {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : fmt . Sprintf ( "Resolver returns an error (most likely something is wrong in %q)." , urr . DomainName ) } )
return
2021-01-12 15:32:42 +00:00
} else if r . Rcode == dns . RcodeNameError {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusNotFound , gin . H { "errmsg" : fmt . Sprintf ( "The domain %q was not found." , urr . DomainName ) } )
return
2021-01-12 15:32:42 +00:00
} else if r . Rcode == dns . RcodeNotImplemented {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , gin . H { "errmsg" : "Resolver returns the request hits non implemented code." } )
return
2021-01-12 15:32:42 +00:00
} else if r . Rcode == dns . RcodeRefused {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusForbidden , gin . H { "errmsg" : "Resolver refused to treat our request." } )
return
2021-01-12 15:32:42 +00:00
} else if r . Rcode != dns . RcodeSuccess {
2021-05-05 01:48:16 +00:00
c . AbortWithStatusJSON ( http . StatusNotAcceptable , gin . H { "errmsg" : fmt . Errorf ( "Resolver returns %s." , dns . RcodeToString [ r . Rcode ] ) } )
return
2020-04-28 10:57:03 +00:00
}
2021-05-05 01:48:16 +00:00
c . JSON ( http . StatusOK , r )
2020-04-28 10:57:03 +00:00
}