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 }