package server
import (
"crypto"
"fmt"
"github.com/hlandau/buildinfo"
"github.com/hlandau/ncdns/backend"
"github.com/hlandau/ncdns/namecoin"
"github.com/hlandau/xlog"
"github.com/miekg/dns"
"gopkg.in/hlandau/madns.v1"
"net"
"os"
"path/filepath"
"strings"
"sync"
)
var log , Log = xlog . New ( "ncdns.server" )
type Server struct {
cfg Config
engine madns . Engine
namecoinConn namecoin . Conn
mux * dns . ServeMux
udpServer * dns . Server
udpConn * net . UDPConn
tcpServer * dns . Server
tcpListener net . Listener
wgStart sync . WaitGroup
}
type Config struct {
Bind string ` default:":53" usage:"Address to bind to (e.g. 0.0.0.0:53)" `
PublicKey string ` default:"" usage:"Path to the DNSKEY KSK public key file" `
PrivateKey string ` default:"" usage:"Path to the KSK's corresponding private key file" `
ZonePublicKey string ` default:"" usage:"Path to the DNSKEY ZSK public key file; if one is not specified, a temporary one is generated on startup and used only for the duration of that process" `
ZonePrivateKey string ` default:"" usage:"Path to the ZSK's corresponding private key file" `
NamecoinRPCUsername string ` default:"" usage:"Namecoin RPC username" `
NamecoinRPCPassword string ` default:"" usage:"Namecoin RPC password" `
NamecoinRPCAddress string ` default:"localhost:8336" usage:"Namecoin RPC server address" `
NamecoinRPCCookiePath string ` default:"" usage:"Namecoin RPC cookie path (if set, used instead of password)" `
CacheMaxEntries int ` default:"100" usage:"Maximum name cache entries" `
SelfName string ` default:"" usage:"The FQDN of this nameserver. If empty, a psuedo-hostname is generated." `
SelfIP string ` default:"127.127.127.127" usage:"The canonical IP address for this service" `
HTTPListenAddr string ` default:"" usage:"Address for webserver to listen at (default: disabled)" `
CanonicalSuffix string ` default:"bit" usage:"Suffix to advertise via HTTP" `
CanonicalNameservers string ` default:"" usage:"Comma-separated list of nameservers to use for NS records. If blank, SelfName (or autogenerated psuedo-hostname) is used." `
canonicalNameservers [ ] string
Hostmaster string ` default:"" usage:"Hostmaster e. mail address" `
VanityIPs string ` default:"" usage:"Comma separated list of IP addresses to place in A/AAAA records at the zone apex (default: don't add any records)" `
vanityIPs [ ] net . IP
TplSet string ` default:"std" usage:"The template set to use" `
TplPath string ` default:"" usage:"The path to the tpl directory (empty: autodetect)" `
ConfigDir string // path to interpret filenames relative to
}
func ( cfg * Config ) cpath ( s string ) string {
return filepath . Join ( cfg . ConfigDir , s )
}
var ncdnsVersion string
func New ( cfg * Config ) ( s * Server , err error ) {
ncdnsVersion = buildinfo . VersionSummary ( "github.com/hlandau/ncdns" , "ncdns" )
s = & Server {
cfg : * cfg ,
namecoinConn : namecoin . Conn {
Username : cfg . NamecoinRPCUsername ,
Password : cfg . NamecoinRPCPassword ,
Server : cfg . NamecoinRPCAddress ,
} ,
}
if s . cfg . NamecoinRPCCookiePath != "" {
s . namecoinConn . GetAuth = cookieRetriever ( s . cfg . NamecoinRPCCookiePath )
}
if s . cfg . CanonicalNameservers != "" {
s . cfg . canonicalNameservers = strings . Split ( s . cfg . CanonicalNameservers , "," )
for i := range s . cfg . canonicalNameservers {
s . cfg . canonicalNameservers [ i ] = dns . Fqdn ( s . cfg . canonicalNameservers [ i ] )
}
}
if s . cfg . VanityIPs != "" {
vanityIPs := strings . Split ( s . cfg . VanityIPs , "," )
for _ , ips := range vanityIPs {
ip := net . ParseIP ( ips )
if ip == nil {
return nil , fmt . Errorf ( "Couldn't parse IP: %s" , ips )
}
s . cfg . vanityIPs = append ( s . cfg . vanityIPs , ip )
}
}
b , err := backend . New ( & backend . Config {
NamecoinConn : s . namecoinConn ,
CacheMaxEntries : cfg . CacheMaxEntries ,
SelfIP : cfg . SelfIP ,
Hostmaster : cfg . Hostmaster ,
CanonicalNameservers : s . cfg . canonicalNameservers ,
VanityIPs : s . cfg . vanityIPs ,
} )
if err != nil {
return
}
ecfg := & madns . EngineConfig {
Backend : b ,
VersionString : ncdnsVersion ,
}
// key setup
if cfg . PublicKey != "" {
ecfg . KSK , ecfg . KSKPrivate , err = s . loadKey ( cfg . PublicKey , cfg . PrivateKey )
if err != nil {
return nil , err
}
}
if cfg . ZonePublicKey != "" {
ecfg . ZSK , ecfg . ZSKPrivate , err = s . loadKey ( cfg . ZonePublicKey , cfg . ZonePrivateKey )
if err != nil {
return nil , err
}
}
if ecfg . KSK != nil && ecfg . ZSK == nil {
return nil , fmt . Errorf ( "Must specify ZSK if KSK is specified" )
}
s . engine , err = madns . NewEngine ( ecfg )
if err != nil {
return
}
s . mux = dns . NewServeMux ( )
s . mux . Handle ( "." , s . engine )
tcpAddr , err := net . ResolveTCPAddr ( "tcp" , s . cfg . Bind )
if err != nil {
return
}
s . tcpListener , err = net . ListenTCP ( "tcp" , tcpAddr )
if err != nil {
return
}
udpAddr , err := net . ResolveUDPAddr ( "udp" , s . cfg . Bind )
if err != nil {
return
}
s . udpConn , err = net . ListenUDP ( "udp" , udpAddr )
if err != nil {
return
}
if cfg . HTTPListenAddr != "" {
err = webStart ( cfg . HTTPListenAddr , s )
if err != nil {
return
}
}
return
}
func ( s * Server ) loadKey ( fn , privateFn string ) ( k * dns . DNSKEY , privatek crypto . PrivateKey , err error ) {
fn = s . cfg . cpath ( fn )
privateFn = s . cfg . cpath ( privateFn )
f , err := os . Open ( fn )
if err != nil {
return
}
rr , err := dns . ReadRR ( f , fn )
if err != nil {
return
}
k , ok := rr . ( * dns . DNSKEY )
if ! ok {
err = fmt . Errorf ( "Loaded record from key file, but it wasn't a DNSKEY" )
return
}
privatef , err := os . Open ( privateFn )
if err != nil {
return
}
privatek , err = k . ReadPrivateKey ( privatef , privateFn )
log . Fatale ( err )
return
}
func ( s * Server ) Start ( ) error {
s . wgStart . Add ( 2 )
s . udpServer = s . runListener ( "udp" )
s . tcpServer = s . runListener ( "tcp" )
s . wgStart . Wait ( )
log . Info ( "Listeners started" )
return nil
}
func ( s * Server ) doRunListener ( ds * dns . Server ) {
err := ds . ActivateAndServe ( )
log . Fatale ( err )
}
func ( s * Server ) runListener ( net string ) * dns . Server {
ds := & dns . Server {
Addr : s . cfg . Bind ,
Net : net ,
Handler : s . mux ,
NotifyStartedFunc : func ( ) {
s . wgStart . Done ( )
} ,
}
switch net {
case "tcp" :
ds . Listener = s . tcpListener
case "udp" :
ds . PacketConn = s . udpConn
default :
panic ( "unreachable" )
}
go s . doRunListener ( ds )
return ds
}
func ( s * Server ) Stop ( ) error {
return nil // TODO
}