Skip to content

Commit b56ba68

Browse files
committed
merged health/version checks, more cleanup
2 parents 6997312 + 00af5b2 commit b56ba68

File tree

3 files changed

+272
-254
lines changed

3 files changed

+272
-254
lines changed

server/handler.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package chserver
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net"
7+
"net/http"
8+
"strings"
9+
"sync/atomic"
10+
"time"
11+
12+
"github.com/jpillora/sizestr"
13+
"golang.org/x/crypto/ssh"
14+
15+
"github.com/jpillora/chisel/share"
16+
)
17+
18+
// handleClientHandler is the main http sebsocket handler for the chisel server
19+
func (s *Server) handleClientHandler(w http.ResponseWriter, r *http.Request) {
20+
//websockets upgrade AND has chisel prefix
21+
upgrade := strings.ToLower(r.Header.Get("Upgrade"))
22+
protocol := r.Header.Get("Sec-WebSocket-Protocol")
23+
if upgrade == "websocket" && protocol == chshare.ProtocolVersion {
24+
s.handleWebsocket(w, r)
25+
return
26+
}
27+
//proxy target was provided
28+
if s.reverseProxy != nil {
29+
s.reverseProxy.ServeHTTP(w, r)
30+
return
31+
}
32+
//no proxy defined, provide access to health/version checks
33+
switch r.URL.String() {
34+
case "/health":
35+
w.Write([]byte("OK\n"))
36+
return
37+
case "/version":
38+
w.Write([]byte(chshare.BuildVersion))
39+
return
40+
}
41+
//missing :O
42+
w.WriteHeader(404)
43+
w.Write([]byte("Not found"))
44+
}
45+
46+
// handleWebsocket is responsible for hanlding the websocket connection
47+
func (s *Server) handleWebsocket(w http.ResponseWriter, req *http.Request) {
48+
id := atomic.AddInt32(&s.sessCount, 1)
49+
clog := s.Fork("session#%d", id)
50+
wsConn, err := upgrader.Upgrade(w, req, nil)
51+
if err != nil {
52+
clog.Debugf("Failed to upgrade (%s)", err)
53+
return
54+
}
55+
conn := chshare.NewWebSocketConn(wsConn)
56+
// perform SSH handshake on net.Conn
57+
clog.Debugf("Handshaking...")
58+
sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.sshConfig)
59+
if err != nil {
60+
s.Debugf("Failed to handshake (%s)", err)
61+
return
62+
}
63+
// pull the users from the session map
64+
var user *chshare.User
65+
if s.users.Len() > 0 {
66+
sid := string(sshConn.SessionID())
67+
user = s.sessions[sid]
68+
defer delete(s.sessions, sid)
69+
}
70+
//verify configuration
71+
clog.Debugf("Verifying configuration")
72+
//wait for request, with timeout
73+
var r *ssh.Request
74+
select {
75+
case r = <-reqs:
76+
case <-time.After(10 * time.Second):
77+
sshConn.Close()
78+
return
79+
}
80+
failed := func(err error) {
81+
r.Reply(false, []byte(err.Error()))
82+
}
83+
if r.Type != "config" {
84+
failed(s.Errorf("expecting config request"))
85+
return
86+
}
87+
c, err := chshare.DecodeConfig(r.Payload)
88+
if err != nil {
89+
failed(s.Errorf("invalid config"))
90+
return
91+
}
92+
if c.Version != chshare.BuildVersion {
93+
v := c.Version
94+
if v == "" {
95+
v = "<unknown>"
96+
}
97+
clog.Infof("Client version (%s) differs from server version (%s)",
98+
v, chshare.BuildVersion)
99+
}
100+
//if user is provided, ensure they have
101+
//access to the desired remotes
102+
if user != nil {
103+
for _, r := range c.Remotes {
104+
addr := r.RemoteHost + ":" + r.RemotePort
105+
if !user.HasAccess(addr) {
106+
failed(s.Errorf("access to '%s' denied", addr))
107+
return
108+
}
109+
}
110+
}
111+
//success!
112+
r.Reply(true, nil)
113+
//prepare connection logger
114+
clog.Debugf("Open")
115+
go s.handleSSHRequests(clog, reqs)
116+
go s.handleSSHChannels(clog, chans)
117+
sshConn.Wait()
118+
clog.Debugf("Close")
119+
}
120+
121+
func (s *Server) handleSSHRequests(clientLog *chshare.Logger, reqs <-chan *ssh.Request) {
122+
for r := range reqs {
123+
switch r.Type {
124+
case "ping":
125+
r.Reply(true, nil)
126+
default:
127+
clientLog.Debugf("Unknown request: %s", r.Type)
128+
}
129+
}
130+
}
131+
132+
func (s *Server) handleSSHChannels(clientLog *chshare.Logger, chans <-chan ssh.NewChannel) {
133+
for ch := range chans {
134+
remote := string(ch.ExtraData())
135+
socks := remote == "socks"
136+
//dont accept socks when --socks5 isn't enabled
137+
if socks && s.socksServer == nil {
138+
clientLog.Debugf("Denied socks request, please enable --socks5")
139+
ch.Reject(ssh.Prohibited, "SOCKS5 is not enabled on the server")
140+
continue
141+
}
142+
//accept rest
143+
stream, reqs, err := ch.Accept()
144+
if err != nil {
145+
clientLog.Debugf("Failed to accept stream: %s", err)
146+
continue
147+
}
148+
go ssh.DiscardRequests(reqs)
149+
//handle stream type
150+
connID := atomic.AddInt32(&s.connCount, 1)
151+
if socks {
152+
go s.handleSocksStream(clientLog.Fork("socks#%05d", connID), stream)
153+
} else {
154+
go s.handleTCPStream(clientLog.Fork(" tcp#%05d", connID), stream, remote)
155+
}
156+
}
157+
}
158+
159+
func (s *Server) handleSocksStream(l *chshare.Logger, src io.ReadWriteCloser) {
160+
conn := chshare.NewRWCConn(src)
161+
// conn.SetDeadline(time.Now().Add(30 * time.Second))
162+
atomic.AddInt32(&s.connOpen, 1)
163+
l.Debugf("%s Openning", s.connStatus())
164+
err := s.socksServer.ServeConn(conn)
165+
atomic.AddInt32(&s.connOpen, -1)
166+
if err != nil && !strings.HasSuffix(err.Error(), "EOF") {
167+
l.Debugf("%s Closed (error: %s)", s.connStatus(), err)
168+
} else {
169+
l.Debugf("%s Closed", s.connStatus())
170+
}
171+
}
172+
173+
func (s *Server) handleTCPStream(l *chshare.Logger, src io.ReadWriteCloser, remote string) {
174+
dst, err := net.Dial("tcp", remote)
175+
if err != nil {
176+
l.Debugf("Remote failed (%s)", err)
177+
src.Close()
178+
return
179+
}
180+
atomic.AddInt32(&s.connOpen, 1)
181+
l.Debugf("%s Open", s.connStatus())
182+
sent, received := chshare.Pipe(src, dst)
183+
atomic.AddInt32(&s.connOpen, -1)
184+
l.Debugf("%s Close (sent %s received %s)", s.connStatus(), sizestr.ToString(sent), sizestr.ToString(received))
185+
}
186+
187+
func (s *Server) connStatus() string {
188+
return fmt.Sprintf("[%d/%d]", atomic.LoadInt32(&s.connOpen), atomic.LoadInt32(&s.connCount))
189+
}

0 commit comments

Comments
 (0)