The big work in progress nothing works commit

master
Hugo Thunnissen 2 years ago
parent cf1667f0bf
commit ba1ad8f13a

@ -0,0 +1,9 @@
package commands
const fakePath = "/bin:/usr/bin"
const fakePipeBuf = "4096"
const trampSuccess = "tramp_exit_status 0\n"
const trampFailure = "tramp_exit_status 1\n"
const lsExecutable = "/bin/ls"
const testExecutable = "/bin/test"
const statExecutable = "/bin/stat"

@ -0,0 +1,45 @@
package commands
import (
"fmt"
"log"
"strings"
)
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
}

@ -0,0 +1,26 @@
package commands
import "strings"
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)
}
}

@ -0,0 +1,15 @@
package commands
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
}

@ -0,0 +1,123 @@
package commands
import (
"fmt"
"log"
"strings"
"git.snorba.art/hugo/nssh/storage"
)
type CommandHandler struct {
shell *Shell
filestore storage.Filestore
}
func NewCommandHandler(shell *Shell, filestore storage.Filestore) *CommandHandler {
return &CommandHandler{
shell: shell,
filestore: filestore,
}
}
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", "/bin/test":
return (&TestCommand{filestore: h.filestore}).Execute(h.shell, comm.Arguments)
case "while":
if len(comm.Arguments) > 6 {
var err error
log.Printf("Pointing tramp to executable for \"%s\"", comm.Arguments[6])
switch comm.Arguments[6] {
case "$d/ls":
_, err = h.shell.WriteOutputString("tramp_executable " + lsExecutable)
case "$d/test":
_, err = h.shell.WriteOutputString("tramp_executable " + testExecutable)
case "$d/stat":
_, err = h.shell.WriteOutputString("tramp_executable " + statExecutable)
}
return err
}
case "ls", "/bin/ls":
if len(comm.Arguments) > 2 &&
comm.Arguments[1] == "-al" &&
comm.Arguments[2] == "/dev/null" {
_, err := h.shell.WriteOutputString(
fmt.Sprintf(
"crw-rw-rw- 1 root root 1, 3 May 5 00:05 /dev/null\n%s",
trampSuccess,
),
)
return err
}
if len(comm.Arguments) > 1 && comm.Arguments[0] == "-lnd" {
_, err := h.shell.WriteOutputString(
fmt.Sprintf(
"drwxrwxr-x 3 1000 1000 28 Apr 28 14:04 %s\n%s",
comm.Arguments[1],
trampSuccess,
),
)
return err
}
// should handle "--color=never --help 2>&1 | grep -iq busybox" so that we
// can pretend to have busybox ls. The assumption is that this is easier
// to emulate than the --dired flag of ls
if len(comm.Arguments) > 7 &&
comm.Arguments[4] == "grep" &&
comm.Arguments[6] == "busybox" {
_, err := h.shell.WriteOutputString(trampSuccess)
return err
}
_, err := h.shell.WriteOutputString(trampFailure)
return err
case "mkdir":
return (&MkdirCommand{filestore: h.filestore}).Execute(comm.Arguments)
default:
log.Printf("Error: Received unexpected command %s", comm.Name)
}
return nil
}

@ -0,0 +1,34 @@
package commands
import (
"strings"
"git.snorba.art/hugo/nssh/storage"
)
type MkdirCommand struct {
filestore storage.Filestore
}
func (c *MkdirCommand) Execute(arguments []string) error {
for _, dir := range arguments {
if len(dir) > 0 && dir[0] != '-' {
var currentPath string
for _, part := range strings.Split(dir, "/") {
if part == "" {
continue
}
currentPath += "/" + part
if exists, _ := c.filestore.DirectoryExists(currentPath); !exists {
err := c.filestore.MakeDirectory(currentPath)
if err != nil {
return err
}
}
}
}
}
return nil
}

