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 forwardLocalConn(logger lager.Logger, localConn net.Conn, conn *ssh.ServerConn, forwardIP string, forwardPort uint32) { defer localConn.Close() var req forwardTCPIPChannelRequest req.ForwardIP = forwardIP req.ForwardPort = forwardPort host, port, err := net.SplitHostPort(localConn.RemoteAddr().String()) if err != nil { logger.Error("failed-to-split-host-port", err) return } req.OriginIP = host _, err = fmt.Sscanf(port, "%d", &req.OriginPort) if err != nil { logger.Error("failed-to-parse-port", err) return } channel, reqs, err := conn.OpenChannel("forwarded-tcpip", ssh.Marshal(req)) if err != nil { logger.Error("failed-to-open-channel", err) return } defer channel.Close() go func() { for r := range reqs { logger.Info("ignoring-request", lager.Data{ "type": r.Type, }) r.Reply(false, nil) } }() wg := new(sync.WaitGroup) pipe := func(to io.WriteCloser, from io.ReadCloser) { // if either end breaks, close both ends to ensure they're both unblocked, // otherwise io.Copy can block forever if e.g. reading after write end has // gone away defer to.Close() defer from.Close() defer wg.Done() io.Copy(to, from) } wg.Add(1) go pipe(localConn, channel) wg.Add(1) go pipe(channel, localConn) wg.Wait() }
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 (h *SCPHandler) SinkRequest(conn ssh.ServerConn, parameters scp.Parameter, pattern string) bool { h.conn = conn // Remote address lookup var err error h.name, err = lookupIP(conn.RemoteAddr().String()) if err != nil { log.Println("Failed to lookup IP: %s", err) return false } log.Println("Accepting SCP request from %s", h.name) return true }
func NewSession(gateway *Gateway, connection *ssh.ServerConn) (*Session, error) { glog.V(1).Infof("new session: user = %s, remote = %v", connection.User(), connection.RemoteAddr()) return &Session{ gateway: gateway, connection: connection, user: connection.User(), remoteAddr: connection.RemoteAddr(), localAddr: connection.LocalAddr(), services: make(map[string]map[uint16]bool), lock: &sync.Mutex{}, active: true, created: time.Now(), used: time.Now(), channelsClosed: 0, bytesRead: 0, bytesWritten: 0, }, nil }
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 } }
// Make new terminal from a session channel func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) { if ch.ChannelType() != "session" { return nil, errors.New("terminal requires session channel") } channel, requests, err := ch.Accept() if err != nil { return nil, err } term := Terminal{ *terminal.NewTerminal(channel, "Connecting..."), sshConn{conn}, channel, } go term.listen(requests) go func() { // FIXME: Is this necessary? conn.Wait() channel.Close() }() return &term, nil }
/* connectionLogger opens a log file for the authenticated connection in the given logDir. It returns the logger itself, as well as the name of the logfile and the session directory. Should look like logdir/address/sessiontime/log The returned *os.File must be closed when it's no longer needed to prevent memory/fd leakage. */ func connectionLogger( sc *ssh.ServerConn, logDir string, ) (lg *log.Logger, name, dir string, file *os.File, err error) { /* Each host gets its own directory */ addrDir, _, err := net.SplitHostPort(sc.RemoteAddr().String()) if nil != err { log.Printf( "Address:%v Unable to split host from port: %v", sc.RemoteAddr().String(), err, ) addrDir = sc.RemoteAddr().String() + "err" } /* Each authenticated session does, as well */ sessionDir := filepath.Join( logDir, addrDir, time.Now().Format(LOGFORMAT), ) if err := os.MkdirAll(sessionDir, 0700); nil != err { return nil, "", "", nil, err } /* Open the main logfile */ logName := filepath.Join(sessionDir, LOGNAME) lf, err := os.OpenFile( logName, os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_EXCL, 0600, ) if nil != err { return nil, "", "", nil, err } /* Logify it. */ return log.New( //lf, io.MultiWriter(lf, os.Stderr), /* DEBUG */ "", log.LstdFlags|log.Lmicroseconds, ), logName, sessionDir, lf, nil }
func (server *Server) handleChannel(newChannel ssh.NewChannel, conn *ssh.ServerConn) { channelType := newChannel.ChannelType() if channelType != "session" { newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("Unknown SSH Channel Type: %s, only `session` is supported", channelType)) server.Logger.Errorf("Rejected SSH Channel Request from %s due to unknown channel type: %s", conn.RemoteAddr().String(), newChannel.ChannelType()) return } channel, requests, err := newChannel.Accept() if err != nil { newChannel.Reject(ssh.ConnectionFailed, "Failed to accept SSH Channel Request, developers are working on it.") server.Logger.Errorf("Rejected SSH Channel Request from %s due to accept request failure: %s", conn.RemoteAddr().String(), err) return } server.Logger.Debugf("Accepted new SSH Channel Request from %s", conn.RemoteAddr().String()) server.handleRequest(channel, requests, conn) }
// NewClient initializes a new client func NewClient(conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request, server *Server) *Client { client := Client{ Idx: clientCounter, ClientID: conn.RemoteAddr().String(), ChannelIdx: 0, Conn: conn, Chans: chans, Reqs: reqs, Server: server, // Default ClientConfig, will be overwritten if a hook is used Config: &ClientConfig{ ImageName: strings.Replace(conn.User(), "_", "/", -1), RemoteUser: "******", AuthenticationMethod: "noauth", AuthenticationComment: "", AuthenticationAttempts: 0, Env: envhelper.Environment{}, Command: make([]string, 0), }, } if server.LocalUser != "" { client.Config.IsLocal = client.Config.ImageName == server.LocalUser } if _, found := server.ClientConfigs[client.ClientID]; !found { server.ClientConfigs[client.ClientID] = client.Config } client.Config = server.ClientConfigs[conn.RemoteAddr().String()] client.Config.Env.ApplyDefaults() clientCounter++ remoteAddr := strings.Split(client.ClientID, ":") log.Infof("Accepted %s for %s from %s port %s ssh2: %s", client.Config.AuthenticationMethod, conn.User(), remoteAddr[0], remoteAddr[1], client.Config.AuthenticationComment) return &client }
func (server *Server) handleRequest(channel ssh.Channel, requests <-chan *ssh.Request, conn *ssh.ServerConn) { defer func() { err := channel.Close() if err != nil { server.Logger.Errorf("Failed to close SSH Channel from %s due to %s", conn.RemoteAddr().String(), err) } server.Logger.Debugf("Close SSH Channel from %s", conn.RemoteAddr().String()) }() for req := range requests { server.Logger.Debugf("Received new SSH Request (type = %s) from %s", req.Type, conn.RemoteAddr().String()) switch req.Type { case "exec": server.handleExecRequest(channel, req, conn) default: var err error if req.Type == "env" { _, err = channel.Stderr().Write([]byte("error: Pages does not support SendEnv.\n")) } else { _, err = channel.Write([]byte("You've successfully authenticated, but Pages does not provide shell access.\n")) } if err != nil && err != io.EOF { server.Logger.Errorf("Failed to Talk to SSH Request due to %s", err) } err = req.Reply(false, nil) if err != nil && err != io.EOF { server.Logger.Errorf("Failed to Reply false to SSH Request due to %s", err) } err = channel.Close() if err != nil && err != io.EOF { server.Logger.Errorf("Failed to close SSH Request due to %s", err) } server.Logger.Errorf("Close SSH Request due to unsupported SSH Request type: %s", req.Type) } 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 (s *Server) handleChannel(conn *ssh.ServerConn, newChan ssh.NewChannel) { ch, reqs, err := newChan.Accept() if err != nil { log.Println("newChan.Accept failed:", err) return } defer ch.Close() for req := range reqs { switch req.Type { case "exec": fail := func(at string, err error) { log.Printf("%s failed: %s", at, err) ch.Stderr().Write([]byte("Internal error.\n")) } if req.WantReply { req.Reply(true, nil) } cmdline := string(req.Payload[4:]) cmdargs, err := shlex.Split(cmdline) if err != nil || len(cmdargs) != 2 { ch.Stderr().Write([]byte("Invalid arguments.\n")) return } if cmdargs[0] != "git-upload-pack" { ch.Stderr().Write([]byte("Only `git fetch` is supported.\n")) return } cmdargs[1] = strings.TrimSuffix(strings.TrimPrefix(cmdargs[1], "/"), ".git") if strings.Contains(cmdargs[1], "..") { ch.Stderr().Write([]byte("Invalid repo.\n")) return } cmd := exec.Command(s.Shell, "-c", cmdargs[0]+" '"+cmdargs[1]+"'") cmd.Dir = s.Dir cmd.Env = append(os.Environ(), "RECEIVE_USER="******"RECEIVE_REPO="+cmdargs[1], ) done, err := attachCmd(cmd, ch, ch.Stderr(), ch) if err != nil { fail("attachCmd", err) return } if err := cmd.Start(); err != nil { fail("cmd.Start", err) return } done.Wait() status, err := exitStatus(cmd.Wait()) if err != nil { fail("exitStatus", err) return } if _, err := ch.SendRequest("exit-status", false, ssh.Marshal(&status)); err != nil { fail("sendExit", err) } return case "env": if req.WantReply { req.Reply(true, nil) } } } }
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) } } } }
func (server *Server) handleChannels(chans <-chan ssh.NewChannel, conn *ssh.ServerConn) { // Service the incoming Channel channel in go routine for newChannel := range chans { server.Logger.Debugf("New SSH Channel Request %s from %s", newChannel.ChannelType(), conn.RemoteAddr().String()) // TODO: Find Channel ID to log go server.handleChannel(newChannel, conn) } }
func newAttacker(conn *ssh.ServerConn, username string, password string) *Attacker { addr := conn.RemoteAddr().String() addr = ipAddrFromRemoteAddr(addr) return &Attacker{addr, username, password} }
func (server *Server) handleExecRequest(channel ssh.Channel, request *ssh.Request, conn *ssh.ServerConn) { doReply := func(ok bool) { err := request.Reply(ok, nil) if err != nil { server.Logger.Errorf("Failed to reply %t to SSH Request from %s due to %s", ok, conn.RemoteAddr().String(), err) } server.Logger.Debugf("Reply to SSH Request `%t` from %s", ok, conn.RemoteAddr().String()) } if len(request.Payload) < 4 { server.Logger.Errorf("Payload must not be shorter than 4 bytes, but only %d bytes", len(request.Payload)) doReply(false) return } header := request.Payload[:4] cmdLen := int64(binary.BigEndian.Uint32(header)) if int64(len(request.Payload)) < 4+cmdLen { server.Logger.Errorf("Payload must not be shorter than %d bytes, but only %d bytes", 4+cmdLen, len(request.Payload)) doReply(false) return } cmd := request.Payload[4 : 4+cmdLen] server.Logger.Debugf("Execute command `%s` via SSH from %s", string(cmd), conn.RemoteAddr().String()) shellCmd := exec.Command(server.Config.ShellPath, "-c", string(cmd)) stdinPipe, err := shellCmd.StdinPipe() if err != nil { server.Logger.Errorf("Failed to create STDIN pipe error for command: %s", err) doReply(false) return } defer stdinPipe.Close() stdoutPipe, err := shellCmd.StdoutPipe() if err != nil { server.Logger.Errorf("Failed to create STDOUT pipe error for command: %s", err) doReply(false) return } defer stdoutPipe.Close() stderrPipe, err := shellCmd.StderrPipe() if err != nil { server.Logger.Errorf("Failed to create STDERR pipe error for command: %s", err) doReply(false) return } defer stderrPipe.Close() sendExitStatus := func() { channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0}) server.Logger.Debugf("Sent exit status 0 to %s", conn.RemoteAddr().String()) } var once sync.Once go func() { io.Copy(stdinPipe, channel) once.Do(sendExitStatus) }() go func() { io.Copy(channel, stdoutPipe) once.Do(sendExitStatus) }() go func() { io.Copy(channel.Stderr(), stderrPipe) once.Do(sendExitStatus) }() err = shellCmd.Start() if err != nil { server.Logger.Errorf("Close SSH Channel from %s due to command error: %s", conn.RemoteAddr().String(), err) doReply(false) return } doReply(true) _, err = shellCmd.Process.Wait() if err != nil { _, ok := err.(*exec.ExitError) if !ok { server.Logger.Errorf("Failed to wait command(PID = %d) due to %s", shellCmd.Process.Pid, err) } return } }