func handleServerConn(sConn *ssh.ServerConn) { defer sConn.Close() for { // Accept reads from the connection, demultiplexes packets // to their corresponding channels and returns when a new // channel request is seen. Some goroutine must always be // calling Accept; otherwise no messages will be forwarded // to the channels. ch, err := sConn.Accept() if err == io.EOF { return } if err != nil { log.Println("handleServerConn Accept:", err) break } // Channels have a type, depending on the application level // protocol intended. In the case of a shell, the type is // "session" and ServerShell may be used to present a simple // terminal interface. if ch.ChannelType() != "session" { ch.Reject(ssh.UnknownChannelType, "unknown channel type") break } go handleChannel(ch) } }
func handleChannels(sshCon *ssh.ServerConn, chans <-chan ssh.NewChannel, sh SessionHandler) { // Service the incoming Channel channel in go routine for newChannel := range chans { handleChannel(newChannel, sh) sshCon.Close() } }
func (u *UrlDispatcher) Dispatch(c context.Context, conn *ssh.ServerConn, ch ssh.NewChannel) { defer conn.Close() // Get channel type chType := ch.ChannelType() // Parse channel URI uri, err := url.ParseRequestURI(chType) if err != nil { u.Logger.Warn("Error parsing channel type", "type", chType, "err", err) ch.Reject(InvalidChannelType, "invalid channel URI") return } else if reject(chType, uri, ch, u.Logger) { return } chType = uri.Path // Parse query params values, err := url.ParseQuery(uri.RawQuery) if err != nil { u.Logger.Warn("Error parsing query params", "values", values, "err", err) ch.Reject(InvalidQueryParams, "invalid query params in channel type") return } // Determine if channel is acceptable (has a registered handler) if !u.Router.HasRoute(chType) { u.Logger.Info("UnknownChannelType", "type", chType) ch.Reject(ssh.UnknownChannelType, chType) return } // Otherwise, accept the channel channel, requests, err := ch.Accept() if err != nil { u.Logger.Warn("Error creating channel", "type", chType, "err", err) ch.Reject(ChannelAcceptError, chType) return } // Handle the channel err = u.Router.Handle(&router.UrlContext{ Path: uri.Path, Context: c, Values: values, Channel: channel, Requests: requests, }) if err != nil { u.Logger.Warn("Error handling channel", "type", chType, "err", err) ch.Reject(ChannelHandleError, fmt.Sprintf("error handling channel: %s", err.Error())) return } }
func (u *SimpleDispatcher) Dispatch(c context.Context, conn *ssh.ServerConn, ch ssh.NewChannel) { defer conn.Close() var ctx *Context if u.PanicHandler != nil { if rcv := recover(); rcv != nil { u.PanicHandler.Handle(ctx, rcv) } } // Get channel type chType := ch.ChannelType() handler, ok := u.Handlers[chType] if !ok { return } // Otherwise, accept the channel channel, requests, err := ch.Accept() if err != nil { u.Logger.Warn("Error creating channel", "type", chType, "err", err) ch.Reject(ChannelAcceptError, chType) return } // Handle the channel ctx = &Context{ Context: c, Channel: channel, Requests: requests, } err = handler.Handle(ctx) if err != nil { u.Logger.Warn("Error handling channel", "type", chType, "err", err) ch.Reject(ChannelHandleError, fmt.Sprintf("error handling channel: %s", err.Error())) return } }
// run should not be called directly, but via start // run will establish an ssh server listening on the backchannel func (t *attachServerSSH) run() error { defer trace.End(trace.Begin("main attach server loop")) var sConn *ssh.ServerConn var chans <-chan ssh.NewChannel var reqs <-chan *ssh.Request var err error // keep waiting for the connection to establish for t.enabled && sConn == nil { conn := t.conn if conn == nil { // Stop has probably been called as t.conn is set in Start and should // never be nil otherwise err := fmt.Errorf("connection provided for backchannel is nil") log.Debug(err.Error()) return err } // wait for backchannel to establish err = backchannel(context.Background(), conn) if err != nil { detail := fmt.Sprintf("failed to establish backchannel: %s", err) log.Error(detail) continue } // create the SSH server sConn, chans, reqs, err = ssh.NewServerConn(*conn, t.sshConfig) if err != nil { detail := fmt.Sprintf("failed to establish ssh handshake: %s", err) log.Error(detail) continue } } if err != nil { detail := fmt.Sprintf("abandoning attempt to start attach server: %s", err) log.Error(detail) return err } defer func() { if sConn != nil { sConn.Close() } }() // Global requests go t.globalMux(reqs) log.Println("ready to service attach requests") // Service the incoming channels for attachchan := range chans { // The only channel type we'll support is attach if attachchan.ChannelType() != attachChannelType { detail := fmt.Sprintf("unknown channel type %s", attachchan.ChannelType()) log.Error(detail) attachchan.Reject(ssh.UnknownChannelType, "unknown channel type") continue } // check we have a Session matching the requested ID bytes := attachchan.ExtraData() if bytes == nil { detail := "attach channel requires ID in ExtraData" log.Error(detail) attachchan.Reject(ssh.Prohibited, detail) continue } sessionid := string(bytes) session, ok := t.config.Sessions[sessionid] reason := "" if !ok { reason = "is unknown" } else if session.Cmd.Process == nil { reason = "process has not been launched" } else if session.Cmd.Process.Signal(syscall.Signal(0)) != nil { reason = "process has exited" } if reason != "" { detail := fmt.Sprintf("attach request: session %s %s", sessionid, reason) log.Error(detail) attachchan.Reject(ssh.Prohibited, detail) continue } log.Infof("accepting incoming channel for %s", sessionid) channel, requests, err := attachchan.Accept() log.Debugf("accepted incoming channel for %s", sessionid) if err != nil { detail := fmt.Sprintf("could not accept channel: %s", err) log.Errorf(detail) continue } // bind the channel to the Session log.Debugf("binding reader/writers for channel for %s", sessionid) session.Outwriter.Add(channel) session.Reader.Add(channel) // cleanup on detach from the session detach := func() { session.Outwriter.Remove(channel) session.Reader.Remove(channel) } // tty's merge stdout and stderr so we don't bind an additional reader in that case // but we need to do so for non-tty if session.Pty == nil { session.Errwriter.Add(channel.Stderr()) // no good way to function chain, so reimplement appropriately detach = func() { session.Outwriter.Remove(channel) session.Reader.Remove(channel) session.Errwriter.Remove(channel) } } log.Debugf("reader/writers bound for channel for %s", sessionid) go t.channelMux(requests, session.Cmd.Process, session.Pty, detach) } log.Info("incoming attach channel closed") return nil }
func (agent *Agent) handleConn(conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) { defer conn.Close() for newChannel := range chans { if newChannel.ChannelType() != "session" { log.Errorf("rejecting unknown channel type: %s\n", newChannel.ChannelType()) newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") continue } channel, requests, err := newChannel.Accept() if err != nil { log.Errorf("failed to accept channel: %s\n", err) return } defer channel.Close() for req := range requests { if req.Type != "exec" { log.Errorf("rejecting non-exec channel request (type=%s)\n", req.Type) req.Reply(false, nil) continue } request, err := ParseRequest(req) if err != nil { log.Errorf("%s\n", err) req.Reply(false, nil) continue } if err = request.ResolvePaths(agent); err != nil { log.Errorf("%s\n", err) req.Reply(false, nil) continue } //log.Errorf("got an agent-request [%s]\n", request.JSON) req.Reply(true, nil) // drain output to the SSH channel stream output := make(chan string) done := make(chan int) go func(out io.Writer, in chan string, done chan int) { for { s, ok := <-in if !ok { break } fmt.Fprintf(out, "%s", s) log.Debugf("%s", s) } close(done) }(channel, output, done) // run the agent request err = request.Run(output) <-done var rc int if exitErr, ok := err.(*exec.ExitError); ok { sys := exitErr.ProcessState.Sys() // os.ProcessState.Sys() may not return syscall.WaitStatus on non-UNIX machines, // so currently this feature only works on UNIX, but shouldn't crash on other OSes if ws, ok := sys.(syscall.WaitStatus); ok { if ws.Exited() { rc = ws.ExitStatus() } else { var signal syscall.Signal if ws.Signaled() { signal = ws.Signal() } if ws.Stopped() { signal = ws.StopSignal() } sigStr, ok := SIGSTRING[signal] if !ok { sigStr = "ABRT" // use ABRT as catch-all signal for any that don't translate log.Infof("Task execution terminted due to %s, translating as ABRT for ssh transport", signal) } else { log.Infof("Task execution terminated due to SIG%s", sigStr) } sigMsg := struct { Signal string CoreDumped bool Error string Lang string }{ Signal: sigStr, CoreDumped: false, Error: fmt.Sprintf("shield-pipe terminated due to SIG%s", sigStr), Lang: "en-US", } channel.SendRequest("exit-signal", false, ssh.Marshal(&sigMsg)) channel.Close() continue } } } else if err != nil { // we got some kind of error that isn't a command execution error, // from a UNIX system, use an magical error code to signal this to // the shield daemon - 16777216 log.Infof("Task could not execute: %s", err) rc = 16777216 } log.Infof("Task completed with rc=%d", rc) byteCode := make([]byte, 4) binary.BigEndian.PutUint32(byteCode, uint32(rc)) // SSH protocol is big-endian byte ordering channel.SendRequest("exit-status", false, byteCode) channel.Close() } } }
func handleRegs(reqs <-chan *ssh.Request, sshConn *ssh.ServerConn) { defer sshConn.Close() for req := range reqs { if req.Type == "keepalive" && req.WantReply { req.Reply(true, nil) continue } var payload tcpipforwardPayload if err := ssh.Unmarshal(req.Payload, &payload); err != nil { fmt.Println("ERROR", err) continue } addr := fmt.Sprintf("%s:%d", payload.Addr, payload.Port) ln, err := net.Listen("tcp", addr) if err != nil { fmt.Println("Unable to listen on address: ", addr) req.Reply(false, nil) continue } defer ln.Close() reply := (payload.Port == 0) && req.WantReply if !reply { req.Reply(true, nil) } else { req.Reply(false, nil) } go func() { fmt.Println("Listening on address: ", ln.Addr().String()) quit := make(chan bool) go func() { go func() { t := time.NewTicker(30 * time.Second) defer t.Stop() for { <-t.C _, _, err := sshConn.SendRequest("keepalive", true, nil) if err != nil { fmt.Println("closed", sshConn) sshConn.Close() return } } }() for { select { case <-quit: return default: conn, err := ln.Accept() if err != nil { continue } go func(conn net.Conn) { p := forwardedTCPPayload{} var err error var portnum int p.Addr = payload.Addr p.Port = payload.Port p.OriginAddr, portnum, err = getHostPortFromAddr(conn.RemoteAddr()) if err != nil { conn.Close() return } p.OriginPort = uint32(portnum) ch, reqs, err := sshConn.OpenChannel("forwarded-tcpip", ssh.Marshal(p)) if err != nil { conn.Close() log.Println("Open forwarded Channel: ", err.Error()) return } go ssh.DiscardRequests(reqs) go func(ch ssh.Channel, conn net.Conn) { close := func() { ch.Close() conn.Close() } go copyConnections(conn, ch, close) }(ch, conn) }(conn) } } }() sshConn.Wait() fmt.Println("Stop forwarding/listening on ", ln.Addr()) quit <- true }() } }
func (server *registrarSSHServer) handleConn(logger lager.Logger, conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) { defer conn.Close() forwardedTCPIPs := make(chan forwardedTCPIP, 1) go server.handleForwardRequests(logger, conn, reqs, forwardedTCPIPs) var processes []ifrit.Process // ensure processes get cleaned up defer func() { cleanupLog := logger.Session("cleanup") for _, p := range processes { cleanupLog.Debug("interrupting") p.Signal(os.Interrupt) } for _, p := range processes { err := <-p.Wait() if err != nil { cleanupLog.Error("process-exited-with-failure", err) } else { cleanupLog.Debug("process-exited-successfully") } } }() for newChannel := range chans { if newChannel.ChannelType() != "session" { logger.Info("rejecting-unknown-channel-type", lager.Data{ "type": newChannel.ChannelType(), }) newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") continue } channel, requests, err := newChannel.Accept() if err != nil { logger.Error("failed-to-accept-channel", err) return } defer channel.Close() for req := range requests { logger.Info("channel-request", lager.Data{ "type": req.Type, }) if req.Type != "exec" { logger.Info("rejecting") req.Reply(false, nil) continue } var request execRequest err = ssh.Unmarshal(req.Payload, &request) if err != nil { logger.Error("malformed-exec-request", err) req.Reply(false, nil) return } switch request.Command { case "register-worker": logger := logger.Session("register-worker") req.Reply(true, nil) process, err := server.continuouslyRegisterWorkerDirectly(logger, channel) if err != nil { logger.Error("failed-to-register", err) return } processes = append(processes, process) err = conn.Wait() logger.Error("connection-closed", err) case "forward-worker": logger := logger.Session("forward-worker") var forwarded forwardedTCPIP select { case forwarded = <-forwardedTCPIPs: logger.Info("forwarded-tcpip", lager.Data{ "bound-port": forwarded.boundPort, }) processes = append(processes, forwarded.process) process, err := server.continuouslyRegisterForwardedWorker(logger, channel, forwarded.boundPort) if err != nil { logger.Error("failed-to-register", err) return } processes = append(processes, process) err = conn.Wait() logger.Error("connection-closed", err) case <-time.After(10 * time.Second): // todo better? logger.Info("never-forwarded-tcpip") } default: logger.Info("invalid-command", lager.Data{ "command": request.Command, }) req.Reply(false, nil) } } } }