@ -0,0 +1,363 @@
package commands
import (
"bufio"
"errors"
"io"
"log"
"os"
"strings"
"unsafe"
"github.com/google/shlex"
"github.com/pkg/term/termios"
"golang.org/x/sys/unix"
)
type Shell struct {
pty *os.File
tty *os.File
prompt []byte
readData []byte
readIndex int
winsize *Winsize
}
type Command struct {
Name string
Arguments []string
}
func (s *Shell) GetPty() *os.File {
return s.pty
}
func (s *Shell) Attach(conn io.ReadWriteCloser) error {
pty, tty, err := termios.Pty()
if err != nil {
return err
}
s.pty = pty
s.tty = tty
if s.winsize != nil {
SetWinsize(s.pty.Fd(), s.winsize)
}
go func() {
defer s.pty.Close()
io.Copy(s.pty, conn)
}()
go func() {
defer conn.Close()
io.Copy(conn, s.pty)
}()
_, err = s.tty.Write(s.prompt)
return err
}
func NewShell(prompt string) *Shell {
return &Shell{
prompt: []byte(prompt),
}
}
func (s *Shell) SetWinsize(w, h uint32) {
s.winsize = &Winsize{Width: uint16(w), Height: uint16(h)}
if s.pty != nil {
SetWinsize(s.pty.Fd(), s.winsize)
}
}
func (s *Shell) Close() (error, error) {
return s.pty.Close(), s.tty.Close()
}
var ErrEmptyCommand error = errors.New("Empty command line")
type State struct {
Variables map[string]string
Commands map[string]CommandExecutor
}
type Statement interface {
Words() []Word
Statements() []Statement
AddStatement(Statement)
Evaluate(state State, output io.Writer) (exitcode uint8)
}
type BaseStatement struct {
words []Word
statements []Statement
}
func (s *BaseStatement) Words() []Word {
return s.words
}
func (s *BaseStatement) Statements() []Statement {
return s.statements
}
func (s *BaseStatement) AddStatement(statement Statement) {
s.statements = append(s.statements, statement)
}
func (s *BaseStatement) AddWord(word Word) {
s.words = append(s.words, word)
}
type Word string
func (w Word) Statements() []Statement {
return []Statement{}
}
func (w Word) AddStatement(statement Statement) {}
func (w Word) AddWord(word Word) {}
type ParserParameters interface {
MakeStatement() Statement
ShouldLeave(Word, rune) bool
Supports(rune) bool
SubParsers() []Parser
}
type Parser interface {
Parse(*bufio.Reader) (Statement, error)
Parameters() ParserParameters
}
type BaseParser struct {
parameters ParserParameters
}
func (p *BaseParser) Parameters() ParserParameters {
return p.parameters
}
type BaseParameters struct {
EntryMarkers []rune
subParsers []Parser
}
func (p *BaseParameters) SubParsers() []Parser {
return p.subParsers
}
func (p *BaseParameters) Supports(r rune) bool {
for _, char := range p.EntryMarkers {
if char == r {
return true
}
}
return false
}
func (p *BaseParameters) MakeStatement() Statement {
return &BaseStatement{}
}
func (p *BaseParameters) ShouldLeave(w Word, r rune) bool {
if r == '\n' || r == ';' {
return true
}
}
func (p *BaseParser) Parse(r *bufio.Reader) (Statement, error) {
var currentWord Word = ""
statement := p.Parameters().MakeStatement()
for {
char, _, err := r.ReadRune()
if err != nil {
return statement, err
}
if char == ' ' || char == '\t' {
statement.AddWord(currentWord)
currentWord = ""
}
if p.Parameters().ShouldLeave(currentWord, char) {
return statement, nil
}
var matchedParser bool
for _, parser := range p.Parameters().SubParsers() {
if parser.Parameters().Supports(char) {
matchedParser = true
nestedStatement, err := parser.Parse(r)
if word, ok := nestedStatement.(Word); ok {
statement.AddWord(word)
} else {
statement.AddStatement(nestedStatement)
}
if err != nil {
return statement, err
}
}
}
if !matchedParser {
currentWord += Word(char)
}
}
}
func (s *Shell) ReadCommand() (*Command, error) {
var currentLine string
var backslash bool
var insideDoubleQuote bool
var insideSingleQuote bool
var maybeHeredoc bool
var insideHeredoc bool
var heredocMarker string
var heredocMarkerComplete bool
var heredocCurrentWord string
reader := bufio.NewReader(s.tty)
GatherCommand:
for {
char, _, err := reader.ReadRune()
if err != nil {
return nil, err
}
if insideHeredoc {
currentLine += string(char)
if !heredocMarkerComplete && char == '<' {
insideHeredoc = false
continue
}
if char == '\n' {
log.Printf("Heredoc current word: \"%s\"", heredocCurrentWord)
if heredocCurrentWord == heredocMarker {
log.Println("exiting heredoc")
insideHeredoc = false
heredocMarkerComplete = false
break GatherCommand
}
if !heredocMarkerComplete {
log.Println("Encountered heredoc marker: " + heredocCurrentWord)
heredocMarker = strings.Trim(heredocCurrentWord, "'")
heredocMarkerComplete = true
}
heredocCurrentWord = ""
continue
}
heredocCurrentWord += string(char)
continue
}
if backslash {
if !insideDoubleQuote && char == '\n' {
currentLine += string(" ")
continue
}
currentLine += "\\" + string(char)
backslash = false
continue
}
switch rune(char) {
case '<':
currentLine += string(char)
if maybeHeredoc {
maybeHeredoc = false
insideHeredoc = true
continue
}
maybeHeredoc = true
continue
case '\\':
backslash = true
continue
case '"':
insideDoubleQuote = !insideDoubleQuote
currentLine += string(char)
case '\'':
insideSingleQuote = !insideSingleQuote
currentLine += string(char)
case '\n':
if insideSingleQuote || insideDoubleQuote {
currentLine += string(char)
continue
}
break GatherCommand
default:
currentLine += string(char)
}
}
commandLine, err := shlex.Split(currentLine)
if err != nil {
return nil, err
}
if len(commandLine) == 0 {
return nil, ErrEmptyCommand
}
comm := &Command{Name: commandLine[0]}
if len(commandLine) > 1 {
comm.Arguments = commandLine[1:]
}
return comm, nil
}
func (s *Shell) SetPrompt(prompt string) {
log.Printf("Changing prompt to \"%s\"", prompt)
s.prompt = []byte(prompt)
}
func (s *Shell) WritePrompt() error {
_, err := s.tty.Write(s.prompt)
return err
}
func (s *Shell) WriteOutput(output []byte) (int, error) {
return s.tty.Write(output)
}
func (s *Shell) WriteOutputString(output string) (int, error) {
return s.WriteOutput([]byte(output))
}
// Winsize stores the Height and Width of a terminal.
type Winsize struct {
Height uint16
Width uint16
x uint16 // unused
y uint16 // unused
}
// SetWinsize sets the size of the given pty.
func SetWinsize(fd uintptr, winsize *Winsize) {
log.Printf("window resize %dx%d", winsize.Width, winsize.Height)
unix.Syscall(
unix.SYS_IOCTL,
fd,
uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(winsize)),
)
}

@ -0,0 +1,60 @@
package shell
type Block struct {
BaseToken
}
type DollarBlock struct {
BaseToken
}
type BlockParameters struct{}
type DollarBlockParameters struct {
BlockParameters
}
func (p *BlockParameters) Enter(i *CharIterator) error {
return i.Next()
}
func (p *DollarBlockParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &WordParameters{},
},
}
}
func (p *DollarBlockParameters) MakeToken() Token {
return &DollarBlock{}
}
func (p *BlockParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &StatementParameters{},
},
}
}
func (p *BlockParameters) Supports(charsBefore []rune, r rune) bool {
if r == '{' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *BlockParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '}' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *BlockParameters) MakeToken() Token {
return &Block{}
}

@ -0,0 +1,22 @@
package builtins
import (
"io"
"strings"
)
type Echo struct{}
func (e *Echo) Name() string {
return "echo"
}
func (e *Echo) Execute(arguments []string, _ io.Reader, stdout io.Writer, stderr io.Writer) uint8 {
_, err := stdout.Write([]byte(strings.Join(arguments, " ")))
if err != nil {
stderr.Write([]byte(err.Error()))
return 1
}
return 0
}

