first version
commit
233ba2732a
@ -0,0 +1 @@
|
|||||||
|
*~
|
@ -0,0 +1,166 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
|
|
@ -0,0 +1,91 @@
|
|||||||
|
obfssh
|
||||||
|
=====
|
||||||
|
|
||||||
|
obfssh is wrapper for ssh protocol, use AES or RC4 to encrypt the transport data,
|
||||||
|
ssh is a good designed protocol and with the good encryption, but the protocol has a especially figerprint,
|
||||||
|
the firewall can easily identify the protocol and block it or QOS it, especial when we use its port forward function to escape from the state censorship.
|
||||||
|
|
||||||
|
obfssh encrypt the ssh protocol and hide the figerprint, the firewall can not identify the protocol.
|
||||||
|
|
||||||
|
We borrow the idea from https://github.com/brl/obfuscated-openssh, but not compatible with it,
|
||||||
|
beause the limitions of golang ssh library.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
server usage example
|
||||||
|
====================
|
||||||
|
|
||||||
|
import "github.com/fangdingjun/obfssh"
|
||||||
|
import "golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
// key for encryption
|
||||||
|
obfs_key := "some keyword"
|
||||||
|
|
||||||
|
// encrypt method
|
||||||
|
obfs_method := "rc4"
|
||||||
|
|
||||||
|
config := &ssh.ServerConfig{
|
||||||
|
// add ssh server configure here
|
||||||
|
// for example auth method, cipher, MAC
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.Listen(":2022")
|
||||||
|
c, err := l.Accept()
|
||||||
|
|
||||||
|
sc, err := obfssh.NewServer(c, config, obfs_method, obfs_key)
|
||||||
|
|
||||||
|
sc.Run()
|
||||||
|
|
||||||
|
|
||||||
|
client usage example
|
||||||
|
====================
|
||||||
|
|
||||||
|
|
||||||
|
import "github.com/fangdingjun/obfssh"
|
||||||
|
import "golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
addr := "localhost:2022"
|
||||||
|
|
||||||
|
// key for encryption
|
||||||
|
obfs_key := "some keyword"
|
||||||
|
|
||||||
|
// encrypt method
|
||||||
|
obfs_method := "rc4"
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
// add ssh client config here
|
||||||
|
// for example auth method
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := net.Dial("tcp", addr)
|
||||||
|
|
||||||
|
// create connection
|
||||||
|
client, err := obfssh.NewClient(c, config, addr, obfs_method, obfs_key)
|
||||||
|
|
||||||
|
// local to remote port forward
|
||||||
|
client.AddLocalForward(":2234:10.0.0.1:3221")
|
||||||
|
|
||||||
|
// remote to local port forward
|
||||||
|
client.AddRemoteForward(":2234:10.2.0.1:3221")
|
||||||
|
|
||||||
|
// dynamic port forward
|
||||||
|
client.AddDynamicForward(":4321")
|
||||||
|
|
||||||
|
// wait to be done
|
||||||
|
client.Run()
|
||||||
|
|
||||||
|
|
||||||
|
limitions
|
||||||
|
========
|
||||||
|
|
||||||
|
now, the server side only implements the port forward function, start shell or execute a command is not suppurted
|
||||||
|
|
||||||
|
if set the `obfs_method` to `none`, obfssh is compatible with standard ssh server/client(OpenSSH)
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
GPLv3, see LICENSE file details
|
@ -0,0 +1,288 @@
|
|||||||
|
package obfssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
socks "github.com/fangdingjun/socks-go"
|
||||||
|
"github.com/golang/crypto/ssh"
|
||||||
|
"github.com/golang/crypto/ssh/terminal"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is ssh client connection
|
||||||
|
type Client struct {
|
||||||
|
conn net.Conn
|
||||||
|
sshConn ssh.Conn
|
||||||
|
client *ssh.Client
|
||||||
|
listeners []net.Listener
|
||||||
|
ch chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient create a new ssh Client
|
||||||
|
//
|
||||||
|
// addr is server address
|
||||||
|
//
|
||||||
|
// method is obfs encrypt method, value is rc4, aes or none or ""
|
||||||
|
//
|
||||||
|
// key is obfs encrypt key
|
||||||
|
//
|
||||||
|
// if set method to none or "", means disable the obfs,
|
||||||
|
// when the obfs is disabled, the client can connect to standard ssh server, like OpenSSH server
|
||||||
|
//
|
||||||
|
func NewClient(c net.Conn, config *ssh.ClientConfig, addr, method, key string) (*Client, error) {
|
||||||
|
Log(DEBUG, "create obfs conn with method %s", method)
|
||||||
|
obfsConn, err := NewObfsConn(&TimedOutConn{c, 15 * time.Second}, method, key, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sshConn, newch, reqs, err := ssh.NewClientConn(obfsConn, addr, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sshClient := ssh.NewClient(sshConn, newch, reqs)
|
||||||
|
client := &Client{
|
||||||
|
conn: c, sshConn: sshConn, client: sshClient,
|
||||||
|
ch: make(chan int),
|
||||||
|
}
|
||||||
|
go client.keepAlive(10*time.Second, 4)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run wait ssh connection to finish
|
||||||
|
func (cc *Client) Run() {
|
||||||
|
select {
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
}
|
||||||
|
// wait port forward to finish
|
||||||
|
if cc.listeners != nil {
|
||||||
|
Log(DEBUG, "wait all channel to be done")
|
||||||
|
go cc.registerSignal()
|
||||||
|
go func() {
|
||||||
|
cc.sshConn.Wait()
|
||||||
|
select {
|
||||||
|
case cc.ch <- 1:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait exit signal
|
||||||
|
select {
|
||||||
|
case <-cc.ch:
|
||||||
|
Log(INFO, "got signal, exit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log(DEBUG, "Done")
|
||||||
|
cc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close the ssh connection
|
||||||
|
// and free all the port forward resources
|
||||||
|
func (cc *Client) Close() {
|
||||||
|
for _, l := range cc.listeners {
|
||||||
|
Log(INFO, "close the listener %s", l.Addr())
|
||||||
|
l.Close()
|
||||||
|
}
|
||||||
|
//Log(DEBUG, "close ssh connection")
|
||||||
|
//cc.sshConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCmd run a single command on server
|
||||||
|
func (cc *Client) RunCmd(cmd string) ([]byte, error) {
|
||||||
|
Log(INFO, "run command %s", cmd)
|
||||||
|
session, err := cc.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
Log(DEBUG, "command exited with error: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
Log(DEBUG, "command exited with no error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d, err := session.CombinedOutput(cmd)
|
||||||
|
session.Close()
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shell start a login shell on server
|
||||||
|
func (cc *Client) Shell() error {
|
||||||
|
Log(DEBUG, "request new session")
|
||||||
|
session, err := cc.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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(DEBUG, "turn terminal mode to raw")
|
||||||
|
oldState, _ := terminal.MakeRaw(0)
|
||||||
|
w, h, _ := terminal.GetSize(0)
|
||||||
|
Log(DEBUG, "request pty")
|
||||||
|
if err := session.RequestPty("xterm", h, w, modes); err != nil {
|
||||||
|
Log(ERROR, "request pty error: %s", err.Error())
|
||||||
|
Log(DEBUG, "restore terminal mode")
|
||||||
|
terminal.Restore(0, oldState)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Log(DEBUG, "request shell")
|
||||||
|
if err := session.Shell(); err != nil {
|
||||||
|
Log(ERROR, "start shell error: %s", err.Error())
|
||||||
|
Log(DEBUG, "restore terminal mode")
|
||||||
|
terminal.Restore(0, oldState)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Wait()
|
||||||
|
Log(DEBUG, "session closed")
|
||||||
|
terminal.Restore(0, oldState)
|
||||||
|
Log(DEBUG, "restore terminal mode")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLocalForward add a local to remote port forward
|
||||||
|
func (cc *Client) AddLocalForward(local, remote string) error {
|
||||||
|
Log(DEBUG, "add local forward %s -> %s", local, remote)
|
||||||
|
l, err := net.Listen("tcp", local)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cc.listeners = append(cc.listeners, l)
|
||||||
|
go func(l net.Listener) {
|
||||||
|
//defer l.Close()
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
Log(DEBUG, "local listen %s closed", l.Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "connection accepted from %s", c.RemoteAddr())
|
||||||
|
go cc.handleLocalForward(c, remote)
|
||||||
|
}
|
||||||
|
}(l)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRemoteForward add a remote to local port forward
|
||||||
|
func (cc *Client) AddRemoteForward(local, remote string) error {
|
||||||
|
Log(DEBUG, "add remote forward %s -> %s", remote, local)
|
||||||
|
l, err := cc.client.Listen("tcp", remote)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.listeners = append(cc.listeners, l)
|
||||||
|
go func(l net.Listener) {
|
||||||
|
//defer l.Close()
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
Log(DEBUG, "remote listener %s closed", l.Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "accept remote forward connection from %s", c.RemoteAddr())
|
||||||
|
go cc.handleRemoteForward(c, local)
|
||||||
|
}
|
||||||
|
}(l)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDynamicForward add a dynamic port forward
|
||||||
|
func (cc *Client) AddDynamicForward(local string) error {
|
||||||
|
Log(DEBUG, "add dynamic forward %s", local)
|
||||||
|
l, err := net.Listen("tcp", local)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cc.listeners = append(cc.listeners, l)
|
||||||
|
go func(l net.Listener) {
|
||||||
|
//defer l.Close()
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
Log(DEBUG, "local listener %s closed", l.Addr())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "accept connection from %s", c.RemoteAddr())
|
||||||
|
go cc.handleDynamicForward(c)
|
||||||
|
}
|
||||||
|
}(l)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *Client) handleLocalForward(conn net.Conn, remote string) {
|
||||||
|
rconn, err := cc.client.Dial("tcp", remote)
|
||||||
|
if err != nil {
|
||||||
|
Log(ERROR, "connect to %s failed: %s", remote, err.Error())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "remote connect to %s success", remote)
|
||||||
|
PipeAndClose(rconn, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *Client) handleRemoteForward(conn net.Conn, local string) {
|
||||||
|
lconn, err := net.Dial("tcp", local)
|
||||||
|
if err != nil {
|
||||||
|
Log(ERROR, "connect to %s failed: %s", local, err.Error())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "connect to %s success", local)
|
||||||
|
PipeAndClose(conn, lconn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *Client) handleDynamicForward(conn net.Conn) {
|
||||||
|
s := socks.SocksConn{conn, cc.client.Dial}
|
||||||
|
s.Serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *Client) keepAlive(interval time.Duration, maxCount int) {
|
||||||
|
count := 0
|
||||||
|
c := time.NewTicker(interval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.C:
|
||||||
|
_, _, err := cc.sshConn.SendRequest("keepalive@openssh.org", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
Log(DEBUG, "keep alive error: %s", err.Error())
|
||||||
|
count++
|
||||||
|
} else {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
if count >= maxCount {
|
||||||
|
Log(ERROR, "keep alive hit max count, exit")
|
||||||
|
cc.sshConn.Close()
|
||||||
|
// send exit signal
|
||||||
|
select {
|
||||||
|
case cc.ch <- 1:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *Client) registerSignal() {
|
||||||
|
c := make(chan os.Signal, 5)
|
||||||
|
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
select {
|
||||||
|
case s1 := <-c:
|
||||||
|
Log(ERROR, "signal %d received, exit", s1)
|
||||||
|
select {
|
||||||
|
case cc.ch <- 1:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,284 @@
|
|||||||
|
package obfssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rc4"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
//"log"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyLength = 16
|
||||||
|
seedLength = 16
|
||||||
|
maxPadding = 1024
|
||||||
|
magicValue uint32 = 0x0BF5CA7E
|
||||||
|
loopCount = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObfsConn implement the net.Conn interface which enrytp/decrpt
|
||||||
|
// the data automatic
|
||||||
|
type ObfsConn struct {
|
||||||
|
net.Conn
|
||||||
|
key []byte
|
||||||
|
cipherRead cipher.Stream
|
||||||
|
cipherWrite cipher.Stream
|
||||||
|
cipherDisabled bool
|
||||||
|
method string
|
||||||
|
writeBuf []byte
|
||||||
|
writeBufLen int
|
||||||
|
//isServer bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewObfsConn initial a ObfsConn
|
||||||
|
// after new return, seed handshake is done
|
||||||
|
func NewObfsConn(c net.Conn, method, key string, isServer bool) (*ObfsConn, error) {
|
||||||
|
|
||||||
|
wc := &ObfsConn{
|
||||||
|
Conn: c,
|
||||||
|
key: []byte(key),
|
||||||
|
cipherDisabled: false,
|
||||||
|
method: method,
|
||||||
|
writeBuf: make([]byte, 8192),
|
||||||
|
writeBufLen: 8192,
|
||||||
|
// isServer: isServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not initial chiper when encrypt method is empty or none
|
||||||
|
if method == "" || method == "none" {
|
||||||
|
wc.DisableObfs()
|
||||||
|
return wc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isServer {
|
||||||
|
if err := wc.readSeed(); err != nil {
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
Log(DEBUG, "read forever")
|
||||||
|
// read forever
|
||||||
|
for {
|
||||||
|
if _, err1 := wc.Conn.Read(buf); err1 != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := wc.writeSeed(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateKey(seed, keyword, iv []byte) []byte {
|
||||||
|
buf := make([]byte, seedLength+len(keyword)+len(iv))
|
||||||
|
|
||||||
|
copy(buf[0:], seed)
|
||||||
|
|
||||||
|
// user key
|
||||||
|
if keyword != nil {
|
||||||
|
copy(buf[seedLength:], keyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(buf[seedLength+len(keyword):], iv)
|
||||||
|
|
||||||
|
o := sha512.Sum512(buf[0:])
|
||||||
|
|
||||||
|
for i := 0; i < loopCount; i++ {
|
||||||
|
o = sha512.Sum512(o[0:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return o[0:keyLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableObfs enable the encryption
|
||||||
|
func (wc *ObfsConn) EnableObfs() {
|
||||||
|
Log(DEBUG, "enable the encryption")
|
||||||
|
wc.cipherDisabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableObfs disable the encryption
|
||||||
|
func (wc *ObfsConn) DisableObfs() {
|
||||||
|
Log(DEBUG, "disable the encryption")
|
||||||
|
wc.cipherDisabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *ObfsConn) writeSeed() error {
|
||||||
|
Log(DEBUG, "begin to write the seed")
|
||||||
|
|
||||||
|
ii, err := rand.Int(rand.Reader, big.NewInt(int64(maxPadding)))
|
||||||
|
if err != nil {
|
||||||
|
//Log(ERROR, "initial the random seed failed: %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i := ii.Int64()
|
||||||
|
Log(DEBUG, "use padding data length %d\n", int(i))
|
||||||
|
buf := make([]byte, seedLength+8+int(i))
|
||||||
|
|
||||||
|
// generate seed
|
||||||
|
rand.Read(buf[0:seedLength])
|
||||||
|
|
||||||
|
// put magic value
|
||||||
|
binary.BigEndian.PutUint32(buf[seedLength:seedLength+4], magicValue)
|
||||||
|
|
||||||
|
// put padding length
|
||||||
|
binary.BigEndian.PutUint32(buf[seedLength+4:seedLength+8], uint32(i))
|
||||||
|
|
||||||
|
// generate padding data
|
||||||
|
rand.Read(buf[24:])
|
||||||
|
|
||||||
|
// generate the key
|
||||||
|
keyToServer := generateKey(buf[0:seedLength], wc.key, []byte("client_to_server"))
|
||||||
|
keyToClient := generateKey(buf[0:seedLength], wc.key, []byte("server_to_client"))
|
||||||
|
|
||||||
|
var r, w cipher.Stream
|
||||||
|
|
||||||
|
// initial the cipher
|
||||||
|
switch strings.ToLower(wc.method) {
|
||||||
|
case "aes":
|
||||||
|
w, r = newAESCipher(keyToServer, keyToClient)
|
||||||
|
case "rc4":
|
||||||
|
w, r = newRC4Cipher(keyToServer, keyToClient)
|
||||||
|
default:
|
||||||
|
return errors.New("unknown cipher type")
|
||||||
|
}
|
||||||
|
|
||||||
|
wc.cipherWrite = w
|
||||||
|
wc.cipherRead = r
|
||||||
|
|
||||||
|
// encrypt the data, except the seed
|
||||||
|
wc.cipherWrite.XORKeyStream(buf[seedLength:], buf[seedLength:])
|
||||||
|
|
||||||
|
_, err = wc.Conn.Write(buf[0:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(DEBUG, "write seed done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *ObfsConn) readSeed() error {
|
||||||
|
Log(DEBUG, "begin to read the seed")
|
||||||
|
buf := make([]byte, seedLength+8)
|
||||||
|
|
||||||
|
// read the data except padding
|
||||||
|
_, err := io.ReadFull(wc.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the key
|
||||||
|
keyToServer := generateKey(buf[0:seedLength], wc.key, []byte("client_to_server"))
|
||||||
|
keyToClient := generateKey(buf[0:seedLength], wc.key, []byte("server_to_client"))
|
||||||
|
|
||||||
|
var w, r cipher.Stream
|
||||||
|
switch strings.ToLower(wc.method) {
|
||||||
|
case "aes":
|
||||||
|
w, r = newAESCipher(keyToClient, keyToServer)
|
||||||
|
case "rc4":
|
||||||
|
w, r = newRC4Cipher(keyToClient, keyToServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
wc.cipherWrite = w
|
||||||
|
wc.cipherRead = r
|
||||||
|
|
||||||
|
// decrypt the magic and padding length
|
||||||
|
wc.cipherRead.XORKeyStream(buf[seedLength:seedLength+8], buf[seedLength:seedLength+8])
|
||||||
|
|
||||||
|
// check magic value
|
||||||
|
magic := binary.BigEndian.Uint32(buf[seedLength : seedLength+4])
|
||||||
|
if magic != magicValue {
|
||||||
|
Log(ERROR, "magic %x check failed from %s", magic, wc.Conn.RemoteAddr())
|
||||||
|
return errors.New("wrong magic value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the padding data
|
||||||
|
padLen := binary.BigEndian.Uint32(buf[seedLength+4 : seedLength+8])
|
||||||
|
|
||||||
|
Log(DEBUG, "padding %d", padLen)
|
||||||
|
|
||||||
|
buf = make([]byte, padLen)
|
||||||
|
if _, err := io.ReadFull(wc, buf[0:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(DEBUG, "read seed done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read read the data from underlying connection
|
||||||
|
// if encryption enabled, decrypt the data and return to plain data to upstream
|
||||||
|
func (wc *ObfsConn) Read(buf []byte) (int, error) {
|
||||||
|
n, err := wc.Conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !wc.cipherDisabled {
|
||||||
|
wc.cipherRead.XORKeyStream(buf[0:n], buf[0:n])
|
||||||
|
}
|
||||||
|
//log.Printf("%+q", buf[0:n])
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write write the data to underlying connection
|
||||||
|
// if encryption enabled, encrypt it before write
|
||||||
|
func (wc *ObfsConn) Write(buf []byte) (int, error) {
|
||||||
|
if !wc.cipherDisabled {
|
||||||
|
bufLen := len(buf)
|
||||||
|
if bufLen > wc.writeBufLen {
|
||||||
|
wc.writeBufLen = bufLen + 8192
|
||||||
|
wc.writeBuf = make([]byte, wc.writeBufLen)
|
||||||
|
}
|
||||||
|
wc.cipherWrite.XORKeyStream(wc.writeBuf[0:bufLen], buf[0:bufLen])
|
||||||
|
return wc.Conn.Write(wc.writeBuf[0:bufLen])
|
||||||
|
}
|
||||||
|
return wc.Conn.Write(buf[0:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAESCipher(key1, key2 []byte) (cipher.Stream, cipher.Stream) {
|
||||||
|
b1, _ := aes.NewCipher(key1)
|
||||||
|
b2, _ := aes.NewCipher(key2)
|
||||||
|
|
||||||
|
m1 := sha1.Sum(key1)
|
||||||
|
iv1 := md5.Sum(m1[0:])
|
||||||
|
|
||||||
|
m2 := sha1.Sum(key2)
|
||||||
|
iv2 := md5.Sum(m2[0:])
|
||||||
|
|
||||||
|
w := cipher.NewCFBEncrypter(b1, iv1[0:])
|
||||||
|
r := cipher.NewCFBDecrypter(b2, iv2[0:])
|
||||||
|
return w, r
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRC4Cipher(key1, key2 []byte) (cipher.Stream, cipher.Stream) {
|
||||||
|
w, _ := rc4.NewCipher(key1)
|
||||||
|
r, _ := rc4.NewCipher(key2)
|
||||||
|
return w, r
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimedOutConn is a net.Conn with read/write timeout set
|
||||||
|
type TimedOutConn struct {
|
||||||
|
net.Conn
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TimedOutConn) Read(b []byte) (int, error) {
|
||||||
|
tc.Conn.SetDeadline(time.Now().Add(tc.Timeout))
|
||||||
|
return tc.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TimedOutConn) Write(b []byte) (int, error) {
|
||||||
|
tc.Conn.SetDeadline(time.Now().Add(tc.Timeout))
|
||||||
|
return tc.Conn.Write(b)
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package obfssh
|
||||||
|
|
||||||
|
/*
|
||||||
|
obfssh is wrapper for ssh protocol, use AES or RC4 to encrypt the transport data,
|
||||||
|
ssh is a good designed protocol and with the good encryption, but the protocol has a especially figerprint,
|
||||||
|
the firewall can easily identify the protocol and block it or QOS it, especial when we use its port forward function to escape from the state censorship.
|
||||||
|
|
||||||
|
obfssh encrypt the ssh protocol and hide the figerprint, the firewall can not identify the protocol.
|
||||||
|
|
||||||
|
We borrow the idea from https://github.com/brl/obfuscated-openssh, but not compatible with it,
|
||||||
|
beause the limitions of golang ssh library.
|
||||||
|
|
||||||
|
server usage example
|
||||||
|
|
||||||
|
import "github.com/fangdingjun/obfssh"
|
||||||
|
import "golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
// key for encryption
|
||||||
|
obfs_key := "some keyword"
|
||||||
|
|
||||||
|
// encrypt method
|
||||||
|
obfs_method := "rc4"
|
||||||
|
|
||||||
|
config := &ssh.ServerConfig{
|
||||||
|
// add ssh server configure here
|
||||||
|
// for example auth method, cipher, MAC
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.Listen(":2022")
|
||||||
|
c, err := l.Accept()
|
||||||
|
|
||||||
|
sc, err := obfssh.NewServer(c, config, obfs_method, obfs_key)
|
||||||
|
|
||||||
|
sc.Run()
|
||||||
|
|
||||||
|
|
||||||
|
client usage example
|
||||||
|
|
||||||
|
import "github.com/fangdingjun/obfssh"
|
||||||
|
import "golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
addr := "localhost:2022"
|
||||||
|
|
||||||
|
// key for encryption
|
||||||
|
obfs_key := "some keyword"
|
||||||
|
|
||||||
|
// encrypt method
|
||||||
|
obfs_method := "rc4"
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
// add ssh client config here
|
||||||
|
// for example auth method
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := net.Dial("tcp", addr)
|
||||||
|
|
||||||
|
// create connection
|
||||||
|
client, err := obfssh.NewClient(c, config, addr, obfs_method, obfs_key)
|
||||||
|
|
||||||
|
// local to remote port forward
|
||||||
|
client.AddLocalForward(":2234:10.0.0.1:3221")
|
||||||
|
|
||||||
|
// remote to local port forward
|
||||||
|
client.AddRemoteForward(":2234:10.2.0.1:3221")
|
||||||
|
|
||||||
|
// dynamic port forward
|
||||||
|
client.AddDynamicForward(":4321")
|
||||||
|
|
||||||
|
// wait to be done
|
||||||
|
client.Run()
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
@ -0,0 +1,2 @@
|
|||||||
|
obfssh_client*
|
||||||
|
run.bat
|
@ -0,0 +1,29 @@
|
|||||||
|
obfssh\_client
|
||||||
|
=============
|
||||||
|
|
||||||
|
this is obfssh\_client example
|
||||||
|
|
||||||
|
|
||||||
|
usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
run server
|
||||||
|
|
||||||
|
go get github.com/fangdingjun/obfssh/obfssh_server
|
||||||
|
|
||||||
|
cp $GOPATH/src/github.com/fangdingjun/obfssh/obfssh_server/config_example.yaml config.yaml
|
||||||
|
|
||||||
|
vim config.yaml
|
||||||
|
|
||||||
|
ssh-keygen -f ssh_host_rsa_key -t rsa 1024
|
||||||
|
|
||||||
|
$GOPATH/bin/obfssh_server -c config.yaml
|
||||||
|
|
||||||
|
|
||||||
|
run client
|
||||||
|
|
||||||
|
go get github.com/fangdingjun/obfssh/obfssh_client
|
||||||
|
|
||||||
|
$GOPATH/bin/obfssh_client -N -D :1234 -obfs_key some_keyworld -obfs_method rc4 -p 2022 -l user2 -pw user2 localhost
|
||||||
|
|
||||||
|
this will create a socks proxy on :1234
|
@ -0,0 +1,228 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fangdingjun/obfssh"
|
||||||
|
"github.com/golang/crypto/ssh"
|
||||||
|
"github.com/golang/crypto/ssh/agent"
|
||||||
|
//"github.com/golang/crypto/ssh/terminal"
|
||||||
|
"time"
|
||||||
|
//"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
//"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
//"sync"
|
||||||
|
//"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var method, encryptKey string
|
||||||
|
|
||||||
|
type stringSlice []string
|
||||||
|
|
||||||
|
func (lf *stringSlice) Set(val string) error {
|
||||||
|
*lf = append(*lf, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lf *stringSlice) String() string {
|
||||||
|
s := ""
|
||||||
|
if lf == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
for _, v := range *lf {
|
||||||
|
s += " "
|
||||||
|
s += v
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var localForwards stringSlice
|
||||||
|
var remoteForwards stringSlice
|
||||||
|
var dynamicForwards stringSlice
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var host, port, user, pass, key string
|
||||||
|
//var localForward, remoteForward, dynamicForward string
|
||||||
|
var notRunCmd bool
|
||||||
|
var debug bool
|
||||||
|
|
||||||
|
flag.StringVar(&user, "l", os.Getenv("USER"), "ssh username")
|
||||||
|
flag.StringVar(&pass, "pw", "", "ssh password")
|
||||||
|
flag.StringVar(&port, "p", "22", "remote port")
|
||||||
|
flag.StringVar(&key, "i", "", "private key file")
|
||||||
|
flag.Var(&localForwards, "L", "forward local port to remote, format [local_host:]local_port:remote_host:remote_port")
|
||||||
|
flag.Var(&remoteForwards, "R", "forward remote port to local, format [remote_host:]remote_port:local_host:local_port")
|
||||||
|
flag.BoolVar(¬RunCmd, "N", false, "not run remote command, useful when do port forward")
|
||||||
|
flag.Var(&dynamicForwards, "D", "enable dynamic forward, format [local_host:]local_port")
|
||||||
|
flag.StringVar(&method, "obfs_method", "", "transport encrypt method, avaliable: rc4, aes, empty means disable encrypt")
|
||||||
|
flag.StringVar(&encryptKey, "obfs_key", "", "transport encrypt key")
|
||||||
|
flag.BoolVar(&debug, "d", false, "verbose mode")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
obfssh.SSHLogLevel = obfssh.DEBUG
|
||||||
|
}
|
||||||
|
auth := []ssh.AuthMethod{}
|
||||||
|
|
||||||
|
// read ssh agent and default auth key
|
||||||
|
if pass == "" && key == "" {
|
||||||
|
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
obfssh.Log(obfssh.DEBUG, "add auth method with agent %s", os.Getenv("SSH_AUTH_SOCK"))
|
||||||
|
auth = append(auth, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||||
|
}
|
||||||
|
|
||||||
|
home := os.Getenv("HOME")
|
||||||
|
for _, f := range []string{
|
||||||
|
".ssh/id_rsa",
|
||||||
|
".ssh/id_dsa",
|
||||||
|
".ssh/identity",
|
||||||
|
".ssh/id_ecdsa",
|
||||||
|
".ssh/id_ed25519",
|
||||||
|
} {
|
||||||
|
k1 := filepath.Join(home, f)
|
||||||
|
if pemBytes, err := ioutil.ReadFile(k1); err == nil {
|
||||||
|
if priKey, err := ssh.ParsePrivateKey(pemBytes); err == nil {
|
||||||
|
obfssh.Log(obfssh.DEBUG, "add private key: %s", k1)
|
||||||
|
auth = append(auth, ssh.PublicKeys(priKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
var cmd string
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
flag.PrintDefaults()
|
||||||
|
log.Fatal("you must specify the remote host")
|
||||||
|
case 1:
|
||||||
|
host = args[0]
|
||||||
|
cmd = ""
|
||||||
|
default:
|
||||||
|
host = args[0]
|
||||||
|
cmd = strings.Join(args[1:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(host, "@") {
|
||||||
|
ss := strings.SplitN(host, "@", 2)
|
||||||
|
user = ss[0]
|
||||||
|
host = ss[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass != "" {
|
||||||
|
obfssh.Log(obfssh.DEBUG, "add password auth method")
|
||||||
|
auth = append(auth, ssh.Password(pass))
|
||||||
|
}
|
||||||
|
|
||||||
|
if key != "" {
|
||||||
|
pemBytes, err := ioutil.ReadFile(key)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
priKey, err := ssh.ParsePrivateKey(pemBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
obfssh.Log(obfssh.DEBUG, "add private key %s", key)
|
||||||
|
auth = append(auth, ssh.PublicKeys(priKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: user,
|
||||||
|
Auth: auth,
|
||||||
|
Timeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
h := net.JoinHostPort(host, port)
|
||||||
|
c, err := net.Dial("tcp", h)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := obfssh.NewClient(c, config, h, method, encryptKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
var local, remote string
|
||||||
|
for _, p := range localForwards {
|
||||||
|
addr := parseForwardAddr(p)
|
||||||
|
if len(addr) != 4 && len(addr) != 3 {
|
||||||
|
log.Printf("wrong forward addr %s, format: [local_host:]local_port:remote_host:remote_port", p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(addr) == 4 {
|
||||||
|
local = strings.Join(addr[:2], ":")
|
||||||
|
remote = strings.Join(addr[2:], ":")
|
||||||
|
} else {
|
||||||
|
local = fmt.Sprintf(":%s", addr[0])
|
||||||
|
remote = strings.Join(addr[1:], ":")
|
||||||
|
}
|
||||||
|
//log.Printf("add local to remote %s->%s", local, remote)
|
||||||
|
if err := client.AddLocalForward(local, remote); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range remoteForwards {
|
||||||
|
addr := parseForwardAddr(p)
|
||||||
|
if len(addr) != 4 && len(addr) != 3 {
|
||||||
|
log.Printf("wrong forward addr %s, format: [local_host:]local_port:remote_host:remote_port", p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(addr) == 4 {
|
||||||
|
remote = strings.Join(addr[:2], ":")
|
||||||
|
local = strings.Join(addr[2:], ":")
|
||||||
|
} else {
|
||||||
|
remote = fmt.Sprintf("0.0.0.0:%s", addr[0])
|
||||||
|
local = strings.Join(addr[1:], ":")
|
||||||
|
}
|
||||||
|
//log.Printf("add remote to local %s->%s", remote, local)
|
||||||
|
if err := client.AddRemoteForward(local, remote); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range dynamicForwards {
|
||||||
|
|
||||||
|
if strings.Index(p, ":") == -1 {
|
||||||
|
local = fmt.Sprintf(":%s", p)
|
||||||
|
} else {
|
||||||
|
local = p
|
||||||
|
}
|
||||||
|
//log.Printf("listen on %s", local)
|
||||||
|
if err := client.AddDynamicForward(local); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !notRunCmd {
|
||||||
|
if cmd != "" {
|
||||||
|
if d, err := client.RunCmd(cmd); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
//log.Printf("%s", string(d))
|
||||||
|
fmt.Printf("%s", string(d))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := client.Shell(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseForwardAddr(s string) []string {
|
||||||
|
ss := strings.FieldsFunc(s, func(c rune) bool {
|
||||||
|
if c == ':' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return ss
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
obfssh_server*
|
||||||
|
authorized_keys
|
@ -0,0 +1,29 @@
|
|||||||
|
obfssh\_server
|
||||||
|
=============
|
||||||
|
|
||||||
|
this is obfssh\_server example
|
||||||
|
|
||||||
|
|
||||||
|
usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
run server
|
||||||
|
|
||||||
|
go get github.com/fangdingjun/obfssh/obfssh_server
|
||||||
|
|
||||||
|
cp $GOPATH/src/github.com/fangdingjun/obfssh/obfssh_server/config_example.yaml config.yaml
|
||||||
|
|
||||||
|
vim config.yaml
|
||||||
|
|
||||||
|
ssh-keygen -f ssh_host_rsa_key -t rsa 1024
|
||||||
|
|
||||||
|
$GOPATH/bin/obfssh_server -c config.yaml
|
||||||
|
|
||||||
|
|
||||||
|
run client
|
||||||
|
|
||||||
|
go get github.com/fangdingjun/obfssh/obfssh_client
|
||||||
|
|
||||||
|
$GOPATH/bin/obfssh_client -N -D :1234 -obfs_key some_keyworld -obfs_method rc4 -p 2022 -l user2 -pw user2 localhost
|
||||||
|
|
||||||
|
this will create a socks proxy on :1234
|
@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/go-yaml/yaml"
|
||||||
|
"github.com/golang/crypto/ssh"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Key string `yaml:"obfs_key"`
|
||||||
|
Debug bool `yaml:"debug"`
|
||||||
|
HostKey string `yaml:"host_key_file"`
|
||||||
|
Method string `yaml:"obfs_method"`
|
||||||
|
Users []serverUser `yaml:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverUser struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
AuthorizedKeyFile string `yaml:"authorized_key_file"`
|
||||||
|
publicKeys []ssh.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *serverConfig) getUser(user string) (serverUser, error) {
|
||||||
|
for _, u := range c.Users {
|
||||||
|
if u.Username == user {
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serverUser{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(f string) (*serverConfig, error) {
|
||||||
|
buf, err := ioutil.ReadFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var c serverConfig
|
||||||
|
if err := yaml.Unmarshal(buf, &c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range c.Users {
|
||||||
|
buf1, err := ioutil.ReadFile(c.Users[i].AuthorizedKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read publickey for %s failed, ignore", c.Users[i].Username)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse authorized_key
|
||||||
|
//var err error
|
||||||
|
var p1 ssh.PublicKey
|
||||||
|
r := bytes.Trim(buf1, " \r\n")
|
||||||
|
for {
|
||||||
|
p1, _, _, r, err = ssh.ParseAuthorizedKey(r)
|
||||||
|
if err != nil {
|
||||||
|
//log.Println(err)
|
||||||
|
//log.Printf("%+v %+v", r, p1)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.Users[i].publicKeys = append(c.Users[i].publicKeys, p1)
|
||||||
|
if len(r) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
# vim: set ft=yaml:
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
# listen port
|
||||||
|
port: 2022
|
||||||
|
|
||||||
|
# the key to encrypt the transport data
|
||||||
|
obfs_key: some_keyword
|
||||||
|
|
||||||
|
# ssh host key file
|
||||||
|
host_key_file: ssh_host_rsa_key
|
||||||
|
|
||||||
|
# the method to encrypt the transport data
|
||||||
|
# avaiable methods: rc4, aes, none or ""
|
||||||
|
# none or "" means disable the obfs encrypt
|
||||||
|
#obfs_method: rc4
|
||||||
|
obfs_method: "rc4"
|
||||||
|
|
||||||
|
# show more log message
|
||||||
|
# value true or false
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
# the users used by ssh server
|
||||||
|
# user can authorite by passwrod or by public key
|
||||||
|
# public key as same as OpenSSH
|
||||||
|
# public key or password must be specify one
|
||||||
|
#
|
||||||
|
users:
|
||||||
|
-
|
||||||
|
# username
|
||||||
|
username: user1
|
||||||
|
# password, empty password means disable password authorize
|
||||||
|
password: ""
|
||||||
|
# public key file
|
||||||
|
authorized_key_file: /path/to/user/authorized_keys
|
||||||
|
-
|
||||||
|
username: user2
|
||||||
|
password: user2
|
||||||
|
authorized_key_file: /path/to/user/authorized_keys
|
||||||
|
-
|
||||||
|
username: user3
|
||||||
|
password: ""
|
||||||
|
authorized_key_file: /path/to/authorized_keys
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"github.com/go-yaml/yaml"
|
||||||
|
//"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
c, err := loadConfig("config_example.yaml")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Printf("%+v", c)
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fangdingjun/obfssh"
|
||||||
|
"github.com/golang/crypto/ssh"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
var configfile string
|
||||||
|
|
||||||
|
flag.StringVar(&configfile, "c", "config.yaml", "configure file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
conf, err := loadConfig(configfile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set log level
|
||||||
|
if conf.Debug {
|
||||||
|
obfssh.SSHLogLevel = obfssh.DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &ssh.ServerConfig{
|
||||||
|
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||||
|
if u, err := conf.getUser(c.User()); err == nil {
|
||||||
|
if u.Password != "" && c.User() == u.Username && string(pass) == u.Password {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("password reject for user %s", c.User())
|
||||||
|
},
|
||||||
|
|
||||||
|
PublicKeyCallback: func(c ssh.ConnMetadata, k ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
|
if u, err := conf.getUser(c.User()); err == nil {
|
||||||
|
for _, pk := range u.publicKeys {
|
||||||
|
if k.Type() == pk.Type() && bytes.Compare(k.Marshal(), pk.Marshal()) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("publickey reject for user %s", c.User())
|
||||||
|
},
|
||||||
|
|
||||||
|
// auth log
|
||||||
|
AuthLogCallback: func(c ssh.ConnMetadata, method string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
obfssh.Log(obfssh.ERROR, "%s", err.Error())
|
||||||
|
obfssh.Log(obfssh.ERROR, "%s auth failed for %s from %s", method, c.User(), c.RemoteAddr())
|
||||||
|
} else {
|
||||||
|
obfssh.Log(obfssh.INFO, "Accepted %s for user %s from %s", method, c.User(), c.RemoteAddr())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
privateBytes, err := ioutil.ReadFile(conf.HostKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.AddHostKey(private)
|
||||||
|
l, err := net.Listen("tcp", fmt.Sprintf(":%d", conf.Port))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obfssh.Log(obfssh.DEBUG, "accept tcp connection from %s", c.RemoteAddr())
|
||||||
|
|
||||||
|
go func(c net.Conn) {
|
||||||
|
sc, err := obfssh.NewServer(c, config, conf.Method, conf.Key)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
obfssh.Log(obfssh.ERROR, "%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sc.Run()
|
||||||
|
}(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA2n/6710+F060gDjqmHXWwBgKKKhykVVH91PcBW9txzCG2HHr
|
||||||
|
dM0RV7GmC703TVTaJZjHb8xWQCLrUorDEdabKte49QrsM5F6tjTqPG+LEazFUmyI
|
||||||
|
EH8/B4ISIKRzW1+G75dFx28CTC8Zr1Ue6PJRDuBJYOJA104EuIxS3fCNiQU0oD/A
|
||||||
|
lkbfl90FmFjth47Pu/hdHN+kbfOA9TO2cpztgjz+6vEd9JjNuc9xR83okfXPjI4i
|
||||||
|
RvjPk4K70LGCGyPkIjbK1vu5zDffwmROGi9BBOR2X17LnCsez2GDP6paz7sIwOOU
|
||||||
|
kikqxm2fjSfOjS1kBB0Yc2o3w7kZ4CTHO/U6BwIDAQABAoIBAQCflpg2Wjkultq5
|
||||||
|
SFj4cCEg/q300kuToOFGYSazhZZ9xRDIaDRchCclkOhBbLtGrTIEAdmw62MXxylv
|
||||||
|
iVA+6Cs/GH7L42VvqNMi3/UxnRrLFxCsSf77ZaUom7UXyGVFGLmapzddGdRoxoIR
|
||||||
|
EK/Z3pqbre+KZtaLKz3BeSRRXBBUQXJopvvfh7UNr/wFQQmLzqFWUO8ommxr6J3w
|
||||||
|
a1JhVPS1MGmxn18teQyB6I9xtw347MAt7NQ1ZQXPbCjr/v1ZNt4bLQrae/S/jWNc
|
||||||
|
4/WmxnK4fAhHNdBjofNKa+SLbcoDkHtUBZquh418oPNE6BUn14qocKlUPASYS+5E
|
||||||
|
zC5UCxzRAoGBAPRylOjt5OTAhl5vMC7EFESK8Wzma2WBZnrjkP+u8NFOLo3cRcvj
|
||||||
|
+jsTO1ilKR7PQJfeU/NBWrIU+wgm22nMGlayHAXrtbueN9/devURbOb7md75AGvS
|
||||||
|
DB0mB4Acuxqn4a2ThTjDi+H6+Oqz0Ab7ps3Wm6cFH0ZRlREE0woAljmJAoGBAOTT
|
||||||
|
eVkJmSzNLqEDtKuBdysTJp+j0hhUediWh8rallvo5e9zMrXJksfAstHJcR8NkqLD
|
||||||
|
VL9K4Vydudw70efPg8siSyNDUjDXCy18Vl3x+xqIXsFVXGybTpLIGj22irKX8ZQZ
|
||||||
|
qpZhHBbFw1terQCFdkAt+B6skJUU4zeiE1uQEUMPAoGAJwt2RY5aFT+7NrJD2/Rt
|
||||||
|
2FTpIx/a36e/mrlmm7BxvrziKr6YV2zetzjnLc2Tt9wa0Sct+Zjix7caMb8jJM75
|
||||||
|
Fgf0+e0gZgtrmVJjJWnXHz3o4fib3Jz8WluMryXnrOZL4dHCYcK6QSo5QCPggn0H
|
||||||
|
s7Enw5HJ4Q1+5e0DWIGnfSECgYEAio1JkpHvP2NVcoUN5jLT9y73Wf4VfknYJT6w
|
||||||
|
JjHIjQot/5ifAdd1mqGhJMl2RzkuqoLfU5yBbFTMbv+Bj3zk7iBrooRmxc/PotEA
|
||||||
|
co3MXzpnNWT8O36mStYCnY9j19OMoQIRelB+c4N3UGG5GvG0shOjgt82BC7LjaoD
|
||||||
|
UpOfAB0CgYBHuda3elvCalfDevia3ZaKKw/JT0CS4J0DKkrWk97Yet/MaDE0k/wk
|
||||||
|
ammJCNckfGI5nH7m6Ljd6A5cKjL3i5TgvMUjNz8qb9f6GiK5fBxLzO3KhgnsoIft
|
||||||
|
r7VjCr/nYvvM4asYwSdoHA6P2XKtOsuB7XEFL4k2EJ3WvCyYcjMvOw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -0,0 +1,300 @@
|
|||||||
|
package obfssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang/crypto/ssh"
|
||||||
|
"github.com/golang/crypto/ssh/terminal"
|
||||||
|
//"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is server connection
|
||||||
|
type Server struct {
|
||||||
|
conn net.Conn
|
||||||
|
sshConn *ssh.ServerConn
|
||||||
|
forwardedPorts map[string]net.Listener
|
||||||
|
exitCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer create a new struct for Server
|
||||||
|
//
|
||||||
|
// c is net.Conn
|
||||||
|
//
|
||||||
|
// config is &ssh.ServerConfig
|
||||||
|
//
|
||||||
|
// method is obfs encrypt method, value is rc4, aes or none or ""
|
||||||
|
//
|
||||||
|
// key is obfs encrypt key
|
||||||
|
//
|
||||||
|
// if set method to none or "", means disable obfs encryption, when the obfs is disabled,
|
||||||
|
// the server can accept connection from standard ssh client, like OpenSSH client
|
||||||
|
//
|
||||||
|
func NewServer(c net.Conn, config *ssh.ServerConfig, method, key string) (*Server, error) {
|
||||||
|
wc, err := NewObfsConn(c, method, key, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sshConn, ch, req, err := ssh.NewServerConn(wc, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//wc.DisableObfs()
|
||||||
|
sc := &Server{conn: c,
|
||||||
|
sshConn: sshConn,
|
||||||
|
forwardedPorts: map[string]net.Listener{},
|
||||||
|
exitCh: make(chan int)}
|
||||||
|
go sc.handleGlobalRequest(req)
|
||||||
|
go sc.handleNewChannelRequest(ch)
|
||||||
|
return sc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run waits for server connection finish
|
||||||
|
func (sc *Server) Run() {
|
||||||
|
sc.sshConn.Wait()
|
||||||
|
Log(DEBUG, "ssh connection closed")
|
||||||
|
sc.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) close() {
|
||||||
|
Log(DEBUG, "close connection")
|
||||||
|
sc.sshConn.Close()
|
||||||
|
//Log(DEBUG, "close listener")
|
||||||
|
for _, l := range sc.forwardedPorts {
|
||||||
|
Log(DEBUG, "close listener %s", l.Addr())
|
||||||
|
l.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) handleNewChannelRequest(ch <-chan ssh.NewChannel) {
|
||||||
|
for newch := range ch {
|
||||||
|
switch newch.ChannelType() {
|
||||||
|
case "session":
|
||||||
|
//go sc.handleSession(newch)
|
||||||
|
//continue
|
||||||
|
case "direct-tcpip":
|
||||||
|
go handleDirectTcpip(newch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Log(DEBUG, "reject channel request %s", newch.ChannelType())
|
||||||
|
newch.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||||
|
//channel, request, err := newch.Accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) handleGlobalRequest(req <-chan *ssh.Request) {
|
||||||
|
for r := range req {
|
||||||
|
switch r.Type {
|
||||||
|
case "tcpip-forward":
|
||||||
|
Log(DEBUG, "request port forward")
|
||||||
|
go sc.handleTcpipForward(r)
|
||||||
|
continue
|
||||||
|
case "cancel-tcpip-forward":
|
||||||
|
Log(DEBUG, "request cancel port forward")
|
||||||
|
go sc.handleCancelTcpipForward(r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Log(DEBUG, "global request %s", r.Type)
|
||||||
|
if r.WantReply {
|
||||||
|
r.Reply(false, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) handleChannelRequest(req <-chan *ssh.Request) {
|
||||||
|
ret := false
|
||||||
|
for r := range req {
|
||||||
|
switch r.Type {
|
||||||
|
case "shell":
|
||||||
|
ret = true
|
||||||
|
case "pty-req":
|
||||||
|
ret = true
|
||||||
|
case "env":
|
||||||
|
ret = true
|
||||||
|
case "exec":
|
||||||
|
ret = false
|
||||||
|
case "subsystem":
|
||||||
|
default:
|
||||||
|
ret = false
|
||||||
|
}
|
||||||
|
if r.WantReply {
|
||||||
|
r.Reply(ret, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type directTcpipMsg struct {
|
||||||
|
Raddr string
|
||||||
|
Rport uint32
|
||||||
|
Laddr string
|
||||||
|
Lport uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) handleSession(newch ssh.NewChannel) {
|
||||||
|
ch, req, err := newch.Accept()
|
||||||
|
if err != nil {
|
||||||
|
Log(ERROR, "%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go sc.handleChannelRequest(req)
|
||||||
|
term := terminal.NewTerminal(ch, "shell>")
|
||||||
|
defer ch.Close()
|
||||||
|
for {
|
||||||
|
line, err := term.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
term.Write([]byte(line))
|
||||||
|
term.Write([]byte("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDirectTcpip(newch ssh.NewChannel) {
|
||||||
|
data := newch.ExtraData()
|
||||||
|
var r directTcpipMsg
|
||||||
|
err := ssh.Unmarshal(data, &r)
|
||||||
|
if err != nil {
|
||||||
|
Log(DEBUG, "invalid ssh parameter")
|
||||||
|
newch.Reject(ssh.ConnectionFailed, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "create connection to %s:%d", r.Raddr, r.Rport)
|
||||||
|
rconn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", r.Raddr, r.Rport))
|
||||||
|
if err != nil {
|
||||||
|
Log(ERROR, "%s", err.Error())
|
||||||
|
newch.Reject(ssh.ConnectionFailed, "invalid argument")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channel, requests, err := newch.Accept()
|
||||||
|
if err != nil {
|
||||||
|
rconn.Close()
|
||||||
|
Log(ERROR, "%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//log.Println("forward")
|
||||||
|
go ssh.DiscardRequests(requests)
|
||||||
|
PipeAndClose(channel, rconn)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcpipForwardAddr struct {
|
||||||
|
Addr string
|
||||||
|
Port uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) handleCancelTcpipForward(req *ssh.Request) {
|
||||||
|
var a tcpipForwardAddr
|
||||||
|
|
||||||
|
if err := ssh.Unmarshal(req.Payload, &a); err != nil {
|
||||||
|
Log(ERROR, "invalid ssh parameter for cancel port forward")
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k := fmt.Sprintf("%s:%d", a.Addr, a.Port)
|
||||||
|
if l, ok := sc.forwardedPorts[k]; ok {
|
||||||
|
l.Close()
|
||||||
|
delete(sc.forwardedPorts, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *Server) handleTcpipForward(req *ssh.Request) {
|
||||||
|
var addr tcpipForwardAddr
|
||||||
|
if err := ssh.Unmarshal(req.Payload, &addr); err != nil {
|
||||||
|
Log(ERROR, "parse ssh data error: %s", err.Error)
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Port > 65535 || addr.Port < 0 {
|
||||||
|
Log(ERROR, "invalid port %d", addr.Port)
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(addr.Addr)
|
||||||
|
if ip == nil {
|
||||||
|
Log(ERROR, "invalid ip %d", addr.Port)
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k := fmt.Sprintf("%s:%d", addr.Addr, addr.Port)
|
||||||
|
|
||||||
|
if _, ok := sc.forwardedPorts[k]; ok {
|
||||||
|
// port in use
|
||||||
|
Log(ERROR, "port in use: %s", k)
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log(DEBUG, "get request for addr: %s, port: %d", addr.Addr, addr.Port)
|
||||||
|
|
||||||
|
l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: ip, Port: int(addr.Port)})
|
||||||
|
if err != nil {
|
||||||
|
// listen failed
|
||||||
|
Log(ERROR, "%s", err.Error())
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a1 := l.Addr()
|
||||||
|
Log(DEBUG, "Listening port %s", a1)
|
||||||
|
p := struct {
|
||||||
|
Port uint32
|
||||||
|
}{
|
||||||
|
uint32(a1.(*net.TCPAddr).Port),
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.forwardedPorts[k] = l
|
||||||
|
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(true, ssh.Marshal(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
Log(ERROR, "%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log(DEBUG, "accept connection from %s", c.RemoteAddr())
|
||||||
|
go func(c net.Conn) {
|
||||||
|
laddr := c.LocalAddr()
|
||||||
|
raddr := c.RemoteAddr()
|
||||||
|
a2 := struct {
|
||||||
|
laddr string
|
||||||
|
lport uint32
|
||||||
|
raddr string
|
||||||
|
rport uint32
|
||||||
|
}{
|
||||||
|
addr.Addr,
|
||||||
|
uint32(laddr.(*net.TCPAddr).Port),
|
||||||
|
raddr.(*net.TCPAddr).IP.String(),
|
||||||
|
uint32(raddr.(*net.TCPAddr).Port),
|
||||||
|
}
|
||||||
|
ch, r, err := sc.sshConn.OpenChannel("forwarded-tcpip", ssh.Marshal(a2))
|
||||||
|
if err != nil {
|
||||||
|
Log(ERROR, "forward port failed: %s", err.Error())
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go ssh.DiscardRequests(r)
|
||||||
|
PipeAndClose(c, ch)
|
||||||
|
}(c)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package obfssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
// DEBUG log level debug
|
||||||
|
DEBUG
|
||||||
|
// INFO log level info
|
||||||
|
INFO
|
||||||
|
// ERROR log level error
|
||||||
|
ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHLogLevel global value for log level
|
||||||
|
var SSHLogLevel = ERROR
|
||||||
|
|
||||||
|
// PipeAndClose pipe the data between c and s, close both when done
|
||||||
|
func PipeAndClose(c io.ReadWriteCloser, s io.ReadWriteCloser) {
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
cc := make(chan int, 2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
io.Copy(c, s)
|
||||||
|
cc <- 1
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
io.Copy(s, c)
|
||||||
|
cc <- 1
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-cc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log log the message by level
|
||||||
|
func Log(level int, s string, args ...interface{}) {
|
||||||
|
if level >= SSHLogLevel {
|
||||||
|
log.Printf(s, args...)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue