func (c *consoleTerminal) Signals() <-chan os.Signal { if c.signals == nil { c.signals = make(chan os.Signal, 1) signal.Notify(c.signals, os.Interrupt, util.GetSigwinch()) } return c.signals }
func (inst *instance) connect(moreCommands func() *string) bool { config := &ssh.ClientConfig{ User: inst.conn.Login.User, Auth: []ssh.AuthMethod{ssh.Password(inst.conn.Login.Password)}, HostKeyCallback: inst.hostKeyCallback, Timeout: 20 * time.Second, } addr := fmt.Sprint(inst.conn.Info.Host, ":", inst.conn.Info.Port) client, err := ssh.Dial("tcp", addr, config) if err != nil { color.Redln("Connecting to", addr, ":", err) return inst.changed } // Each ClientConn can support multiple interactive sessions, // represented by a Session. inst.session, err = client.NewSession() if err != nil { color.Redln("Failed to create session:", err) return inst.changed } defer inst.session.Close() // Set up terminal modes modes := ssh.TerminalModes{ ssh.ECHO: 1, // disable echoing ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud } exit := make(chan bool, 1) go func() { for e := range inst.terminal.ExitRequests() { exit <- e } }() var procExit int32 = 0 shellOutFunc := func(stdErrOut io.Reader, stdin io.Writer, name string, nextCommand func() *string, startWait *sync.Cond) { buf := make([]byte, 1024) for { if atomic.LoadInt32(&procExit) != 0 { return } n, err := stdErrOut.Read(buf) if err != nil { if err == io.EOF { exit <- true return } fmt.Fprintln(inst.terminal.Stderr(), "ssh", name, "error", err) exit <- true return } _, err = inst.terminal.Stdout().Write(buf[:n]) if err != nil { if err == io.EOF { exit <- true return } fmt.Fprintln(inst.terminal.Stderr(), "ssh", name, "error", err) return } //fmt.Fprint(inst.terminal.Stderr(), str) str := string(buf[:n]) if strings.HasSuffix(str, "assword: ") || strings.HasSuffix(str, "$ ") || strings.HasSuffix(str, "# ") { if answer := nextCommand(); answer != nil { stdin.Write([]byte(*answer)) stdin.Write([]byte("\n")) } } } } inFunc := func(sshStdin io.WriteCloser, startWait *sync.Cond) { inputBufChan := make(chan []byte, 32) buffers := &sync.Pool{ New: func() interface{} { return make([]byte, 1024) }, } go func() { writeRightNow := make([]byte, 0, 3) for { buf := buffers.Get().([]byte) n, err := inst.terminal.Stdin().Read(buf) buf = buf[:n] //fmt.Fprintln(inst.terminal.Stdout(), "my stdin got", util.DebugString(buf)) if err != nil && err != io.EOF { fmt.Fprintln(inst.terminal.Stdout(), "my stdin got error", err) inputBufChan <- nil startWait.Broadcast() return } writeRightNow = writeRightNow[:0] if err == io.EOF { writeRightNow = append(writeRightNow, 0x04) inputBufChan <- nil startWait.Broadcast() return } // ctrl+c, ctrl+d, ctrl+z for _, c := range []byte{0x04, 0x03, 0x1a} { if bytes.Contains(buf, []byte{c}) { writeRightNow = append(writeRightNow, c) } } // handle questions if newLinePos := bytes.Index(buf, []byte{'\r'}); newLinePos != -1 { // see if we have any answer handlers select { case handler := <-inst.questions: answer := string(buf[:newLinePos]) handler(answer) // munch the string buf = buf[newLinePos+1:] if len(buf) == 0 { if cap(buf) > 256 { buffers.Put(buf) } continue } default: // do nothing } } if len(writeRightNow) > 0 { _, err = sshStdin.Write(writeRightNow) if err != nil { fmt.Fprintln(inst.terminal.Stderr(), "stdin got error", err) inputBufChan <- nil startWait.Broadcast() return } } else { inputBufChan <- buf } } }() // wait for the start commands to finish startWait.L.Lock() startWait.Wait() startWait.L.Unlock() for buf := range inputBufChan { if buf == nil { fmt.Fprintln(inst.terminal.Stderr(), "closing stdin:", sshStdin.Close()) exit <- true return } trans := util.TransformInput(buf) //fmt.Fprintln(inst.terminal.Stderr(), "sending", util.DebugString(trans)) _, err := sshStdin.Write(trans) if err != nil { fmt.Fprintln(inst.terminal.Stderr(), "stdin got error", err) fmt.Fprintln(inst.terminal.Stderr(), "closing stdin:", sshStdin.Close()) exit <- true return } if cap(buf) > 256 { buffers.Put(buf) } } } defer func() { atomic.StoreInt32(&procExit, 1) }() startWait := sync.NewCond(&sync.Mutex{}) nextCommand := util.GetCommandFunc(inst.conn, startWait, moreCommands) sshStdin, err := inst.session.StdinPipe() if err != nil { color.Redln("Error opening stdin pipe", err) return inst.changed } go inFunc(sshStdin, startWait) sshStdout, err := inst.session.StdoutPipe() if err != nil { color.Redln("Error opening stdout pipe", err) return inst.changed } go shellOutFunc(sshStdout, sshStdin, "stdout", nextCommand, startWait) sshStderr, err := inst.session.StderrPipe() if err != nil { color.Redln("Error opening stderr pipe", err) return inst.changed } go shellOutFunc(sshStderr, sshStdin, "stderr", nextCommand, startWait) // Request pseudo terminal if err := inst.session.RequestPty("xterm", 80, 40, modes); err != nil { color.Redln("request for pseudo terminal failed:", err) return inst.changed } // Start remote shell if err := inst.session.Shell(); err != nil { color.Redln("failed to start shell:", err) return inst.changed } inst.SendWindowSize() signalWatcher := func() { for s := range inst.terminal.Signals() { sshSignal, ok := signalMap[s] if !ok { color.Yellowln("Unknown signal", s) } else if sshSignal != "" { inst.session.Signal(sshSignal) } if s == util.GetSigwinch() { inst.SendWindowSize() } else if s == syscall.SIGTERM { exit <- true } } } go signalWatcher() <-exit atomic.StoreInt32(&procExit, 1) return inst.changed }