Skip to content

Commit c73c161

Browse files
committed
🎉 Import source
0 parents  commit c73c161

15 files changed

+548
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea/
2+
vendor/
3+
.vscode/
4+
build/

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ADDR ?= :1000
2+
APP_SH ?= /bin/bash
3+
4+
.PHONY: run
5+
run:
6+
@go run ./cmd/telshell -addr=$(ADDR) -shell=$(APP_SH)

cmd/telshell/main.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"github.com/x1unix/telshell"
6+
"github.com/x1unix/telshell/internal/app"
7+
"go.uber.org/zap"
8+
)
9+
10+
func init() {
11+
l, err := zap.NewDevelopment()
12+
if err != nil {
13+
panic(err)
14+
}
15+
16+
zap.ReplaceGlobals(l)
17+
}
18+
19+
20+
func main() {
21+
shellFlags := telshell.ShellArgs[:]
22+
addr := flag.String("addr", ":1000", "Address to listen")
23+
shell := flag.String("shell", telshell.DefaultShell, "Shell to use")
24+
flag.Var(&shellFlags, "shellarg", "Shell args")
25+
flag.Parse()
26+
if err := start(*addr, *shell, shellFlags); err != nil {
27+
zap.S().Fatal(err)
28+
}
29+
}
30+
31+
func start(addr, shell string, shellArgs []string) error {
32+
ctx, _ := app.GetApplicationContext()
33+
h := telshell.NewShellHandler(shell, shellArgs...)
34+
srv := telshell.NewServer(h)
35+
36+
/*go func() {
37+
zap.S().Info("listening to ctx")
38+
for {
39+
select {
40+
case <-ctx.Done():
41+
zap.S().Info("ctx.Done()")
42+
default:
43+
}
44+
}
45+
}()*/
46+
47+
return srv.Start(ctx, addr)
48+
}

go.mod

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module github.com/x1unix/telshell
2+
3+
go 1.13
4+
5+
require (
6+
github.com/pkg/errors v0.8.1
7+
go.uber.org/atomic v1.5.1 // indirect
8+
go.uber.org/multierr v1.4.0 // indirect
9+
go.uber.org/zap v1.13.0
10+
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
11+
golang.org/x/tools v0.0.0-20200102200121-6de373a2766c // indirect
12+
)

go.sum

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2+
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
6+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
7+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
8+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
9+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
10+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
11+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
12+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
13+
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
14+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
15+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
16+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
17+
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
18+
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
19+
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
20+
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
21+
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
22+
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
23+
go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
24+
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
25+
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
26+
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
27+
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
28+
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
29+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
30+
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
31+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
32+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
33+
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
34+
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
35+
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
36+
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
37+
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
38+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
39+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
40+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
41+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
42+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
43+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
44+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
45+
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
46+
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
47+
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
48+
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
49+
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
50+
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
51+
golang.org/x/tools v0.0.0-20200102200121-6de373a2766c h1:PBxLbymhzlh6kZuAXmeh8JK2tAJR0GM5Q/W71G2QJ40=
52+
golang.org/x/tools v0.0.0-20200102200121-6de373a2766c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
53+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
54+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
55+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
56+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
57+
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
58+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
59+
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
60+
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

handler.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package telshell
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/pkg/errors"
7+
"go.uber.org/zap"
8+
"io"
9+
"os"
10+
"os/exec"
11+
)
12+
13+
const bufferSize = 4
14+
15+
type Handler interface {
16+
Handle(ctx context.Context, rw io.ReadWriter) error
17+
}
18+
19+
type ShellHandler struct {
20+
shellPath string
21+
shellArgs []string
22+
log *zap.SugaredLogger
23+
}
24+
25+
func NewShellHandler(shell string, args ...string) ShellHandler {
26+
return ShellHandler{
27+
shellPath: shell,
28+
shellArgs: args,
29+
log: zap.S().Named("shell"),
30+
}
31+
}
32+
33+
func (s ShellHandler) Handle(ctx context.Context, rw io.ReadWriter) error {
34+
fmt.Fprintf(rw, "Current shell is %q\n", s.shellPath)
35+
36+
wrapCtx, cancelFn := context.WithCancel(ctx)
37+
wrapper := NewTerminalWrapper(s.log, rw, bufferSize)
38+
cmd := exec.CommandContext(ctx, s.shellPath, "-i")
39+
cmd.Env = os.Environ()
40+
if err := wrapper.Listen(wrapCtx, cmd); err != nil {
41+
return err
42+
}
43+
44+
if err := cmd.Start(); err != nil {
45+
return errors.Wrap(err, "failed to start shell instance")
46+
}
47+
48+
defer cancelFn()
49+
return cmd.Wait()
50+
}

internal/app/array_flags.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package app
2+
3+
type FlagsArray []string
4+
5+
func (i *FlagsArray) String() string {
6+
return "my string representation"
7+
}
8+
9+
func (i *FlagsArray) Set(value string) error {
10+
*i = append(*i, value)
11+
return nil
12+
}

