Compare commits

..

No commits in common. 'ws' and 'tls' have entirely different histories.
ws ... tls

@ -14,11 +14,10 @@ import (
"syscall"
"time"
"github.com/containerd/console"
"github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log"
socks "github.com/fangdingjun/socks-go"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/terminal"
)
// Client is ssh client connection
@ -30,7 +29,6 @@ type Client struct {
err error
ctx context.Context
cancel context.CancelFunc
authAgent agent.ExtendedAgent
}
// NewClient create a new ssh Client
@ -38,6 +36,8 @@ type Client struct {
// addr is server address
//
// conf is the client configure
//
//
func NewClient(c net.Conn, config *ssh.ClientConfig, addr string, conf *Conf) (*Client, error) {
//obfsConn := &TimedOutConn{c, conf.Timeout}
sshConn, newch, reqs, err := ssh.NewClientConn(c, addr, config)
@ -54,11 +54,6 @@ func NewClient(c net.Conn, config *ssh.ClientConfig, addr string, conf *Conf) (*
return client, nil
}
// SetAuthAgent set auth agent
func (cc *Client) SetAuthAgent(_agent agent.ExtendedAgent) {
cc.authAgent = _agent
}
// Client return *ssh.Client
func (cc *Client) Client() *ssh.Client {
return cc.client
@ -69,21 +64,21 @@ func (cc *Client) Run() error {
defer cc.Close()
defer cc.cancel()
go cc.registerSignal()
time.Sleep(1 * time.Second)
select {
case <-time.After(1 * time.Second):
}
// wait port forward to finish
if cc.listeners != nil {
log.Debugf("wait all channel to be done")
go cc.registerSignal()
go func() {
cc.err = cc.sshConn.Wait()
log.Debugf("connection hang up")
cc.cancel()
//close(cc.ch)
}()
}
<-cc.ctx.Done()
}
return cc.err
}
@ -128,106 +123,63 @@ func (cc *Client) Close() {
}
// RunCmd run a single command on server
func (cc *Client) RunCmd(cmd string) error {
go cc.runCmd(cmd)
return nil
}
func (cc *Client) runCmd(cmd string) error {
defer cc.cancel()
func (cc *Client) RunCmd(cmd string) ([]byte, error) {
log.Debugf("run command %s", cmd)
session, err := cc.client.NewSession()
if err != nil {
log.Debugf("new session error: %s", err.Error())
cc.err = err
return err
log.Debugf("command exited with error: %s", err.Error())
} else {
log.Debugf("command exited with no error")
}
defer session.Close()
session.Stdin = os.Stdin
session.Stderr = os.Stderr
session.Stdout = os.Stdout
if err = session.Run(cmd); err != nil {
cc.err = err
return err
if err != nil {
return nil, err
}
return nil
d, err := session.CombinedOutput(cmd)
session.Close()
return d, err
}
// Shell start a login shell on server
func (cc *Client) Shell() error {
go cc.shell()
return nil
}
func (cc *Client) shell() error {
defer cc.cancel()
log.Debugf("request new session")
session, err := cc.client.NewSession()
if err != nil {
cc.err = err
return err
}
defer session.Close()
modes := ssh.TerminalModes{}
_console := console.Current()
defer _console.Reset()
session.Stdin = os.Stdin
session.Stdout = os.Stdout
session.Stderr = os.Stderr
modes := ssh.TerminalModes{
ssh.ECHO: 1,
ssh.TTY_OP_ISPEED: 14400,
ssh.TTY_OP_OSPEED: 14400,
}
// this make CTRL+C works
log.Debugf("turn terminal mode to raw")
_console.SetRaw()
ws, _ := _console.Size()
oldState, _ := terminal.MakeRaw(0)
defer func() {
log.Debugf("restore terminal mode")
terminal.Restore(0, oldState)
}()
w, h, _ := terminal.GetSize(0)
log.Debugf("request pty")
if err := session.RequestPty("xterm", int(ws.Height), int(ws.Width), modes); err != nil {
if err := session.RequestPty("xterm", h, w, modes); err != nil {
log.Errorf("request pty error: %s", err.Error())
cc.err = err
return err
}
if cc.authAgent != nil {
log.Debugln("request auth agent forwarding")
if err = agent.RequestAgentForwarding(session); err == nil {
if err1 := agent.ForwardToAgent(cc.client, cc.authAgent); err1 != nil {
log.Debugln(err1)
}
} else {
log.Debugln(err)
}
}
// register console change signal
consoleChange(_console, session)
session.Stdin = _console
session.Stdout = os.Stdout
session.Stderr = os.Stderr
log.Debugf("request shell")
if err := session.Shell(); err != nil {
log.Errorf("start shell error: %s", err.Error())
cc.err = err
return err
}
ch := make(chan struct{}, 1)
go func() {
if err = session.Wait(); err != nil {
log.Errorln(err)
cc.err = err
}
session.Wait()
log.Debugf("session closed")
ch <- struct{}{}
}()
select {
case <-ch:
case <-cc.ctx.Done():
}
return nil
}
@ -417,7 +369,6 @@ func (cc *Client) registerSignal() {
}
// AddDynamicHTTPForward add a http dynamic forward through
//
// secure channel
func (cc *Client) AddDynamicHTTPForward(addr string) error {
log.Debugf("add dynamic http listen: %s", addr)

@ -5,15 +5,8 @@ import (
"net"
"testing"
"time"
"github.com/fangdingjun/go-log/v5"
)
func TestTimedOutConn(t *testing.T) {
testTimedOutConn(t, true)
testTimedOutConn(t, false)
}
func testTimedOutConn(t *testing.T, _timeout bool) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
@ -30,8 +23,7 @@ func testTimedOutConn(t *testing.T, _timeout bool) {
go func() {
s, err := l.Accept()
if err != nil {
log.Errorf("accept failed: %s", err)
return
t.Fatalf("accept failed: %s", err)
}
defer s.Close()
@ -42,8 +34,7 @@ func testTimedOutConn(t *testing.T, _timeout bool) {
n, err := sConn.Read(buf)
if err != nil {
log.Errorf("server read failed: %s", err)
return
t.Fatalf("server read failed: %s", err)
}
if _timeout {

@ -1,36 +0,0 @@
//go:build windows
// +build windows
package obfssh
import (
"errors"
"os/exec"
"os/user"
"syscall"
"github.com/containerd/console"
"golang.org/x/crypto/ssh"
)
func consoleChange(_console console.Console, session *ssh.Session) {
}
func newPty() (console.Console, string, error) {
return nil, "", errors.New("not supported")
}
func setProcAttr(attr *syscall.SysProcAttr) {
}
func setTermios(fd int, args ssh.TerminalModes) error {
return errors.New("not supported")
}
func setUserEnv(_cmd *exec.Cmd, u *user.User, attr *syscall.SysProcAttr) {
if u == nil {
return
}
_cmd.Dir = u.HomeDir
}

@ -1,139 +0,0 @@
//go:build darwin || freebsd || linux || openbsd || solaris
// +build darwin freebsd linux openbsd solaris
package obfssh
import (
"fmt"
"os"
"os/exec"
"os/signal"
"os/user"
"strconv"
"strings"
"syscall"
"github.com/containerd/console"
"github.com/fangdingjun/go-log/v5"
"golang.org/x/crypto/ssh"
"golang.org/x/sys/unix"
)
func consoleChange(_console console.Console, session *ssh.Session) {
ch := make(chan os.Signal, 2)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for {
select {
case <-ch:
ws, _ := _console.Size()
_winCh := windowChange{Rows: uint32(ws.Height), Columns: uint32(ws.Width)}
d := ssh.Marshal(_winCh)
ok, err := session.SendRequest("window-change", true, d)
log.Debugf("send window change request %+v %+v", ok, err)
}
}
}()
}
func newPty() (console.Console, string, error) {
return console.NewPty()
}
func setProcAttr(attr *syscall.SysProcAttr) {
attr.Setsid = true
attr.Setctty = true
}
func setFlag(f *uint32, k uint8, v uint32) {
v1, ok := termiosMap[k]
if !ok {
return
}
if v != 0 {
*f |= v1
return
}
*f &^= v1
}
func applyTermios(flag *unix.Termios, t ssh.TerminalModes) {
for k, v := range t {
switch k {
case ssh.IGNPAR, ssh.PARMRK, ssh.INPCK, ssh.ISTRIP, ssh.INLCR, ssh.IGNCR, ssh.ICRNL, ssh.IUCLC, ssh.IXON, ssh.IXANY, ssh.IXOFF, ssh.IMAXBEL:
setFlag(&flag.Iflag, k, v)
case ssh.OPOST, ssh.OLCUC, ssh.ONLCR, ssh.OCRNL, ssh.ONOCR, ssh.ONLRET:
setFlag(&flag.Oflag, k, v)
case ssh.CS7, ssh.CS8, ssh.PARENB, ssh.PARODD:
setFlag(&flag.Cflag, k, v)
case ssh.ISIG, ssh.ICANON, ssh.XCASE, ssh.ECHO, ssh.ECHOE, ssh.ECHOK, ssh.ECHONL, ssh.ECHOCTL, ssh.ECHOKE, ssh.NOFLSH, ssh.TOSTOP, ssh.PENDIN, ssh.IEXTEN:
setFlag(&flag.Lflag, k, v)
case ssh.VEOF, ssh.VEOL, ssh.VEOL2, ssh.VDISCARD, ssh.VDSUSP, ssh.VERASE, ssh.VINTR, ssh.VKILL, ssh.VLNEXT, ssh.VQUIT, ssh.VREPRINT, ssh.VSTART, ssh.VSTATUS, ssh.VSTOP, ssh.VSUSP, ssh.VSWTCH, ssh.VWERASE:
v1, ok := termiosMap[k]
if ok {
flag.Cc[v1] = uint8(v)
}
case ssh.TTY_OP_ISPEED:
flag.Ispeed = v
case ssh.TTY_OP_OSPEED:
flag.Ospeed = v
}
}
}
func setTermios(fd int, args ssh.TerminalModes) error {
t1, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return err
}
log.Debugf("before %+v", t1)
applyTermios(t1, args)
err = unix.IoctlSetTermios(fd, unix.TCSETS, t1)
if err != nil {
return err
}
t1, err = unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return err
}
log.Debugf("after %+v", t1)
return nil
}
func setUserEnv(_cmd *exec.Cmd, u *user.User, attr *syscall.SysProcAttr) {
if u == nil {
return
}
_uid, _ := strconv.ParseUint(u.Uid, 10, 32)
_gid, _ := strconv.ParseUint(u.Gid, 10, 32)
_cmd.Env = append(_cmd.Env, fmt.Sprintf("HOME=%s", u.HomeDir))
_cmd.Env = append(_cmd.Env, fmt.Sprintf("LOGNAME=%s", u.Name))
_cmd.Dir = u.HomeDir
if os.Getuid() != 0 {
return
}
if attr.Credential == nil {
attr.Credential = &syscall.Credential{}
}
attr.Credential.Uid = uint32(_uid)
attr.Credential.Gid = uint32(_gid)
for _, _env := range _cmd.Env {
ss := strings.Split(_env, "=")
if ss[0] == "SSH_AUTH_SOCK" {
os.Chown(ss[1], int(_uid), int(_gid))
}
if ss[0] == "SSH_TTY" {
os.Chown(ss[1], int(_uid), 0)
}
}
}

@ -1,123 +0,0 @@
package obfssh
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
log "github.com/fangdingjun/go-log/v5"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
)
type Dialer struct {
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
Proxy func() (*url.URL, error)
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake
// is done there and TLSClientConfig is ignored.
TLSClientConfig *tls.Config
NetConf *Conf
}
func (d *Dialer) Dial(addr string, conf *ssh.ClientConfig) (*Client, error) {
if d.NetConf.Timeout == 0 {
d.NetConf.Timeout = 15 * time.Second
}
if d.NetConf.KeepAliveInterval == 0 {
d.NetConf.KeepAliveInterval = 10
}
if d.NetConf.KeepAliveMax == 0 {
d.NetConf.KeepAliveMax = 3
}
var dialFunc func(network, addr string) (net.Conn, error)
if d.NetDial == nil {
dialFunc = dialer.Dial
}
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
if d.Proxy != nil {
dialFunc = func(network, addr string) (net.Conn, error) {
var conn net.Conn
var err error
u1, _ := d.Proxy()
if u1 == nil {
return dialer.Dial(network, addr)
}
log.Debugf("connect to proxy %s", u1.String())
switch u1.Scheme {
case "http":
conn, err = dialHTTPProxy(addr, u1)
case "https":
conn, err = dialHTTPSProxy(addr, u1)
case "socks5":
conn, err = dialSocks5Proxy(addr, u1)
default:
return nil, fmt.Errorf("unknown proxy scheme %s", u1.Scheme)
}
if err != nil {
log.Errorf("connect to proxy error %s", err)
}
return conn, err
}
}
switch u.Scheme {
case "":
conn, err := dialFunc("tcp", u.Host)
if err != nil {
return nil, err
}
return NewClient(&TimedOutConn{Conn: conn, Timeout: d.NetConf.Timeout}, conf, u.Host, d.NetConf)
case "tls":
conn, err := dialFunc("tcp", u.Host)
if err != nil {
return nil, err
}
conn = tls.Client(&TimedOutConn{Conn: conn, Timeout: d.NetConf.Timeout}, d.TLSClientConfig)
return NewClient(conn, conf, u.Host, d.NetConf)
case "ws":
fallthrough
case "wss":
_addr := fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path)
_dailer := websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
c, err := dialFunc(network, addr)
return &TimedOutConn{Conn: c, Timeout: d.NetConf.Timeout}, err
},
TLSClientConfig: d.TLSClientConfig,
}
wsconn, res, err := _dailer.Dial(_addr, nil)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusSwitchingProtocols {
return nil, fmt.Errorf("websocket connect failed, http code %d", res.StatusCode)
}
_conn := &wsConn{Conn: wsconn, buf: new(bytes.Buffer), mu: new(sync.Mutex), ch: make(chan struct{})}
go _conn.readLoop()
return NewClient(_conn, conf, u.Host, d.NetConf)
default:
return nil, fmt.Errorf("unknow scheme %s", u.Scheme)
}
}
func (d *Dialer) DialContext(ctx context.Context, addr string, conf *ssh.ClientConfig) (*Client, error) {
return nil, nil
}

@ -3,14 +3,17 @@ module github.com/fangdingjun/obfssh
go 1.13
require (
github.com/containerd/console v1.0.3
github.com/fangdingjun/go-log/v5 v5.0.0
github.com/fangdingjun/protolistener v0.0.0-20230216120836-271b401928b8
github.com/fangdingjun/socks-go v0.0.0-20220901073602-f35f0e0139ec
github.com/bgentry/speakeasy v0.1.0
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296
github.com/fangdingjun/socks-go v0.0.0-20180926100003-fc6f0a9ee1f4
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gorilla/websocket v1.5.0
github.com/kr/fs v0.1.0
github.com/pkg/sftp v1.11.0
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
github.com/kr/pretty v0.1.0 // indirect
github.com/kr/pty v1.1.8
github.com/pkg/errors v0.8.1 // indirect
github.com/pkg/sftp v1.10.0
github.com/stretchr/testify v1.4.0 // indirect
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

@ -1,89 +1,48 @@
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fangdingjun/go-log/v5 v5.0.0 h1:vdh9Bk9C4ZFL6KoO6rII73zQIyaLf7hFdBvucO/ckiE=
github.com/fangdingjun/go-log/v5 v5.0.0/go.mod h1:V012Oxo0/pSbccX4OFSp9MJglXwNsZo2ByBBorr7zzM=
github.com/fangdingjun/protolistener v0.0.0-20230216120836-271b401928b8 h1:Fe7sbY3NZQBoHinferw+lJW2QdUeAMRfg4soVeRUC4I=
github.com/fangdingjun/protolistener v0.0.0-20230216120836-271b401928b8/go.mod h1:JnZqYZE3SzLtua269awu+0zGWxN3++Ehmf25lUnslcw=
github.com/fangdingjun/socks-go v0.0.0-20220901073602-f35f0e0139ec h1:gri5Uh0VMajB6oL9g+dvf/ZwoWSe4F5CaDzOKVQqc6s=
github.com/fangdingjun/socks-go v0.0.0-20220901073602-f35f0e0139ec/go.mod h1:i5fUj/NaF32p2LLmn8EWHF1CQukVH8oMgPyhjC4JxFk=
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc h1:Tdk7VsmsFo3d0NqHTy3SRoRnkduOxwXgR65gQsq8kXY=
github.com/fangdingjun/go-log v0.0.0-20190821073628-ae332053d6dc/go.mod h1:oi7jbIScCbha6TbVzmrJP6igIHt+jcvvEgSJ7Ww1GkI=
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296 h1:2c6agkdoPVSyvdJ0B+5DhOb1BQpso7a7zlBxXUnttmY=
github.com/fangdingjun/protolistener v0.0.0-20190821093313-6d5d2138f296/go.mod h1:RoT81rjdN8gQ1w/z7NiFkxV6VzkT4NZ43XIt0lu8tcc=
github.com/fangdingjun/socks-go v0.0.0-20180926100003-fc6f0a9ee1f4 h1:c3Iw/znf2xe2uut9zUTueO6XHyTTLugrbN9fAE4NAkg=
github.com/fangdingjun/socks-go v0.0.0-20180926100003-fc6f0a9ee1f4/go.mod h1:0P4kTlyyh76uY1Li3cyw4pOIKGL9RmXXWTQYFLS1ZaM=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI=
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go=
github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg=
github.com/pkg/sftp v1.10.0 h1:DGA1KlA9esU6WcicH+P8PxFZOl15O6GYtab1cIJdOlE=
github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
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/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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=

@ -14,13 +14,13 @@ import (
"syscall"
"time"
"github.com/fangdingjun/go-log/v5"
"github.com/bgentry/speakeasy"
"github.com/fangdingjun/go-log"
"github.com/fangdingjun/obfssh"
"github.com/kr/fs"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/terminal"
)
type options struct {
@ -102,7 +102,13 @@ func createSFTPConn(host, user string, cfg *options) (*sftp.Client, error) {
if cfg.Passwd == "" && cfg.PrivateKey == "" {
var pkeys []ssh.Signer
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
//auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
if signers, err := agent.NewClient(aconn).Signers(); err == nil {
log.Debugf("add private key from agent")
pkeys = append(pkeys, signers...)
} else {
log.Debugf("get key from agent failed: %s", err)
}
} else {
log.Debugf("dial to agent failed: %s", err)
}
@ -571,10 +577,8 @@ func makeDirs(p string, c dirInterface) error {
func passwordAuth() (string, error) {
// read password from console
fmt.Fprintf(os.Stdout, "Password: ")
s, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintf(os.Stdout, "\n")
return strings.Trim(string(s), " \r\n"), err
s, err := speakeasy.Ask("Password: ")
return strings.Trim(s, " \r\n"), err
}
//

@ -1,14 +1,61 @@
package main
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net"
"net/textproto"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log"
socks "github.com/fangdingjun/socks-go"
)
type httpProxyConn struct {
c net.Conn
r io.Reader
}
func (hc *httpProxyConn) Read(b []byte) (int, error) {
return hc.r.Read(b)
}
func (hc *httpProxyConn) Write(b []byte) (int, error) {
return hc.c.Write(b)
}
func (hc *httpProxyConn) Close() error {
return hc.c.Close()
}
func (hc *httpProxyConn) LocalAddr() net.Addr {
return hc.c.LocalAddr()
}
func (hc *httpProxyConn) RemoteAddr() net.Addr {
return hc.c.RemoteAddr()
}
func (hc *httpProxyConn) SetDeadline(t time.Time) error {
return hc.c.SetDeadline(t)
}
func (hc *httpProxyConn) SetReadDeadline(t time.Time) error {
return hc.c.SetReadDeadline(t)
}
func (hc *httpProxyConn) SetWriteDeadline(t time.Time) error {
return hc.c.SetWriteDeadline(t)
}
// validate the interface implements
var _ net.Conn = &httpProxyConn{}
func updateProxyFromEnv(cfg *config) {
if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 {
log.Debugf("proxy already specified by config, not parse environment proxy")
@ -61,3 +108,99 @@ func updateProxyFromEnv(cfg *config) {
}
}
}
func httpProxyHandshake(c net.Conn, host string, port int) (net.Conn, error) {
fmt.Fprintf(c, "CONNECT %s:%d HTTP/1.1\r\n", host, port)
fmt.Fprintf(c, "Host: %s:%d\r\n", host, port)
fmt.Fprintf(c, "User-Agent: go/1.7\r\n")
fmt.Fprintf(c, "\r\n")
r := bufio.NewReader(c)
tp := textproto.NewReader(r)
// read status line
statusLine, err := tp.ReadLine()
if err != nil {
return nil, err
}
if statusLine[0:4] != "HTTP" {
return nil, fmt.Errorf("not http reply")
}
status := strings.Fields(statusLine)[1]
statusCode, err := strconv.Atoi(status)
if err != nil {
return nil, err
}
if statusCode != 200 {
return nil, fmt.Errorf("http status error %d", statusCode)
}
// read header
if _, err = tp.ReadMIMEHeader(); err != nil {
return nil, err
}
return &httpProxyConn{c: c, r: r}, nil
}
func dialHTTPProxy(host string, port int, p proxy) (net.Conn, error) {
c, err := dialer.Dial("tcp", net.JoinHostPort(p.Host, fmt.Sprintf("%d", p.Port)))
if err != nil {
return nil, err
}
c1, err := httpProxyHandshake(c, host, port)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialHTTPSProxy(host string, port int, p proxy) (net.Conn, error) {
hostname := p.Host
if p.SNI != "" {
hostname = p.SNI
}
tlsconfig := &tls.Config{
ServerName: hostname,
InsecureSkipVerify: p.Insecure,
}
c, err := tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(p.Host, fmt.Sprintf("%d", p.Port)), tlsconfig)
if err != nil {
return nil, err
}
if err := c.Handshake(); err != nil {
c.Close()
return nil, err
}
c1, err := httpProxyHandshake(c, host, port)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialSocks5Proxy(host string, port int, p proxy) (net.Conn, error) {
c, err := dialer.Dial("tcp", net.JoinHostPort(p.Host, fmt.Sprintf("%d", p.Port)))
if err != nil {
return nil, err
}
c1 := &socks.Client{Conn: c}
c2, err := c1.Dial("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)))
if err != nil {
c1.Close()
return nil, err
}
return c2, err
}

@ -6,17 +6,16 @@ import (
"fmt"
"io/ioutil"
"net"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/fangdingjun/go-log/v5"
"github.com/bgentry/speakeasy"
"github.com/fangdingjun/go-log"
"github.com/fangdingjun/obfssh"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"golang.org/x/crypto/ssh/terminal"
)
var dialer = &net.Dialer{Timeout: 15 * time.Second}
@ -34,6 +33,7 @@ func main() {
flag.StringVar(&cfg.Password, "pw", "", "ssh password")
flag.IntVar(&cfg.Port, "p", 22, "remote port")
flag.StringVar(&cfg.PrivateKey, "i", "", "private key file")
flag.BoolVar(&cfg.TLS, "tls", false, "use tls or not")
flag.BoolVar(&cfg.TLSInsecure, "tls-insecure", false, "insecure tls connnection")
flag.Var(&cfg.LocalForwards, "L", "forward local port to remote, format [local_host:]local_port:remote_host:remote_port")
flag.Var(&cfg.RemoteForwards, "R", "forward remote port to local, format [remote_host:]remote_port:local_host:local_port")
@ -85,7 +85,6 @@ func main() {
var agentConn net.Conn
var err error
var agentClient agent.ExtendedAgent
// read ssh agent and default auth key
if cfg.Password == "" && cfg.PrivateKey == "" {
@ -115,8 +114,14 @@ func main() {
if err == nil {
defer agentConn.Close()
log.Debugf("add auth method with agent %s", os.Getenv("SSH_AUTH_SOCK"))
agentClient = agent.NewClient(agentConn)
auth = append(auth, ssh.PublicKeysCallback(agentClient.Signers))
agentClient := agent.NewClient(agentConn)
//auth = append(auth, ssh.PublicKeysCallback(agentClient.Signers))
signers, err := agentClient.Signers()
if err == nil {
pkeys = append(pkeys, signers...)
} else {
log.Debugf("get key from agent failed: %s", err)
}
} else {
log.Debugf("connect to agent failed")
}
@ -147,13 +152,10 @@ func main() {
cmd = strings.Join(args, " ")
}
var serverName string
if strings.Contains(host, "@") {
u, _ := url.Parse(host)
cfg.Username = u.User.Username()
u.User = nil
host = u.String()
serverName, _, _ = net.SplitHostPort(u.Host)
ss := strings.SplitN(host, "@", 2)
cfg.Username = ss[0]
host = ss[1]
}
// process user specified private key
@ -195,36 +197,68 @@ func main() {
// parse environment proxy
updateProxyFromEnv(&cfg)
rhost := net.JoinHostPort(host, fmt.Sprintf("%d", cfg.Port))
var c net.Conn
if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 {
switch cfg.Proxy.Scheme {
case "http":
log.Debugf("use http proxy %s:%d to connect to server",
cfg.Proxy.Host, cfg.Proxy.Port)
c, err = dialHTTPProxy(host, cfg.Port, cfg.Proxy)
case "https":
log.Debugf("use https proxy %s:%d to connect to server",
cfg.Proxy.Host, cfg.Proxy.Port)
c, err = dialHTTPSProxy(host, cfg.Port, cfg.Proxy)
case "socks5":
log.Debugf("use socks proxy %s:%d to connect to server",
cfg.Proxy.Host, cfg.Proxy.Port)
c, err = dialSocks5Proxy(host, cfg.Port, cfg.Proxy)
default:
err = fmt.Errorf("unsupported scheme: %s", cfg.Proxy.Scheme)
}
} else {
log.Debugf("dail to %s", rhost)
c, err = dialer.Dial("tcp", rhost)
}
if err != nil {
log.Fatal(err)
}
log.Debugf("dail success")
timeout := time.Duration(cfg.KeepaliveInterval*2) * time.Second
var _conn = c
conn := &obfssh.TimedOutConn{Conn: c, Timeout: timeout}
if cfg.TLS {
log.Debugf("begin tls handshake")
_conn = tls.Client(conn, &tls.Config{
ServerName: host,
InsecureSkipVerify: cfg.TLSInsecure,
})
if err := _conn.(*tls.Conn).Handshake(); err != nil {
log.Fatal(err)
}
log.Debugf("tls handshake done")
}
conf := &obfssh.Conf{
Timeout: timeout,
KeepAliveInterval: time.Duration(cfg.KeepaliveInterval) * time.Second,
KeepAliveMax: cfg.KeepaliveMax,
}
dialer := &obfssh.Dialer{
Proxy: func() (*url.URL, error) {
if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 {
return &url.URL{
Scheme: cfg.Proxy.Scheme,
Host: fmt.Sprintf("%s:%d", cfg.Proxy.Host, cfg.Proxy.Port),
}, nil
}
return nil, nil
},
TLSClientConfig: &tls.Config{ServerName: serverName, InsecureSkipVerify: cfg.TLSInsecure},
NetConf: conf,
}
client, err := dialer.Dial(host, config)
log.Debugf("ssh negotation")
client, err := obfssh.NewClient(_conn, config, rhost, conf)
if err != nil {
log.Fatal(err)
}
if agentClient != nil {
client.SetAuthAgent(agentClient)
}
log.Debugf("ssh negotation success")
var local, remote string
@ -269,7 +303,7 @@ func main() {
}
for _, p := range cfg.DynamicForwards {
if !strings.Contains(p, ":") {
if strings.Index(p, ":") == -1 {
local = fmt.Sprintf(":%s", p)
} else {
local = p
@ -281,7 +315,7 @@ func main() {
}
for _, p := range cfg.DynamicHTTP {
if !strings.Contains(p, ":") {
if strings.Index(p, ":") == -1 {
local = fmt.Sprintf(":%s", p)
} else {
local = p
@ -297,9 +331,12 @@ func main() {
if !cfg.NotRunCmd {
if cmd != "" {
if err := client.RunCmd(cmd); err != nil {
if d, err := client.RunCmd(cmd); err != nil {
log.Errorln(err)
hasErr = true
} else {
//log.Printf("%s", string(d))
fmt.Printf("%s", string(d))
}
} else {
if err := client.Shell(); err != nil {
@ -322,7 +359,10 @@ func main() {
func parseForwardAddr(s string) []string {
ss := strings.FieldsFunc(s, func(c rune) bool {
return c == ':'
if c == ':' {
return true
}
return false
})
return ss
}
@ -346,28 +386,20 @@ func keyboardAuth(user, instruction string, question []string, echos []bool) (an
func passwordAuth() (string, error) {
// read password from console
fmt.Fprintf(os.Stdout, "Password: ")
s, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintf(os.Stdout, "\n")
return strings.Trim(string(s), " \r\n"), err
s, err := speakeasy.Ask("Password: ")
return strings.Trim(s, " \r\n"), err
}
func usage() {
usageStr := `Usage:
obfssh -N -d -D [bind_address:]port -f configfile
-tls-insecure -log_file /path/to/file
-tls -tls-insecure -log_file /path/to/file
-log_count 10 -log_size 10
-log_level INFO
-i identity_file -L [bind_address:]port:host:hostport
-l login_name -pw password -p port
-http [bind_addr:]port
-R [bind_address:]port:host:hostport host [command]
host can be multiple forms, example:
user@example.com
tls://hostname
ws://hostname/path
wss://hostname/path
-R [bind_address:]port:host:hostport [user@]hostname [command]
Options:
-d verbose mode
@ -428,8 +460,11 @@ Options:
encrypt ssh connection
similar like -D, but input is http, not socks5
-tls
connect to server via TLS
-tls-insecure
do not verify server's tls ceritificate when use tls:// or wss://
do not verify server's tls ceritificate
-log_file
log file, default stdout

@ -4,7 +4,7 @@ import (
"bytes"
"io/ioutil"
"github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log"
"github.com/go-yaml/yaml"
"golang.org/x/crypto/ssh"
)

@ -12,7 +12,7 @@ import (
"os/signal"
"syscall"
"github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log"
"github.com/fangdingjun/obfssh"
"github.com/fangdingjun/protolistener"
"golang.org/x/crypto/ssh"
@ -74,7 +74,7 @@ func main() {
if u, err := conf.getUser(c.User()); err == nil {
for _, pk := range u.publicKeys {
if k.Type() == pk.Type() &&
bytes.Equal(k.Marshal(), pk.Marshal()) {
bytes.Compare(k.Marshal(), pk.Marshal()) == 0 {
return true
}
}
@ -152,6 +152,7 @@ func main() {
defer c.Close()
sc, err := obfssh.NewServer(c, config, sconf)
if err != nil {
c.Close()
log.Errorf("%s", err.Error())
return
}

@ -1,151 +0,0 @@
package obfssh
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net"
"net/textproto"
"net/url"
"strconv"
"strings"
"time"
log "github.com/fangdingjun/go-log/v5"
socks "github.com/fangdingjun/socks-go"
)
type httpProxyConn struct {
c net.Conn
r io.Reader
}
func (hc *httpProxyConn) Read(b []byte) (int, error) {
return hc.r.Read(b)
}
func (hc *httpProxyConn) Write(b []byte) (int, error) {
return hc.c.Write(b)
}
func (hc *httpProxyConn) Close() error {
return hc.c.Close()
}
func (hc *httpProxyConn) LocalAddr() net.Addr {
return hc.c.LocalAddr()
}
func (hc *httpProxyConn) RemoteAddr() net.Addr {
return hc.c.RemoteAddr()
}
func (hc *httpProxyConn) SetDeadline(t time.Time) error {
return hc.c.SetDeadline(t)
}
func (hc *httpProxyConn) SetReadDeadline(t time.Time) error {
return hc.c.SetReadDeadline(t)
}
func (hc *httpProxyConn) SetWriteDeadline(t time.Time) error {
return hc.c.SetWriteDeadline(t)
}
// validate the interface implements
var _ net.Conn = &httpProxyConn{}
func httpProxyHandshake(c net.Conn, addr string) (net.Conn, error) {
log.Debugf("http handshake with %s", addr)
fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", addr)
fmt.Fprintf(c, "Host: %s\r\n", addr)
fmt.Fprintf(c, "User-Agent: go/1.7\r\n")
fmt.Fprintf(c, "\r\n")
r := bufio.NewReader(c)
tp := textproto.NewReader(r)
// read status line
statusLine, err := tp.ReadLine()
if err != nil {
return nil, err
}
if statusLine[0:4] != "HTTP" {
return nil, fmt.Errorf("not http reply")
}
status := strings.Fields(statusLine)[1]
statusCode, err := strconv.Atoi(status)
if err != nil {
return nil, err
}
if statusCode != 200 {
return nil, fmt.Errorf("http status error %d", statusCode)
}
// read header
if _, err = tp.ReadMIMEHeader(); err != nil {
return nil, err
}
return &httpProxyConn{c: c, r: r}, nil
}
func dialHTTPProxy(addr string, p *url.URL) (net.Conn, error) {
log.Debugf("dial to %s", p.Host)
c, err := dialer.Dial("tcp", p.Host)
if err != nil {
return nil, err
}
c1, err := httpProxyHandshake(c, addr)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialHTTPSProxy(addr string, p *url.URL) (net.Conn, error) {
hostname := p.Host
tlsconfig := &tls.Config{
ServerName: hostname,
InsecureSkipVerify: true,
}
c, err := tls.DialWithDialer(dialer, "tcp", p.Host, tlsconfig)
if err != nil {
return nil, err
}
if err := c.Handshake(); err != nil {
c.Close()
return nil, err
}
c1, err := httpProxyHandshake(c, addr)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialSocks5Proxy(addr string, p *url.URL) (net.Conn, error) {
c, err := dialer.Dial("tcp", p.Host)
if err != nil {
return nil, err
}
c1 := &socks.Client{Conn: c}
c2, err := c1.Dial("tcp", addr)
if err != nil {
c1.Close()
return nil, err
}
return c2, err
}

@ -0,0 +1,14 @@
// +build linux darwin
package obfssh
import (
"io"
"os/exec"
"github.com/kr/pty"
)
func startPty(cmd *exec.Cmd) (io.ReadWriteCloser, error) {
return pty.Start(cmd)
}

@ -0,0 +1,11 @@
package obfssh
import (
"errors"
"io"
"os/exec"
)
func startPty(cmd *exec.Cmd) (io.ReadWriteCloser, error) {
return nil, errors.New("not implement")
}

@ -1,4 +1,3 @@
//go:build !linux
// +build !linux
package obfssh

@ -1,4 +1,3 @@
//go:build linux && !cgo
// +build linux,!cgo
package obfssh

@ -1,4 +1,3 @@
//go:build linux && cgo
// +build linux,cgo
package obfssh

@ -1,20 +1,15 @@
package obfssh
import (
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"os/user"
"runtime"
"syscall"
"time"
"github.com/containerd/console"
"github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
@ -34,6 +29,8 @@ type Server struct {
// config is &ssh.ServerConfig
//
// conf is the server configure
//
//
func NewServer(c net.Conn, config *ssh.ServerConfig, conf *Conf) (*Server, error) {
sshConn, ch, req, err := ssh.NewServerConn(&TimedOutConn{c, 15 * 60 * time.Second}, config)
if err != nil {
@ -149,252 +146,78 @@ type exitStatus struct {
Status uint32
}
type ptyReq struct {
Term string
Columns uint32
Rows uint32
Width uint32
Height uint32
Mode string
}
type windowChange struct {
Columns uint32
Rows uint32
Width uint32
Height uint32
}
func parseTerminalModes(s string) ssh.TerminalModes {
// log.Debugf("%x", s)
s1 := []byte(s)
t := ssh.TerminalModes{}
for i := 0; i < len(s1); i += 5 {
k := uint8(s1[i])
if k == 0 {
break
}
v := binary.BigEndian.Uint32(s1[i+1 : i+5])
t[k] = v
// log.Debugf("k %d, v %d", k, v)
}
return t
}
type session struct {
ch ssh.Channel
env []string
_console console.Console
ptsname string
cmd *exec.Cmd
user string
func (sc *Server) handleSession(newch ssh.NewChannel) {
ch, req, err := newch.Accept()
if err != nil {
log.Errorf("%s", err.Error())
return
}
func (s *session) handleSubsystem(payload []byte) bool {
var _cmd args
if err := ssh.Unmarshal(payload, &_cmd); err != nil {
log.Errorln(err)
return false
}
if _cmd.Arg != "sftp" { // only support sftp
ret := false
var cmd *exec.Cmd
var env []string
for r := range req {
switch r.Type {
case "subsystem":
if err := ssh.Unmarshal(r.Payload, &_cmd); err == nil {
if _cmd.Arg == "sftp" { // only support sftp
ret = true
log.Debugf("handle sftp request")
go serveSFTP(ch)
} else {
log.Debugln("subsystem", _cmd.Arg, "not support")
return false
}
log.Debugf("handle sftp request")
go serveSFTP(s.ch)
return true
} else {
ret = false
log.Debugln("get subsystem arg error", err)
}
func (s *session) handleShell() bool {
var cmd *exec.Cmd
case "shell":
ret = true
if runtime.GOOS == "windows" {
s.env = append(s.env, "SHELL=powershell")
cmd = exec.Command("powershell")
} else {
s.env = append(s.env, "SHELL=/bin/bash")
cmd = exec.Command("/bin/bash", "-l")
}
s.cmd = cmd
cmd.Env = s.env
go handleShell(cmd, s.ch, s._console, s.ptsname, s.user)
return true
}
func (s *session) handleExec(payload []byte) bool {
var _cmd args
var cmd *exec.Cmd
if err := ssh.Unmarshal(payload, &_cmd); err != nil {
log.Errorln(err)
return false
cmd = exec.Command("bash", "-l")
}
cmd.Env = env
go handleShell(cmd, ch)
case "signal":
log.Debugln("got signal")
ret = true
case "exec":
ret = true
if err = ssh.Unmarshal(r.Payload, &_cmd); err == nil {
log.Infoln("execute command", _cmd.Arg)
if runtime.GOOS == "windows" {
s.env = append(s.env, "SHELL=powershell")
cmd = exec.Command("powershell", "-Command", _cmd.Arg)
} else {
s.env = append(s.env, "SHELL=/bin/bash")
cmd = exec.Command("/bin/bash", "-c", _cmd.Arg)
cmd = exec.Command("bash", "-c", _cmd.Arg)
}
s.cmd = cmd
cmd.Env = s.env
go handleShell(cmd, s.ch, s._console, s.ptsname, s.user)
return true
}
func (s *session) handlePtyReq(payload []byte) bool {
var _ptyReq ptyReq
var err error
if err = ssh.Unmarshal(payload, &_ptyReq); err != nil {
log.Errorln(err)
return false
}
log.Debugf("pty req Rows: %d, Columns: %d, Mode: %x", _ptyReq.Rows, _ptyReq.Columns, _ptyReq.Mode)
termios := parseTerminalModes(_ptyReq.Mode)
log.Debugf("parsed terminal mode %+v", termios)
s._console, s.ptsname, err = newPty()
if err != nil {
log.Errorln(err)
return false
}
log.Debugf("allocate pty %s", s.ptsname)
log.Debugf("set termios")
if err1 := setTermios(int(s._console.Fd()), termios); err1 != nil {
log.Errorln(err)
return false
}
s.env = append(s.env, fmt.Sprintf("SSH_TTY=%s", s.ptsname))
s.env = append(s.env, fmt.Sprintf("TERM=%s", _ptyReq.Term))
ws, _ := s._console.Size()
log.Debugf("current console %+v", ws)
ws.Height = uint16(_ptyReq.Rows)
ws.Width = uint16(_ptyReq.Columns)
if err = s._console.Resize(ws); err != nil {
log.Errorln(err)
return false
}
return true
cmd.Env = env
//cmd.Stdin = ch
go handleCommand(cmd, ch)
} else {
log.Debugln(err)
ret = false
}
func (s *session) handleEnv(payload []byte) bool {
case "pty-req":
ret = true
case "env":
var arg envArgs
if err := ssh.Unmarshal(payload, &arg); err != nil {
log.Errorln(err)
return false
}
ret = true
if err = ssh.Unmarshal(r.Payload, &arg); err == nil {
log.Debugf("got env %s=%s", arg.Name, arg.Value)
s.env = append(s.env, fmt.Sprintf("%s=%s", arg.Name, arg.Value))
return true
}
func (s *session) handleWindowChange(payload []byte) bool {
var _windowChange windowChange
if err := ssh.Unmarshal(payload, &_windowChange); err != nil {
log.Errorln(err)
return false
}
log.Debugf("window change %+v", _windowChange)
if s._console == nil {
// ignore
return true
}
ws, err := s._console.Size()
if err != nil {
log.Errorln(err)
return false
}
log.Debugf("current console %+v", ws)
ws.Height = uint16(_windowChange.Rows)
ws.Width = uint16(_windowChange.Columns)
if err := s._console.Resize(ws); err != nil {
log.Errorln(err)
return false
}
return true
}
func (sc *Server) handleAuthAgentForward(sess *session) bool {
f, err := ioutil.TempFile("", "agent-")
if err != nil {
log.Errorln(err)
return false
}
p := f.Name()
f.Close()
os.Remove(p)
l, err := net.Listen("unix", p)
if err != nil {
log.Errorln(err)
return false
}
sess.env = append(sess.env, fmt.Sprintf("SSH_AUTH_SOCK=%s", p))
sc.forwardedPorts[p] = l
go func() {
defer os.Remove(p)
for {
c, err := l.Accept()
if err != nil {
log.Errorln(err)
break
}
go func(c net.Conn) {
ch, req, err := sc.sshConn.OpenChannel("auth-agent@openssh.com", nil)
if err != nil {
c.Close()
log.Errorln(err)
return
}
go ssh.DiscardRequests(req)
PipeAndClose(ch, c)
}(c)
}
}()
return true
}
func (sc *Server) handleSession(newch ssh.NewChannel) {
ch, req, err := newch.Accept()
if err != nil {
log.Errorf("%s", err.Error())
return
env = append(env, fmt.Sprintf("%s=%s", arg.Name, arg.Value))
} else {
log.Debugln("parse env failed", err)
ret = false
}
sess := &session{ch: ch, user: sc.sshConn.User()}
for r := range req {
ret := false
switch r.Type {
case "subsystem":
ret = sess.handleSubsystem(r.Payload)
case "shell":
ret = sess.handleShell()
case "exec":
ret = sess.handleExec(r.Payload)
case "pty-req":
ret = sess.handlePtyReq(r.Payload)
case "env":
ret = sess.handleEnv(r.Payload)
case "window-change":
ret = sess.handleWindowChange(r.Payload)
case "auth-agent-req@openssh.com":
ret = sc.handleAuthAgentForward(sess)
case "signal":
log.Debugln("got signal")
ret = true
default:
ret = false
}
log.Debugf("session request %s, reply %v", r.Type, ret)
@ -403,63 +226,34 @@ func (sc *Server) handleSession(newch ssh.NewChannel) {
r.Reply(ret, nil)
}
}
if sess.cmd != nil && sess.cmd.Process != nil {
log.Debugf("kill the running process %s", sess.cmd.Args)
p := sess.cmd.Process
if err := p.Kill(); err != nil {
log.Debugln(err)
}
time.Sleep(100 * time.Millisecond)
if err := p.Signal(os.Kill); err != nil {
log.Debugln(err)
}
}
log.Debugln("session ended.")
}
func handleShell(cmd *exec.Cmd, ch ssh.Channel, _console console.Console, ptsname string, _user string) {
defer func() {
ch.Close()
if _console != nil {
_console.Close()
}
}()
func handleShell(cmd *exec.Cmd, ch ssh.Channel) {
defer ch.Close()
var _pty io.ReadWriteCloser
var err error
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
log.Infoln("start shell")
_u, err := user.Lookup(_user)
//_pty, err = pty.Start(cmd)
if runtime.GOOS == "unix" || runtime.GOOS == "linux" {
_pty, err = startPty(cmd)
if err != nil {
log.Errorln(err)
}
setUserEnv(cmd, _u, cmd.SysProcAttr)
if _console != nil {
_tty, err := os.OpenFile(ptsname, syscall.O_RDWR|syscall.O_NOCTTY, 0600)
if err != nil {
log.Errorln(err)
log.Debugln("start pty", err)
ch.SendRequest("exit-status", false,
ssh.Marshal(exitStatus{Status: 127}))
return
}
defer _tty.Close()
cmd.Stderr = _tty
cmd.Stdout = _tty
cmd.Stdin = _tty
setProcAttr(cmd.SysProcAttr)
}
go io.Copy(ch, _console)
go io.Copy(_console, ch)
} else {
if runtime.GOOS == "unix" || runtime.GOOS == "linux" {
defer _pty.Close()
go io.Copy(ch, _pty)
go io.Copy(_pty, ch)
} else { // windows
cmd.Stderr = ch
cmd.Stdout = ch
// cmd.Stdin = ch
in, err := cmd.StdinPipe()
if err != nil {
ch.SendRequest("exit-status", false,
@ -469,16 +263,14 @@ func handleShell(cmd *exec.Cmd, ch ssh.Channel, _console console.Console, ptsnam
go func() {
defer in.Close()
io.Copy(in, ch)
}()
}
if err := cmd.Start(); err != nil {
log.Debugln("start command ", err)
ch.SendRequest("exit-status", false,
ssh.Marshal(exitStatus{Status: 126}))
return
}
}
code := 0
if err = cmd.Wait(); err != nil {
log.Debugln(err)
@ -488,7 +280,36 @@ func handleShell(cmd *exec.Cmd, ch ssh.Channel, _console console.Console, ptsnam
}
}
}
cmd.Process = nil
ch.SendRequest("exit-status", false,
ssh.Marshal(exitStatus{Status: uint32(code)}))
}
func handleCommand(cmd *exec.Cmd, ch ssh.Channel) {
defer ch.Close()
cmd.Stdout = ch
cmd.Stderr = ch
//log.Debugln("execute command", cmd)
in, err := cmd.StdinPipe()
if err != nil {
log.Debugln(err)
ch.SendRequest("exit-status", false,
ssh.Marshal(exitStatus{Status: 127}))
return
}
go func() {
defer in.Close()
io.Copy(in, ch)
}()
code := 0
if err := cmd.Run(); err != nil {
log.Debugln(err)
if exiterr, ok := err.(*exec.ExitError); ok {
if s, ok := exiterr.Sys().(syscall.WaitStatus); ok {
code = s.ExitStatus()
}
}
}
ch.SendRequest("exit-status", false,
ssh.Marshal(exitStatus{Status: uint32(code)}))
}
@ -564,7 +385,7 @@ func (sc *Server) handleTcpipForward(req *ssh.Request) {
return
}
if addr.Port > 65535 {
if addr.Port > 65535 || addr.Port < 0 {
log.Errorf("invalid port %d", addr.Port)
if req.WantReply {
req.Reply(false, nil)

@ -1,63 +0,0 @@
package obfssh
import (
"golang.org/x/crypto/ssh"
)
var termiosMap = map[uint8]uint32{
ssh.VINTR: 0,
ssh.VQUIT: 1,
ssh.VERASE: 2,
ssh.VKILL: 3,
ssh.VEOF: 4,
ssh.VSTART: 8,
ssh.VSTOP: 9,
ssh.VSUSP: 10,
ssh.VEOL: 11,
ssh.VREPRINT: 12,
ssh.VDISCARD: 13,
ssh.VWERASE: 14,
ssh.VLNEXT: 15,
ssh.VEOL2: 16,
ssh.IGNPAR: 0000004,
ssh.PARMRK: 0000010,
ssh.INPCK: 0000020,
ssh.ISTRIP: 0000040,
ssh.INLCR: 0000100,
ssh.IGNCR: 0000200,
ssh.ICRNL: 0000400,
ssh.IUCLC: 0001000,
ssh.IXON: 0002000,
ssh.IXANY: 0004000,
ssh.IXOFF: 0010000,
ssh.IMAXBEL: 0020000,
ssh.OPOST: 0000001,
ssh.PARENB: 0000400,
ssh.OLCUC: 0000002,
ssh.ONLCR: 0000004,
ssh.OCRNL: 0000010,
ssh.ONOCR: 0000020,
ssh.ONLRET: 0000040,
ssh.CS7: 0000040,
ssh.CS8: 0000060,
ssh.PARODD: 0001000,
ssh.ISIG: 0000001,
ssh.ICANON: 0000002,
ssh.XCASE: 0000004,
ssh.ECHO: 0000010,
ssh.ECHOE: 0000020,
ssh.ECHOK: 0000040,
ssh.ECHONL: 0000100,
ssh.NOFLSH: 0000200,
ssh.TOSTOP: 0000400,
ssh.ECHOCTL: 0001000,
ssh.ECHOKE: 0004000,
ssh.PENDIN: 0040000,
ssh.IEXTEN: 0100000,
/*
ssh.VFLUSH: 0,
ssh.VSWTCH: 0,
ssh.VSTATUS: 0,
ssh.VDSUSP: 0,
*/
}

@ -3,7 +3,7 @@ package obfssh
import (
"io"
"github.com/fangdingjun/go-log/v5"
"github.com/fangdingjun/go-log"
)
// PipeAndClose pipe the data between c and s, close both when done

85
ws.go

@ -1,85 +0,0 @@
package obfssh
import (
"bytes"
"errors"
"io"
"net"
"sync"
"time"
log "github.com/fangdingjun/go-log/v5"
"github.com/gorilla/websocket"
)
type wsConn struct {
*websocket.Conn
buf *bytes.Buffer
mu *sync.Mutex
ch chan struct{}
}
var _ net.Conn = &wsConn{}
func (wc *wsConn) readLoop() {
for {
_, data, err := wc.ReadMessage()
if err != nil {
log.Debugln(err)
close(wc.ch)
break
}
wc.mu.Lock()
wc.buf.Write(data)
wc.mu.Unlock()
select {
case wc.ch <- struct{}{}:
default:
}
}
}
func (wc *wsConn) Read(buf []byte) (int, error) {
wc.mu.Lock()
n, err := wc.buf.Read(buf)
if err == nil {
wc.mu.Unlock()
return n, err
}
wc.mu.Unlock()
if err != io.EOF {
return 0, err
}
// EOF, no data avaliable, read again
select {
case _, ok := <-wc.ch:
if !ok {
return 0, errors.New("connection closed")
}
}
wc.mu.Lock()
defer wc.mu.Unlock()
return wc.buf.Read(buf)
}
func (wc *wsConn) Write(buf []byte) (int, error) {
err := wc.WriteMessage(websocket.BinaryMessage, buf)
return len(buf), err
}
func (wc *wsConn) SetDeadline(t time.Time) error {
if err := wc.SetReadDeadline(t); err != nil {
return err
}
if err := wc.SetWriteDeadline(t); err != nil {
return err
}
return nil
}
Loading…
Cancel
Save