Skip to content

Commit 04bb837

Browse files
authored
feat: add support for X11 forwarding (#2)
1 parent fcea999 commit 04bb837

File tree

5 files changed

+94
-3
lines changed

5 files changed

+94
-3
lines changed

server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Server struct {
4242
PasswordHandler PasswordHandler // password authentication handler
4343
PublicKeyHandler PublicKeyHandler // public key authentication handler
4444
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
45+
X11Callback X11Callback // callback for allowing X11 forwarding, denies all if nil
4546
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
4647
LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
4748
ReversePortForwardingCallback ReversePortForwardingCallback // callback for allowing reverse port forwarding, denies all if nil

session.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ type Session interface {
6969
// of whether or not a PTY was accepted for this session.
7070
Pty() (Pty, <-chan Window, bool)
7171

72+
// X11 returns X11 forwarding information and a boolean of whether or not X11
73+
// forwarding was accepted for this session.
74+
X11() (X11, bool)
75+
7276
// Signals registers a channel to receive signals sent from the client. The
7377
// channel must handle signal sends or it will block the SSH request loop.
7478
// Registering nil will unregister the channel from signal sends. During the
@@ -106,6 +110,7 @@ func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.Ne
106110
conn: conn,
107111
handler: srv.Handler,
108112
ptyCb: srv.PtyCallback,
113+
x11Cb: srv.X11Callback,
109114
sessReqCb: srv.SessionRequestCallback,
110115
subsystemHandlers: srv.SubsystemHandlers,
111116
ctx: ctx,
@@ -122,9 +127,11 @@ type session struct {
122127
handled bool
123128
exited bool
124129
pty *Pty
130+
x11 *X11
125131
winch chan Window
126132
env []string
127133
ptyCb PtyCallback
134+
x11Cb X11Callback
128135
sessReqCb SessionRequestCallback
129136
rawCmd string
130137
subsystem string
@@ -226,6 +233,13 @@ func (sess *session) Pty() (Pty, <-chan Window, bool) {
226233
return Pty{}, sess.winch, false
227234
}
228235

236+
func (sess *session) X11() (X11, bool) {
237+
if sess.x11 != nil {
238+
return *sess.x11, true
239+
}
240+
return X11{}, false
241+
}
242+
229243
func (sess *session) Signals(c chan<- Signal) {
230244
sess.Lock()
231245
defer sess.Unlock()
@@ -353,6 +367,23 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
353367
close(sess.winch)
354368
}()
355369
req.Reply(ok, nil)
370+
case "x11-req":
371+
if sess.handled || sess.x11 != nil {
372+
req.Reply(false, nil)
373+
continue
374+
}
375+
x11Req, ok := parseX11Request(req.Payload)
376+
if !ok {
377+
req.Reply(false, nil)
378+
continue
379+
}
380+
sess.x11 = &x11Req
381+
if sess.x11Cb != nil {
382+
ok := sess.x11Cb(sess.ctx, x11Req)
383+
req.Reply(ok, nil)
384+
continue
385+
}
386+
req.Reply(false, nil)
356387
case "window-change":
357388
if sess.pty == nil {
358389
req.Reply(false, nil)

session_test.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,51 @@ func TestPty(t *testing.T) {
228228
<-done
229229
}
230230

231+
func TestX11(t *testing.T) {
232+
t.Parallel()
233+
done := make(chan struct{})
234+
session, _, cleanup := newTestSession(t, &Server{
235+
X11Callback: func(ctx Context, x11 X11) bool {
236+
return true
237+
},
238+
Handler: func(s Session) {
239+
x11Req, isX11 := s.X11()
240+
if !isX11 {
241+
t.Fatalf("expected x11 but none requested")
242+
}
243+
if !x11Req.SingleConnection {
244+
t.Fatalf("expected single connection but got %#v", x11Req.SingleConnection)
245+
}
246+
close(done)
247+
},
248+
}, nil)
249+
defer cleanup()
250+
251+
reply, err := session.SendRequest("x11-req", true, gossh.Marshal(X11{
252+
SingleConnection: true,
253+
AuthProtocol: "MIT-MAGIC-COOKIE-1",
254+
AuthCookie: "deadbeef",
255+
ScreenNumber: 1,
256+
}))
257+
if err != nil {
258+
t.Fatalf("expected nil but got %v", err)
259+
}
260+
if !reply {
261+
t.Fatalf("expected true but got %v", reply)
262+
}
263+
err = session.Shell()
264+
if err != nil {
265+
t.Fatalf("expected nil but got %v", err)
266+
}
267+
session.Close()
268+
<-done
269+
}
270+
231271
func TestPtyResize(t *testing.T) {
232272
t.Parallel()
233-
winch0 := Window{40, 80}
234-
winch1 := Window{80, 160}
235-
winch2 := Window{20, 40}
273+
winch0 := Window{40, 80, 0, 0}
274+
winch1 := Window{80, 160, 0, 0}
275+
winch2 := Window{20, 40, 0, 0}
236276
winches := make(chan Window)
237277
done := make(chan bool)
238278
session, _, cleanup := newTestSession(t, &Server{

ssh.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type KeyboardInteractiveHandler func(ctx Context, challenger gossh.KeyboardInter
4747
// PtyCallback is a hook for allowing PTY sessions.
4848
type PtyCallback func(ctx Context, pty Pty) bool
4949

50+
// X11Callback is a hook for allowing X11 forwarding.
51+
type X11Callback func(ctx Context, x11 X11) bool
52+
5053
// SessionRequestCallback is a callback for allowing or denying SSH sessions.
5154
type SessionRequestCallback func(sess Session, requestType string) bool
5255

@@ -106,6 +109,16 @@ type Pty struct {
106109
Modes gossh.TerminalModes
107110
}
108111

112+
// X11 represents a X11 forwarding request.
113+
type X11 struct {
114+
// SingleConnection is whether the X11 connection should be closed after
115+
// the first use.
116+
SingleConnection bool
117+
AuthProtocol string
118+
AuthCookie string
119+
ScreenNumber uint32
120+
}
121+
109122
// Serve accepts incoming SSH connections on the listener l, creating a new
110123
// connection goroutine for each. The connection goroutines read requests and
111124
// then calls handler to handle sessions. Handler is typically nil, in which

util.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ func parsePtyRequest(payload []byte) (pty Pty, ok bool) {
5353
return
5454
}
5555

56+
// parseX11Request parses an X11 forwarding request.
57+
// See https://www.rfc-editor.org/rfc/rfc4254#section-6.3
58+
func parseX11Request(payload []byte) (x11 X11, ok bool) {
59+
return x11, ssh.Unmarshal(payload, &x11) == nil
60+
}
61+
5662
func parseTerminalModes(in []byte) (modes ssh.TerminalModes, ok bool) {
5763
// See https://datatracker.ietf.org/doc/html/rfc4254#section-8
5864
// 8. Encoding of Terminal Modes

0 commit comments

Comments
 (0)