func (ctrl *Controller) terminal(ctx scope.Context, ch ssh.Channel) { defer ch.Close() lines := make(chan string) term := terminal.NewTerminal(ch, "> ") go func() { for ctx.Err() == nil { line, err := term.ReadLine() if err != nil { ctx.Terminate(err) return } lines <- line } }() for { var line string select { case <-ctx.Done(): return case line = <-lines: } cmd := parse(line) fmt.Printf("[control] > %v\n", cmd) switch cmd[0] { case "": continue case "quit": return case "shutdown": ctrl.ctx.Terminate(fmt.Errorf("shutdown initiated from console")) default: runCommand(ctx.Fork(), ctrl, cmd[0], term, cmd[1:]) } } }
func pipeRequests(psChannel, sChannel ssh.Channel, psRequests, sRequests <-chan *ssh.Request) { defer func() { return if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() defer sChannel.Close() defer psChannel.Close() for { select { case lRequest, ok := <-psRequests: if !ok { return } if err := forwardRequest(lRequest, sChannel); err != nil { fmt.Println("Error: " + err.Error()) continue } case rRequest, ok := <-sRequests: if !ok { return } if err := forwardRequest(rRequest, psChannel); err != nil { fmt.Println("Error: " + err.Error()) continue } } } }
func ProxyRequests(logger lager.Logger, channelType string, reqs <-chan *ssh.Request, channel ssh.Channel) { logger = logger.Session("proxy-requests", lager.Data{ "channel-type": channelType, }) logger.Info("started") defer logger.Info("completed") defer channel.Close() for req := range reqs { logger.Info("request", lager.Data{ "type": req.Type, "wantReply": req.WantReply, "payload": req.Payload, }) success, err := channel.SendRequest(req.Type, req.WantReply, req.Payload) if err != nil { logger.Error("send-request-failed", err) continue } if req.WantReply { req.Reply(success, nil) } } }
func handleSessionRequests(logger log.Logger, channel ssh.Channel, requests <-chan *ssh.Request, system datamodel.System, user datamodel.User) { defer channel.Close() // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "shell" request. for req := range requests { ok := false switch req.Type { case "shell": ok = true if len(req.Payload) > 0 { fmt.Println(string(req.Payload)) // We don't accept any // commands, only the // default shell. ok = false } case "pty-req": // Responding 'ok' here will let the client // know we have a pty ready for input ok = true go startTerminal(logger, channel, system, user) default: // fmt.Println("default req: ", req) } req.Reply(ok, nil) } }
func (s *shellHandler) startTerminal(parentTomb tomb.Tomb, sshConn *ssh.ServerConn, channel ssh.Channel) error { defer channel.Close() prompt := ">>> " term := terminal.NewTerminal(channel, prompt) // // Try to make the terminal raw // oldState, err := terminal.MakeRaw(0) // if err != nil { // logger.Warn("Error making terminal raw: ", err.Error()) // } // defer terminal.Restore(0, oldState) // Get username username, ok := sshConn.Permissions.Extensions["username"] if !ok { username = "******" } // Write ascii text term.Write([]byte(fmt.Sprintf("\r\n Nice job, %s! You are connected!\r\n", username))) defer term.Write([]byte(fmt.Sprintf("\r\nGoodbye, %s!\r\n", username))) // Start REPL for { select { case <-parentTomb.Dying(): return nil default: s.logger.Info("Reading line...") input, err := term.ReadLine() if err != nil { fmt.Errorf("Readline() error") return err } // Process line line := strings.TrimSpace(input) if len(line) > 0 { // Log input and handle exit requests if line == "exit" || line == "quit" { s.logger.Info("Closing connection") return nil } // Echo input channel.Write(term.Escape.Green) channel.Write([]byte(line + "\r\n")) channel.Write(term.Escape.Reset) } } } return nil }
func (s *sshServer) handleRequests(channel ssh.Channel, in <-chan *ssh.Request) error { env := make(map[string]string) for req := range in { switch req.Type { default: log.Printf("unrecognized ssh request type=%q payload=%s wantreply=%t", req.Type, req.Payload, req.WantReply) req.Reply(false, nil) // unhandled; tell them so case "env": var e envReq if err := ssh.Unmarshal(req.Payload, &e); err != nil { req.Reply(false, nil) return err } req.Reply(true, nil) env[string(e.Key)] = string(e.Val) case "exec": var e execReq if err := ssh.Unmarshal(req.Payload, &e); err != nil { req.Reply(false, nil) return err } req.Reply(true, nil) var cmdbuf bytes.Buffer for k, v := range env { cmdbuf.WriteString(k) cmdbuf.WriteByte('=') cmdbuf.WriteString(v) cmdbuf.WriteByte(' ') } cmdbuf.Write(e.Command) log.Printf("Running command %q", cmdbuf.String()) cmd := &packer.RemoteCmd{Command: cmdbuf.String()} cmd.Stdout = channel cmd.Stderr = channel.Stderr() var rc int if err := s.comm.Start(cmd); err != nil { rc = 255 // TODO: What is a better choice here? } else { cmd.Wait() rc = cmd.ExitStatus } channel.CloseWrite() channel.SendRequest("exit-status", false, []byte{0, 0, 0, byte(rc)}) channel.Close() } } return nil }
func (s *shellHandler) Handle(parentTomb tomb.Tomb, sshConn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error { defer channel.Close() users, err := s.system.Users() if err != nil { return err } user, err := users.Get(sshConn.Permissions.Extensions["username"]) if err != nil { return err } // Create tomb for terminal goroutines var t tomb.Tomb // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "shell" request. for { select { case <-parentTomb.Dying(): t.Kill(nil) return t.Wait() case req := <-requests: ok := false switch req.Type { case "shell": ok = true if len(req.Payload) > 0 { fmt.Println(string(req.Payload)) // We don't accept any // commands, only the // default shell. ok = false } case "pty-req": // Responding 'ok' here will let the client // know we have a pty ready for input ok = true go s.startTerminal(t, channel, s.system, user) default: // fmt.Println("default req: ", req) } req.Reply(ok, nil) } } return nil }
func (s *shellHandler) Handle(parentTomb tomb.Tomb, sshConn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error { defer channel.Close() s.logger.Info("WooHoo!!! Inside Handler!") // Create tomb for terminal goroutines var t tomb.Tomb // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "shell" request. t.Go(func() error { OUTER: for { select { case <-parentTomb.Dying(): t.Kill(nil) break OUTER case req := <-requests: if req == nil { break OUTER } ok := false switch req.Type { case "shell": ok = true if len(req.Payload) > 0 { // fmt.Println(string(req.Payload)) // We don't accept any // commands, only the // default shell. ok = false } case "pty-req": // Responding 'ok' here will let the client // know we have a pty ready for input ok = true t.Go(func() error { return s.startTerminal(t, sshConn, channel) }) } req.Reply(ok, nil) } } return nil }) return t.Wait() }
func handleChannelRequests(logger log.Logger, channel ssh.Channel, requests <-chan *ssh.Request, system datamodel.System, user datamodel.User) { defer channel.Close() for req := range requests { if req.Type == "skl" { logger.Info("SKL request", "request", string(req.Payload)) req.Reply(true, nil) } else { if req.WantReply { req.Reply(false, nil) } } } }
func proxy(reqs1, reqs2 <-chan *ssh.Request, channel1, channel2 ssh.Channel) { var closer sync.Once closeFunc := func() { channel1.Close() channel2.Close() } defer closer.Do(closeFunc) closerChan := make(chan bool, 1) go func() { io.Copy(channel1, channel2) closerChan <- true }() go func() { io.Copy(channel2, channel1) closerChan <- true }() for { select { case req := <-reqs1: if req == nil { return } b, err := channel2.SendRequest(req.Type, req.WantReply, req.Payload) if err != nil { return } req.Reply(b, nil) case req := <-reqs2: if req == nil { return } b, err := channel1.SendRequest(req.Type, req.WantReply, req.Payload) if err != nil { return } req.Reply(b, nil) case <-closerChan: return } } }
func (sshd *SSHD) handleChannel(channel ssh.Channel, requests <-chan *ssh.Request, container string) { cmd := exec.Command("docker", "exec", "-i", "-t", container, "/bin/bash") closeChannel := func() { channel.Close() _, err := cmd.Process.Wait() if err != nil { log.Errorf("failed to exit docker exec (%s)", err) } } fp, err := pty.Start(cmd) if err != nil { log.Error("pty.Start: ", err) closeChannel() return } go func() { for req := range requests { log.Debugf("new request: %s", req.Type) switch req.Type { case "shell": if len(req.Payload) == 0 { req.Reply(true, nil) } case "pty-req": termLen := req.Payload[3] w, h := sshd.parseDims(req.Payload[termLen+4:]) sshd.setWinsize(fp.Fd(), w, h) req.Reply(true, nil) case "window-change": w, h := sshd.parseDims(req.Payload) sshd.setWinsize(fp.Fd(), w, h) case "env": } } }() var once sync.Once cp := func(dst io.Writer, src io.Reader) { io.Copy(dst, src) once.Do(closeChannel) } go cp(channel, fp) go cp(fp, channel) }
// Payload: int: command size, string: command func handleExec(ch ssh.Channel, req *ssh.Request) { command := string(req.Payload[4:]) gitCmds := []string{"git-receive-pack", "git-upload-pack"} valid := false for _, cmd := range gitCmds { if strings.HasPrefix(command, cmd) { valid = true } } if !valid { ch.Write([]byte("command is not a GIT command\r\n")) ch.Close() return } ch.Write([]byte("well done!\r\n")) ch.Close() }
func handleChannel(ch ssh.Channel) { term := terminal.NewTerminal(ch, "> ") serverTerm := &ssh.ServerTerminal{ Term: term, Channel: ch, } ch.Accept() defer ch.Close() for { line, err := serverTerm.ReadLine() if err == io.EOF { return } if err != nil { log.Println("handleChannel readLine err:", err) continue } fmt.Println(line) } }
/* handleChannelRequests handles proxying requests read from reqs to the SSH channel c. info is used for logging. */ func handleChannelRequests( reqs <-chan *ssh.Request, c ssh.Channel, info string, ) { for r := range reqs { go handleRequest( r, func( /* Ugh, a closure */ name string, wantReply bool, payload []byte, ) (bool, []byte, error) { b, e := c.SendRequest(name, wantReply, payload) return b, nil, e }, func() error { return c.Close() }, /* Another? */ info, ) } }
// Payload: int: command size, string: command func handleExec(ch ssh.Channel, req *ssh.Request) { command := string(req.Payload[4:]) client, session, err := connectUpstream() if err != nil { ch.Write([]byte("fail to connect upstream: " + err.Error() + "\r\n")) ch.Close() return } exitStatus, err := pipe(ch, client, session, command) if err != nil { ch.Write([]byte("fail to pipe command:" + err.Error())) ch.Close() return } exitStatusBuffer := make([]byte, 4) binary.PutUvarint(exitStatusBuffer, uint64(exitStatus)) log.Println("forward exit-code", exitStatus, "to client") _, err = ch.SendRequest("exit-status", false, exitStatusBuffer) if err != nil { log.Println("Failed to forward exit-status to client:", err) } ch.Close() client.Close() log.Println("End of exec") }
// This executes the commands requested to be run on the server. // Used to test the SSH secret backend. func executeServerCommand(ch ssh.Channel, req *ssh.Request) { command := string(req.Payload[4:]) cmd := exec.Command("/bin/bash", []string{"-c", command}...) req.Reply(true, nil) cmd.Stdout = ch cmd.Stderr = ch cmd.Stdin = ch err := cmd.Start() if err != nil { panic(fmt.Sprintf("Error starting the command: '%s'", err)) } go func() { _, err := cmd.Process.Wait() if err != nil { panic(fmt.Sprintf("Error while waiting for command to finish:'%s'", err)) } ch.Close() }() }
func (s *SSHServer) execHandler(channel ssh.Channel, request *ssh.Request) error { defer channel.Close() var payload = struct { Value string }{} if err := ssh.Unmarshal(request.Payload, &payload); err != nil { return fmt.Errorf("failed to unmarshal payload: %v", err) } result, status, err := s.runCmd(payload.Value) if err != nil { return fmt.Errorf("failed to run command: %v", err) } if err := sendCmdResult(channel, result, status); err != nil { return fmt.Errorf("failed to send result: %v", err) } if err := request.Reply(true, nil); err != nil { return fmt.Errorf("failed to send reply: %v", err) } return nil }
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 } }
func (s *SSHServer) shellHandler(channel ssh.Channel) error { defer channel.Close() term := terminal.NewTerminal(channel, "# ") for { line, err := term.ReadLine() if err != nil { break } if line == "" { return nil } result, status, err := s.runCmd(line) if err != nil { return fmt.Errorf("failed to run command: %v", err) } if err := sendCmdResult(channel, result, status); err != nil { return fmt.Errorf("failed to send result: %v", err) } } return nil }
func forwardUnixSocket(channel ssh.Channel, addr string) { conn, err := net.Dial("unix", addr) if err != nil { return } var wg sync.WaitGroup wg.Add(2) go func() { io.Copy(conn, channel) conn.(*net.UnixConn).CloseWrite() wg.Done() }() go func() { io.Copy(channel, conn) channel.CloseWrite() wg.Done() }() wg.Wait() conn.Close() channel.Close() }
func (s *server) handleExec(ch ssh.Channel, req *ssh.Request, authInfo map[string]string) { defer ch.Close() args := strings.SplitN(string(req.Payload[4:]), " ", 2) //remove the 4 bytes of git protocol indicating line length command := args[0] repo := strings.TrimSuffix(strings.TrimPrefix(args[1], "'/"), ".git'") //auth the user if os.Getenv("GITHUB_AUTH") == "true" { gauth, err := auth.NewGithubAuth() if err != nil { writePktLine(fmt.Sprintf("github auth error, contact an administrator: %s", err), ch) return } if err := gauth.Authenticate(authInfo["user"], authInfo["public_key"], repo); err != nil { writePktLine(fmt.Sprintf("github auth failed: %s", err), ch) return } } //check if allowed command allowed := []string{pullCmd, pushCmd} ok := false for _, c := range allowed { if command == c { ok = true break } } if !ok { log.Infof("command %s not allowed on this server", command) writePktLine(fmt.Sprintf("%s not allowed on this server", command), ch) return } log.Infof("receiving %s command for repo %s", command, repo) repoPath, err := s.prepareRepo(repo) if err != nil { log.Errorf("unable to create repo: %v", err) writePktLine(err.Error(), ch) return } defer func() { if err := s.unlockRepo(repo); err != nil { log.Errorf("unable to unlock repo: %v", err) writePktLine(err.Error(), ch) } }() cmd := exec.Command(command, repoPath) wg, err := attachCmd(cmd, ch) if err != nil { log.Errorf("unable to attach command stdio: %v", err) writePktLine(err.Error(), ch) return } if err := cmd.Start(); err != nil { log.Errorf("unable to start command: %v", err) writePktLine(err.Error(), ch) return } wg.Wait() syscallErr := cmd.Wait() ch.SendRequest("exit-status", false, ssh.Marshal(exitStatus(syscallErr))) }
Context("when a session channel is opened", func() { var channel ssh.Channel var requests <-chan *ssh.Request BeforeEach(func() { var err error channel, requests, err = client.OpenChannel("session", nil) Expect(err).NotTo(HaveOccurred()) go ssh.DiscardRequests(requests) }) AfterEach(func() { if channel != nil { channel.Close() } }) Context("and an exec request fails to unmarshal", func() { It("rejects the request", func() { accepted, err := channel.SendRequest("exec", true, ssh.Marshal(struct{ Bogus uint32 }{Bogus: 1138})) Expect(err).NotTo(HaveOccurred()) Expect(accepted).To(BeFalse()) }) }) Context("and an env request fails to unmarshal", func() { It("rejects the request", func() { accepted, err := channel.SendRequest("env", true, ssh.Marshal(struct{ Bogus int }{Bogus: 1234})) Expect(err).NotTo(HaveOccurred())
// answer handles answering requests and channel requests // // Currently, an exec must be either "ping", "git-receive-pack" or // "git-upload-pack". Anything else will result in a failure response. Right // now, we leave the channel open on failure because it is unclear what the // correct behavior for a failed exec is. // // Support for setting environment variables via `env` has been disabled. func (s *server) answer(channel ssh.Channel, requests <-chan *ssh.Request, condata string, sshconn *ssh.ServerConn) error { defer channel.Close() // Answer all the requests on this connection. for req := range requests { ok := false switch req.Type { case "env": o := &EnvVar{} ssh.Unmarshal(req.Payload, o) log.Info("Key='%s', Value='%s'\n", o.Name, o.Value) req.Reply(true, nil) case "exec": clean := cleanExec(req.Payload) parts := strings.SplitN(clean, " ", 2) switch parts[0] { case "ping": err := Ping(channel, req) if err != nil { log.Info("Error pinging: %s", err) } return err case "git-receive-pack", "git-upload-pack": if len(parts) < 2 { log.Info("Expected two-part command.") req.Reply(ok, nil) break } repoName, err := cleanRepoName(parts[1]) if err != nil { log.Err("Illegal repo name: %s.", err) channel.Stderr().Write([]byte("No repo given")) return err } wrapErr := wrapInLock(s.pushLock, repoName, time.Duration(0), s.runReceive(req, sshconn, channel, repoName, parts, condata)) if wrapErr == errAlreadyLocked { log.Info(multiplePush) // The error must be in git format if pktErr := gitPktLine(channel, fmt.Sprintf("ERR %v\n", multiplePush)); pktErr != nil { log.Err("Failed to write to channel: %s", err) } sendExitStatus(1, channel) req.Reply(false, nil) return nil } var xs uint32 if wrapErr != nil { log.Err("Failed git receive: %v", err) xs = 1 } sendExitStatus(xs, channel) return nil default: log.Info("Illegal command is '%s'\n", clean) req.Reply(false, nil) return nil } if err := sendExitStatus(0, channel); err != nil { log.Err("Failed to write exit status: %s", err) } return nil default: // We simply ignore all of the other cases and leave the // channel open to take additional requests. log.Info("Received request of type %s\n", req.Type) req.Reply(false, nil) } } return nil }
func (BadHandler) Handle(t tomb.Tomb, conn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error { defer channel.Close() return fmt.Errorf("An error occurred") }
func (e *EchoHandler) Handle(t tomb.Tomb, conn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) error { defer channel.Close() e.logger.Info("echo handle called!") // Create tomb for terminal goroutines var tmb tomb.Tomb type msg struct { line []byte isPrefix bool err error } in := make(chan msg) defer close(in) reader := bufio.NewReader(channel) tmb.Go(func() error { tmb.Go(func() error { for { line, pre, err := reader.ReadLine() if err != nil { tmb.Kill(nil) return nil } select { case in <- msg{line, pre, err}: case <-t.Dying(): tmb.Kill(nil) return nil case <-tmb.Dying(): return nil } } }) tmb.Go(func() error { for { e.logger.Info("time: ", time.Now()) select { case <-tmb.Dying(): return nil case <-t.Dying(): tmb.Kill(nil) return nil case m := <-in: if m.err != nil { tmb.Kill(m.err) return m.err } // Send echo channel.Write(m.line) } } }) return nil }) return tmb.Wait() }
// answer handles answering requests and channel requests // // Currently, an exec must be either "ping", "git-receive-pack" or // "git-upload-pack". Anything else will result in a failure response. Right // now, we leave the channel open on failure because it is unclear what the // correct behavior for a failed exec is. // // Support for setting environment variables via `env` has been disabled. func (s *server) answer(channel ssh.Channel, requests <-chan *ssh.Request, sshConn string) error { defer channel.Close() // Answer all the requests on this connection. for req := range requests { ok := false // I think that ideally what we want to do here is pass this on to // the Cookoo router and let it handle each Type on its own. switch req.Type { case "env": o := &EnvVar{} ssh.Unmarshal(req.Payload, o) fmt.Printf("Key='%s', Value='%s'\n", o.Name, o.Value) req.Reply(true, nil) case "exec": clean := cleanExec(req.Payload) parts := strings.SplitN(clean, " ", 2) router := s.c.Get("cookoo.Router", nil).(*cookoo.Router) // TODO: Should we unset the context value 'cookoo.Router'? // We need a shallow copy of the context to avoid race conditions. cxt := s.c.Copy() cxt.Put("SSH_CONNECTION", sshConn) // Only allow commands that we know about. switch parts[0] { case "ping": cxt.Put("channel", channel) cxt.Put("request", req) sshPing := cxt.Get("route.sshd.sshPing", "sshPing").(string) err := router.HandleRequest(sshPing, cxt, true) if err != nil { log.Warnf(s.c, "Error pinging: %s", err) } return err case "git-receive-pack", "git-upload-pack": if len(parts) < 2 { log.Warn(s.c, "Expected two-part command.\n") req.Reply(ok, nil) break } req.Reply(true, nil) // We processed. Yay. cxt.Put("channel", channel) cxt.Put("request", req) cxt.Put("operation", parts[0]) cxt.Put("repository", parts[1]) sshGitReceive := cxt.Get("route.sshd.sshGitReceive", "sshGitReceive").(string) err := router.HandleRequest(sshGitReceive, cxt, true) var xs uint32 if err != nil { log.Errf(s.c, "Failed git receive: %v", err) xs = 1 } sendExitStatus(xs, channel) return nil default: log.Warnf(s.c, "Illegal command is '%s'\n", clean) req.Reply(false, nil) return nil } if err := sendExitStatus(0, channel); err != nil { log.Errf(s.c, "Failed to write exit status: %s", err) } return nil default: // We simply ignore all of the other cases and leave the // channel open to take additional requests. log.Infof(s.c, "Received request of type %s\n", req.Type) req.Reply(false, nil) } } return nil }
func (c *Client) runCommand(channel ssh.Channel, entrypoint string, command []string) { var cmd *exec.Cmd var err error if c.Config.IsLocal { cmd = exec.Command(entrypoint, command...) } else { // checking if a container already exists for this user existingContainer := "" if !c.Server.NoJoin { cmd = exec.Command("docker", "ps", "--filter=label=ssh2docker", fmt.Sprintf("--filter=label=image=%s", c.Config.ImageName), fmt.Sprintf("--filter=label=user=%s", c.Config.RemoteUser), "--quiet", "--no-trunc") cmd.Env = c.Config.Env.List() buf, err := cmd.CombinedOutput() if err != nil { log.Warnf("docker ps ... failed: %v", err) channel.Close() return } existingContainer = strings.TrimSpace(string(buf)) } // Opening Docker process if existingContainer != "" { // Attaching to an existing container args := []string{"exec"} if len(c.Config.DockerExecArgs) > 0 { args = append(args, c.Config.DockerExecArgs...) if err := c.alterArgs(args); err != nil { log.Errorf("Failed to execute template on args: %v", err) return } } else { inlineExec, err := c.alterArg(c.Server.DockerExecArgsInline) if err != nil { log.Errorf("Failed to execute template on arg: %v", err) return } execArgs, err := shlex.Split(inlineExec) if err != nil { log.Errorf("Failed to split arg %q: %v", inlineExec, err) return } args = append(args, execArgs...) } args = append(args, existingContainer) if entrypoint != "" { args = append(args, entrypoint) } args = append(args, command...) log.Debugf("Executing 'docker %s'", strings.Join(args, " ")) cmd = exec.Command("docker", args...) cmd.Env = c.Config.Env.List() } else { // Creating and attaching to a new container args := []string{"run"} if len(c.Config.DockerRunArgs) > 0 { args = append(args, c.Config.DockerRunArgs...) if err := c.alterArgs(args); err != nil { log.Errorf("Failed to execute template on args: %v", err) return } } else { inlineRun, err := c.alterArg(c.Server.DockerRunArgsInline) if err != nil { log.Errorf("Failed to execute template on arg: %v", err) return } runArgs, err := shlex.Split(inlineRun) if err != nil { log.Errorf("Failed to split arg %q: %v", inlineRun, err) return } args = append(args, runArgs...) } args = append(args, "--label=ssh2docker", fmt.Sprintf("--label=user=%s", c.Config.RemoteUser), fmt.Sprintf("--label=image=%s", c.Config.ImageName)) if c.Config.User != "" { args = append(args, "-u", c.Config.User) } if entrypoint != "" { args = append(args, "--entrypoint", entrypoint) } args = append(args, c.Config.ImageName) args = append(args, command...) log.Debugf("Executing 'docker %s'", strings.Join(args, " ")) cmd = exec.Command("docker", args...) cmd.Env = c.Config.Env.List() } } if c.Server.Banner != "" { banner := c.Server.Banner banner = strings.Replace(banner, "\r", "", -1) banner = strings.Replace(banner, "\n", "\n\r", -1) fmt.Fprintf(channel, "%s\n\r", banner) } cmd.Stdout = channel cmd.Stdin = channel cmd.Stderr = channel var wg sync.WaitGroup if c.Config.UseTTY { cmd.Stdout = c.Tty cmd.Stdin = c.Tty cmd.Stderr = c.Tty wg.Add(1) go func() { io.Copy(channel, c.Pty) wg.Done() }() wg.Add(1) go func() { io.Copy(c.Pty, channel) wg.Done() }() defer wg.Wait() } cmd.SysProcAttr = &syscall.SysProcAttr{ Setctty: c.Config.UseTTY, Setsid: true, } err = cmd.Start() if err != nil { log.Warnf("cmd.Start failed: %v", err) channel.Close() return } if err := cmd.Wait(); err != nil { log.Warnf("cmd.Wait failed: %v", err) } channel.Close() log.Debugf("cmd.Wait done") }
func prepHandle(channel ssh.Channel, requests <-chan *ssh.Request, handler Handler) error { if channel == nil { return fmt.Errorf("No session channel received.\n") } defer channel.Close() var termtype string var width, height uint32 var shell = false var pty = false after := time.After(time.Second * 3) for !(shell && pty) { select { case request, ok := <-requests: if !ok { return nil } fmt.Printf("request: %s\n", request.Type) switch request.Type { case "shell": // TODO: Maybe only allow zero-paylength shells (no command) // if len(request.Payload) == 0 shell = true request.Reply(true, nil) case "pty-req": _, termtype, width, height = requestPtyReq(request) pty = true request.Reply(true, nil) default: request.Reply(false, nil) } case <-after: return fmt.Errorf("No shell and pty request after 3 seconds") } } t := term.New(term.Resolve(termtype), channel) if t == nil { return fmt.Errorf("Unknown terminal type '%s'", termtype) } t.SetDimensions(width, height) windowsize := make(chan WindowSize) go func() { for request := range requests { switch request.Type { case "window-change": _, width, height := requestWindowChange(request) windowsize <- WindowSize{Width: width, Height: height} // No reply to window change //request.Reply(true, nil) default: request.Reply(false, nil) } } }() reader := bufio.NewReader(channel) return handler.Handle(reader, t, windowsize) }
func proxyChannel(c *ConnInsight, mcha ssh.Channel, mreq <-chan *ssh.Request, master ssh.NewChannel, client *SSHClient, killer <-chan struct{}) error { do := new(sync.Once) cochan, coreq, err := client.OpenChannel(master.ChannelType(), master.ExtraData()) checkError(err, fmt.Sprintf("Creating Client Channel for %s", client.RemoteAddr().String())) if err != nil { return err } stop := make(chan struct{}) endClose := func() { close(stop) } flux.GoDefer("proxyChannelCopy", func() { defer cochan.Close() defer mcha.Close() func() { ploop: for { select { case <-stop: break ploop case <-killer: break ploop case slx, ok := <-coreq: if !ok { return } Reply(slx, mcha, c) switch slx.Type { case "exit-status": break ploop } case mlx, ok := <-mreq: if !ok { return } Reply(mlx, cochan, c) switch mlx.Type { case "exit-status": break ploop } } } }() }) mastercloser := io.ReadCloser(mcha) slavecloser := io.ReadCloser(cochan) wrapmaster := io.MultiWriter(mcha, c.Out()) wrapsl := io.MultiWriter(cochan, c.In()) flux.GoDefer("CopyToSlave", func() { defer do.Do(endClose) io.Copy(wrapsl, mastercloser) }) flux.GoDefer("CopyToMaster", func() { defer do.Do(endClose) io.Copy(wrapmaster, slavecloser) }) flux.GoDefer("CopyCloser", func() { defer c.Close() <-stop mx := mastercloser.Close() checkError(mx, "Master Writer Closer") sx := slavecloser.Close() checkError(sx, "Slave Writer Closer") ex := client.Close() checkError(ex, "Client Writer Closer") }) return nil }
func pipe(ch ssh.Channel, client *ssh.Client, session *ssh.Session, command string) (int, error) { targetStderr, err := session.StderrPipe() if err != nil { return -1, errors.New("fail to pipe stderr: " + err.Error()) } targetStdout, err := session.StdoutPipe() if err != nil { return -1, errors.New("fail to pipe stdout: " + err.Error()) } targetStdin, err := session.StdinPipe() if err != nil { return -1, errors.New("fail to pipe stdin: " + err.Error()) } go io.Copy(targetStdin, ch) go io.Copy(ch.Stderr(), targetStderr) go io.Copy(ch, targetStdout) err = session.Start(command) if err != nil { ch.Write([]byte("Error when starting '" + command + "': " + err.Error())) ch.Close() } err = session.Wait() if err != nil { if err, ok := err.(*ssh.ExitError); ok { return err.ExitStatus(), nil } else { return -1, errors.New("failed to wait ssh command: " + err.Error()) } } return 0, nil }