a geicko-2 based round robin ranking system designed to test c++ battleship submissions
battleship.dunkirk.sh
1package ssh
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "net"
8 "sync"
9
10 "github.com/anmitsu/go-shlex"
11 gossh "golang.org/x/crypto/ssh"
12)
13
14// Session provides access to information about an SSH session and methods
15// to read and write to the SSH channel with an embedded Channel interface from
16// crypto/ssh.
17//
18// When Command() returns an empty slice, the user requested a shell. Otherwise
19// the user is performing an exec with those command arguments.
20//
21// TODO: Signals
22type Session interface {
23 gossh.Channel
24
25 // User returns the username used when establishing the SSH connection.
26 User() string
27
28 // RemoteAddr returns the net.Addr of the client side of the connection.
29 RemoteAddr() net.Addr
30
31 // LocalAddr returns the net.Addr of the server side of the connection.
32 LocalAddr() net.Addr
33
34 // Environ returns a copy of strings representing the environment set by the
35 // user for this session, in the form "key=value".
36 Environ() []string
37
38 // Exit sends an exit status and then closes the session.
39 Exit(code int) error
40
41 // Command returns a shell parsed slice of arguments that were provided by the
42 // user. Shell parsing splits the command string according to POSIX shell rules,
43 // which considers quoting not just whitespace.
44 Command() []string
45
46 // RawCommand returns the exact command that was provided by the user.
47 RawCommand() string
48
49 // Subsystem returns the subsystem requested by the user.
50 Subsystem() string
51
52 // PublicKey returns the PublicKey used to authenticate. If a public key was not
53 // used it will return nil.
54 PublicKey() PublicKey
55
56 // Context returns the connection's context. The returned context is always
57 // non-nil and holds the same data as the Context passed into auth
58 // handlers and callbacks.
59 //
60 // The context is canceled when the client's connection closes or I/O
61 // operation fails.
62 Context() Context
63
64 // Permissions returns a copy of the Permissions object that was available for
65 // setup in the auth handlers via the Context.
66 Permissions() Permissions
67
68 // EmulatedPty returns true if the session is emulating a PTY using PtyWriter.
69 EmulatedPty() bool
70
71 // Pty returns PTY information, a channel of window size changes, and a boolean
72 // of whether or not a PTY was accepted for this session.
73 Pty() (Pty, <-chan Window, bool)
74
75 // Signals registers a channel to receive signals sent from the client. The
76 // channel must handle signal sends or it will block the SSH request loop.
77 // Registering nil will unregister the channel from signal sends. During the
78 // time no channel is registered signals are buffered up to a reasonable amount.
79 // If there are buffered signals when a channel is registered, they will be
80 // sent in order on the channel immediately after registering.
81 Signals(c chan<- Signal)
82
83 // Break regisers a channel to receive notifications of break requests sent
84 // from the client. The channel must handle break requests, or it will block
85 // the request handling loop. Registering nil will unregister the channel.
86 // During the time that no channel is registered, breaks are ignored.
87 Break(c chan<- bool)
88}
89
90// maxSigBufSize is how many signals will be buffered
91// when there is no signal channel specified
92const maxSigBufSize = 128
93
94func DefaultSessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx Context) {
95 ch, reqs, err := newChan.Accept()
96 if err != nil {
97 // TODO: trigger event callback
98 return
99 }
100 sess := &session{
101 Channel: ch,
102 conn: conn,
103 handler: srv.Handler,
104 ptyCb: srv.PtyCallback,
105 ptyHandler: srv.PtyHandler,
106 sessReqCb: srv.SessionRequestCallback,
107 subsystemHandlers: srv.SubsystemHandlers,
108 ctx: ctx,
109 }
110 ctx.SetValue(ContextKeySession, sess)
111 sess.handleRequests(reqs)
112}
113
114type session struct {
115 sync.Mutex
116 gossh.Channel
117 conn *gossh.ServerConn
118 handler Handler
119 subsystemHandlers map[string]SubsystemHandler
120 handled bool
121 exited bool
122 pty *Pty
123 winch chan Window
124 env []string
125 ptyCb PtyCallback
126 ptyHandler PtyHandler
127 sessReqCb SessionRequestCallback
128 rawCmd string
129 subsystem string
130 ctx Context
131 sigCh chan<- Signal
132 sigBuf []Signal
133 breakCh chan<- bool
134}
135
136func (sess *session) Stderr() io.ReadWriter {
137 if sess.pty != nil && sess.EmulatedPty() {
138 return NewPtyReadWriter(sess.Channel.Stderr())
139 }
140 return sess.Channel.Stderr()
141}
142
143func (sess *session) Write(p []byte) (int, error) {
144 if sess.pty != nil && sess.EmulatedPty() {
145 return NewPtyWriter(sess.Channel).Write(p)
146 }
147 return sess.Channel.Write(p)
148}
149
150func (sess *session) PublicKey() PublicKey {
151 sessionkey := sess.ctx.Value(ContextKeyPublicKey)
152 if sessionkey == nil {
153 return nil
154 }
155 return sessionkey.(PublicKey)
156}
157
158func (sess *session) Permissions() Permissions {
159 // use context permissions because its properly
160 // wrapped and easier to dereference
161 perms := sess.ctx.Value(ContextKeyPermissions).(*Permissions)
162 return *perms
163}
164
165func (sess *session) Context() Context {
166 return sess.ctx
167}
168
169func (sess *session) Exit(code int) error {
170 sess.Lock()
171 defer sess.Unlock()
172 if sess.exited {
173 return errors.New("Session.Exit called multiple times")
174 }
175 sess.exited = true
176
177 status := struct{ Status uint32 }{uint32(code)}
178 _, err := sess.SendRequest("exit-status", false, gossh.Marshal(&status))
179 if err != nil {
180 return err
181 }
182 return sess.Close()
183}
184
185func (sess *session) User() string {
186 return sess.conn.User()
187}
188
189func (sess *session) RemoteAddr() net.Addr {
190 return sess.conn.RemoteAddr()
191}
192
193func (sess *session) LocalAddr() net.Addr {
194 return sess.conn.LocalAddr()
195}
196
197func (sess *session) Environ() []string {
198 return append([]string(nil), sess.env...)
199}
200
201func (sess *session) RawCommand() string {
202 return sess.rawCmd
203}
204
205func (sess *session) Command() []string {
206 cmd, _ := shlex.Split(sess.rawCmd, true)
207 return append([]string(nil), cmd...)
208}
209
210func (sess *session) Subsystem() string {
211 return sess.subsystem
212}
213
214func (sess *session) EmulatedPty() bool {
215 return sess.ctx.Value(contextKeyEmulatePty) == true
216}
217
218func (sess *session) Pty() (Pty, <-chan Window, bool) {
219 if sess.pty != nil && (sess.EmulatedPty() || !sess.pty.IsZero()) {
220 return *sess.pty, sess.winch, true
221 }
222 return Pty{}, sess.winch, false
223}
224
225func (sess *session) Signals(c chan<- Signal) {
226 sess.Lock()
227 defer sess.Unlock()
228 sess.sigCh = c
229 if len(sess.sigBuf) > 0 {
230 go func() {
231 for _, sig := range sess.sigBuf {
232 sess.sigCh <- sig
233 }
234 }()
235 }
236}
237
238func (sess *session) Break(c chan<- bool) {
239 sess.Lock()
240 defer sess.Unlock()
241 sess.breakCh = c
242}
243
244func (sess *session) handleRequests(reqs <-chan *gossh.Request) {
245 for req := range reqs {
246 switch req.Type {
247 case "shell", "exec":
248 if sess.handled {
249 req.Reply(false, nil)
250 continue
251 }
252
253 payload := struct{ Value string }{}
254 gossh.Unmarshal(req.Payload, &payload)
255 sess.rawCmd = payload.Value
256
257 // If there's a session policy callback, we need to confirm before
258 // accepting the session.
259 if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
260 sess.rawCmd = ""
261 req.Reply(false, nil)
262 continue
263 }
264
265 sess.handled = true
266 req.Reply(true, nil)
267
268 go func() {
269 if sess.pty != nil && !sess.pty.IsZero() {
270 // TODO: log error to server
271 go io.Copy(sess.pty, sess) // nolint: errcheck
272 go io.Copy(sess, sess.pty) // nolint: errcheck
273 }
274 sess.handler(sess)
275 sess.Exit(0)
276 if sess.pty != nil && !sess.pty.IsZero() {
277 sess.pty.Close() // nolint: errcheck
278 }
279 }()
280 case "subsystem":
281 if sess.handled {
282 req.Reply(false, nil)
283 continue
284 }
285
286 payload := struct{ Value string }{}
287 gossh.Unmarshal(req.Payload, &payload)
288 sess.subsystem = payload.Value
289
290 // If there's a session policy callback, we need to confirm before
291 // accepting the session.
292 if sess.sessReqCb != nil && !sess.sessReqCb(sess, req.Type) {
293 sess.rawCmd = ""
294 req.Reply(false, nil)
295 continue
296 }
297
298 handler := sess.subsystemHandlers[payload.Value]
299 if handler == nil {
300 handler = sess.subsystemHandlers["default"]
301 }
302 if handler == nil {
303 req.Reply(false, nil)
304 continue
305 }
306
307 sess.handled = true
308 req.Reply(true, nil)
309
310 go func() {
311 handler(sess)
312 sess.Exit(0)
313 }()
314 case "env":
315 if sess.handled {
316 req.Reply(false, nil)
317 continue
318 }
319 var kv struct{ Key, Value string }
320 gossh.Unmarshal(req.Payload, &kv)
321 sess.env = append(sess.env, fmt.Sprintf("%s=%s", kv.Key, kv.Value))
322 req.Reply(true, nil)
323 case "signal":
324 var payload struct{ Signal string }
325 gossh.Unmarshal(req.Payload, &payload)
326 sess.Lock()
327 if sess.sigCh != nil {
328 sess.sigCh <- Signal(payload.Signal)
329 } else {
330 if len(sess.sigBuf) < maxSigBufSize {
331 sess.sigBuf = append(sess.sigBuf, Signal(payload.Signal))
332 }
333 }
334 sess.Unlock()
335 case "pty-req":
336 if sess.handled || sess.pty != nil {
337 req.Reply(false, nil)
338 continue
339 }
340 ptyReq, ok := parsePtyRequest(req.Payload)
341 if !ok {
342 req.Reply(false, nil)
343 continue
344 }
345 if sess.ptyCb != nil {
346 ok := sess.ptyCb(sess.ctx, ptyReq)
347 if !ok {
348 req.Reply(false, nil)
349 continue
350 }
351 }
352
353 sess.pty = &ptyReq
354 sess.winch = make(chan Window, 1)
355 sess.winch <- ptyReq.Window
356
357 if sess.ptyHandler != nil {
358 closer, err := sess.ptyHandler(sess.ctx, sess, ptyReq)
359 if err != nil {
360 // TODO: handle error
361 req.Reply(false, nil)
362 continue
363 }
364
365 defer closer() // nolint: errcheck
366
367 if !sess.EmulatedPty() && !sess.pty.IsZero() {
368 go func() {
369 for win := range sess.winch {
370 if err := resizePty(sess, win); err != nil {
371 // TODO: handle error
372 continue
373 }
374 }
375 }()
376 }
377 }
378
379 defer func() {
380 // when reqs is closed
381 close(sess.winch)
382 }()
383 req.Reply(ok, nil)
384 case "window-change":
385 if sess.pty == nil {
386 req.Reply(false, nil)
387 continue
388 }
389 win, _, ok := parseWindow(req.Payload)
390 if ok {
391 sess.pty.Window = win
392 sess.winch <- win
393 }
394 req.Reply(ok, nil)
395 case agentRequestType:
396 // TODO: option/callback to allow agent forwarding
397 SetAgentRequested(sess.ctx)
398 req.Reply(true, nil)
399 case "break":
400 ok := false
401 sess.Lock()
402 if sess.breakCh != nil {
403 sess.breakCh <- true
404 ok = true
405 }
406 req.Reply(ok, nil)
407 sess.Unlock()
408 default:
409 // TODO: debug log
410 req.Reply(false, nil)
411 }
412 }
413}
414
415func (s *session) ptyAllocate(term string, win Window, modes gossh.TerminalModes) (func() error, error) {
416 p, err := newPty(s.ctx, term, win, modes)
417 if err != nil {
418 return nil, err
419 }
420
421 s.pty = &Pty{
422 Term: term,
423 Window: win,
424 Modes: modes,
425 impl: p,
426 }
427
428 return p.Close, nil
429}
430
431func resizePty(sess *session, win Window) error {
432 if sess.pty == nil {
433 return nil
434 }
435
436 return sess.pty.Resize(win.Width, win.Height)
437}