internal/app/context.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
"sync"
8+
"syscall"
9+
)
10+
11+
var once = &sync.Once{}
12+
13+
var (
14+
ctx context.Context
15+
cancelFunc context.CancelFunc
16+
)
17+
18+
// GetApplicationContext returns application context for graceful shutdown
19+
func GetApplicationContext() (context.Context, context.CancelFunc) {
20+
once.Do(func() {
21+
ctx, cancelFunc = context.WithCancel(context.Background())
22+
23+
go func() {
24+
signals := []os.Signal{syscall.SIGTERM, syscall.SIGINT, os.Interrupt}
25+
sigChan := make(chan os.Signal, 1)
26+
signal.Notify(sigChan, signals...)
27+
defer signal.Reset(signals...)
28+
<-sigChan
29+
cancelFunc()
30+
}()
31+
})
32+
33+
return ctx, cancelFunc
34+
}

internal/helpers/hexdump.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package helpers
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
)
7+
8+
const cols = 8
9+
10+
func BytesToMessage(data []byte) string {
11+
str := strings.Builder{}
12+
for _, b := range data {
13+
str.WriteByte(b)
14+
str.WriteString(" 0x" + strconv.FormatInt(int64(b), 16))
15+
str.WriteRune('\n')
16+
}
17+
18+
return str.String()
19+
}
20+
21+
func BytesToHex(data []byte) string {
22+
str := strings.Builder{}
23+
for _, b := range data {
24+
str.WriteString(" 0x" + strconv.FormatInt(int64(b), 16))
25+
}
26+
27+
return str.String()
28+
}

internal/helpers/net.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package helpers
2+
3+
import (
4+
"strings"
5+
)
6+
7+
// IsErrClosing is workaround for detecting usage of closed TCP connection.
8+
// Required, since poll.ErrClosing is private and wrapped in net.OpError.
9+
//
10+
// See: https://github.com/golang/go/issues/10176
11+
func IsErrClosing(err error) bool {
12+
if err == nil {
13+
return false
14+
}
15+
return strings.Contains(err.Error(), "use of closed network connection")
16+
}

iowrapper.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package telshell
2+
3+
import (
4+
"context"
5+
"github.com/x1unix/telshell/internal/helpers"
6+
"go.uber.org/zap"
7+
"io"
8+
"os/exec"
9+
)
10+
11+
const (
12+
charCr = 0xD
13+
charNul = 0x0
14+
)
15+
16+
type TerminalWrapper struct {
17+
buffSize int
18+
client io.ReadWriter
19+
log *zap.SugaredLogger
20+
}
21+
22+
func NewTerminalWrapper(log *zap.SugaredLogger, client io.ReadWriter, buffSize int) TerminalWrapper {
23+
return TerminalWrapper{client: client, log: log, buffSize: buffSize}
24+
}
25+
26+
func (w TerminalWrapper) Listen(ctx context.Context, cmd *exec.Cmd) error {
27+
return w.listenHost(ctx, cmd)
28+
}
29+
30+
func (w TerminalWrapper) listenHost(ctx context.Context, cmd *exec.Cmd) error {
31+
stdin, err := cmd.StdinPipe()
32+
if err != nil {
33+
return err
34+
}
35+
36+
stdout, err := cmd.StdoutPipe()
37+
if err != nil {
38+
return err
39+
}
40+
41+
stderr, err := cmd.StderrPipe()
42+
if err != nil {
43+
return err
44+
}
45+
46+
go w.readFromHost(ctx, stdout)
47+
go w.readFromHost(ctx, stderr)
48+
go w.writeToHost(ctx, stdin)
49+
50+
return nil
51+
}
52+
53+
func (w TerminalWrapper) readFromHost(ctx context.Context, r io.Reader) {
54+
for {
55+
select {
56+
case <-ctx.Done():
57+
return
58+
default:
59+
}
60+
61+
buff := make([]byte, w.buffSize)
62+
_, _ = r.Read(buff)
63+
w.client.Write(buff)
64+
}
65+
}
66+
67+
func (w TerminalWrapper) writeToHost(ctx context.Context, dest io.Writer) {
68+
for {
69+
select {
70+
case <-ctx.Done():
71+
return
72+
default:
73+
}
74+
arr := make([]byte, w.buffSize)
75+
_, err := w.client.Read(arr)
76+
if err == io.EOF || helpers.IsErrClosing(err) {
77+
return
78+
}
79+
if err != nil {
80+
w.log.Error(err)
81+
continue
82+
}
83+
84+
//w.log.Infof("got data (%d bytes):\n%s", c, helpers.BytesToMessage(arr))
85+
filtered := w.filterChars(arr)
86+
dest.Write(filtered)
87+
}
88+
}
89+
90+
func (w TerminalWrapper) filterChars(msg []byte) []byte {
91+
filtered := make([]byte, 0, len(msg))
92+
for _, b := range msg {
93+
switch b {
94+
case charCr, charNul:
95+
continue
96+
default:
97+
}
98+
99+
filtered = append(filtered, b)
100+
}
101+
102+
return filtered
103+
}

platform_posix.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// +build !windows,!js,!nacl
2+
3+
package telshell
4+
5+
import "github.com/x1unix/telshell/internal/app"
6+
7+
var (
8+
DefaultShell = "/bin/sh"
9+
ShellArgs = app.FlagsArray{"-i"}
10+
)

0 commit comments

Comments
 (0)