@ -0,0 +1,8 @@
package shell
import "io"
type CommandExecutor interface {
Name() string
Execute(arguments []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (exitcode uint8)
}

@ -0,0 +1,132 @@
package shell
import (
"io"
"log"
)
type DollarParameters struct {
ignoreParams *IgnoreFirstCharParameters
}
func (p *DollarParameters) SubParsers() []Parser {
if p.ignoreParams == nil {
p.ignoreParams = &IgnoreFirstCharParameters{}
}
return []Parser{
&BaseParser{
parameters: p.ignoreParams,
},
&BaseParser{
parameters: &WordParameters{},
},
&BaseParser{
parameters: &SubshellParameters{},
},
&BaseParser{
parameters: &DollarBlockParameters{},
},
}
}
func (p *DollarParameters) Enter(_ *CharIterator) error {
if p.ignoreParams != nil {
p.ignoreParams.Reset()
}
return nil
}
func (p *DollarParameters) Supports(charsBefore []rune, r rune) bool {
if r == '$' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *DollarParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if isWhitespace(r) || r == ';' {
return true
}
return false
}
func (p *DollarParameters) Leave(i *CharIterator) error {
return i.Previous()
}
func (p *DollarParameters) MakeToken() Token {
return &Dollar{}
}
type Dollar struct {
BaseToken
}
func (d *Dollar) Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8) {
if len(d.tokens) > 1 {
logstr := "Unexpected tokens after dollar sign: " + d.tokens[1].String()
log.Println(logstr)
stderr.Write([]byte(logstr))
return []string{""}, 2
}
if word, ok := d.tokens[0].(*Word); ok {
return []string{state.Variable(word.String())}, 0
} else if block, ok := d.tokens[0].(*DollarBlock); ok && len(block.Tokens()) > 0 {
if word, ok := block.Tokens()[0].(*Word); ok {
return []string{state.Variable(word.String())}, 0
} else {
log.Println("Whaat")
}
} else {
logstr := "Unexpected tokens after dollar sign: " + d.String()
log.Println(logstr)
stderr.Write([]byte(logstr))
return []string{""}, 2
}
return []string{""}, 1
}
type IgnoreFirstCharParameters struct {
entered bool
}
func (p *IgnoreFirstCharParameters) Reset() {
p.entered = false
}
func (p *IgnoreFirstCharParameters) Enter(_ *CharIterator) error {
p.entered = true
return nil
}
func (p *IgnoreFirstCharParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &CharParserParameters{},
},
}
}
func (p *IgnoreFirstCharParameters) MakeToken() Token {
return nil
}
func (p *IgnoreFirstCharParameters) Supports(_ []rune, r rune) bool {
if !p.entered {
return true
}
return false
}
func (p *IgnoreFirstCharParameters) ShouldLeave(_ []rune, r rune) bool {
return true
}

@ -0,0 +1,54 @@
package shell
import (
"bufio"
"io"
"strings"
"testing"
"github.com/stretchr/testify/suite"
)
type DollarTest struct {
suite.Suite
}
func (t *DollarTest) TestParse() {
parser := &BaseParser{&DollarParameters{}}
reader := bufio.NewReader(strings.NewReader("$variable"))
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.ErrorIs(err, io.EOF)
t.IsType(&Dollar{}, token)
t.IsType(&Word{}, token.Tokens()[0])
t.Equal(token.Tokens()[0].String(), "variable")
}
func (t *DollarTest) TestExpand() {
parser := &BaseParser{&DollarParameters{}}
reader := bufio.NewReader(strings.NewReader("$variable $hey"))
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.IsType(&Dollar{}, token)
t.IsType(&Word{}, token.Tokens()[0])
state := NewShellState()
state.SetVariable("variable", "Test")
expanded, exit := token.(Expandable).Expand(state, nil, nil)
t.Equal(uint8(0), exit)
t.Equal(expanded[0], "Test")
}
func TestDollarTest(t *testing.T) {
suite.Run(t, new(DollarTest))
}

@ -0,0 +1,55 @@
package shell
import (
"fmt"
"io"
)
type LineParameters struct{}
func (p *LineParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &StatementParameters{},
},
}
}
func (p *LineParameters) Supports(charsBefore []rune, r rune) bool {
return true
}
func (p *LineParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '\n' {
return countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *LineParameters) MakeToken() Token {
return &Line{}
}
type Line struct {
BaseToken
}
func (l *Line) Evaluate(state State, stdin io.Reader, stdout io.Writer, stderr io.Writer) uint8 {
var retval uint8 = 0
for _, token := range l.Tokens() {
if eval, ok := token.(Evalable); ok {
retval = eval.Evaluate(state, stdin, stdout, stderr)
} else {
stderr.Write([]byte(
fmt.Sprintf(
"shell: Syntax error near unexpected token: %s",
token.String(),
),
))
}
}
return retval
}

@ -0,0 +1,143 @@
package shell
import (
"bufio"
"errors"
"fmt"
"reflect"
)
type Parser interface {
Parse(*CharIterator, *CharCollection) (Token, error)
Parameters() ParserParameters
}
type BaseParser struct {
parameters ParserParameters
}
func (p *BaseParser) Parameters() ParserParameters {
return p.parameters
}
type CharIterator struct {
reader *bufio.Reader
currentChar rune
lastChar rune
}
func NewCharIterator(reader *bufio.Reader) (*CharIterator, error) {
char, _, err := reader.ReadRune()
if err != nil {
return nil, err
}
return &CharIterator{
reader: reader,
currentChar: char,
}, nil
}
func (i *CharIterator) Next() error {
var err error
i.lastChar = i.currentChar
i.currentChar, _, err = i.reader.ReadRune()
return err
}
func (i *CharIterator) Previous() error {
if i.lastChar == -1 {
return errors.New("Chariterator can only go back once")
}
err := i.reader.UnreadRune()
if err != nil {
return err
}
i.currentChar = i.lastChar
i.lastChar = -1
return err
}
type CharCollection struct {
chars []rune
}
func (c *CharCollection) Append(r rune) {
c.chars = append(c.chars, r)
}
func (c *CharCollection) Chars() []rune {
return c.chars
}
func (i CharIterator) Current() rune {
return i.currentChar
}
func (p *BaseParser) Parse(i *CharIterator, charsBefore *CharCollection) (Token, error) {
token := p.Parameters().MakeToken()
if enter, ok := p.parameters.(Enterable); ok {
err := enter.Enter(i)
if err != nil {
return nil, err
}
}
parsers := p.Parameters().SubParsers()
ParseLoop:
for {
char := i.Current()
if p.Parameters().ShouldLeave(charsBefore.Chars(), char) {
if leave, ok := p.Parameters().(Leavable); ok {
err := leave.Leave(i)
if err != nil {
return token, err
}
}
return token, nil
}
var matchedParser bool
for _, parser := range parsers {
if parser.Parameters().Supports(charsBefore.Chars(), char) {
matchedParser = true
nestedToken, err := parser.Parse(i, charsBefore)
if token != nil {
token.AddToken(nestedToken)
}
if err != nil {
return token, err
}
charsBefore.Append(char)
err = i.Next()
if err != nil {
return token, err
}
continue ParseLoop
}
}
if !matchedParser {
return token, fmt.Errorf(
"Parser encountered unsupported token or char. Parser: %s full text: %s -\n Char: %s",
reflect.TypeOf(*&p.parameters).String(),
string(charsBefore.Chars()),
string(char),
)
}
}
}

@ -0,0 +1,20 @@
package shell
type ParserParameters interface {
MakeToken() Token
ShouldLeave(charsBefore []rune, r rune) bool
Supports(charsBefore []rune, r rune) bool
SubParsers() []Parser
}
type Enterable interface {
Enter(*CharIterator) error
}
type Resetable interface {
Reset()
}
type Leavable interface {
Leave(*CharIterator) error
}

@ -0,0 +1,44 @@
package shell
import (
"bufio"
"strings"
"testing"
"github.com/stretchr/testify/suite"
)
type CharIteratorTest struct {
suite.Suite
}
func (t *CharIteratorTest) TestNextAndBacktrack() {
charIterator, err := NewCharIterator(bufio.NewReader(strings.NewReader("abcdefg")))
t.NoError(err)
t.Equal(string('a'), string(charIterator.Current()))
err = charIterator.Next()
t.NoError(err)
t.Equal(string('b'), string(charIterator.Current()))
err = charIterator.Previous()
t.NoError(err)
t.Equal(string('a'), string(charIterator.Current()))
err = charIterator.Previous()
t.Error(err)
t.Equal("Chariterator can only go back once", err.Error())
err = charIterator.Next()
t.NoError(err)
t.Equal(string('b'), string(charIterator.Current()))
}
func TestCharIteratorTest(t *testing.T) {
suite.Run(t, new(CharIteratorTest))
}

@ -0,0 +1,30 @@
package shell
type Pipe struct {
BaseToken
}
type PipeParameters struct {
StatementParameters
}
func (p *PipeParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &StatementParameters{},
},
}
}
func (p *PipeParameters) Supports(charsBefore []rune, r rune) bool {
if r == '|' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *PipeParameters) MakeToken() Token {
return &Pipe{}
}

