func handleChannel(ch ssh.NewChannel) { if ch.ChannelType() != "session" { log.Printf("[ERROR] unknown channel type %q", ch.ChannelType()) ch.Reject(ssh.UnknownChannelType, "unknown channel type") return } channel, requests, err := ch.Accept() if err != nil { log.Printf("[ERROR] could not accept channel %q", err) } defer channel.Close() var master, slave *os.File defer func() { if master != nil { master.Close() } if slave != nil { slave.Close() } }() var wg sync.WaitGroup wg.Add(1) go func(in <-chan *ssh.Request) { for req := range in { log.Printf("[INFO] got request %q", req.Type) switch req.Type { case "shell": if len(req.Payload) > 0 { // do not accept any extra commands for shell req.Reply(false, nil) return } req.Reply(true, nil) case "pty-req": pty, tty, err := pty.Open() if err != nil { log.Printf("[ERROR] failed to open a pty %q", err) return } master, slave = pty, tty wg.Done() // FIXME handle pty request req.Reply(true, nil) case "window-change": // FIXME handle window resize req.Reply(true, nil) } } }(requests) wg.Wait() go io.Copy(channel, master) go io.Copy(master, channel) s := bufio.NewScanner(slave) for { prompt(slave) if !s.Scan() { break } line := s.Text() // clear the terminal and continue if line == "cls" { fmt.Fprintf(slave, "\033[2J") fmt.Fprintf(slave, "\033[1;1H") continue } tokens := strings.Split(line, " ") cmd := exec.Command("docker", tokens...) cmd.Stdin = slave cmd.Stdout = slave cmd.Stderr = slave cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} log.Println("command: %#v", cmd) if err := cmd.Start(); err != nil { fmt.Fprintf(master, "invalid docker cmd %q\n", line) } if err := cmd.Wait(); err != nil { fmt.Fprintf(master, "cmd %q failed\n", line) } fmt.Fprint(slave, "\r\n") } }
func (chi *ChanInfo) Serve(conn ssh.Conn, newChan ssh.NewChannel) (err error) { log.Info("new channel: %s (len: %d)", newChan.ChannelType(), len(newChan.ExtraData())) err = chi.onType(newChan.ChannelType(), newChan.ExtraData()) if err != nil { newChan.Reject(ssh.ResourceShortage, err.Error()) log.Error("reject channel: %s", err.Error()) return } chout, outreqs, err := conn.OpenChannel( newChan.ChannelType(), newChan.ExtraData()) if err != nil { newChan.Reject(ssh.UnknownChannelType, err.Error()) log.Error("reject channel: %s", err.Error()) return } log.Debug("open channel ok.") chin, inreqs, err := newChan.Accept() if err != nil { log.Error("could not accept channel.") return } log.Debug("accept channel ok.") go chi.serveReqs(chin, outreqs) go chi.serveReqs(chout, inreqs) _, ok := <-chi.ch if !ok { return } switch chi.Type { case "local", "remote": go MultiCopyClose(chin, chout, &DebugStream{"out"}) go MultiCopyClose(chout, chin, &DebugStream{"in"}) case "sshagent": go MultiCopyClose(chin, chout, &DebugStream{"out"}) go MultiCopyClose(chout, chin, &DebugStream{"in"}) case "shell": l, err := chi.prepareFile("") if err != nil { return err } go MultiCopyClose(chin, chout, l.CreateSubLogger(byte(0x01))) go MultiCopyClose(chout, chin, l.CreateSubLogger(byte(0x02))) case "exec": l, err := chi.prepareFile(strings.Join(chi.ExecCmds, "\r")) if err != nil { return err } go MultiCopyClose(chin, chout, l.CreateSubLogger(byte(0x01))) go MultiCopyClose(chout, chin, l.CreateSubLogger(byte(0x02))) case "scpto": go MultiCopyClose(chin, chout, CreateScpStream(chi)) go MultiCopyClose(chout, chin) case "scpfrom": go MultiCopyClose(chin, chout) go MultiCopyClose(chout, chin, CreateScpStream(chi)) default: log.Warning("redirect before setup") chin.Close() chout.Close() } return }
func 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-receive-pack" { ch.Stderr().Write([]byte("Only `git push` 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 } if err := ensureCacheRepo(cmdargs[1]); err != nil { fail("ensureCacheRepo", err) return } cmd := exec.Command("git-shell", "-c", cmdargs[0]+" '"+cmdargs[1]+"'") cmd.Dir = *repoPath 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) } } } }
// handleChannel runs the needed commands on a given SSH server connection against the user // that opened the communication channel. func 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": assert := func(at string, err error) bool { if err != nil { log.Printf("%s failed: %s", at, err) ch.Stderr().Write([]byte("Internal error.\n")) return true } return false } defer func() { log.Printf("Connection lost from %s", conn.RemoteAddr().String()) }() if req.WantReply { req.Reply(true, nil) } cmdline := string(req.Payload[4:]) cmdargs, err := shlex.Split(cmdline) if assert("shlex.Split", err) { return } if len(cmdargs) != 2 { ch.Stderr().Write([]byte("Invalid arguments.\n")) return } if cmdargs[0] != "git-receive-pack" { ch.Stderr().Write([]byte("Only `git push` is supported.\n")) return } user := conn.Permissions.Extensions["user"] reponame := strings.TrimSuffix(strings.TrimPrefix(cmdargs[1], "/"), ".git") log.Printf("Push from %s at %s", user, reponame) if err := makeGitRepo(reponame); err != nil { ch.Stderr().Write([]byte("Error: " + err.Error())) return } log.Printf("Writing hooks...") err = ioutil.WriteFile(reponame+"/hooks/pre-receive", []byte(`#!/bin/bash strip_remote_prefix() { sed -u "s/^/"$'\e[1G'"/" } set -eo pipefail; while read oldrev newrev refname; do /app/cloudchaser -etcd-machine `+*etcduplink+` pre $newrev 2>&1 | strip_remote_prefix done`), 0755) if err != nil { return } err = ioutil.WriteFile(reponame+"/hooks/post-receive", []byte(`#!/bin/bash strip_remote_prefix() { sed -u "s/^/"$'\e[1G'"/" } set -eo pipefail; while read oldrev newrev refname; do /app/builder -etcd-host `+*etcduplink+` $REPO ${refname##*/} $newrev 2>&1 | strip_remote_prefix done`), 0755) if err != nil { return } log.Printf("Doing git receive...") receive := exec.Command("git-receive-pack", reponame) if conn.Permissions.Extensions["environ"] != "" { receive.Env = append(receive.Env, strings.Split(conn.Permissions.Extensions["environ"], "\n")...) } receive.Env = append(receive.Env, "USER="******"user"]) receive.Env = append(receive.Env, "REMOTE_HOST="+conn.RemoteAddr().String()) receive.Env = append(receive.Env, "REPO="+reponame) done, err := attachCmd(receive, ch, ch.Stderr(), ch) if err != nil { ch.Stderr().Write([]byte("Error: " + err.Error())) return } if assert("receive.Start", receive.Start()) { return } done.Wait() log.Printf("Receive done") status, rcvErr := exitStatus(receive.Wait()) if rcvErr != nil { ch.Stderr().Write([]byte("Error: " + rcvErr.Error())) return } _, err = ch.SendRequest("exit-status", false, ssh.Marshal(&status)) assert("sendExit", err) return case "env": if req.WantReply { req.Reply(true, nil) } default: return } } }