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}