@ -0,0 +1,92 @@
package shell
import (
"io"
"log"
"strings"
)
type QuoteParameters struct{}
func (p *QuoteParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &DollarParameters{},
},
&BaseParser{
parameters: &CharParserParameters{},
},
}
}
func (p *QuoteParameters) Supports(charsBefore []rune, r rune) bool {
if r == '"' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *QuoteParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '"' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *QuoteParameters) MakeToken() Token {
return &Quote{}
}
type Quote struct {
BaseToken
}
func (q *Quote) Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8) {
var retcode uint8
var str string
for _, token := range q.Tokens() {
if expand, ok := token.(Expandable); ok {
log.Println(expand)
var expansion []string
expansion, retcode = expand.Expand(state, stdin, stderr)
str += strings.Join(expansion, " ")
continue
}
str += token.String()
}
return []string{strings.Trim(str, "'")}, retcode
}
type SingleQuoteParameters struct {
QuoteParameters
}
type SingleQuote struct {
BaseToken
}
func (p *SingleQuoteParameters) Supports(charsBefore []rune, r rune) bool {
if r == '\'' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *SingleQuoteParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '\'' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}

@ -0,0 +1,61 @@
package shell
import (
"bufio"
"fmt"
"io"
"strings"
"testing"
"git.snorba.art/hugo/nssh/commands/shell/builtins"
"github.com/stretchr/testify/suite"
)
type QuotesTest struct {
suite.Suite
}
func (t *QuotesTest) TestParse() {
expectedContents := fmt.Sprintf("'%s'", "word1 word2 word3\n word4")
parser := &BaseParser{&QuoteParameters{}}
reader := bufio.NewReader(strings.NewReader(expectedContents))
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.ErrorIs(err, io.EOF)
t.Equal(expectedContents, token.String())
expanded, _ := token.(Expandable).Expand(nil, nil, nil)
t.Equal("word1 word2 word3\n word4", strings.Join(expanded, " "))
}
func (t *QuotesTest) TestExpandVariables() {
parser := &BaseParser{&QuoteParameters{}}
reader := bufio.NewReader(
strings.NewReader("This is a $variable ${variable2} $variable"),
)
state := NewShellState()
state.SetVariable("variable", "supergood")
state.SetVariable("variable2", "thing")
echo := &builtins.Echo{}
state.SetCommand(echo.Name(), echo)
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.ErrorIs(err, io.EOF)
expanded, _ := token.(Expandable).Expand(state, nil, nil)
t.Equal("This is a supergood thing supergood", expanded[0])
}
func TestQuotesTest(t *testing.T) {
suite.Run(t, new(QuotesTest))
}

@ -1,4 +1,4 @@
package main
package shell
import (
"bufio"

@ -0,0 +1,112 @@
package shell
type State interface {
Variable(string) string
Command(string) CommandExecutor
Env(string) string
Pushd(string) error
Popd() error
Cwd() string
SetVariable(string, string)
SetCommand(string, CommandExecutor)
SetEnv(string, string)
Clone() State
}
func NewShellState() *ShellState {
return &ShellState{
variables: map[string]string{},
commands: map[string]CommandExecutor{},
env: map[string]string{},
dirstack: []string{},
}
}
type ShellState struct {
variables map[string]string
commands map[string]CommandExecutor
env map[string]string
dirstack []string
}
func (s *ShellState) Variable(n string) string {
if v, ok := s.variables[n]; ok {
return v
}
return ""
}
func (s *ShellState) Command(n string) CommandExecutor {
if v, ok := s.commands[n]; ok {
return v
}
return nil
}
func (s *ShellState) Env(n string) string {
if v, ok := s.env[n]; ok {
return v
}
return ""
}
func (s *ShellState) SetVariable(n string, v string) {
s.variables[n] = v
}
func (s *ShellState) SetCommand(n string, e CommandExecutor) {
s.commands[n] = e
}
func (s *ShellState) SetEnv(n string, v string) {
s.env[n] = v
}
func (s *ShellState) Pushd(dir string) error {
s.dirstack = append([]string{dir}, s.dirstack...)
return nil
}
func (s *ShellState) Popd() error {
if len(s.dirstack) == 0 {
return nil
}
s.dirstack = s.dirstack[1:]
return nil
}
func (s *ShellState) Cwd() string {
if len(s.dirstack) == 0 {
return ""
}
return s.dirstack[0]
}
func (s *ShellState) Clone() State {
state := NewShellState()
for k, v := range s.commands {
state.SetCommand(k, v)
}
for k, v := range s.variables {
state.SetVariable(k, v)
}
for k, v := range s.env {
state.SetEnv(k, v)
}
state.dirstack = make([]string, len(s.dirstack))
copy(s.dirstack, state.dirstack)
return state
}

@ -0,0 +1,49 @@
package shell
type Statement struct {
BaseToken
}
type StatementParameters struct{}
func (p *StatementParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &QuoteParameters{},
},
&BaseParser{
parameters: &SingleQuoteParameters{},
},
&BaseParser{
parameters: &WordParameters{},
},
&BaseParser{
parameters: &DollarParameters{},
},
&BaseParser{
parameters: &PipeParameters{},
},
&BaseParser{
parameters: &SubshellParameters{},
},
&BaseParser{
parameters: &BlockParameters{},
},
}
}
func (p *StatementParameters) Supports(charsBefore []rune, r rune) bool {
return true
}
func (p *StatementParameters) MakeToken() Token {
return &Statement{}
}
func (p *StatementParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '\n' || r == ';' {
return countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}

@ -0,0 +1,63 @@
package shell
type SubshellParameters struct{}
func (p *SubshellParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &StatementParameters{},
},
}
}
func (p *SubshellParameters) Supports(charsBefore []rune, r rune) bool {
if r == '(' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *SubshellParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == ')' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *SubshellParameters) MakeToken() Token {
return &Subshell{}
}
type Subshell struct {
BaseToken
}
type Backtick struct {
Subshell
}
type BacktickParameters struct {
SubshellParameters
}
func (p *BacktickParameters) Supports(charsBefore []rune, r rune) bool {
if r == '`' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *BacktickParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if r == '`' {
return len(charsBefore) == 0 ||
countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}

@ -0,0 +1,41 @@
package shell
import "io"
type Token interface {
Tokens() []Token
AddToken(Token)
String() string
}
type Expandable interface {
Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8)
}
type Evalable interface {
Evaluate(state State, stdin io.Reader, stdout io.Writer, stderr io.Writer) uint8
}
type BaseToken struct {
tokens []Token
}
func (t *BaseToken) Tokens() []Token {
return t.tokens
}
func (t *BaseToken) AddToken(token Token) {
if token != nil {
t.tokens = append(t.tokens, token)
}
}
func (t *BaseToken) String() string {
var str string
for _, token := range t.Tokens() {
str += token.String()
}
return str
}

