func connect(c *types.Connection, terminal types.Terminal, moreCommands func() *string) bool { cmd := &exec.Cmd{} cmd.Path = "/usr/bin/ssh" cmd.Args = []string{"-v", "-p", fmt.Sprint(c.Info.Port), "-l", c.Login.User, c.Info.Host} var procExit int32 = 0 outFunc := func(pipe *os.File, name string, nextCommand func() *string, startWait *sync.Cond) { buf := make([]byte, 1024) wrotePassword := false for { if atomic.LoadInt32(&procExit) != 0 { return } n, err := pipe.Read(buf) str := string(buf[:n]) fmt.Fprint(terminal.Stdout(), str) if strings.HasSuffix(str, "assword: ") && !wrotePassword && c.Options.LoginMacro { pipe.Write([]byte(c.Login.Password)) pipe.Write([]byte("\n")) wrotePassword = true } else if strings.HasSuffix(str, "assword: ") || strings.HasSuffix(str, "$ ") || strings.HasSuffix(str, "# ") { if answer := nextCommand(); answer != nil { pipe.Write([]byte(*answer)) pipe.Write([]byte("\n")) } } if strings.HasSuffix(str, "Are you sure you want to continue connecting (yes/no)? ") { pipe.Write([]byte("yes\n")) } if err != nil { if err != io.EOF { return } fmt.Fprintln(terminal.Stderr(), "pipe", name, "error", err) return } } } inFunc := func(pipe io.WriteCloser, inputConsole io.Reader, startWait *sync.Cond) { input := make(chan []byte, 32) buffers := &sync.Pool{ New: func() interface{} { return make([]byte, 1024) }, } go func(pipe io.WriteCloser, input chan []byte, startWait *sync.Cond, buffers *sync.Pool) { for { buf := buffers.Get().([]byte) n, err := inputConsole.Read(buf) if err != nil && err != io.EOF { fmt.Fprintln(terminal.Stderr(), "my stdin got error", err) input <- nil startWait.Broadcast() return } var write []byte if err == io.EOF { write = []byte{0x04} input <- nil startWait.Broadcast() return } for _, c := range []byte{0x04, 0x03, 0x1a} { if bytes.Contains(buf[:n], []byte{c}) { write = append(write, c) } } if write != nil { _, err = pipe.Write(write) if err != nil { fmt.Fprintln(terminal.Stderr(), "stdin got error", err) input <- nil startWait.Broadcast() return } } else { input <- buf[:n] } } }(pipe, input, startWait, buffers) startWait.L.Lock() startWait.Wait() startWait.L.Unlock() for buf := range input { if buf == nil { fmt.Fprintln(terminal.Stderr(), "closing stdIn:", pipe.Close()) return } _, err := pipe.Write(buf) if err != nil { fmt.Fprintln(terminal.Stderr(), "stdin got error", err) fmt.Fprintln(terminal.Stderr(), "closing stdIn:", pipe.Close()) return } if cap(buf) > 256 { buffers.Put(buf) } } } startWait := sync.NewCond(&sync.Mutex{}) nextCommand := util.GetCommandFunc(c, startWait, moreCommands) sendSize := func(out *os.File, cmd *exec.Cmd) { var row, col C.int C.getsize(&row, &col) C.setsize(C.int(out.Fd()), row, col) cmd.Process.Signal(syscall.SIGWINCH) } signalWatcher := func(out *os.File, cmd *exec.Cmd) { for s := range terminal.Signals() { if s == syscall.SIGWINCH { sendSize(out, cmd) } else if s == os.Interrupt { cmd.Process.Signal(syscall.SIGINT) } else if s == syscall.SIGSTOP { cmd.Process.Signal(syscall.SIGSTOP) } } } pty, err := pty.Start(cmd) p(err, "starting ssh") go outFunc(pty, "pty", nextCommand, startWait) go inFunc(pty, terminal.Stdin(), startWait) go signalWatcher(pty, cmd) sendSize(pty, cmd) go func(exit <-chan bool) { for _ = range exit { if atomic.LoadInt32(&procExit) != 0 { return } err := cmd.Process.Kill() p(err, "Killing ssh process") return } }(terminal.ExitRequests()) err = cmd.Wait() atomic.StoreInt32(&procExit, 1) if err != nil { fmt.Fprintln(terminal.Stderr(), "SSH Process ended:", err) } return false }
func main() { defer func() { if err := recover(); err != nil { color.Redln(err) if DEBUG { debug.PrintStack() } os.Exit(1) } }() if DEBUG { go http.ListenAndServe(":3000", nil) } pathP := flag.String("connectionsPath", connectionsPath, "Path to PuTTY connections.xml") verbose := false useFuzzySimple := false useOwnSSH := false flag.BoolVar(&verbose, "verbose", false, "Display more info, such as hostnames and passwords") flag.BoolVar(&verbose, "v", false, "Display more info, such as hostnames and passwords") flag.BoolVar(&useFuzzySimple, "simple", false, "Use simple interface") flag.BoolVar(&useOwnSSH, "ssh", true, "Use golang ssh client instead of os-client") flag.Parse() if pathP != nil { connectionsPath = *pathP } if flag.NArg() > 1 { flag.Usage() color.Yellowln("Usage: pcm [search term]") panic("Only one arg allowed.") } var searchFor string if flag.NArg() == 1 { searchFor = flag.Arg(0) } conf := loadConns() var conn *types.Connection if useFuzzySimple { conn = fuzzySimple(&conf, searchFor) } else { conn = selectConnection(&conf, searchFor) } if conn == nil { return } color.Yellowln("Using", conn.Info.Name) color.Redln(conn.Info.Host, conn.Info.Port) if verbose { color.Yellow("User: "******"Password: "******"Commands:") f := util.GetCommandFunc(conn, nil, func() *string { return nil }) for { if v := f(); v == nil { break } else { fmt.Println(v) } } } //fmt.Println(conn.Login) //fmt.Println(conn.Command) var console types.Terminal = &consoleTerminal{ exit: make(chan bool), } oldState, err := util.SetupTerminal() p(err, "making terminal raw") defer util.RestoreTerminal(oldState) var changed bool if useOwnSSH { changed = ssh.Connect(conn, console, func() *string { return nil }) } else { changed = connect(conn, console, func() *string { return nil }) } if changed { saveConns(&conf) } }
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 }