// Manhole connects os.Stdin, os.Stdout, and os.Stderr to an interactive shell // session on the Machine m. Manhole blocks until the shell session has ended. // If os.Stdin does not refer to a TTY, Manhole returns immediately with a nil // error. func Manhole(m Machine) error { fd := int(os.Stdin.Fd()) if !terminal.IsTerminal(fd) { return nil } tstate, _ := terminal.MakeRaw(fd) defer terminal.Restore(fd, tstate) client, err := m.SSHClient() if err != nil { return fmt.Errorf("SSH client failed: %v", err) } defer client.Close() session, err := client.NewSession() if err != nil { return fmt.Errorf("SSH session failed: %v", err) } defer session.Close() session.Stdin = os.Stdin session.Stdout = os.Stdout session.Stderr = os.Stderr modes := ssh.TerminalModes{ ssh.TTY_OP_ISPEED: 115200, ssh.TTY_OP_OSPEED: 115200, } cols, lines, err := terminal.GetSize(int(os.Stdin.Fd())) if err != nil { return err } if err = session.RequestPty(os.Getenv("TERM"), lines, cols, modes); err != nil { return fmt.Errorf("failed to request pseudo terminal: %s", err) } if err := session.Shell(); err != nil { return fmt.Errorf("failed to start shell: %s", err) } if err := session.Wait(); err != nil { return fmt.Errorf("failed to wait for session: %s", err) } return nil }
func (app *h2i) Main() error { cfg := &tls.Config{ ServerName: app.host, NextProtos: strings.Split(*flagNextProto, ","), InsecureSkipVerify: *flagInsecure, } hostAndPort := withPort(app.host) log.Printf("Connecting to %s ...", hostAndPort) tc, err := tls.Dial("tcp", hostAndPort, cfg) if err != nil { return fmt.Errorf("Error dialing %s: %v", withPort(app.host), err) } log.Printf("Connected to %v", tc.RemoteAddr()) defer tc.Close() if err := tc.Handshake(); err != nil { return fmt.Errorf("TLS handshake: %v", err) } if !*flagInsecure { if err := tc.VerifyHostname(app.host); err != nil { return fmt.Errorf("VerifyHostname: %v", err) } } state := tc.ConnectionState() log.Printf("Negotiated protocol %q", state.NegotiatedProtocol) if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" { return fmt.Errorf("Could not negotiate protocol mutually") } if _, err := io.WriteString(tc, http2.ClientPreface); err != nil { return err } app.framer = http2.NewFramer(tc, tc) oldState, err := terminal.MakeRaw(0) if err != nil { return err } defer terminal.Restore(0, oldState) var screen = struct { io.Reader io.Writer }{os.Stdin, os.Stdout} app.term = terminal.NewTerminal(screen, "h2i> ") lastWord := regexp.MustCompile(`.+\W(\w+)$`) app.term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) { if key != '\t' { return } if pos != len(line) { // TODO: we're being lazy for now, only supporting tab completion at the end. return } // Auto-complete for the command itself. if !strings.Contains(line, " ") { var name string name, _, ok = lookupCommand(line) if !ok { return } return name, len(name), true } _, c, ok := lookupCommand(line[:strings.IndexByte(line, ' ')]) if !ok || c.complete == nil { return } if strings.HasSuffix(line, " ") { app.logf("%s", strings.Join(c.complete(), " ")) return line, pos, true } m := lastWord.FindStringSubmatch(line) if m == nil { return line, len(line), true } soFar := m[1] var match []string for _, cand := range c.complete() { if len(soFar) > len(cand) || !strings.EqualFold(cand[:len(soFar)], soFar) { continue } match = append(match, cand) } if len(match) == 0 { return } if len(match) > 1 { // TODO: auto-complete any common prefix app.logf("%s", strings.Join(match, " ")) return line, pos, true } newLine = line[:len(line)-len(soFar)] + match[0] return newLine, len(newLine), true } errc := make(chan error, 2) go func() { errc <- app.readFrames() }() go func() { errc <- app.readConsole() }() return <-errc }