@ -0,0 +1,19 @@
package shell
func countBackslashSuffixes(chars []rune) int {
backslashCount := 0
for i := len(chars) - 1; i > 0; i-- {
if chars[i] == '\\' {
backslashCount += 1
continue
}
break
}
return backslashCount
}
func isWhitespace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n'
}

@ -0,0 +1,26 @@
package shell
import (
"testing"
"github.com/stretchr/testify/suite"
)
type UtilTest struct {
suite.Suite
}
func (t *UtilTest) TestCountBackslashSuffixes() {
three := []rune{'a', '\\', 'b', '\\', '\\', '\\'}
t.Equal(3, countBackslashSuffixes(three))
six := []rune{'a', '\\', 'b', '\\', '\\', '\\', '\\', '\\', '\\'}
t.Equal(6, countBackslashSuffixes(six))
zero := []rune{'a', '\\', 'b', '\\', '\\', '\\', 'c'}
t.Equal(0, countBackslashSuffixes(zero))
}
func TestUtilTest(t *testing.T) {
suite.Run(t, new(UtilTest))
}

@ -0,0 +1,45 @@
package shell
type WhitespaceParameters struct{}
func (p *WhitespaceParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &WhitespaceParameters{},
},
}
}
func (p *WhitespaceParameters) Supports(charsBefore []rune, r rune) bool {
return isWhitespace(r)
}
func (p *WhitespaceParameters) ShouldLeave(charsBefore []rune, r rune) bool {
return !p.Supports(charsBefore, r)
}
func (p *WhitespaceParameters) MakeToken() Token {
return &Whitespace{}
}
type Whitespace struct {
tokens []Token
}
func (q *Whitespace) AddToken(s Token) {
q.tokens = append(q.tokens, s)
}
func (q *Whitespace) Tokens() []Token {
return q.tokens
}
func (q *Whitespace) String() string {
var str string
for _, t := range q.Tokens() {
str += t.String()
}
return str
}

@ -0,0 +1,128 @@
package shell
import (
"io"
"log"
"regexp"
"strings"
)
func NewWord(wordS string) *Word {
word := new(Word)
for _, c := range wordS {
word.AddToken(Char(c))
}
return word
}
type Word struct {
chars []Char
consecutiveBackslashes int
}
func (w *Word) Tokens() []Token {
return []Token{}
}
func (w *Word) AddToken(token Token) {
if char, ok := token.(Char); ok {
if rune(char) == '\\' {
w.consecutiveBackslashes += 1
} else if w.consecutiveBackslashes%2 == 1 {
if len(w.chars) > 0 {
w.chars = w.chars[:len(w.chars)-1]
} else {
log.Println(
"Parse error: funky shit is happening, a word with 0" +
" chars was expected to contain a backslash",
)
}
w.consecutiveBackslashes = 0
}
w.chars = append(w.chars, char)
}
}
func (w *Word) Expand(_ State, _ io.Reader, _ io.Writer) ([]string, uint8) {
var str string
for _, char := range w.chars {
str += string(char)
}
return []string{str}, 0
}
func (w *Word) String() string {
str, _ := w.Expand(nil, nil, nil)
return strings.Join(str, "")
}
type WordParameters struct{}
func (p *WordParameters) MakeToken() Token {
return new(Word)
}
var wordRegexp = regexp.MustCompile("[0-9a-zA-Z_.~/:\\\\=-]")
func (p *WordParameters) Supports(charsBefore []rune, r rune) bool {
return wordRegexp.MatchString(string(r))
}
func (p *WordParameters) ShouldLeave(charsBefore []rune, r rune) bool {
if !p.Supports(charsBefore, r) {
return countBackslashSuffixes(charsBefore)%2 == 0
}
return false
}
func (p *WordParameters) Leave(i *CharIterator) error {
return i.Previous()
}
func (p *WordParameters) SubParsers() []Parser {
return []Parser{
&BaseParser{
parameters: &CharParserParameters{},
},
}
}
type Char rune
func (c Char) Tokens() []Token {
return nil
}
func (c Char) AddToken(_ Token) {}
func (c Char) String() string {
return string(c)
}
type CharParserParameters struct {
currentRune rune
}
func (p *CharParserParameters) MakeToken() Token {
return Char(p.currentRune)
}
func (p *CharParserParameters) Supports(charsBefore []rune, r rune) bool {
p.currentRune = r
return true
}
func (p *CharParserParameters) ShouldLeave(charsBefore []rune, r rune) bool {
return true
}
func (p *CharParserParameters) SubParsers() []Parser {
return nil
}

