diff --git a/backend/backend.go b/backend/backend.go index 444a42c..5fe0e74 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -18,7 +18,7 @@ import "time" // Provides an abstract zone file for the Namecoin .bit TLD. type Backend struct { //s *Server - nc namecoin.Conn + nc *namecoin.Client // caches map keys are stream isolation ID's; items are of type *Domain caches map[string]*lru.Cache cacheMutex sync.Mutex @@ -29,7 +29,7 @@ var log, Log = xlog.New("ncdns.backend") // Backend configuration. type Config struct { - NamecoinConn namecoin.Conn + NamecoinConn *namecoin.Client // Timeout (in milliseconds) for Namecoin RPC requests NamecoinTimeout int @@ -63,9 +63,6 @@ func New(cfg *Config) (backend *Backend, err error) { b.cfg = *cfg b.nc = b.cfg.NamecoinConn - //b.nc.Username = cfg.RPCUsername - //b.nc.Password = cfg.RPCPassword - //b.nc.Server = cfg.RPCAddress b.caches = make(map[string]*lru.Cache) @@ -355,13 +352,13 @@ func (b *Backend) resolveName(name, streamIsolationID string) (jsonValue string, return fv, nil } - // The btcjson package has quite a long timeout, far in excess of standard + // The rpcclient package has quite a long timeout, far in excess of standard // DNS timeouts. We need to return an error response rapidly if we can't // query the backend. Be generous with the timeout as responses from the // Namecoin JSON-RPC seem sluggish sometimes. result := make(chan struct{}, 1) go func() { - jsonValue, err = b.nc.Query(name, streamIsolationID) + jsonValue, err = b.nc.NameQuery(name, streamIsolationID) log.Errore(err, "failed to query namecoin") result <- struct{}{} }() diff --git a/namecoin/namecoin.go b/namecoin/namecoin.go index ebdeaa3..734eed6 100644 --- a/namecoin/namecoin.go +++ b/namecoin/namecoin.go @@ -1,220 +1,55 @@ package namecoin -// btcjson had to be modified a bit to get correct error reporting. import ( - extratypes "github.com/hlandau/ncbtcjsontypes" - "github.com/hlandauf/btcjson" - "gopkg.in/hlandau/madns.v2/merr" - - "expvar" "fmt" - "sync/atomic" -) - -var cQueryCalls = expvar.NewInt("ncdns.namecoin.numQueryCalls") -var cSyncCalls = expvar.NewInt("ncdns.namecoin.numSyncCalls") -var cFilterCalls = expvar.NewInt("ncdns.namecoin.numFilterCalls") -var cScanCalls = expvar.NewInt("ncdns.namecoin.numScanCalls") -var cCurHeightCalls = expvar.NewInt("ncdns.namecoin.numCurHeightCalls") - -// Used for generating IDs for JSON-RPC requests. -var idCounter int32 - -func newID() int32 { - return atomic.AddInt32(&idCounter, 1) -} - -// Used to query a Namecoin JSON-RPC interface. Initialize the struct with a -// username, password, and address (hostname:port). -type Conn struct { - Username string - Password string - - // If set, this is called to obtain the username and password instead of - // using the Username and Password fields. - GetAuth func() (username, password string, err error) - Server string -} + "github.com/namecoin/btcd/btcjson" + "github.com/namecoin/btcd/rpcclient" + "gopkg.in/hlandau/madns.v2/merr" -func (nc *Conn) getAuth() (username string, password string, err error) { - if nc.GetAuth == nil { - return nc.Username, nc.Password, nil - } + "github.com/namecoin/ncrpcclient" +) - return nc.GetAuth() +// Client represents an ncrpcclient.Client with an additional DNS-friendly +// convenience wrapper around NameShow. +type Client struct { + *ncrpcclient.Client } -func (nc *Conn) rpcSend(cmd btcjson.Cmd) (btcjson.Reply, error) { - username, password, err := nc.getAuth() +func New(config *rpcclient.ConnConfig, ntfnHandlers *rpcclient.NotificationHandlers) (*Client, error) { + ncClient, err := ncrpcclient.New(config, ntfnHandlers) if err != nil { - return btcjson.Reply{}, err + return nil, err } - return btcjson.RpcSend(username, password, nc.Server, cmd) + return &Client{ncClient}, nil } -// Query the Namecoin daemon for a Namecoin domain (e.g. d/example). -// If the domain exists, returns the value stored in Namecoin, which should be JSON. -// Note that this will return domain data even if the domain is expired. -func (nc *Conn) Query(name string, streamIsolationID string) (v string, err error) { - cQueryCalls.Add(1) - +// NameQuery returns the value of a name. If the name doesn't exist, the error +// returned will be merr.ErrNoSuchDomain. +func (c *Client) NameQuery(name string, streamIsolationID string) (string, error) { // TODO: Pass stream isolation ID to namecoind, and remove this error if streamIsolationID != "" { return "", fmt.Errorf("Stream isolation ID '%s' is not yet passed to namecoind", streamIsolationID) } - cmd, err := extratypes.NewNameShowCmd(newID(), name) - if err != nil { - //log.Info("NC NEWCMD ", err) - return "", err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return "", err - } - - if r.Error != nil { - //log.Info("RPC error: ", r.Error) - if r.Error.Code == -4 { - return "", merr.ErrNoSuchDomain - } - return "", r.Error - } - - if r.Result == nil { - //log.Info("NC NILRESULT") - return "", fmt.Errorf("got nil result") - } - - if nsr, ok := r.Result.(*extratypes.NameShowReply); ok { - //log.Info("NC OK") - return nsr.Value, nil - } - - //log.Info("NC BADREPLY") - return "", fmt.Errorf("bad reply") -} - -var ErrSyncNoSuchBlock = fmt.Errorf("no block exists with given hash") - -const rpcInvalidAddressOrKey = -5 - -func (nc *Conn) Sync(hash string, count int, wait bool) ([]extratypes.NameSyncEvent, error) { - cSyncCalls.Add(1) - - cmd, err := extratypes.NewNameSyncCmd(newID(), hash, count, wait) - if err != nil { - return nil, err - } - - r, err := nc.rpcSend(cmd) + nameData, err := c.NameShow(name) if err != nil { - return nil, err - } - - if r.Error != nil { - if r.Error.Code == rpcInvalidAddressOrKey { - return nil, ErrSyncNoSuchBlock + if jerr, ok := err.(*btcjson.RPCError); ok { + if jerr.Code == btcjson.ErrRPCWallet { + // ErrRPCWallet from name_show indicates that + // the name does not exist. + return "", merr.ErrNoSuchDomain + } } - return nil, r.Error - } - - if r.Result == nil { - return nil, fmt.Errorf("got nil result") - } - if nsr, ok := r.Result.(extratypes.NameSyncReply); ok { - return []extratypes.NameSyncEvent(nsr), nil - } - - return nil, fmt.Errorf("bad reply") -} - -func (nc *Conn) CurHeight() (int, error) { - cCurHeightCalls.Add(1) - - cmd, err := btcjson.NewGetInfoCmd(newID()) - if err != nil { - return 0, err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return 0, err - } - - if r.Error != nil { - return 0, r.Error - } - - if r.Result == nil { - return 0, fmt.Errorf("got nil result") - } - - if rep, ok := r.Result.(*btcjson.InfoResult); ok { - return int(rep.Blocks), nil - } - - return 0, fmt.Errorf("bad reply") -} - -func (nc *Conn) Filter(regexp string, maxage, from, count int) (names []extratypes.NameFilterItem, err error) { - cFilterCalls.Add(1) - - cmd, err := extratypes.NewNameFilterCmd(newID(), regexp, maxage, from, count) - if err != nil { - return nil, err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return nil, err - } - - if r.Error != nil { - return nil, r.Error - } - - if r.Result == nil { - return nil, fmt.Errorf("got nil result") - } - - if nsr, ok := r.Result.(extratypes.NameFilterReply); ok { - return []extratypes.NameFilterItem(nsr), nil - } - - return nil, fmt.Errorf("bad reply") -} - -func (nc *Conn) Scan(from string, count int) (names []extratypes.NameFilterItem, err error) { - cScanCalls.Add(1) - - cmd, err := extratypes.NewNameScanCmd(newID(), from, count) - if err != nil { - return nil, err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return nil, err - } - - if r.Error != nil { - return nil, r.Error + // Some error besides NXDOMAIN happened; pass that error + // through unaltered. + return "", err } - if r.Result == nil { - return nil, fmt.Errorf("got nil result") - } + // TODO: check the "value_error" field for errors and report those to the caller. - if nsr, ok := r.Result.(extratypes.NameFilterReply); ok { - return []extratypes.NameFilterItem(nsr), nil - } - - return nil, fmt.Errorf("bad reply") + // We got the name data. Return the value. + return nameData.Value, nil } - -// © 2014 Hugo Landau GPLv3 or later diff --git a/ncdt/ncdt.go b/ncdt/ncdt.go index e845824..4202d11 100644 --- a/ncdt/ncdt.go +++ b/ncdt/ncdt.go @@ -7,12 +7,14 @@ import "fmt" import "os" import "strconv" import "io/ioutil" +import "github.com/namecoin/btcd/rpcclient" import "github.com/namecoin/ncdns/util" var rpchost = flag.String("rpchost", "", "Namecoin RPC host:port") var rpcuser = flag.String("rpcuser", "", "Namecoin RPC username") var rpcpass = flag.String("rpcpass", "", "Namecoin RPC password") -var conn namecoin.Conn +var rpccookiepath = flag.String("rpccookiepath", "", "Namecoin RPC cookie path (used if password is unspecified)") +var conn *namecoin.Client func usage() { fmt.Fprintf(os.Stderr, "Usage: ncdt [options] [ ...]\n") @@ -21,6 +23,7 @@ func usage() { fmt.Fprintf(os.Stderr, " -rpchost=host:port Namecoin RPC server address } only required for RPC retrieval\n") fmt.Fprintf(os.Stderr, " -rpcuser=username Namecoin RPC username }\n") fmt.Fprintf(os.Stderr, " -rpcpass=password Namecoin RPC password }\n") + fmt.Fprintf(os.Stderr, " -rpccookiepath=path Namecoin RPC cookie path }\n") os.Exit(2) } @@ -41,7 +44,7 @@ func translateValue(k, v string) (string, error) { f = os.NewFile(uintptr(n), "-") } else if len(v) == 1 { - return conn.Query(k, "") + return conn.NameQuery(k, "") } else { f, err = os.Open(v) } @@ -71,9 +74,26 @@ func main() { usage() } - conn.Username = *rpcuser - conn.Password = *rpcpass - conn.Server = *rpchost + // Connect to local namecoin core RPC server using HTTP POST mode. + connCfg := &rpcclient.ConnConfig{ + Host: *rpchost, + User: *rpcuser, + Pass: *rpcpass, + CookiePath: *rpccookiepath, + HTTPPostMode: true, // Namecoin core only supports HTTP POST mode + DisableTLS: true, // Namecoin core does not provide TLS by default + } + + var err error + + // Notice the notification parameter is nil since notifications are + // not supported in HTTP POST mode. + conn, err = namecoin.New(connCfg, nil) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating RPC client: %v\n", err) + os.Exit(1) + } + defer conn.Shutdown() for i := 0; i+1 < len(args); i += 2 { k := args[i] @@ -83,7 +103,7 @@ func main() { os.Exit(1) } - v, err := translateValue(k, v) + v, err = translateValue(k, v) if err != nil { fmt.Fprintf(os.Stderr, "failed to translate value: %v\n", err) os.Exit(1) diff --git a/ncdumpzone/ncdumpzone.go b/ncdumpzone/ncdumpzone.go index 2428657..0b5329a 100644 --- a/ncdumpzone/ncdumpzone.go +++ b/ncdumpzone/ncdumpzone.go @@ -8,7 +8,7 @@ import ( "github.com/hlandau/xlog" "github.com/miekg/dns" - extratypes "github.com/hlandau/ncbtcjsontypes" + "github.com/namecoin/ncbtcjson" "github.com/namecoin/ncdns/namecoin" "github.com/namecoin/ncdns/ncdomain" "github.com/namecoin/ncdns/rrtourl" @@ -41,7 +41,7 @@ func dumpRR(rr dns.RR, dest io.Writer, format string) error { return nil } -func dumpName(item *extratypes.NameFilterItem, conn namecoin.Conn, +func dumpName(item *ncbtcjson.NameShowResult, conn *namecoin.Client, dest io.Writer, format string) error { // The order in which name_scan returns results is seemingly rather // random, so we can't stop when we see a non-d/ name, so just skip it. @@ -55,7 +55,7 @@ func dumpName(item *extratypes.NameFilterItem, conn namecoin.Conn, } getNameFunc := func(k string) (string, error) { - return conn.Query(k, "") + return conn.NameQuery(k, "") } var errors []error @@ -83,7 +83,7 @@ func dumpName(item *extratypes.NameFilterItem, conn namecoin.Conn, // Dump extracts all domain names from conn, formats them according to the // specified format, and writes the result to dest. -func Dump(conn namecoin.Conn, dest io.Writer, format string) error { +func Dump(conn *namecoin.Client, dest io.Writer, format string) error { if format != "zonefile" && format != "firefox-override" && format != "url-list" { return fmt.Errorf("Invalid \"format\" argument: %s", format) @@ -93,7 +93,7 @@ func Dump(conn namecoin.Conn, dest io.Writer, format string) error { continuing := 0 for { - results, err := conn.Scan(currentName, perCall) + results, err := conn.NameScan(currentName, perCall) if err != nil { return fmt.Errorf("scan: %s", err) } diff --git a/ncdumpzone/ncdumpzone/ncdumpzone.go b/ncdumpzone/ncdumpzone/ncdumpzone.go index 99fadee..2f6fc11 100644 --- a/ncdumpzone/ncdumpzone/ncdumpzone.go +++ b/ncdumpzone/ncdumpzone/ncdumpzone.go @@ -4,6 +4,7 @@ import ( "os" "github.com/hlandau/xlog" + "github.com/namecoin/btcd/rpcclient" "gopkg.in/hlandau/easyconfig.v1" "gopkg.in/hlandau/easyconfig.v1/cflag" @@ -21,13 +22,15 @@ var ( "Namecoin RPC username") rpcpassFlag = cflag.String(flagGroup, "namecoinrpcpassword", "", "Namecoin RPC password") + rpccookiepathFlag = cflag.String(flagGroup, "namecoinrpccookiepath", "", + "Namecoin RPC cookie path (used if password is unspecified)") formatFlag = cflag.String(flagGroup, "format", "zonefile", "Output "+ "format. \"zonefile\" = DNS zone file. "+ "\"firefox-override\" = Firefox cert_override.txt format. "+ "\"url-list\" = URL list.") ) -var conn namecoin.Conn +var conn *namecoin.Client var config = easyconfig.Configurator{ ProgramName: "ncdumpzone", @@ -39,9 +42,23 @@ func main() { log.Fatalf("Couldn't parse configuration: %s", err) } - conn.Server = rpchostFlag.Value() - conn.Username = rpcuserFlag.Value() - conn.Password = rpcpassFlag.Value() + // Connect to local namecoin core RPC server using HTTP POST mode. + connCfg := &rpcclient.ConnConfig{ + Host: rpchostFlag.Value(), + User: rpcuserFlag.Value(), + Pass: rpcpassFlag.Value(), + CookiePath: rpccookiepathFlag.Value(), + HTTPPostMode: true, // Namecoin core only supports HTTP POST mode + DisableTLS: true, // Namecoin core does not provide TLS by default + } + + // Notice the notification parameter is nil since notifications are + // not supported in HTTP POST mode. + conn, err = namecoin.New(connCfg, nil) + if err != nil { + log.Fatalf("Couldn't create RPC client: %s", err) + } + defer conn.Shutdown() err = ncdumpzone.Dump(conn, os.Stdout, formatFlag.Value()) if err != nil { diff --git a/server/cookiefile.go b/server/cookiefile.go deleted file mode 100644 index cc43ae3..0000000 --- a/server/cookiefile.go +++ /dev/null @@ -1,59 +0,0 @@ -package server - -import ( - "fmt" - "io/ioutil" - "os" - "strings" - "time" -) - -func readCookieFile(path string) (username, password string, err error) { - b, err := ioutil.ReadFile(path) - if err != nil { - return - } - - s := strings.TrimSpace(string(b)) - parts := strings.SplitN(s, ":", 2) - if len(parts) != 2 { - err = fmt.Errorf("malformed cookie file") - return - } - - username, password = parts[0], parts[1] - return -} - -func cookieRetriever(path string) func() (username, password string, err error) { - lastCheckTime := time.Time{} - lastModTime := time.Time{} - - curUsername, curPassword := "", "" - var curError error - - doUpdate := func() { - if !lastCheckTime.IsZero() && time.Now().Before(lastCheckTime.Add(30*time.Second)) { - return - } - - lastCheckTime = time.Now() - - st, err := os.Stat(path) - if err != nil { - curError = err - return - } - - modTime := st.ModTime() - if !modTime.Equal(lastModTime) { - lastModTime = modTime - curUsername, curPassword, curError = readCookieFile(path) - } - } - - return func() (username, password string, err error) { - doUpdate() - return curUsername, curPassword, curError - } -} diff --git a/server/server.go b/server/server.go index 7b7086a..f0b5f25 100644 --- a/server/server.go +++ b/server/server.go @@ -12,9 +12,10 @@ import ( "github.com/hlandau/buildinfo" "github.com/hlandau/xlog" "github.com/miekg/dns" + "github.com/namecoin/btcd/rpcclient" "github.com/namecoin/ncdns/backend" "github.com/namecoin/ncdns/namecoin" - "gopkg.in/hlandau/madns.v2" + madns "gopkg.in/hlandau/madns.v2" ) var log, Log = xlog.New("ncdns.server") @@ -23,7 +24,7 @@ type Server struct { cfg Config engine madns.Engine - namecoinConn namecoin.Conn + namecoinConn *namecoin.Client mux *dns.ServeMux udpServer *dns.Server @@ -43,7 +44,7 @@ type Config struct { NamecoinRPCUsername string `default:"" usage:"Namecoin RPC username"` NamecoinRPCPassword string `default:"" usage:"Namecoin RPC password"` NamecoinRPCAddress string `default:"127.0.0.1:8336" usage:"Namecoin RPC server address"` - NamecoinRPCCookiePath string `default:"" usage:"Namecoin RPC cookie path (if set, used instead of password)"` + NamecoinRPCCookiePath string `default:"" usage:"Namecoin RPC cookie path (used if password is unspecified)"` NamecoinRPCTimeout int `default:"1500" usage:"Timeout (in milliseconds) for Namecoin RPC requests"` CacheMaxEntries int `default:"100" usage:"Maximum name cache entries"` SelfName string `default:"" usage:"The FQDN of this nameserver. If empty, a pseudo-hostname is generated."` @@ -72,17 +73,26 @@ var ncdnsVersion string func New(cfg *Config) (s *Server, err error) { ncdnsVersion = buildinfo.VersionSummary("github.com/namecoin/ncdns", "ncdns") - s = &Server{ - cfg: *cfg, - namecoinConn: namecoin.Conn{ - Username: cfg.NamecoinRPCUsername, - Password: cfg.NamecoinRPCPassword, - Server: cfg.NamecoinRPCAddress, - }, + // Connect to local namecoin core RPC server using HTTP POST mode. + connCfg := &rpcclient.ConnConfig{ + Host: cfg.NamecoinRPCAddress, + User: cfg.NamecoinRPCUsername, + Pass: cfg.NamecoinRPCPassword, + CookiePath: cfg.NamecoinRPCCookiePath, + HTTPPostMode: true, // Namecoin core only supports HTTP POST mode + DisableTLS: true, // Namecoin core does not provide TLS by default + } + + // Notice the notification parameter is nil since notifications are + // not supported in HTTP POST mode. + client, err := namecoin.New(connCfg, nil) + if err != nil { + return nil, err } - if s.cfg.NamecoinRPCCookiePath != "" { - s.namecoinConn.GetAuth = cookieRetriever(s.cfg.NamecoinRPCCookiePath) + s = &Server{ + cfg: *cfg, + namecoinConn: client, } if s.cfg.CanonicalNameservers != "" { diff --git a/server/web.go b/server/web.go index 3cd3111..d024a42 100644 --- a/server/web.go +++ b/server/web.go @@ -140,7 +140,7 @@ func (ws *webServer) handleLookup(rw http.ResponseWriter, req *http.Request) { info.JSONValue = req.FormValue("value") info.Value = strings.Trim(info.JSONValue, " \t\r\n") if info.Value == "" { - info.Value, info.ExistenceError = ws.s.namecoinConn.Query(info.NamecoinName, "") + info.Value, info.ExistenceError = ws.s.namecoinConn.NameQuery(info.NamecoinName, "") if info.ExistenceError != nil { return } @@ -170,7 +170,7 @@ func (ws *webServer) handleLookup(rw http.ResponseWriter, req *http.Request) { } func (ws *webServer) resolveFunc(name string) (string, error) { - return ws.s.namecoinConn.Query(name, "") + return ws.s.namecoinConn.NameQuery(name, "") } func (ws *webServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go b/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go index 190a274..132b729 100644 --- a/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go +++ b/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go @@ -37,7 +37,7 @@ var zoneDataMux sync.Mutex // situation, .bit domains must stop resolving until the issue is corrected. // Forcing ncdns to exit is the least complex way to achieve this. -func watchZone(conn namecoin.Conn) { +func watchZone(conn *namecoin.Client) { for { var result bytes.Buffer @@ -125,7 +125,7 @@ func profileInUse() bool { // Start starts 2 background threads that synchronize the blockchain's TLSA // records to a Firefox profile's cert_override.txt. It accepts a connection // to access Namecoin Core, as well as a host suffix (usually "bit"). -func Start(conn namecoin.Conn, suffix string) error { +func Start(conn *namecoin.Client, suffix string) error { if syncEnableFlag.Value() { go watchZone(conn) go watchProfile(suffix)