From ec379574005134dbcffdb715b5ccac2071e06335 Mon Sep 17 00:00:00 2001 From: lyc8503 Date: Mon, 23 Jan 2023 20:57:17 +0800 Subject: [PATCH] sort code --- core/EasyConnectClient.go | 87 ++++++++++++++++++++++++++ core/protocol.go | 22 +++---- core/web_login.go | 128 ++++++++++++++++++++++++-------------- gui/component/adapter.go | 41 +++++------- main.go | 42 ++++++++----- 5 files changed, 220 insertions(+), 100 deletions(-) create mode 100644 core/EasyConnectClient.go diff --git a/core/EasyConnectClient.go b/core/EasyConnectClient.go new file mode 100644 index 0000000..a20aced --- /dev/null +++ b/core/EasyConnectClient.go @@ -0,0 +1,87 @@ +package core + +import ( + "errors" + "net" + + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +type EasyConnectClient struct { + queryConn net.Conn + clientIp []byte + token *[48]byte + twfId string + + endpoint *EasyConnectEndpoint + ipStack *stack.Stack + + server string + username string + password string +} + +func NewEasyConnectClient(server string) *EasyConnectClient { + return &EasyConnectClient{ + server: server, + } +} + +func (client *EasyConnectClient) Login(username string, password string) ([]byte, error) { + client.username = username + client.password = password + + // Web login part (Get TWFID & ECAgent Token => Final token used in binary stream) + twfId, err := WebLogin(client.server, client.username, client.password) + + // Store TWFID for AuthSMS + client.twfId = twfId + if err != nil { + return nil, err + } + + return client.LoginByTwfId(twfId) +} + +func (client *EasyConnectClient) AuthSMSCode(code string) ([]byte, error) { + if client.twfId == "" { + return nil, errors.New("SMS Auth not required") + } + + twfId, err := AuthSms(client.server, client.username, client.password, client.twfId, code) + if err != nil { + return nil, err + } + + return client.LoginByTwfId(twfId) +} + +func (client *EasyConnectClient) LoginByTwfId(twfId string) ([]byte, error) { + agentToken, err := ECAgentToken(client.server, twfId) + if err != nil { + return nil, err + } + + client.token = (*[48]byte)([]byte(agentToken + twfId)) + + // Query IP (keep the connection used so it's not closed too early, otherwise i/o stream will be closed) + client.clientIp, client.queryConn, err = QueryIp(client.server, client.token) + if err != nil { + return nil, err + } + + return client.clientIp, nil +} + +func (client *EasyConnectClient) ServeSocks5(socksBind string, debugDump bool) { + // Link-level endpoint used in gvisor netstack + client.endpoint = &EasyConnectEndpoint{} + client.ipStack = SetupStack(client.clientIp, client.endpoint) + + // Sangfor Easyconnect protocol + StartProtocol(client.endpoint, client.server, client.token, + &[4]byte{client.clientIp[3], client.clientIp[2], client.clientIp[1], client.clientIp[0]}, debugDump) + + // Socks5 server + ServeSocks5(client.ipStack, client.clientIp, socksBind) +} diff --git a/core/protocol.go b/core/protocol.go index a2c76bb..73364b3 100644 --- a/core/protocol.go +++ b/core/protocol.go @@ -7,6 +7,7 @@ import ( "log" "net" "os" + "runtime/debug" tls "github.com/refraction-networking/utls" ) @@ -43,16 +44,12 @@ func TLSConn(server string) (*tls.UConn, error) { return conn, nil } -func MustTLSConn(server string) *tls.UConn { +func QueryIp(server string, token *[48]byte) ([]byte, *tls.UConn, error) { conn, err := TLSConn(server) if err != nil { - panic(err) + debug.PrintStack() + return nil, nil, err } - return conn -} - -func MustQueryIp(server string, token *[48]byte) ([]byte, *tls.UConn) { - conn := MustTLSConn(server) // defer conn.Close() // Query IP conn CAN NOT be closed, otherwise tx/rx handshake will fail @@ -63,7 +60,8 @@ func MustQueryIp(server string, token *[48]byte) ([]byte, *tls.UConn) { n, err := conn.Write(message) if err != nil { - panic(err) + debug.PrintStack() + return nil, nil, err } log.Printf("query ip: wrote %d bytes", n) @@ -72,17 +70,19 @@ func MustQueryIp(server string, token *[48]byte) ([]byte, *tls.UConn) { reply := make([]byte, 0x80) n, err = conn.Read(reply) if err != nil { - panic(err) + debug.PrintStack() + return nil, nil, err } log.Printf("query ip: read %d bytes", n) DumpHex(reply[:n]) if reply[0] != 0x00 { - panic("unexpected query ip reply.") + debug.PrintStack() + return nil, nil, errors.New("unexpected query ip reply") } - return reply[4:8], conn + return reply[4:8], conn, nil } func BlockRXStream(server string, token *[48]byte, ipRev *[4]byte, ep *EasyConnectEndpoint, debug bool) error { diff --git a/core/web_login.go b/core/web_login.go index 1d8b3cb..5d252e6 100644 --- a/core/web_login.go +++ b/core/web_login.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "crypto/tls" "encoding/hex" - "fmt" + "errors" "io" "log" "math/big" @@ -13,13 +13,16 @@ import ( "net/http" "net/url" "regexp" + "runtime/debug" "strconv" "strings" utls "github.com/refraction-networking/utls" ) -func WebLogin(server string, username string, password string) string { +var ERR_NEXT_AUTH_SMS = errors.New("SMS Code required.") + +func WebLogin(server string, username string, password string) (string, error) { server = "https://" + server c := &http.Client{ @@ -32,7 +35,8 @@ func WebLogin(server string, username string, password string) string { resp, err := c.Get(addr) if err != nil { - panic(err) + debug.PrintStack() + return "", err } defer resp.Body.Close() @@ -68,7 +72,8 @@ func WebLogin(server string, username string, password string) string { encryptedPassword, err := rsa.EncryptPKCS1v15(rand.Reader, &pubKey, []byte(password)) if err != nil { - panic(err) + debug.PrintStack() + return "", err } encryptedPasswordHex := hex.EncodeToString(encryptedPassword) log.Printf("Encrypted Password: %s", encryptedPasswordHex) @@ -89,78 +94,104 @@ func WebLogin(server string, username string, password string) string { resp, err = c.Do(req) if err != nil { - panic(err) + debug.PrintStack() + return "", err } n, _ = resp.Body.Read(buf) defer resp.Body.Close() - if strings.Contains(string(buf[:n]), "0") { - panic("Login FAILED: " + string(buf[:n])) - } + // log.Printf("First stage login response: %s", string(buf[:n])) - if strings.Contains(string(buf[:n]), "-1") { - log.Print("No NextAuth found.") - } else if strings.Contains(string(buf[:n]), "auth/sms") { - log.Print("SMS code required") + // SMS Code Process + if strings.Contains(string(buf[:n]), "auth/sms") { + log.Print("SMS code required.") addr = server + "/por/login_sms.csp?apiversion=1" log.Printf("SMS Request: " + addr) - req, err := http.NewRequest("POST", addr, nil) + req, err = http.NewRequest("POST", addr, nil) req.Header.Set("Cookie", "TWFID="+twfId) resp, err = c.Do(req) if err != nil { - panic(err) + debug.PrintStack() + return "", err } - n, _ = resp.Body.Read(buf) + n, _ := resp.Body.Read(buf) defer resp.Body.Close() if !strings.Contains(string(buf[:n]), "验证码已发送到您的手机") { - panic("unexpected sms resp: " + string(buf[:n])) + debug.PrintStack() + return "", errors.New("unexpected sms resp: " + string(buf[:n])) } log.Printf("SMS Code is sent or still valid.") - fmt.Print(">>>Please enter your sms code<<<:") - smsCode := "" - fmt.Scan(&smsCode) - - addr = server + "/por/login_sms1.csp?apiversion=1" - log.Printf("SMS Request: " + addr) - form := url.Values{ - "svpn_inputsms": {smsCode}, - } - - req, err = http.NewRequest("POST", addr, strings.NewReader(form.Encode())) - req.Header.Set("Cookie", "TWFID="+twfId) - - resp, err = c.Do(req) - if err != nil { - panic(err) - } - - n, _ = resp.Body.Read(buf) - defer resp.Body.Close() - - if !strings.Contains(string(buf[:n]), "Auth sms suc") { - panic("SMS Code verification FAILED: " + string(buf[:n])) - } - - twfId = string(regexp.MustCompile(`(.*)`).FindSubmatch(buf[:n])[1]) - log.Print("SMS Code verification SUCCESS") + return twfId, ERR_NEXT_AUTH_SMS + } + if strings.Contains(string(buf[:n]), "-1") || !strings.Contains(string(buf[:n]), "") { + log.Print("No NextAuth found.") } else { - panic("Not implemented auth: " + string(buf[:n])) + debug.PrintStack() + return "", errors.New("Not implemented auth: " + string(buf[:n])) + } + + if !strings.Contains(string(buf[:n]), "1") { + debug.PrintStack() + return "", errors.New("Login FAILED: " + string(buf[:n])) + } + + twfIdMatch := regexp.MustCompile(`(.*)`).FindSubmatch(buf[:n]) + if twfIdMatch != nil { + twfId = string(twfIdMatch[1]) + log.Printf("Update twfId: %s", twfId) } log.Printf("Web Login process done.") - return twfId + return twfId, nil } -func ECAgentToken(server string, twfId string) string { +func AuthSms(server string, username string, password string, twfId string, smsCode string) (string, error) { + c := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }} + + buf := make([]byte, 40960) + + addr := "https://" + server + "/por/login_sms1.csp?apiversion=1" + log.Printf("SMS Request: " + addr) + form := url.Values{ + "svpn_inputsms": {smsCode}, + } + + req, err := http.NewRequest("POST", addr, strings.NewReader(form.Encode())) + req.Header.Set("Cookie", "TWFID="+twfId) + + resp, err := c.Do(req) + if err != nil { + debug.PrintStack() + return "", err + } + + n, _ := resp.Body.Read(buf) + defer resp.Body.Close() + + if !strings.Contains(string(buf[:n]), "Auth sms suc") { + debug.PrintStack() + return "", errors.New("SMS Code verification FAILED: " + string(buf[:n])) + } + + twfId = string(regexp.MustCompile(`(.*)`).FindSubmatch(buf[:n])[1]) + log.Print("SMS Code verification SUCCESS") + + return twfId, nil +} + +func ECAgentToken(server string, twfId string) (string, error) { dialConn, err := net.Dial("tcp", server) defer dialConn.Close() conn := utls.UClient(dialConn, &utls.Config{InsecureSkipVerify: true}, utls.HelloGolang) @@ -177,8 +208,9 @@ func ECAgentToken(server string, twfId string) string { buf := make([]byte, 40960) n, err := conn.Read(buf) if n == 0 || err != nil { - panic("ECAgent Request invalid: error " + err.Error() + "\n" + string(buf[:n])) + debug.PrintStack() + return "", errors.New("ECAgent Request invalid: error " + err.Error() + "\n" + string(buf[:n])) } - return hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId)[:31] + "\x00" + return hex.EncodeToString(conn.HandshakeState.ServerHello.SessionId)[:31] + "\x00", nil } diff --git a/gui/component/adapter.go b/gui/component/adapter.go index f781589..1c95b0c 100644 --- a/gui/component/adapter.go +++ b/gui/component/adapter.go @@ -4,37 +4,30 @@ import ( "EasierConnect/core" "fmt" "log" - "os" ) func Process(host, port, username, password, socksBind string) { - // CLI args - //host, port, username, password, socksBind := "", 0, "", "", "" - - _, debugDump := os.LookupEnv("DEBUG_DUMP") - - if host == "" || username == "" || password == "" { - log.Fatal("Missing required cli args, refer to `EasierConnect --help`.") - } server := fmt.Sprintf("%s:%s", host, port) + client := core.NewEasyConnectClient(server) - // Web login part (Get TWFID & ECAgent Token => Final token used in binary stream) - twfId := core.WebLogin(server, username, password) - agentToken := core.ECAgentToken(server, twfId) - token := (*[48]byte)([]byte(agentToken + twfId)) + var ip []byte + var err error + ip, err = client.Login(username, password) + if err == core.ERR_NEXT_AUTH_SMS { + // TODO: input sms code via gui + fmt.Print(">>>Please enter your sms code<<<:") + smsCode := "" + fmt.Scan(&smsCode) - // Query IP (keep the connection used, so it's not closed too early, otherwise i/o stream will be closed) - ip, conn := core.MustQueryIp(server, token) - defer conn.Close() - log.Printf("IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + ip, err = client.AuthSMSCode(smsCode) + } - // Link-level endpoint used in gvisor netstack - endpoint := &core.EasyConnectEndpoint{} - ipStack := core.SetupStack(ip, endpoint) + if err != nil { + // TODO: show error in gui + panic(err) + } - // Sangfor Easyconnect protocol - core.StartProtocol(endpoint, server, token, &[4]byte{ip[3], ip[2], ip[1], ip[0]}, debugDump) + log.Printf("Login success, your IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) - // Socks5 server - core.ServeSocks5(ipStack, ip, socksBind) + client.ServeSocks5(socksBind, false) } diff --git a/main.go b/main.go index e84b3cf..82a91d3 100644 --- a/main.go +++ b/main.go @@ -9,38 +9,46 @@ import ( func main() { // CLI args - host, port, username, password, socksBind := "", 0, "", "", "" + host, port, username, password, socksBind, twfId := "", 0, "", "", "", "" flag.StringVar(&host, "server", "", "EasyConnect server address (e.g. vpn.nju.edu.cn)") flag.StringVar(&username, "username", "", "Your username") flag.StringVar(&password, "password", "", "Your password") flag.StringVar(&socksBind, "socks-bind", ":1080", "The addr socks5 server listens on (e.g. 0.0.0.0:1080)") + flag.StringVar(&twfId, "twf-id", "", "Login using twfID captured (mostly for debug usage)") flag.IntVar(&port, "port", 443, "EasyConnect port address (e.g. 443)") debugDump := false flag.BoolVar(&debugDump, "debug-dump", false, "Enable traffic debug dump (only for debug usage)") flag.Parse() - if host == "" || username == "" || password == "" { + if host == "" || ((username == "" || password == "") && twfId == "") { log.Fatal("Missing required cli args, refer to `EasierConnect --help`.") } server := fmt.Sprintf("%s:%d", host, port) - // Web login part (Get TWFID & ECAgent Token => Final token used in binary stream) - twfId := core.WebLogin(server, username, password) - agentToken := core.ECAgentToken(server, twfId) - token := (*[48]byte)([]byte(agentToken + twfId)) + client := core.NewEasyConnectClient(server) - // Query IP (keep the connection used so it's not closed too early, otherwise i/o stream will be closed) - ip, conn := core.MustQueryIp(server, token) - defer conn.Close() - log.Printf("IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) + var ip []byte + var err error + if twfId != "" { + if len(twfId) != 16 { + panic("len(twfid) should be 16!") + } + ip, err = client.LoginByTwfId(twfId) + } else { + ip, err = client.Login(username, password) + if err == core.ERR_NEXT_AUTH_SMS { + fmt.Print(">>>Please enter your sms code<<<:") + smsCode := "" + fmt.Scan(&smsCode) - // Link-level endpoint used in gvisor netstack - endpoint := &core.EasyConnectEndpoint{} - ipStack := core.SetupStack(ip, endpoint) + ip, err = client.AuthSMSCode(smsCode) + } + } - // Sangfor Easyconnect protocol - core.StartProtocol(endpoint, server, token, &[4]byte{ip[3], ip[2], ip[1], ip[0]}, debugDump) + if err != nil { + log.Fatal(err.Error()) + } + log.Printf("Login success, your IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) - // Socks5 server - core.ServeSocks5(ipStack, ip, socksBind) + client.ServeSocks5(socksBind, debugDump) }