@ -0,0 +1,86 @@
package shell
import (
"bufio"
"io"
"strings"
"testing"
"github.com/stretchr/testify/suite"
)
type WordTest struct {
suite.Suite
}
func (t *WordTest) TestSupports() {
p := &WordParameters{}
noChars := []rune{}
t.True(p.Supports(noChars, 'a'))
t.True(p.Supports(noChars, '-'))
t.True(p.Supports(noChars, 'A'))
t.True(p.Supports(noChars, '0'))
t.True(p.Supports(noChars, '.'))
}
func (t *WordTest) TestShouldLeave() {
p := &WordParameters{}
noChars := []rune{}
t.True(p.ShouldLeave(noChars, ' '))
t.True(p.ShouldLeave(noChars, '\t'))
t.True(p.ShouldLeave(noChars, '\n'))
}
func (t *WordTest) TestAddToken() {
word := NewWord("")
word.AddToken(Char('a'))
t.Equal("a", word.String())
}
func (t *WordTest) TestParse() {
parser := &BaseParser{&WordParameters{}}
reader := bufio.NewReader(strings.NewReader("word1 word2 word\\ 3"))
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.NoError(err)
t.Equal("word1", token.String())
iterator.Next()
token, err = parser.Parse(iterator, &CharCollection{})
t.NoError(err)
t.Equal("", token.String())
iterator.Next()
iterator.Next()
token, err = parser.Parse(iterator, &CharCollection{})
t.NoError(err)
t.Equal("word2", token.String())
iterator.Next()
iterator.Next()
token, err = parser.Parse(iterator, &CharCollection{})
t.ErrorIs(err, io.EOF)
t.Equal("word 3", token.String())
}
func (t *WordTest) TestParseBackslash() {
parser := &BaseParser{&WordParameters{}}
reader := bufio.NewReader(strings.NewReader("w\\\\\\\\\\o=rd1\\ \\$wo\\\\rd2\\\\\\ word\\ 3"))
iterator, err := NewCharIterator(reader)
t.NoError(err)
token, err := parser.Parse(iterator, &CharCollection{})
t.ErrorIs(err, io.EOF)
t.Equal("w\\\\\\\\o=rd1 $wo\\\\rd2\\\\ word 3", token.String())
}
func TestWordTest(t *testing.T) {
suite.Run(t, new(WordTest))
}

@ -0,0 +1,150 @@
package commands
import (
"errors"
"log"
"strconv"
"golang.org/x/sys/unix"
)
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
}

@ -0,0 +1,71 @@
package commands
import (
"log"
"git.snorba.art/hugo/nssh/storage"
)
type TestCommand struct {
filestore storage.Filestore
}
var fakeDirs = map[string]bool{
"/bin": true,
"/usr/bin": true,
"/sbin": true,
}
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])
var exists bool
var err error
if answer, ok := fakeDirs[args[1]]; ok {
exists = answer
} else {
exists, err = c.filestore.DirectoryExists(args[1])
if err != nil {
return err
}
}
if exists {
log.Printf("Directory %s exists", args[1])
_, err := shell.WriteOutputString(trampSuccess)
return err
}
_, err = shell.WriteOutputString(trampFailure)
return err
}
if len(args) > 1 && (args[0] == "-e" || args[0] == "-r") {
log.Printf("Checking for existance of file %s", args[1])
// Tramp sometimes throws in a sentence "this file does not exist", my
// guess is it does this to check if the test command is working
// properly. This catches that and makes the test command fail
// appropriately.
if len(args) > 5 && args[2] == "this" && args[5] == "not" {
_, err := shell.WriteOutputString(trampFailure)
return err
}
if exists, _ := c.filestore.Exists(args[1]); exists {
log.Printf("File %s exists", args[1])
_, err := shell.WriteOutputString(trampSuccess)
return err
}
_, err := shell.WriteOutputString(trampFailure)
return err
}
return nil
}

@ -3,14 +3,21 @@ module git.snorba.art/hugo/nssh
go 1.18
require (
github.com/SkynetLabs/go-skynet/v2 v2.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/ipfs/go-ipfs-api v0.3.0
github.com/pkg/term v1.1.0
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
)
require (
github.com/btcsuite/btcd v0.22.1 // indirect
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
github.com/creack/pty v1.1.18 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/ipfs/go-cid v0.2.0 // indirect
github.com/ipfs/go-ipfs-api v0.3.0 // indirect
github.com/ipfs/go-ipfs-files v0.1.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 // indirect
@ -28,15 +35,10 @@ require (
github.com/multiformats/go-multicodec v0.4.1 // indirect
github.com/multiformats/go-multihash v0.1.0 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect
github.com/pkg/term v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b // indirect
gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)

@ -1,11 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/SkynetLabs/go-skynet/v2 v2.1.0 h1:fYUoe2Lu8epLBkd+B2jaKbS82KQ2Vv+eqLG0R8zMwZM=
github.com/SkynetLabs/go-skynet/v2 v2.1.0/go.mod h1:XOk0zwGlXeGjHQgmhXTEk7qTD6FVv3dXPW38Wh3XsIc=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c=
github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
@ -13,49 +12,28 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg=
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I=
github.com/ipfs/go-cid v0.2.0 h1:01JTiihFq9en9Vz0lc0VDWvZe/uBonGpzo4THP0vcQ0=
@ -120,33 +98,27 @@ github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0Erjhoj
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU=
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs=
github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b h1:wA3QeTsaAXybLL2kb2cKhCAQTHgYTMwuI8lBlJSv5V8=
github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b/go.mod h1:xT1Y5p2JR2PfSZihE0s4mjdJaRGp1waCTf5JzhQLBck=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8=
gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc=
gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -170,7 +142,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -190,17 +161,11 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -209,7 +174,6 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -221,32 +185,17 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

@ -1,324 +0,0 @@
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
}

