mirror of
https://github.com/jpillora/chisel
synced 2026-06-08 15:07:02 +00:00
201 lines
5.2 KiB
Go
201 lines
5.2 KiB
Go
package chserver
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/jpillora/chisel/share"
|
|
)
|
|
|
|
// 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 = "<unknown>"
|
|
}
|
|
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 s.handleSocksStream(clientLog.Fork("socksconn#%d", connID), stream)
|
|
} else {
|
|
go chshare.HandleTCPStream(clientLog.Fork("conn#%d", connID), &s.connStats, stream, remote)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleSocksStream(l *chshare.Logger, src io.ReadWriteCloser) {
|
|
conn := chshare.NewRWCConn(src)
|
|
s.connStats.Open()
|
|
l.Debugf("%s Opening", s.connStats)
|
|
err := s.socksServer.ServeConn(conn)
|
|
s.connStats.Close()
|
|
if err != nil && !strings.HasSuffix(err.Error(), "EOF") {
|
|
l.Debugf("%s: Closed (error: %s)", s.connStats, err)
|
|
} else {
|
|
l.Debugf("%s: Closed", s.connStats)
|
|
}
|
|
}
|