Exemple #1
0
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
}
Exemple #2
0
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
}