@ -1,289 +0,0 @@
2022/05/05 11:04:59 Private Key generated
2022/05/05 11:05:06 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
2022/05/05 11:05:06 received request of type "pty-req"
2022/05/05 11:05:06 window resize 106x42
2022/05/05 11:05:06 pty-req 'xterm-256color'
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "env"
2022/05/05 11:05:06 received request of type "shell"
2022/05/05 11:05:09 command: "ls" []
2022/05/05 11:05:09 Error: Received unexpected command ls
2022/05/05 11:05:26 command: "test" [-d /var/www]
2022/05/05 11:05:26 Checking for existance of directory /var/www
2022/05/05 11:06:36 command: "locale" []
2022/05/05 11:06:36 Ignoring "locale" command with unsupported parameters: []
2022/05/05 11:06:39 command: "locale" [-a]
2022/05/05 11:06:39 Tramp requested locale, returning fake values: [C C.UTF-8 POSIX en_US.utf8]
2022/05/05 11:07:01 command: "exec" []
2022/05/05 11:07:08 command: "PS1=hallo" []
2022/05/05 11:07:08 Changing prompt to "hallo"
2022/05/05 11:07:21 command: "exec" [PS1=heey]
2022/05/05 11:07:21 Changing prompt to "heey"
2022/05/05 11:07:35 command: "exec" [HAAi=DOEI PS1=heeaaaa]
2022/05/05 11:07:35 Changing prompt to "heeaaaa"
2022/05/05 11:08:57 command: "echo" [heeeeey]
2022/05/05 11:09:06 Received EOF, closing TTY
2022/05/05 11:09:06 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
2022/05/05 11:09:06 received request of type "pty-req"
2022/05/05 11:09:06 window resize 106x42
2022/05/05 11:09:06 pty-req 'xterm-256color'
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "env"
2022/05/05 11:09:06 received request of type "shell"
2022/05/05 11:09:11 command: "echo" [heey]
2022/05/05 11:09:16 command: "echo" [
faiwiefjawiejfwae
ajeifwajef
]
2022/05/05 11:09:38 Heredoc current word: "HAAAAAA"
2022/05/05 11:09:38 Encountered heredoc marker: HAAAAAA
2022/05/05 11:09:40 Heredoc current word: "fijwoiajfawoie"
2022/05/05 11:09:40 Heredoc current word: "afjwaoeifj"
2022/05/05 11:09:41 Heredoc current word: "awfjoiwjfe"
2022/05/05 11:09:41 Heredoc current word: ""
2022/05/05 11:09:42 Heredoc current word: "jawif;eijaw"
2022/05/05 11:09:42 Heredoc current word: ""
2022/05/05 11:09:49 Heredoc current word: "HAAAAAAA"
2022/05/05 11:09:59 Heredoc current word: "HAAAAAA"
2022/05/05 11:09:59 exiting heredoc
2022/05/05 11:09:59 command: "while" [read -r heey; do hahahaha; done <<HAAAAAA fijwoiajfawoie afjwaoeifj awfjoiwjfe jawif;eijaw HAAAAAAA HAAAAAA]
2022/05/05 11:09:59 Error: Received unexpected command while
2022/05/05 11:10:24 Received EOF, closing TTY
2022/05/05 11:11:22 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
2022/05/05 11:11:22 received request of type "pty-req"
2022/05/05 11:11:22 window resize 106x42
2022/05/05 11:11:22 pty-req 'xterm-256color'
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "env"
2022/05/05 11:11:22 received request of type "shell"
2022/05/05 11:11:25 command: "stty" [-echo]
2022/05/05 11:11:29 command: "fjieawif;waiejf;iajwf;awijef;aiwjf;awijef;oaiwejf;awiej;ijw" []
2022/05/05 11:11:29 Error: Received unexpected command fjieawif;waiejf;iajwf;awijef;aiwjf;awijef;oaiwejf;awiej;ijw
2022/05/05 11:11:34 Received EOF, closing TTY
2022/05/05 12:18:04 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
2022/05/05 12:18:04 received request of type "pty-req"
2022/05/05 12:18:04 window resize 0x0
2022/05/05 12:18:04 pty-req 'dumb'
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "env"
2022/05/05 12:18:04 received request of type "shell"
2022/05/05 12:18:04 command: "exec" [env TERM=dumb INSIDE_EMACS=27.2,tramp:2.4.5.27.2 ENV= HISTFILE=~/.tramp_history PROMPT_COMMAND= PS1=#$ PS2= PS3= /bin/sh]
2022/05/05 12:18:04 Changing prompt to "#$ "
2022/05/05 12:18:04 command: "(cd" [~/) 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 command: "set" [+o vi +o emacs]
2022/05/05 12:18:04 Ignoring "set" command
2022/05/05 12:18:04 command: "stty" [-inlcr -onlcr -echo kill ^U erase ^H]
2022/05/05 12:18:04 command: "echo" [foo]
2022/05/05 12:18:04 command: "PS1=///eac57010bddba5dae79f08f3cd43067c#$" [PS2= PS3= PROMPT_COMMAND=]
2022/05/05 12:18:04 Changing prompt to "///eac57010bddba5dae79f08f3cd43067c#$"
2022/05/05 12:18:04 command: "echo" ["`uname -sr`" 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 Tramp requested uname, returning a fake one: Linux 5.16.19-76051619-generic
2022/05/05 12:18:04 command: "(echo" [foo ; echo bar)]
2022/05/05 12:18:04 Handling tramp's foobar test
2022/05/05 12:18:04 command: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/local/bin:/local/freeware/bin:/local/gnu/bin:/usr/freeware/bin:/usr/pkg/bin:/usr/contrib/bin:/opt/bin:/opt/sbin:/opt/local/bin" [&& export PATH]
2022/05/05 12:18:04 command: "mesg" [n 2>/dev/null; biff n 2>/dev/null]
2022/05/05 12:18:04 Error: Received unexpected command mesg
2022/05/05 12:18:04 command: "stty" [tab0]
2022/05/05 12:18:04 command: "stty" [iutf8 2>/dev/null]
2022/05/05 12:18:04 Warning, tramp requested unexpected stty option 2>/dev/null
2022/05/05 12:18:04 command: "echo" ["`tty`" 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 Telling tramp that it's dealing with a tty
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "LC_ALL en_US.utf8"
2022/05/05 12:18:04 Heredoc current word: "ENV ''"
2022/05/05 12:18:04 Heredoc current word: "TMOUT 0"
2022/05/05 12:18:04 Heredoc current word: "LC_CTYPE ''"
2022/05/05 12:18:04 Heredoc current word: "PAGER cat"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read var val; do export $var=$val; done <<cf8110e8e028188d140944ad469ceb8f LC_ALL en_US.utf8 ENV TMOUT 0 LC_CTYPE PAGER cat cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 command: "unset" [CDPATH HISTORY MAIL MAILCHECK MAILPATH autocorrect correct]
2022/05/05 12:18:04 Error: Received unexpected command unset
2022/05/05 12:18:04 command: "test" [-e / 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 command: "/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 Error: Received unexpected command /bin/test
2022/05/05 12:18:04 command: "/usr/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 Error: Received unexpected command /usr/bin/test
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
2022/05/05 12:18:04 Heredoc current word: "/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/ls && test -f $d/ls; then echo tramp_executable $d/ls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
2022/05/05 12:18:04 Heredoc current word: "/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gnuls && test -f $d/gnuls; then echo tramp_executable $d/gnuls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
2022/05/05 12:18:04 Heredoc current word: "/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gls && test -f $d/gls; then echo tramp_executable $d/gls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 command: "test" [-e / 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 command: "/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 Error: Received unexpected command /bin/test
2022/05/05 12:18:04 command: "/usr/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
2022/05/05 12:18:04 Error: Received unexpected command /usr/bin/test
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
2022/05/05 12:18:04 Heredoc current word: "/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/ls && test -f $d/ls; then echo tramp_executable $d/ls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
2022/05/05 12:18:04 Heredoc current word: "/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gnuls && test -f $d/gnuls; then echo tramp_executable $d/gnuls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
2022/05/05 12:18:04 Heredoc current word: "/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
2022/05/05 12:18:04 Heredoc current word: "/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
2022/05/05 12:18:04 exiting heredoc
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gls && test -f $d/gls; then echo tramp_executable $d/gls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
2022/05/05 12:18:04 Error: Received unexpected command while
2022/05/05 12:18:04 command: "test" [-r /hallo 2>/dev/null; echo tramp_exit_status $?]

@ -4,6 +4,7 @@ import (
"io/ioutil"
"log"
"git.snorba.art/hugo/nssh/storage"
ipfsapi "github.com/ipfs/go-ipfs-api"
"golang.org/x/crypto/ssh"
)
@ -30,8 +31,7 @@ func main() {
authorizedKeysBytes = rest
}
// shell :=
filestore, err := NewIPFSFilestore(ipfsapi.NewShell("127.0.0.1:5001"), WebDir)
filestore, err := storage.NewIPFSFilestore(ipfsapi.NewShell("127.0.0.1:5001"), WebDir)
if err != nil {
log.Fatal(err)
}

@ -12,6 +12,8 @@ import (
"log"
"net"
"git.snorba.art/hugo/nssh/commands"
"git.snorba.art/hugo/nssh/storage"
"golang.org/x/crypto/ssh"
)
@ -58,7 +60,7 @@ func parseDims(b []byte) (uint32, uint32) {
type Sshd struct {
AuthorizedKeysMap map[string]bool
Filestore Filestore
Filestore storage.Filestore
}
func (s *Sshd) Listen(iface string) error {
@ -140,7 +142,7 @@ func (s *Sshd) HandleRequests(listener net.Listener, config *ssh.ServerConfig) e
}
session := &SshSession{
shell: NewShell("user@host:/# "),
shell: commands.NewShell("user@host:/# "),
filestore: s.Filestore,
}
@ -155,8 +157,8 @@ func (s *Sshd) HandleRequests(listener net.Listener, config *ssh.ServerConfig) e
}
type SshSession struct {
shell *Shell
filestore Filestore
shell *commands.Shell
filestore storage.Filestore
}
func (s *SshSession) HandleRequests(in <-chan *ssh.Request) {
@ -185,7 +187,7 @@ func (s *SshSession) HandleRequests(in <-chan *ssh.Request) {
func (s *SshSession) RunShell(channel io.ReadWriteCloser) {
s.shell.Attach(channel)
handler := NewCommandHandler(s.shell, s.filestore)
handler := commands.NewCommandHandler(s.shell, s.filestore)
go func() {
for {
@ -197,14 +199,17 @@ func (s *SshSession) RunShell(channel io.ReadWriteCloser) {
log.Println("Error reading command: ", err)
}
if !errors.Is(err, ErrEmptyCommand) {
if !errors.Is(err, commands.ErrEmptyCommand) {
s.shell.Close()
return
}
} else {
log.Printf("command: \"%s\" %s", comm.Name, comm.Arguments)
handler.Handle(comm)
err := handler.Handle(comm)
if err != nil {
log.Println("Error handling command: " + err.Error())
}
}
err = s.shell.WritePrompt()

@ -0,0 +1,18 @@
package storage
import "io"
type File struct {
Name string `json:"name"`
AbsolutePath string `json:"absolute_path"`
Contents io.Reader `json:"-"`
}
type Filestore interface {
Get(name string) (*File, error)
Set(file *File) error
Delete(file *File) error
Exists(name string) (bool, error)
MakeDirectory(name string) error
DirectoryExists(name string) (bool, error)
}

@ -1,27 +1,16 @@
package main
package storage
import (
"errors"
"io"
"log"
"strings"
"time"
ipfsapi "github.com/ipfs/go-ipfs-api"
"golang.org/x/net/context"
)
type File struct {
Name string `json:"name"`
AbsolutePath string `json:"absolute_path"`
Contents io.Reader `json:"-"`
}
type Filestore interface {
Get(name string) (*File, error)
Set(file *File) error
Delete(file *File) error
Exists(name string) (bool, error)
}
func NewIPFSFilestore(ipfs *ipfsapi.Shell, rootDir string) (*IPFSFilestore, error) {
store := &IPFSFilestore{
ipfs: ipfs,
@ -75,7 +64,7 @@ func newContext() context.Context {
func (s *IPFSFilestore) Exists(name string) (bool, error) {
ctx := newContext()
_, err := s.ipfs.FilesStat(ctx, s.rootDir+"/"+name)
_, err := s.ipfs.FilesStat(ctx, s.filepath(name))
if err != nil {
if err.Error() == "files/stat: file does not exist" {
return false, nil
@ -87,6 +76,34 @@ func (s *IPFSFilestore) Exists(name string) (bool, error) {
return true, nil
}
func (s *IPFSFilestore) DirectoryExists(name string) (bool, error) {
ctx := newContext()
st, err := s.ipfs.FilesStat(ctx, s.filepath(name))
if err != nil {
if err.Error() == "files/stat: file does not exist" {
return false, nil
}
return false, &ErrFailedFileExistsCheck{parent: err}
}
if st.Type != "directory" {
return false, nil
}
return true, nil
}
func (s *IPFSFilestore) filepath(name string) string {
return s.rootDir + "/" + strings.TrimPrefix(name, "/")
}
func (s *IPFSFilestore) MakeDirectory(name string) error {
filepath := s.filepath(name)
log.Printf("Making directory \"%s\"", filepath)
return s.ipfs.FilesMkdir(newContext(), filepath)
}
type FileReader struct {
ipfs *ipfsapi.Shell
file *File
@ -125,7 +142,7 @@ func (s *IPFSFilestore) Get(name string) (*File, error) {
file := &File{
Name: name,
AbsolutePath: s.rootDir + "/" + name,
AbsolutePath: s.filepath(name),
}
file.Contents = &FileReader{
@ -137,7 +154,7 @@ func (s *IPFSFilestore) Get(name string) (*File, error) {
}
func (s *IPFSFilestore) Set(file *File) error {
return s.ipfs.FilesWrite(newContext(), s.rootDir+"/"+file.Name, file.Contents)
return s.ipfs.FilesWrite(newContext(), s.filepath(file.Name), file.Contents)
}
func (s *IPFSFilestore) Delete(file *File) error {
Loading…
Cancel
Save