mirror of
https://github.com/fumiama/go-nd-portal.git
synced 2026-06-28 07:00:26 +08:00
init
This commit is contained in:
9
base64.go
Normal file
9
base64.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package gondportal
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
const (
|
||||||
|
PortalBase64Table = "LVoJPiCN2R8G90yg+hmFHuacZ1OWMnrsSTXkYpUq/3dlbfKwv6xztjI7DeBE45QA"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Base64Encoding = base64.NewEncoding(PortalBase64Table)
|
||||||
15
base64_test.go
Normal file
15
base64_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package gondportal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBase64(t *testing.T) {
|
||||||
|
buf := strings.Builder{}
|
||||||
|
base64.NewEncoder(Base64Encoding, &buf).Write([]byte("123456"))
|
||||||
|
assert.Equal(t, "9F2z0JHI", buf.String())
|
||||||
|
}
|
||||||
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module github.com/fumiama/go-nd-portal
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/FloatTech/floatbox v0.0.0-20221121151328-cac4af114b44
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
||||||
22
go.sum
Normal file
22
go.sum
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
github.com/FloatTech/floatbox v0.0.0-20221121151328-cac4af114b44 h1:/Er1NIsb3giFG0rQqA2TO1JkN8TTBDRcmaektAvEfK8=
|
||||||
|
github.com/FloatTech/floatbox v0.0.0-20221121151328-cac4af114b44/go.mod h1:DUd62cKBBLBK+8HEABQ6hM2bfZ3TMLOAMKaf1R2w7Pw=
|
||||||
|
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
|
||||||
|
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
21
helper.go
Normal file
21
helper.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package gondportal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BytesToString 没有内存开销的转换
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToBytes 没有内存开销的转换
|
||||||
|
func StringToBytes(s string) (b []byte) {
|
||||||
|
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||||
|
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||||
|
bh.Data = sh.Data
|
||||||
|
bh.Len = sh.Len
|
||||||
|
bh.Cap = sh.Len
|
||||||
|
return b
|
||||||
|
}
|
||||||
103
portal.go
Normal file
103
portal.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package gondportal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FloatTech/floatbox/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrIllegalIPv4 = errors.New("illegal ipv4")
|
||||||
|
ErrUnexpectedChallengeResponse = errors.New("unexpected challenge response")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Portal struct {
|
||||||
|
nam string
|
||||||
|
pwd string
|
||||||
|
ip net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPortal(name, password string, ipv4 net.IP) (*Portal, error) {
|
||||||
|
if len(ipv4) != 4 {
|
||||||
|
return nil, ErrIllegalIPv4
|
||||||
|
}
|
||||||
|
return &Portal{
|
||||||
|
nam: name,
|
||||||
|
pwd: password,
|
||||||
|
ip: ipv4,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Portal) GetChallenge() (string, error) {
|
||||||
|
data, err := web.RequestDataWith(
|
||||||
|
web.NewDefaultClient(),
|
||||||
|
fmt.Sprintf(PortalGetChallenge, "gondportal", p.nam, p.ip, time.Now().UnixMilli()),
|
||||||
|
"GET", "", PortalHeaderUA,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(data) < 12 {
|
||||||
|
return "", ErrUnexpectedChallengeResponse
|
||||||
|
}
|
||||||
|
type rsp struct {
|
||||||
|
Challenge string `json:"challenge"`
|
||||||
|
Ecode int `json:"ecode"`
|
||||||
|
Msg string `json:"error_msg"`
|
||||||
|
}
|
||||||
|
var r rsp
|
||||||
|
err = json.Unmarshal(data[11:len(data)-1], &r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if r.Ecode != 0 {
|
||||||
|
return "", errors.New(r.Msg)
|
||||||
|
}
|
||||||
|
return r.Challenge, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Portal) PasswordHMd5(challenge string) string {
|
||||||
|
var buf [16]byte
|
||||||
|
h := hmac.New(md5.New, StringToBytes(challenge))
|
||||||
|
_, _ = h.Write(StringToBytes(p.pwd))
|
||||||
|
return hex.EncodeToString(h.Sum(buf[:0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Portal) Login(challenge string) error {
|
||||||
|
info := EncodeUserInfo(p.String(), challenge)
|
||||||
|
hmd5 := p.PasswordHMd5(challenge)
|
||||||
|
data, err := web.RequestDataWith(
|
||||||
|
web.NewDefaultClient(),
|
||||||
|
fmt.Sprintf(PortalLogin, "gondportal", p.nam, hmd5, p.ip, p.CheckSum(challenge, hmd5, info), info, time.Now().UnixMilli()),
|
||||||
|
"GET", "", PortalHeaderUA,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(data) < 12 {
|
||||||
|
return ErrUnexpectedChallengeResponse
|
||||||
|
}
|
||||||
|
type rsp struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
var r rsp
|
||||||
|
err = json.Unmarshal(data[11:len(data)-1], &r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.Error == "ok" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New(r.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Portal) String() string {
|
||||||
|
return fmt.Sprintf(PortalUserInfo, p.nam, p.pwd, p.ip)
|
||||||
|
}
|
||||||
96
server.go
Normal file
96
server.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package gondportal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PortalServerIP = "10.253.0.237"
|
||||||
|
PortalGetChallenge = "http://" + PortalServerIP + "/cgi-bin/get_challenge?callback=%s&username=%s@dx-uestc&ip=%v&_=%d"
|
||||||
|
PortalLogin = "http://" + PortalServerIP + "/cgi-bin/srun_portal?callback=%s&action=login&username=%s@dx-uestc&password={MD5}%s&ac_id=1&ip=%v&chksum=%s&info={SRBX1}%s&n=200&type=1&os=Windows+10&name=Windows&double_stack=0&_=%d"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PortalHeaderAccept = "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01"
|
||||||
|
PortalHeaderUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PortalUserInfo = `{"username":"%s@dx-uestc","password":"%s","ip":"%v","acid":"1","enc_ver":"srun_bx1"}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func EncodeUserInfo(info, challenge string) string {
|
||||||
|
if len(info) == 0 || len(challenge) == 0 || len(challenge)%4 != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sc := len(info)
|
||||||
|
if sc%4 != 0 {
|
||||||
|
sc = (sc/4 + 1) * 4
|
||||||
|
}
|
||||||
|
userinfo := make([]byte, sc)
|
||||||
|
copy(userinfo, info)
|
||||||
|
v := make([]uint32, sc/4, sc/4+1)
|
||||||
|
for i := 0; i < sc/4; i++ {
|
||||||
|
v[i] = binary.LittleEndian.Uint32(userinfo[i*4 : i*4+4])
|
||||||
|
}
|
||||||
|
v = append(v, uint32(len(info)))
|
||||||
|
sc = len(challenge)
|
||||||
|
if sc < 16 {
|
||||||
|
sc = 16
|
||||||
|
}
|
||||||
|
k := make([]uint32, sc/4)
|
||||||
|
token := StringToBytes(challenge)
|
||||||
|
for i := 0; i < sc/4; i++ {
|
||||||
|
k[i] = binary.LittleEndian.Uint32(token[i*4 : i*4+4])
|
||||||
|
}
|
||||||
|
n := len(v) - 1
|
||||||
|
z := v[n]
|
||||||
|
d := uint32(0)
|
||||||
|
for q := 0; q < 6+52/(n+1); q++ {
|
||||||
|
d += uint32(0x86014019|0x183639A0) & uint32(0x8CE0D9BF|0x731F2640)
|
||||||
|
e := (d >> 2) & 3
|
||||||
|
for p := 0; p < n; p++ {
|
||||||
|
y := v[p+1]
|
||||||
|
m := (z >> 5) ^ (y << 2)
|
||||||
|
m += ((y >> 3) ^ (z << 4)) ^ (d ^ y)
|
||||||
|
m += k[(uint32(p)&3)^e] ^ z
|
||||||
|
v[p] += m & (0xEFB8D130 | 0x10472ECF)
|
||||||
|
z = v[p]
|
||||||
|
}
|
||||||
|
y := v[0]
|
||||||
|
m := (z >> 5) ^ (y << 2)
|
||||||
|
m += ((y >> 3) ^ (z << 4)) ^ (d ^ y)
|
||||||
|
m += k[uint32(n)&3^e] ^ z
|
||||||
|
v[n] += m & (0xBB390742 | 0x44C6F8BD)
|
||||||
|
z = v[n]
|
||||||
|
}
|
||||||
|
lv := make([]byte, len(v)*4)
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
binary.LittleEndian.PutUint32(lv[i*4:i*4+4], v[i])
|
||||||
|
}
|
||||||
|
return Base64Encoding.EncodeToString(lv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Portal) CheckSum(challenge, hmd5, info string) string {
|
||||||
|
var buf [20]byte
|
||||||
|
h := sha1.New()
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write(StringToBytes(p.nam))
|
||||||
|
_, _ = h.Write([]byte("@dx-uestc"))
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write(StringToBytes(hmd5))
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write([]byte("1")) // ac_id
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write(StringToBytes(p.ip.String()))
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write([]byte("200")) // n
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write([]byte("1")) // type
|
||||||
|
_, _ = h.Write(StringToBytes(challenge))
|
||||||
|
_, _ = h.Write([]byte("{SRBX1}"))
|
||||||
|
_, _ = h.Write(StringToBytes(info))
|
||||||
|
return hex.EncodeToString(h.Sum(buf[:0]))
|
||||||
|
}
|
||||||
79
server_test.go
Normal file
79
server_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package gondportal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeInfo(t *testing.T) {
|
||||||
|
info := `{"username":"2000010101001@dx-uestc","password":"12345678","ip":"1.2.3.4","acid":"1","enc_ver":"srun_bx1"}`
|
||||||
|
sc := len(info)
|
||||||
|
if sc%4 != 0 {
|
||||||
|
sc = (sc/4 + 1) * 4
|
||||||
|
}
|
||||||
|
userinfo := make([]byte, sc)
|
||||||
|
copy(userinfo, info)
|
||||||
|
v := make([]uint32, sc/4, sc/4+1)
|
||||||
|
for i := 0; i < sc/4; i++ {
|
||||||
|
v[i] = binary.LittleEndian.Uint32(userinfo[i*4 : i*4+4])
|
||||||
|
}
|
||||||
|
v = append(v, uint32(len(info)))
|
||||||
|
assert.Equal(t, []uint32{1937056379, 1634628197, 975332717, 808464930, 808529968, 808529969, 1681928496, 1702178168, 576943219, 1634738732, 1870099315, 975332466, 858927394, 926299444, 573317688, 975335529, 841888034, 875442990, 1629629474, 577005923, 573645370, 1852121644, 1702256483, 574235250, 1853190771, 829973087, 32034, 106}, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeKey(t *testing.T) {
|
||||||
|
challenge := "c312a4194d4310695b71d92ac3c740198a14a7a280022f89408edec4e932d1e5"
|
||||||
|
sc := len(challenge)
|
||||||
|
k := make([]uint32, sc/4)
|
||||||
|
token := StringToBytes(challenge)
|
||||||
|
for i := 0; i < sc/4; i++ {
|
||||||
|
k[i] = binary.LittleEndian.Uint32(token[i*4 : i*4+4])
|
||||||
|
}
|
||||||
|
assert.Equal(t, []uint32{842085219, 959525985, 859071540, 959852593, 825713205, 1630681444, 929248099, 959524916, 875651384, 845231969, 842018872, 959997490, 1698181172, 878929252, 842217829, 895824228}, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeUserInfo(t *testing.T) {
|
||||||
|
u, err := NewPortal("2000010101001", "12345678", net.IPv4(1, 2, 3, 4).To4())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(u.String())
|
||||||
|
r := EncodeUserInfo(u.String(), "c312a4194d4310695b71d92ac3c740198a14a7a280022f89408edec4e932d1e5")
|
||||||
|
assert.Equal(t, "LMDd8Hmfuq32k+whLiNtcuRwEVxEswfsm4rKEoAoGnFeDlMijgeXC6mtK3nTlrNmjwoEmRyLsWePyrFzDd/EI7EfgKh2gF3c9dGmUrlFO9cy6PFqBDShWsGaAuatVgZLhKBOACTShgxGraRJBoA9WS==", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHMd5(t *testing.T) {
|
||||||
|
h := hmac.New(md5.New, []byte("c312a4194d4310695b71d92ac3c740198a14a7a280022f89408edec4e932d1e5"))
|
||||||
|
h.Write([]byte("1234567890"))
|
||||||
|
assert.Equal(t, "69ff50d80e734878259dbee3322591a7", hex.EncodeToString(h.Sum(nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSha1(t *testing.T) {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write([]byte("123456"))
|
||||||
|
assert.Equal(t, "7c4a8d09ca3762af61e59520943dc26494f8941b", hex.EncodeToString(h.Sum(nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckSum(t *testing.T) {
|
||||||
|
u, err := NewPortal("2000010101001", "1234567890", net.IPv4(1, 2, 3, 4).To4())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(u.String())
|
||||||
|
s := u.CheckSum(
|
||||||
|
"c312a4194d4310695b71d92ac3c740198a14a7a280022f89408edec4e932d1e5",
|
||||||
|
"69ff50d80e734878259dbee3322591a7",
|
||||||
|
EncodeUserInfo(
|
||||||
|
u.String(),
|
||||||
|
"c312a4194d4310695b71d92ac3c740198a14a7a280022f89408edec4e932d1e5",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert.Equal(t, "3785bd1e1fa71a2b26470b5faa64aad9130ae418", s)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user