github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbhttp/manager/manager.go (about)

     1  package manager
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"fmt"
     6  	"net/http"
     7  	"runtime"
     8  	"sync"
     9  
    10  	"github.com/keybase/client/go/kbhttp"
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	context "golang.org/x/net/context"
    14  )
    15  
    16  type SrvTokenMode int
    17  
    18  const (
    19  	SrvTokenModeDefault   = iota
    20  	SrvTokenModeUnchecked // use with caution!
    21  )
    22  
    23  type srvEndpoint struct {
    24  	tokenMode SrvTokenMode
    25  	serve     func(w http.ResponseWriter, req *http.Request)
    26  }
    27  
    28  type Srv struct {
    29  	libkb.Contextified
    30  
    31  	httpSrv   *kbhttp.Srv
    32  	endpoints map[string]srvEndpoint
    33  	token     string
    34  	startMu   sync.Mutex
    35  }
    36  
    37  func NewSrv(g *libkb.GlobalContext) *Srv {
    38  	h := &Srv{
    39  		Contextified: libkb.NewContextified(g),
    40  		endpoints:    make(map[string]srvEndpoint),
    41  	}
    42  	h.initHTTPSrv()
    43  	h.startHTTPSrv()
    44  	g.PushShutdownHook(func(mctx libkb.MetaContext) error {
    45  		h.httpSrv.Stop()
    46  		return nil
    47  	})
    48  	go h.monitorAppState()
    49  	return h
    50  }
    51  
    52  func (r *Srv) debug(ctx context.Context, msg string, args ...interface{}) {
    53  	r.G().Log.CDebugf(ctx, "Srv: %s", fmt.Sprintf(msg, args...))
    54  }
    55  
    56  func (r *Srv) initHTTPSrv() {
    57  	startPort := r.G().GetEnv().GetAttachmentHTTPStartPort()
    58  	r.httpSrv = kbhttp.NewSrv(r.G().GetLog(), kbhttp.NewRandomPortRangeListenerSource(startPort, 18000))
    59  }
    60  
    61  func (r *Srv) startHTTPSrv() {
    62  	r.startMu.Lock()
    63  	defer r.startMu.Unlock()
    64  	ctx := context.Background()
    65  	token, _ := libkb.RandHexString("", 32)
    66  	maxTries := 2
    67  	success := false
    68  	for i := 0; i < maxTries; i++ {
    69  		if err := r.httpSrv.Start(); err != nil {
    70  			if err == kbhttp.ErrPinnedPortInUse {
    71  				// If we hit this, just try again and get a different port.
    72  				// The advantage is that backing in and out of the thread will restore attachments,
    73  				// whereas if we do nothing you need to bkg/foreground.
    74  				r.debug(ctx, "startHTTPSrv: pinned port taken error, re-initializing and trying again")
    75  				r.initHTTPSrv()
    76  				continue
    77  			}
    78  			r.debug(ctx, "startHTTPSrv: failed to start HTTP server: %s", err)
    79  			break
    80  		}
    81  		success = true
    82  		break
    83  	}
    84  	if !success {
    85  		r.debug(ctx, "startHTTPSrv: exhausted attempts to start HTTP server, giving up")
    86  		return
    87  	}
    88  	for endpoint, serveDesc := range r.endpoints {
    89  		r.HandleFunc(endpoint, serveDesc.tokenMode, serveDesc.serve)
    90  	}
    91  	addr, err := r.httpSrv.Addr()
    92  	if err != nil {
    93  		r.debug(ctx, "startHTTPSrv: failed to get address after start?: %s", err)
    94  	} else {
    95  		r.debug(ctx, "startHTTPSrv: start success: addr: %s", addr)
    96  	}
    97  	r.token = token
    98  	r.debug(ctx, "startHTTPSrv: addr: %s token: %s", addr, r.token)
    99  	r.G().NotifyRouter.HandleHTTPSrvInfoUpdate(ctx, keybase1.HttpSrvInfo{
   100  		Address: addr,
   101  		Token:   r.token,
   102  	})
   103  }
   104  
   105  func (r *Srv) monitorAppState() {
   106  	ctx := context.Background()
   107  	r.debug(ctx, "monitorAppState: starting up")
   108  	state := keybase1.MobileAppState_FOREGROUND
   109  	// We don't need this on Android
   110  	if runtime.GOOS == "android" {
   111  		return
   112  	}
   113  	for {
   114  		state = <-r.G().MobileAppState.NextUpdate(&state)
   115  		switch state {
   116  		case keybase1.MobileAppState_FOREGROUND, keybase1.MobileAppState_BACKGROUNDACTIVE:
   117  			r.startHTTPSrv()
   118  		case keybase1.MobileAppState_BACKGROUND, keybase1.MobileAppState_INACTIVE:
   119  			r.httpSrv.Stop()
   120  		}
   121  	}
   122  }
   123  
   124  func (r *Srv) HandleFunc(endpoint string, tokenMode SrvTokenMode,
   125  	serve func(w http.ResponseWriter, req *http.Request)) {
   126  	r.httpSrv.HandleFunc("/"+endpoint, func(w http.ResponseWriter, req *http.Request) {
   127  		switch tokenMode {
   128  		case SrvTokenModeDefault:
   129  			if !hmac.Equal([]byte(req.URL.Query().Get("token")), []byte(r.token)) {
   130  				r.debug(context.Background(), "HandleFunc: token failed: %s != %s",
   131  					req.URL.Query().Get("token"), r.token)
   132  				w.WriteHeader(http.StatusForbidden)
   133  				return
   134  			}
   135  		case SrvTokenModeUnchecked:
   136  			// serve needs to authenticate on its own
   137  		}
   138  		serve(w, req)
   139  	})
   140  	r.endpoints[endpoint] = srvEndpoint{
   141  		tokenMode: tokenMode,
   142  		serve:     serve,
   143  	}
   144  }
   145  
   146  func (r *Srv) Active() bool {
   147  	return r.httpSrv.Active()
   148  }
   149  
   150  func (r *Srv) Addr() (string, error) {
   151  	r.startMu.Lock()
   152  	defer r.startMu.Unlock()
   153  	return r.httpSrv.Addr()
   154  }
   155  
   156  func (r *Srv) Token() string {
   157  	r.startMu.Lock()
   158  	defer r.startMu.Unlock()
   159  	return r.token
   160  }