package chserver import ( "context" "net/http" "strings" "sync/atomic" "time" chshare "github.com/jpillora/chisel/share" "golang.org/x/crypto/ssh" ) // handleClientHandler is the main http websocket handler for the chisel server func (s *Server) handleClientHandler(w http.ResponseWriter, r *http.Request) { //websockets upgrade AND has chisel prefix upgrade := strings.ToLower(r.Header.Get("Upgrade")) protocol := r.Header.Get("Sec-WebSocket-Protocol") if upgrade == "websocket" && strings.HasPrefix(protocol, "chisel-") { if protocol == chshare.ProtocolVersion { s.handleWebsocket(w, r) return } //print into server logs and silently fall-through s.Infof("ignored client connection using protocol '%s', expected '%s'", protocol, chshare.ProtocolVersion) } //proxy target was provided if s.reverseProxy != nil { s.reverseProxy.ServeHTTP(w, r) return } //no proxy defined, provide access to health/version checks switch r.URL.String() { case "/health": w.Write([]byte("OK\n")) return case "/version": w.Write([]byte(chshare.BuildVersion)) return } //missing :O w.WriteHeader(404) w.Write([]byte("Not found")) } // handleWebsocket is responsible for handling the websocket connection func (s *Server) handleWebsocket(w http.ResponseWriter, req *http.Request) { id := atomic.AddInt32(&s.sessCount, 1) clog := s.Fork("session#%d", id) wsConn, err := upgrader.Upgrade(w, req, nil) if err != nil { clog.Debugf("Failed to upgrade (%s)", err) return } conn := chshare.NewWebSocketConn(wsConn) // perform SSH handshake on net.Conn clog.Debugf("Handshaking...") sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.sshConfig) if err != nil { s.Debugf("Failed to handshake (%s)", err) return } // pull the users from the session map var user *chshare.User if s.users.Len() > 0 { sid := string(sshConn.SessionID()) user, _ = s.sessions.Get(sid) s.sessions.Del(sid) } //verify configuration clog.Debugf("Verifying configuration") //wait for request, with timeout var r *ssh.Request select { case r = <-reqs: case <-time.After(10 * time.Second): sshConn.Close() return } failed := func(err error) { clog.Debugf("Failed: %s", err) r.Reply(false, []byte(err.Error())) } if r.Type != "config" { failed(s.Errorf("expecting config request")) return } c, err := chshare.DecodeConfig(r.Payload) if err != nil { failed(s.Errorf("invalid config")) return } //print if client and server versions dont match if c.Version != chshare.BuildVersion { v := c.Version if v == "" { v = "" } clog.Infof("Client version (%s) differs from server version (%s)", v, chshare.BuildVersion) } //confirm reverse tunnels are allowed for _, r := range c.Remotes { if r.Reverse && !s.reverseOk { clog.Debugf("Denied reverse port forwarding request, please enable --reverse") failed(s.Errorf("Reverse port forwaring not enabled on server")) return } } //if user is provided, ensure they have //access to the desired remotes if user != nil { for _, r := range c.Remotes { var addr string if r.Reverse { addr = "R:" + r.LocalHost + ":" + r.LocalPort } else { addr = r.RemoteHost + ":" + r.RemotePort } if !user.HasAccess(addr) { failed(s.Errorf("access to '%s' denied", addr)) return } } } //set up reverse port forwarding ctx, cancel := context.WithCancel(context.Background()) defer cancel() for i, r := range c.Remotes { if r.Reverse { proxy := chshare.NewTCPProxy(s.Logger, func() ssh.Conn { return sshConn }, i, r) if err := proxy.Start(ctx); err != nil { failed(s.Errorf("%s", err)) return } } } //success! r.Reply(true, nil) //prepare connection logger clog.Debugf("Open") go s.handleSSHRequests(clog, reqs) go s.handleSSHChannels(clog, chans) sshConn.Wait() clog.Debugf("Close") } func (s *Server) handleSSHRequests(clientLog *chshare.Logger, reqs <-chan *ssh.Request) { for r := range reqs { switch r.Type { case "ping": r.Reply(true, nil) default: clientLog.Debugf("Unknown request: %s", r.Type) } } } func (s *Server) handleSSHChannels(clientLog *chshare.Logger, chans <-chan ssh.NewChannel) { for ch := range chans { remote := string(ch.ExtraData()) socks := remote == "socks" //dont accept socks when --socks5 isn't enabled if socks && s.socksServer == nil { clientLog.Debugf("Denied socks request, please enable --socks5") ch.Reject(ssh.Prohibited, "SOCKS5 is not enabled on the server") continue } //accept rest stream, reqs, err := ch.Accept() if err != nil { clientLog.Debugf("Failed to accept stream: %s", err) continue } go ssh.DiscardRequests(reqs) //handle stream type connID := s.connStats.New() if socks { go chshare.HandleSocksStream(clientLog.Fork("socksconn#%d", connID), s.socksServer, &s.connStats, stream) } else { go chshare.HandleTCPStream(clientLog.Fork("conn#%d", connID), &s.connStats, stream, remote) } } }