Beispiel #1
0
func (inst *instance) hostKeyCallback(hostname string, remote net.Addr, key ssh.PublicKey) error {
	oldPublicKey, err := hex.DecodeString(inst.conn.Options.SSHPublicKey)
	if err != nil {
		return errors.New("XML is corrupt: " + err.Error())
	}
	newPublicKey := key.Marshal()
	//TODO correctly marshal/unmarshal into xml

	newPublicMD5 := md5.Sum(newPublicKey)
	newPublicString := hex.EncodeToString(newPublicMD5[:])

	if len(oldPublicKey) == 0 {
		color.Yellowln("Registering new SSH Public Key", key.Type(),
			newPublicString)
		inst.conn.Options.SSHPublicKey = hex.EncodeToString(newPublicKey)
		inst.changed = true
		return nil
	}

	oldPublicMD5 := md5.Sum(oldPublicKey)
	oldPublicString := hex.EncodeToString(oldPublicMD5[:])

	same := subtle.ConstantTimeCompare(newPublicKey, oldPublicKey)
	if same == 1 {
		return nil
	}
	color.Redln("-----POSSIBLE ATTACK-----\nSSH key changed! expected (md5):",
		oldPublicString,
		"got:", newPublicString, "type:", key.Type())
	inst.terminal.Stderr().Write([]byte("Accept change [Ny]? "))

	buf := make([]byte, 128)
	n, err := inst.terminal.Stdin().Read(buf)
	if err != nil {
		color.Yellowln("Error reading answer:", err)
		return err
	}
	inst.terminal.Stderr().Write([]byte{'\n'})

	text := strings.ToLower(string(buf[:n]))
	if text == "y" || text == "yes" {
		inst.conn.Options.SSHPublicKey = hex.EncodeToString(newPublicKey)
		inst.changed = true
		color.Yellowln("Saving new public key to connections.xml on exit.")
		return nil
	}
	return errors.New("Public key not accepted")
}
Beispiel #2
0
func fuzzySimple(conf *types.Configuration, searchFor string) *types.Connection {
	words := listWords(conf.AllConnections)

	reader := bufio.NewReader(os.Stdin)
	var found string
	for {
		color.Yellow("Search for: ")
		var input string
		var err error
		if searchFor != "" {
			input = searchFor
			searchFor = ""
		} else {
			input, err = reader.ReadString('\n')
			p(err, "reading stdin")
			input = strings.Trim(input, "\r\n ")
		}
		if input == "" {
			for _, v := range words {
				sort.Stable(StringList(words))
				fmt.Println(v)
			}
			continue
		}
		suggs := fuzzy.RankFindFold(input, words)
		if len(suggs) > 1 {
			sort.Stable(suggs)
			color.Yellowln("Suggestions:")
			for _, v := range suggs {
				fmt.Println(v.Target)
			}
			color.Redln("Please try again.")
		} else if len(suggs) == 0 {
			color.Redln("Nothing found for", input+". Please try again.")
		} else {
			found = suggs[0].Target
			break
		}
	}
	conn := conf.AllConnections[found]
	return conn
}
Beispiel #3
0
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)
	}
}
Beispiel #4
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
}