decred.org/dcrwallet/v3@v3.1.0/internal/rpc/jsonrpc/server.go (about)

     1  // Copyright (c) 2013-2015 The btcsuite developers
     2  // Copyright (c) 2017-2019 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package jsonrpc
     7  
     8  import (
     9  	"context"
    10  	"crypto/sha256"
    11  	"crypto/subtle"
    12  	"encoding/base64"
    13  	"encoding/json"
    14  	"io"
    15  	"net"
    16  	"net/http"
    17  	"runtime/trace"
    18  	"sync"
    19  	"sync/atomic"
    20  	"time"
    21  
    22  	"decred.org/dcrwallet/v3/errors"
    23  	"decred.org/dcrwallet/v3/internal/loader"
    24  	"decred.org/dcrwallet/v3/rpc/jsonrpc/types"
    25  	"github.com/decred/dcrd/chaincfg/v3"
    26  	"github.com/decred/dcrd/dcrjson/v4"
    27  	dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    28  	"github.com/gorilla/websocket"
    29  )
    30  
    31  type websocketClient struct {
    32  	conn          *websocket.Conn
    33  	authenticated bool
    34  	allRequests   chan []byte
    35  	responses     chan []byte
    36  	cancel        func()
    37  	quit          chan struct{} // closed on disconnect
    38  	wg            sync.WaitGroup
    39  }
    40  
    41  func newWebsocketClient(c *websocket.Conn, cancel func(), authenticated bool) *websocketClient {
    42  	return &websocketClient{
    43  		conn:          c,
    44  		authenticated: authenticated,
    45  		allRequests:   make(chan []byte),
    46  		responses:     make(chan []byte),
    47  		cancel:        cancel,
    48  		quit:          make(chan struct{}),
    49  	}
    50  }
    51  
    52  func (c *websocketClient) send(b []byte) error {
    53  	select {
    54  	case c.responses <- b:
    55  		return nil
    56  	case <-c.quit:
    57  		return errors.New("websocket client disconnected")
    58  	}
    59  }
    60  
    61  // Server holds the items the RPC server may need to access (auth,
    62  // config, shutdown, etc.)
    63  type Server struct {
    64  	httpServer   http.Server
    65  	walletLoader *loader.Loader
    66  	listeners    []net.Listener
    67  	authsha      *[sha256.Size]byte // nil when basic auth is disabled
    68  	upgrader     websocket.Upgrader
    69  
    70  	cfg Options
    71  
    72  	wg      sync.WaitGroup
    73  	quit    chan struct{}
    74  	quitMtx sync.Mutex
    75  
    76  	requestShutdownChan chan struct{}
    77  
    78  	activeNet *chaincfg.Params
    79  }
    80  
    81  type handler struct {
    82  	fn     func(*Server, context.Context, interface{}) (interface{}, error)
    83  	noHelp bool
    84  }
    85  
    86  // jsonAuthFail sends a message back to the client if the http auth is rejected.
    87  func jsonAuthFail(w http.ResponseWriter) {
    88  	w.Header().Add("WWW-Authenticate", `Basic realm="dcrwallet RPC"`)
    89  	http.Error(w, "401 Unauthorized.", http.StatusUnauthorized)
    90  }
    91  
    92  // NewServer creates a new server for serving JSON-RPC client connections,
    93  // both HTTP POST and websocket.
    94  func NewServer(opts *Options, activeNet *chaincfg.Params, walletLoader *loader.Loader, listeners []net.Listener) *Server {
    95  	serveMux := http.NewServeMux()
    96  	const rpcAuthTimeoutSeconds = 10
    97  	server := &Server{
    98  		httpServer: http.Server{
    99  			Handler: serveMux,
   100  
   101  			// Timeout connections which don't complete the initial
   102  			// handshake within the allowed timeframe.
   103  			ReadTimeout: time.Second * rpcAuthTimeoutSeconds,
   104  		},
   105  		walletLoader: walletLoader,
   106  		cfg:          *opts,
   107  		listeners:    listeners,
   108  		// A hash of the HTTP basic auth string is used for a constant
   109  		// time comparison.
   110  		upgrader: websocket.Upgrader{
   111  			// Allow all origins.
   112  			CheckOrigin: func(r *http.Request) bool { return true },
   113  		},
   114  		quit:                make(chan struct{}),
   115  		requestShutdownChan: make(chan struct{}, 1),
   116  		activeNet:           activeNet,
   117  	}
   118  	if opts.Username != "" && opts.Password != "" {
   119  		h := sha256.Sum256(httpBasicAuth(opts.Username, opts.Password))
   120  		server.authsha = &h
   121  	}
   122  
   123  	serveMux.Handle("/", throttledFn(opts.MaxPOSTClients,
   124  		func(w http.ResponseWriter, r *http.Request) {
   125  			w.Header().Set("Connection", "close")
   126  			w.Header().Set("Content-Type", "application/json")
   127  			r.Close = true
   128  
   129  			if err := server.checkAuthHeader(r); err != nil {
   130  				log.Warnf("Failed authentication attempt from client %s",
   131  					r.RemoteAddr)
   132  				jsonAuthFail(w)
   133  				return
   134  			}
   135  			server.wg.Add(1)
   136  			defer server.wg.Done()
   137  			server.postClientRPC(w, r)
   138  		}))
   139  
   140  	serveMux.Handle("/ws", throttledFn(opts.MaxWebsocketClients,
   141  		func(w http.ResponseWriter, r *http.Request) {
   142  			authenticated := false
   143  			switch server.checkAuthHeader(r) {
   144  			case nil:
   145  				authenticated = true
   146  			case errNoAuth:
   147  				// nothing
   148  			default:
   149  				// If auth was supplied but incorrect, rather than simply
   150  				// being missing, immediately terminate the connection.
   151  				log.Warnf("Failed authentication attempt from client %s",
   152  					r.RemoteAddr)
   153  				jsonAuthFail(w)
   154  				return
   155  			}
   156  
   157  			conn, err := server.upgrader.Upgrade(w, r, nil)
   158  			if err != nil {
   159  				log.Warnf("Cannot websocket upgrade client %s: %v",
   160  					r.RemoteAddr, err)
   161  				return
   162  			}
   163  			ctx := withRemoteAddr(r.Context(), r.RemoteAddr)
   164  			ctx, cancel := context.WithCancel(ctx)
   165  			wsc := newWebsocketClient(conn, cancel, authenticated)
   166  			server.websocketClientRPC(ctx, wsc)
   167  		}))
   168  
   169  	for _, lis := range listeners {
   170  		server.serve(lis)
   171  	}
   172  
   173  	return server
   174  }
   175  
   176  // httpBasicAuth returns the UTF-8 bytes of the HTTP Basic authentication
   177  // string:
   178  //
   179  //	"Basic " + base64(username + ":" + password)
   180  func httpBasicAuth(username, password string) []byte {
   181  	const header = "Basic "
   182  	base64 := base64.StdEncoding
   183  
   184  	b64InputLen := len(username) + len(":") + len(password)
   185  	b64Input := make([]byte, 0, b64InputLen)
   186  	b64Input = append(b64Input, username...)
   187  	b64Input = append(b64Input, ':')
   188  	b64Input = append(b64Input, password...)
   189  
   190  	output := make([]byte, len(header)+base64.EncodedLen(b64InputLen))
   191  	copy(output, header)
   192  	base64.Encode(output[len(header):], b64Input)
   193  	return output
   194  }
   195  
   196  // serve serves HTTP POST and websocket RPC for the JSON-RPC RPC server.
   197  // This function does not block on lis.Accept.
   198  func (s *Server) serve(lis net.Listener) {
   199  	s.wg.Add(1)
   200  	go func() {
   201  		defer s.wg.Done()
   202  		log.Infof("Listening on %s", lis.Addr())
   203  		err := s.httpServer.Serve(lis)
   204  		log.Tracef("Finished serving RPC: %v", err)
   205  	}()
   206  }
   207  
   208  // Stop gracefully shuts down the rpc server by stopping and disconnecting all
   209  // clients.  This blocks until shutdown completes.
   210  func (s *Server) Stop() {
   211  	s.quitMtx.Lock()
   212  	select {
   213  	case <-s.quit:
   214  		s.quitMtx.Unlock()
   215  		return
   216  	default:
   217  	}
   218  
   219  	// Stop all the listeners.
   220  	for _, listener := range s.listeners {
   221  		err := listener.Close()
   222  		if err != nil {
   223  			log.Errorf("Cannot close listener `%s`: %v",
   224  				listener.Addr(), err)
   225  		}
   226  	}
   227  
   228  	// Signal the remaining goroutines to stop.
   229  	close(s.quit)
   230  	s.quitMtx.Unlock()
   231  
   232  	// Wait for all remaining goroutines to exit.
   233  	s.wg.Wait()
   234  }
   235  
   236  // handlerClosure creates a closure function for handling requests of the given
   237  // method.  This may be a request that is handled directly by dcrwallet, or
   238  // a chain server request that is handled by passing the request down to dcrd.
   239  //
   240  // NOTE: These handlers do not handle special cases, such as the authenticate
   241  // method.  Each of these must be checked beforehand (the method is already
   242  // known) and handled accordingly.
   243  func (s *Server) handlerClosure(ctx context.Context, request *dcrjson.Request) lazyHandler {
   244  	log.Debugf("RPC method %q invoked by %v", request.Method, remoteAddr(ctx))
   245  	return lazyApplyHandler(s, ctx, request)
   246  }
   247  
   248  // errNoAuth represents an error where authentication could not succeed
   249  // due to a missing Authorization HTTP header.
   250  var errNoAuth = errors.E("missing Authorization header")
   251  
   252  // checkAuthHeader checks any HTTP Basic authentication supplied by a client
   253  // in the HTTP request r.
   254  //
   255  // The authentication comparison is time constant.
   256  func (s *Server) checkAuthHeader(r *http.Request) error {
   257  	if s.authsha == nil {
   258  		return nil
   259  	}
   260  	authhdr := r.Header["Authorization"]
   261  	if len(authhdr) == 0 {
   262  		return errNoAuth
   263  	}
   264  
   265  	authsha := sha256.Sum256([]byte(authhdr[0]))
   266  	cmp := subtle.ConstantTimeCompare(authsha[:], s.authsha[:])
   267  	if cmp != 1 {
   268  		return errors.New("invalid Authorization header")
   269  	}
   270  	return nil
   271  }
   272  
   273  // throttledFn wraps an http.HandlerFunc with throttling of concurrent active
   274  // clients by responding with an HTTP 429 when the threshold is crossed.
   275  func throttledFn(threshold int64, f http.HandlerFunc) http.Handler {
   276  	return throttled(threshold, f)
   277  }
   278  
   279  // throttled wraps an http.Handler with throttling of concurrent active
   280  // clients by responding with an HTTP 429 when the threshold is crossed.
   281  func throttled(threshold int64, h http.Handler) http.Handler {
   282  	var active int64
   283  
   284  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   285  		current := atomic.AddInt64(&active, 1)
   286  		defer atomic.AddInt64(&active, -1)
   287  
   288  		if current-1 >= threshold {
   289  			log.Warnf("Reached threshold of %d concurrent active clients", threshold)
   290  			http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests)
   291  			return
   292  		}
   293  
   294  		h.ServeHTTP(w, r)
   295  	})
   296  }
   297  
   298  // idPointer returns a pointer to the passed ID, or nil if the interface is nil.
   299  // Interface pointers are usually a red flag of doing something incorrectly,
   300  // but this is only implemented here to work around an oddity with dcrjson,
   301  // which uses empty interface pointers for response IDs.
   302  func idPointer(id interface{}) (p *interface{}) {
   303  	if id != nil {
   304  		p = &id
   305  	}
   306  	return
   307  }
   308  
   309  // invalidAuth checks whether a websocket request is a valid (parsable)
   310  // authenticate request and checks the supplied username and passphrase
   311  // against the server auth.
   312  func (s *Server) invalidAuth(req *dcrjson.Request) bool {
   313  	cmd, err := dcrjson.ParseParams(types.Method(req.Method), req.Params)
   314  	if err != nil {
   315  		return false
   316  	}
   317  	authCmd, ok := cmd.(*dcrdtypes.AuthenticateCmd)
   318  	if !ok {
   319  		return false
   320  	}
   321  	// Authenticate commands are invalid when no basic auth is used
   322  	if s.authsha == nil {
   323  		return true
   324  	}
   325  	// Check credentials.
   326  	login := authCmd.Username + ":" + authCmd.Passphrase
   327  	auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
   328  	authSha := sha256.Sum256([]byte(auth))
   329  	return subtle.ConstantTimeCompare(authSha[:], s.authsha[:]) != 1
   330  }
   331  
   332  func (s *Server) websocketClientRead(ctx context.Context, wsc *websocketClient) {
   333  	for {
   334  		_, request, err := wsc.conn.ReadMessage()
   335  		if err != nil {
   336  			if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
   337  				log.Warnf("Websocket receive failed from client %s: %v",
   338  					remoteAddr(ctx), err)
   339  			}
   340  			close(wsc.allRequests)
   341  			wsc.cancel()
   342  			break
   343  		}
   344  		wsc.allRequests <- request
   345  	}
   346  }
   347  
   348  func (s *Server) websocketClientRespond(ctx context.Context, wsc *websocketClient) {
   349  	// A for-select with a read of the quit channel is used instead of a
   350  	// for-range to provide clean shutdown.  This is necessary due to
   351  	// WebsocketClientRead (which sends to the allRequests chan) not closing
   352  	// allRequests during shutdown if the remote websocket client is still
   353  	// connected.
   354  out:
   355  	for {
   356  		select {
   357  		case reqBytes, ok := <-wsc.allRequests:
   358  			if !ok {
   359  				// client disconnected
   360  				break out
   361  			}
   362  
   363  			var req dcrjson.Request
   364  			err := json.Unmarshal(reqBytes, &req)
   365  			if err != nil {
   366  				log.Warnf("Failed unmarshal of JSON-RPC request object "+
   367  					"from client %s", remoteAddr(ctx))
   368  				if !wsc.authenticated {
   369  					// Disconnect immediately.
   370  					break out
   371  				}
   372  				resp := makeResponse(req.ID, nil,
   373  					dcrjson.ErrRPCInvalidRequest)
   374  				mresp, err := json.Marshal(resp)
   375  				// We expect the marshal to succeed.  If it
   376  				// doesn't, it indicates some non-marshalable
   377  				// type in the response.
   378  				if err != nil {
   379  					panic(err)
   380  				}
   381  				err = wsc.send(mresp)
   382  				if err != nil {
   383  					break out
   384  				}
   385  				continue
   386  			}
   387  
   388  			if req.Method == "authenticate" {
   389  				log.Debugf("RPC method authenticate invoked by %s",
   390  					remoteAddr(ctx))
   391  				switch {
   392  				case wsc.authenticated:
   393  					log.Warnf("Multiple authentication attempts from %s",
   394  						remoteAddr(ctx))
   395  					break out
   396  				case s.invalidAuth(&req):
   397  					log.Warnf("Failed authentication attempt from %s",
   398  						remoteAddr(ctx))
   399  					break out
   400  				}
   401  				wsc.authenticated = true
   402  				resp := makeResponse(req.ID, nil, nil)
   403  				// Expected to never fail.
   404  				mresp, err := json.Marshal(resp)
   405  				if err != nil {
   406  					panic(err)
   407  				}
   408  				err = wsc.send(mresp)
   409  				if err != nil {
   410  					break out
   411  				}
   412  				continue
   413  			}
   414  
   415  			if !wsc.authenticated {
   416  				// Disconnect immediately.
   417  				break out
   418  			}
   419  
   420  			switch req.Method {
   421  			case "stop":
   422  				log.Debugf("RPC method stop invoked by %s", remoteAddr(ctx))
   423  				resp := makeResponse(req.ID,
   424  					"dcrwallet stopping.", nil)
   425  				mresp, err := json.Marshal(resp)
   426  				// Expected to never fail.
   427  				if err != nil {
   428  					panic(err)
   429  				}
   430  				err = wsc.send(mresp)
   431  				if err != nil {
   432  					break out
   433  				}
   434  				s.requestProcessShutdown()
   435  				break out
   436  
   437  			default:
   438  				req := req // Copy for the closure
   439  				ctx, task := trace.NewTask(ctx, req.Method)
   440  				f := s.handlerClosure(ctx, &req)
   441  				wsc.wg.Add(1)
   442  				go func() {
   443  					defer task.End()
   444  					defer wsc.wg.Done()
   445  					resp, jsonErr := f()
   446  					mresp, err := dcrjson.MarshalResponse(req.Jsonrpc, req.ID, resp, jsonErr)
   447  					if err != nil {
   448  						log.Errorf("Unable to marshal response to client %s: %v",
   449  							remoteAddr(ctx), err)
   450  					} else {
   451  						_ = wsc.send(mresp)
   452  					}
   453  				}()
   454  			}
   455  
   456  		case <-s.quit:
   457  			break out
   458  		}
   459  	}
   460  
   461  	// allow client to disconnect after all handler goroutines are done
   462  	wsc.wg.Wait()
   463  	close(wsc.responses)
   464  	s.wg.Done()
   465  }
   466  
   467  func (s *Server) websocketClientSend(ctx context.Context, wsc *websocketClient) {
   468  	defer s.wg.Done()
   469  	const deadline time.Duration = 2 * time.Second
   470  out:
   471  	for {
   472  		select {
   473  		case response, ok := <-wsc.responses:
   474  			if !ok {
   475  				// client disconnected
   476  				break out
   477  			}
   478  			err := wsc.conn.SetWriteDeadline(time.Now().Add(deadline))
   479  			if err != nil {
   480  				log.Warnf("Cannot set write deadline on "+
   481  					"client %s: %v", remoteAddr(ctx), err)
   482  			}
   483  			err = wsc.conn.WriteMessage(websocket.TextMessage,
   484  				response)
   485  			if err != nil {
   486  				log.Warnf("Failed websocket send to client "+
   487  					"%s: %v", remoteAddr(ctx), err)
   488  				break out
   489  			}
   490  
   491  		case <-s.quit:
   492  			break out
   493  		}
   494  	}
   495  	close(wsc.quit)
   496  	log.Infof("Disconnected websocket client %s", remoteAddr(ctx))
   497  }
   498  
   499  // websocketClientRPC starts the goroutines to serve JSON-RPC requests over a
   500  // websocket connection for a single client.
   501  func (s *Server) websocketClientRPC(ctx context.Context, wsc *websocketClient) {
   502  	log.Infof("New websocket client %s", remoteAddr(ctx))
   503  
   504  	// Clear the read deadline set before the websocket hijacked
   505  	// the connection.
   506  	if err := wsc.conn.SetReadDeadline(time.Time{}); err != nil {
   507  		log.Warnf("Cannot remove read deadline: %v", err)
   508  	}
   509  
   510  	// WebsocketClientRead is intentionally not run with the waitgroup
   511  	// so it is ignored during shutdown.  This is to prevent a hang during
   512  	// shutdown where the goroutine is blocked on a read of the
   513  	// websocket connection if the client is still connected.
   514  	go s.websocketClientRead(ctx, wsc)
   515  
   516  	s.wg.Add(2)
   517  	go s.websocketClientRespond(ctx, wsc)
   518  	go s.websocketClientSend(ctx, wsc)
   519  
   520  	<-wsc.quit
   521  }
   522  
   523  // maxRequestSize specifies the maximum number of bytes in the request body
   524  // that may be read from a client.  This is currently limited to 4MB.
   525  const maxRequestSize = 1024 * 1024 * 4
   526  
   527  // postClientRPC processes and replies to a JSON-RPC client request.
   528  func (s *Server) postClientRPC(w http.ResponseWriter, r *http.Request) {
   529  	ctx := withRemoteAddr(r.Context(), r.RemoteAddr)
   530  
   531  	body := http.MaxBytesReader(w, r.Body, maxRequestSize)
   532  	rpcRequest, err := io.ReadAll(body)
   533  	if err != nil {
   534  		// TODO: what if the underlying reader errored?
   535  		log.Warnf("Request from client %v exceeds maximum size", r.RemoteAddr)
   536  		http.Error(w, "413 Request Too Large.",
   537  			http.StatusRequestEntityTooLarge)
   538  		return
   539  	}
   540  
   541  	// First check whether wallet has a handler for this request's method.
   542  	// If unfound, the request is sent to the chain server for further
   543  	// processing.  While checking the methods, disallow authenticate
   544  	// requests, as they are invalid for HTTP POST clients.
   545  	var req dcrjson.Request
   546  	err = json.Unmarshal(rpcRequest, &req)
   547  	if err != nil {
   548  		resp, err := dcrjson.MarshalResponse(req.Jsonrpc, req.ID, nil, dcrjson.ErrRPCInvalidRequest)
   549  		if err != nil {
   550  			log.Errorf("Unable to marshal response to client %s: %v",
   551  				r.RemoteAddr, err)
   552  			http.Error(w, "500 Internal Server Error",
   553  				http.StatusInternalServerError)
   554  			return
   555  		}
   556  		_, err = w.Write(resp)
   557  		if err != nil {
   558  			log.Warnf("Cannot write invalid request request to "+
   559  				"client %s: %v", r.RemoteAddr, err)
   560  		}
   561  		return
   562  	}
   563  
   564  	ctx, task := trace.NewTask(ctx, req.Method)
   565  	defer task.End()
   566  
   567  	// Create the response and error from the request.  Two special cases
   568  	// are handled for the authenticate and stop request methods.
   569  	var res interface{}
   570  	var jsonErr *dcrjson.RPCError
   571  	var stop bool
   572  	switch req.Method {
   573  	case "authenticate":
   574  		log.Warnf("Invalid RPC method authenticate invoked by HTTP POST client %s",
   575  			r.RemoteAddr)
   576  		// Drop it.
   577  		return
   578  	case "stop":
   579  		log.Debugf("RPC method stop invoked by %s", r.RemoteAddr)
   580  		stop = true
   581  		res = "dcrwallet stopping"
   582  	default:
   583  		res, jsonErr = s.handlerClosure(ctx, &req)()
   584  	}
   585  
   586  	// Marshal and send.
   587  	mresp, err := dcrjson.MarshalResponse(req.Jsonrpc, req.ID, res, jsonErr)
   588  	if err != nil {
   589  		log.Errorf("Unable to marshal response to client %s: %v",
   590  			r.RemoteAddr, err)
   591  		http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
   592  		return
   593  	}
   594  	_, err = w.Write(mresp)
   595  	if err != nil {
   596  		log.Warnf("Failed to write response to client %s: %v",
   597  			r.RemoteAddr, err)
   598  	}
   599  
   600  	if stop {
   601  		s.requestProcessShutdown()
   602  	}
   603  }
   604  
   605  func (s *Server) requestProcessShutdown() {
   606  	s.requestShutdownChan <- struct{}{}
   607  }
   608  
   609  // RequestProcessShutdown returns a channel that is sent to when an authorized
   610  // client requests remote shutdown.
   611  func (s *Server) RequestProcessShutdown() <-chan struct{} {
   612  	return s.requestShutdownChan
   613  }