| @@ -238,7 +238,17 @@ func (s *Server) handleSession(sconn *ssh.ServerConn, ch ssh.Channel, reqs <-cha |
| 238 | 238 | continue |
| 239 | 239 | } |
| 240 | 240 | req.Reply(true, nil) |
| 241 | | code := s.runExec(sconn, ch, payload.Command) |
| 241 | // x/crypto closes reqs when the client closes the channel. That |
| 242 | // is how a follow learns nobody is reading: the CLI's shared |
| 243 | // connection outlives a Ctrl-C, the channel does not. |
| 244 | done := make(chan struct{}) |
| 245 | go func() { |
| 246 | for r := range reqs { |
| 247 | r.Reply(false, nil) |
| 248 | } |
| 249 | close(done) |
| 250 | }() |
| 251 | code := s.runExec(sconn, ch, payload.Command, done) |
| 242 | 252 | sendExit(ch, code) |
| 243 | 253 | return |
| 244 | 254 | case "shell": |
| @@ -260,7 +270,7 @@ func sendExit(ch ssh.Channel, code int) { |
| 260 | 270 | ch.SendRequest("exit-status", false, ssh.Marshal(&msg)) |
| 261 | 271 | } |
| 262 | 272 | |
| 263 | | func (s *Server) runExec(sconn *ssh.ServerConn, ch ssh.Channel, cmdline string) int { |
| 273 | func (s *Server) runExec(sconn *ssh.ServerConn, ch ssh.Channel, cmdline string, done <-chan struct{}) int { |
| 264 | 274 | ext := sconn.Permissions.Extensions |
| 265 | 275 | if blob := ext["anon-key"]; blob != "" { |
| 266 | 276 | return s.runAnonymous(ch, blob, cmdline) |
| @@ -273,7 +283,7 @@ func (s *Server) runExec(sconn *ssh.ServerConn, ch ssh.Channel, cmdline string) |
| 273 | 283 | return protocol.ExitDenied |
| 274 | 284 | } |
| 275 | 285 | _ = s.st.TouchSSHKey(keyID) |
| 276 | | return Exec(s.cfg, s.st, user, ext["scope"], ext["key-fp"], cmdline, ch, ch, ch.Stderr()) |
| 286 | return Exec(s.cfg, s.st, user, ext["scope"], ext["key-fp"], cmdline, ch, ch, ch.Stderr(), done) |
| 277 | 287 | } |
| 278 | 288 | |
| 279 | 289 | // runAnonymous handles a session from an unregistered key: the register |
| @@ -304,7 +314,7 @@ func (s *Server) runAnonymous(ch ssh.Channel, keyB64, cmdline string) int { |
| 304 | 314 | // single dispatch path shared by the embedded listener and the system-sshd |
| 305 | 315 | // forced command (gitbayd shell). |
| 306 | 316 | func Exec(cfg config.Config, st *store.Store, user store.User, scope, source, cmdline string, |
| 307 | | stdin io.Reader, stdout, stderr io.Writer) int { |
| 317 | stdin io.Reader, stdout, stderr io.Writer, done <-chan struct{}) int { |
| 308 | 318 | if user.Disabled { |
| 309 | 319 | fmt.Fprintln(stderr, "this account is disabled; contact the instance admin") |
| 310 | 320 | return protocol.ExitDenied |
| @@ -341,6 +351,7 @@ func Exec(cfg config.Config, st *store.Store, user store.User, scope, source, cm |
| 341 | 351 | Stdin: stdin, |
| 342 | 352 | Stdout: stdout, |
| 343 | 353 | Stderr: stderr, |
| 354 | Done: done, |
| 344 | 355 | } |
| 345 | 356 | return control.Dispatch(ctx, argv) |
| 346 | 357 | } |