github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }