func pipePty(cmd *exec.Cmd, handler *interactive.ShellHandler) error {
	// Start the shell as TTY
	f, err := pty.Start(cmd)
	if err == nil {
		// Connect pipes (close stderr as tty only has two streams)
		go ioext.CopyAndClose(f, handler.StdinPipe())
		go ioext.CopyAndClose(handler.StdoutPipe(), f)
		go handler.StderrPipe().Close()

		// Start communication
		handler.Communicate(func(cols, rows uint16) error {
			return setTTYSize(f, cols, rows)
		}, func() error {
			if cmd.Process != nil {
				return cmd.Process.Kill()
			}
			return nil
		})
	}
	// If pty wasn't supported for some reason we fall back to normal execution
	if err == pty.ErrUnsupported {
		return pipeCommand(cmd, handler)
	}
	if err != nil {
		handler.Communicate(nil, func() error {
			// If cmd.Start() failed, then we don't have a process, but we start
			// the communication flow anyways.
			if cmd.Process != nil {
				return cmd.Process.Kill()
			}
			return nil
		})
	}
	return err
}
Beispiel #2
0
func (cmd) Execute(arguments map[string]interface{}) bool {
	URL := arguments["<URL>"].(string)
	command := arguments["<command>"].([]string)
	tty := isatty.IsTerminal(os.Stdout.Fd())

	// Parse URL
	u, err := url.Parse(URL)
	if err != nil {
		fmt.Println("Failed to parse URL, error: ", err)
		return false
	}
	qs := u.Query()

	// Set the command, if we have one
	if len(command) > 0 {
		qs["command"] = command
	}

	// Set tty=true if we're in a tty
	if tty {
		qs.Set("tty", "true")
	}

	// Update query string
	u.RawQuery = qs.Encode()

	// Connect to remove websocket
	ws, res, err := dialer.Dial(u.String(), nil)
	if err == websocket.ErrBadHandshake {
		fmt.Println("Failed to connect, status: ", res.StatusCode)
		return false
	}
	if err != nil {
		fmt.Println("Failed to connect, error: ", err)
		return false
	}

	// Create shell client
	shell := shellclient.New(ws)

	// Switch terminal to raw mode
	cleanup := func() {}
	if tty {
		cleanup = SetupRawTerminal(shell.SetSize)
	}

	// Connect pipes
	go ioext.CopyAndClose(shell.StdinPipe(), os.Stdin)
	go io.Copy(os.Stdout, shell.StdoutPipe())
	go io.Copy(os.Stderr, shell.StderrPipe())

	// Wait for shell to be done
	success, _ := shell.Wait()

	// If we were in a tty we let's restore state
	cleanup()

	return success
}
func (s *ShellServer) handleShell(ws *websocket.Conn, shell engines.Shell) {
	done := make(chan struct{})

	// Create a shell handler
	s.updateRefCount(1)
	handler := NewShellHandler(ws, s.log.WithField("shell-instance-id", s.nextID()))

	// Connect pipes
	go ioext.CopyAndClose(shell.StdinPipe(), handler.StdinPipe())
	go ioext.CopyAndClose(handler.StdoutPipe(), shell.StdoutPipe())
	go ioext.CopyAndClose(handler.StderrPipe(), shell.StderrPipe())

	// Start streaming
	handler.Communicate(shell.SetSize, shell.Abort)

	// Wait for call to abort all shells
	go func() {
		select {
		case <-s.done:
			shell.Abort()
		case <-done:
		}
	}()

	// Wait for the shell to terminate
	success, _ := shell.Wait()
	handler.Terminated(success)
	s.updateRefCount(-1)

	// Close done so we stop waiting for abort on all shells
	select {
	case <-done:
	default:
		close(done)
	}
}
// StartProcess starts a new process with given arguments, environment variables,
// and current working folder, running as given user.
//
// Returns an human readable error explaining why the sub-process couldn't start
// if not successful.
func StartProcess(options ProcessOptions) (*Process, error) {
	// Default arguments to system shell
	if len(options.Arguments) == 0 {
		options.Arguments = []string{defaultShell}
	}

	// If WorkingFolder isn't set find home folder of options.Owner (if set)
	// or current user
	if options.WorkingFolder == "" {
		if options.Owner != nil {
			options.WorkingFolder = options.Owner.homeFolder
		} else {
			u, err := user.Current()
			if err != nil {
				panic(fmt.Sprintf("Failed to lookup current user, error: %s", err))
			}
			options.WorkingFolder = u.HomeDir
		}
	}

	// Default stdout to os.DevNul
	if options.Stdout == nil {
		options.Stdout = ioext.WriteNopCloser(ioutil.Discard)
	}

	// Default stderr to stdout
	if options.Stderr == nil {
		options.Stderr = options.Stdout
	}

	// Create process and command
	p := &Process{}
	p.cmd = exec.Command(options.Arguments[0], options.Arguments[1:]...)
	p.cmd.Env = formatEnv(options.Environment)
	p.cmd.Dir = options.WorkingFolder

	// Set owner for the process
	if options.Owner != nil {
		p.cmd.SysProcAttr = &syscall.SysProcAttr{
			Credential: &syscall.Credential{
				Uid: options.Owner.uid,
				Gid: options.Owner.gid,
			},
		}
	}

	// Start the process
	var err error
	if !options.TTY {
		p.cmd.Stdin = options.Stdin
		p.cmd.Stdout = options.Stdout
		p.cmd.Stderr = options.Stderr
		p.stdin = options.Stdin
		p.stdout = options.Stdout
		p.stderr = options.Stderr
		err = p.cmd.Start()
	} else {
		p.pty, err = pty.Start(p.cmd)
		if options.Stdin != nil {
			p.sockets.Add(1)
			go func() {
				io.Copy(p.pty, options.Stdin)
				p.sockets.Done()
				// Kill process when stdin ends (if running as TTY)
				p.Kill()
			}()
		}
		p.sockets.Add(1)
		go func() {
			ioext.CopyAndClose(options.Stdout, p.pty)
			p.sockets.Done()
		}()
	}

	if err != nil {
		return nil, fmt.Errorf("Unable to execute binary, error: %s", err)
	}

	// Go wait for result
	go p.waitForResult()

	return p, nil
}