You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
6.7 KiB
Go
325 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type CommandHandler struct {
|
|
shell *Shell
|
|
filestore Filestore
|
|
}
|
|
|
|
func NewCommandHandler(shell *Shell, filestore Filestore) *CommandHandler {
|
|
return &CommandHandler{
|
|
shell: shell,
|
|
filestore: filestore,
|
|
}
|
|
}
|
|
|
|
func Control(c rune) rune {
|
|
return c & 0x1f
|
|
}
|
|
|
|
func sttyCharFromDesc(c string) (rune, error) {
|
|
if len(c) > 2 || len(c) < 1 {
|
|
return 'i', errors.New(
|
|
"stty char description has unsupported length: " +
|
|
strconv.Itoa(len(c)),
|
|
)
|
|
}
|
|
|
|
if rune(c[0]) == '^' {
|
|
return Control(rune(c[1])), nil
|
|
}
|
|
|
|
return rune(c[0]), nil
|
|
}
|
|
|
|
const fakeUname = "Linux 5.16.19-76051619-generic"
|
|
|
|
type CommandExecutor interface {
|
|
Execute(shell *Shell, args []string) error
|
|
}
|
|
|
|
type SttyCommand struct{}
|
|
|
|
func (c *SttyCommand) Execute(shell *Shell, args []string) error {
|
|
// Tramp uses:
|
|
// -inlcr: don't translate newline to carriage return
|
|
// -onlcr: don't translate newline to carriage return-newline
|
|
// -echo: echo input characters
|
|
// kill CHAR: CHAR will erase the current line
|
|
// erase CHAR: CHAR will erase the last character typed
|
|
// icanon: enable special charactersq
|
|
|
|
fd := int(shell.GetPty().Fd())
|
|
const ioctlReadTermios = unix.TCGETS // Linux
|
|
const ioctlWriteTermios = unix.TCSETS // Linux
|
|
|
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
newState := *termios
|
|
|
|
var skipNext bool
|
|
for i, arg := range args {
|
|
if skipNext {
|
|
skipNext = false
|
|
continue
|
|
}
|
|
|
|
var disable bool
|
|
if arg[0] == '-' {
|
|
disable = true
|
|
arg = arg[1:]
|
|
}
|
|
|
|
switch arg {
|
|
case "onlcr":
|
|
if disable {
|
|
newState.Iflag &^= unix.ONLCR
|
|
continue
|
|
}
|
|
|
|
newState.Iflag |= unix.ONLCR
|
|
case "inlcr":
|
|
if disable {
|
|
newState.Iflag &^= unix.INLCR
|
|
continue
|
|
}
|
|
|
|
newState.Iflag |= unix.INLCR
|
|
case "echo":
|
|
if disable {
|
|
newState.Lflag &^= unix.ECHO
|
|
continue
|
|
}
|
|
|
|
newState.Lflag |= unix.ECHO
|
|
case "kill":
|
|
skipNext = true
|
|
|
|
char, err := sttyCharFromDesc(args[i+1])
|
|
if err != nil {
|
|
log.Printf(
|
|
"Warning: Not applying unsupported character description for stty kill: %s",
|
|
args[i+1],
|
|
)
|
|
continue
|
|
}
|
|
|
|
newState.Cc[unix.VKILL] = uint8(char)
|
|
case "erase":
|
|
skipNext = true
|
|
|
|
char, err := sttyCharFromDesc(args[i+1])
|
|
if err != nil {
|
|
log.Printf(
|
|
"Warning: Not applying unsupported character description for stty erase: %s",
|
|
args[i+1],
|
|
)
|
|
continue
|
|
}
|
|
|
|
newState.Cc[unix.VERASE] = uint8(char)
|
|
case "icanon":
|
|
if disable {
|
|
newState.Lflag &^= unix.ICANON
|
|
continue
|
|
}
|
|
|
|
newState.Lflag |= unix.ICANON
|
|
|
|
case "tab0":
|
|
newState.Oflag = (newState.Oflag & ^uint32(unix.TABDLY)) | unix.TAB0
|
|
case "iutf8":
|
|
if disable {
|
|
newState.Iflag &^= unix.IUTF8
|
|
continue
|
|
}
|
|
|
|
newState.Iflag |= unix.IUTF8
|
|
|
|
default:
|
|
log.Printf(
|
|
"Warning, tramp requested unexpected stty option %s",
|
|
arg,
|
|
)
|
|
}
|
|
}
|
|
|
|
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type EchoCommand struct{}
|
|
|
|
func (c *EchoCommand) Execute(shell *Shell, args []string) error {
|
|
if len(args) > 1 {
|
|
if "\"`uname -sr`\"" == args[0]+" "+args[1] {
|
|
log.Println("Tramp requested uname, returning a fake one: " + fakeUname)
|
|
_, err := shell.WriteOutput(
|
|
[]byte(
|
|
fmt.Sprintf(
|
|
"\"%s\"\n%s\n",
|
|
fakeUname,
|
|
"tramp_exit_status 0",
|
|
),
|
|
),
|
|
)
|
|
return err
|
|
}
|
|
|
|
if "\"`tty`\"" == args[0] {
|
|
log.Println("Telling tramp that it's dealing with a tty")
|
|
_, err := shell.WriteOutputString("\"/dev/pts/0\"\n" + trampSuccess)
|
|
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
if args[0] == "~root" {
|
|
log.Println("Telling tramp root's home directory")
|
|
_, err := shell.WriteOutputString("/root")
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, err := shell.WriteOutput([]byte(strings.Join(args, " ") + "\n"))
|
|
return err
|
|
}
|
|
|
|
func AssignmentsToMap(args []string) map[string]string {
|
|
var assignments = make(map[string]string)
|
|
for _, arg := range args {
|
|
split := strings.Split(arg, "=")
|
|
if len(split) > 1 {
|
|
assignments[split[0]] = split[1]
|
|
}
|
|
}
|
|
|
|
return assignments
|
|
}
|
|
|
|
func ApplyEnvIfPresent(shell *Shell, comm *Command) {
|
|
var args []string
|
|
args = append(args, comm.Name)
|
|
args = append(args, comm.Arguments...)
|
|
|
|
assignments := AssignmentsToMap(args)
|
|
if ps1, ok := assignments["PS1"]; ok {
|
|
shell.SetPrompt(ps1)
|
|
}
|
|
}
|
|
|
|
type TestCommand struct {
|
|
filestore Filestore
|
|
}
|
|
|
|
func (c *TestCommand) Execute(shell *Shell, args []string) error {
|
|
if len(args) > 0 && args[0] == "0" {
|
|
_, err := shell.WriteOutputString(trampSuccess)
|
|
return err
|
|
}
|
|
|
|
if len(args) > 1 && args[0] == "-d" {
|
|
log.Printf("Checking for existance of directory %s", args[1])
|
|
exists, err := c.filestore.Exists(args[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if exists {
|
|
_, err := shell.WriteOutputString(trampSuccess)
|
|
return err
|
|
}
|
|
|
|
_, err = shell.WriteOutputString(trampFailure)
|
|
return err
|
|
}
|
|
|
|
if len(args) > 1 && args[0] == "-r" {
|
|
_, err := shell.WriteOutputString(trampSuccess)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
const fakePath = "/bin:/usr/bin"
|
|
const fakePipeBuf = "4096"
|
|
const trampSuccess = "tramp_exit_status 0\n"
|
|
const trampFailure = "tramp_exit_status 1\n"
|
|
|
|
type GetConfCommand struct{}
|
|
|
|
func (c *GetConfCommand) Execute(shell *Shell, args []string) error {
|
|
if len(args) > 0 {
|
|
switch args[0] {
|
|
case "PATH":
|
|
shell.WriteOutputString(fakePath + "\n" + trampSuccess)
|
|
case "PIPE_BUF":
|
|
shell.WriteOutputString(fakePipeBuf + "\n" + trampSuccess)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *CommandHandler) Handle(comm *Command) error {
|
|
if strings.Contains(comm.Name, "=") {
|
|
ApplyEnvIfPresent(h.shell, comm)
|
|
return nil
|
|
}
|
|
|
|
switch comm.Name {
|
|
case "exec":
|
|
ApplyEnvIfPresent(h.shell, comm)
|
|
case "(cd":
|
|
h.shell.WriteOutput([]byte("tramp_exit_status 0\n"))
|
|
case "echo":
|
|
(&EchoCommand{}).Execute(h.shell, comm.Arguments)
|
|
case "(echo":
|
|
if strings.Join(comm.Arguments, " ") == "foo ; echo bar)" {
|
|
log.Println("Handling tramp's foobar test")
|
|
h.shell.WriteOutputString("foo\nbar\n")
|
|
}
|
|
case "stty":
|
|
return (&SttyCommand{}).Execute(h.shell, comm.Arguments)
|
|
case "set":
|
|
log.Println("Ignoring \"set\" command")
|
|
case "locale":
|
|
if len(comm.Arguments) > 0 {
|
|
if comm.Arguments[0] == "-a" {
|
|
locales := []string{"C", "C.UTF-8", "POSIX", "en_US.utf8"}
|
|
log.Println("Tramp requested locale, returning fake values: ", locales)
|
|
h.shell.WriteOutputString(
|
|
strings.Join(locales, "\n") + "\n",
|
|
)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
log.Println("Ignoring \"locale\" command with unsupported parameters: ", comm.Arguments)
|
|
case "getconf":
|
|
return (&GetConfCommand{}).Execute(h.shell, comm.Arguments)
|
|
case "test":
|
|
return (&TestCommand{filestore: h.filestore}).Execute(h.shell, comm.Arguments)
|
|
default:
|
|
log.Printf("Error: Received unexpected command %s", comm.Name)
|
|
}
|
|
|
|
return nil
|
|
}
|