mirror of https://github.com/XTLS/Xray-core
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
2.9 KiB
131 lines
2.9 KiB
package shadowsocks |
|
|
|
import ( |
|
"crypto/cipher" |
|
"crypto/hmac" |
|
"crypto/sha256" |
|
"hash/crc64" |
|
"strings" |
|
"sync" |
|
|
|
"github.com/xtls/xray-core/common/dice" |
|
"github.com/xtls/xray-core/common/protocol" |
|
) |
|
|
|
// Validator stores valid Shadowsocks users. |
|
type Validator struct { |
|
sync.RWMutex |
|
users []*protocol.MemoryUser |
|
|
|
behaviorSeed uint64 |
|
behaviorFused bool |
|
} |
|
|
|
var ErrNotFound = newError("Not Found") |
|
|
|
// Add a Shadowsocks user. |
|
func (v *Validator) Add(u *protocol.MemoryUser) error { |
|
v.Lock() |
|
defer v.Unlock() |
|
|
|
account := u.Account.(*MemoryAccount) |
|
if !account.Cipher.IsAEAD() && len(v.users) > 0 { |
|
return newError("The cipher is not support Single-port Multi-user") |
|
} |
|
v.users = append(v.users, u) |
|
|
|
if !v.behaviorFused { |
|
hashkdf := hmac.New(sha256.New, []byte("SSBSKDF")) |
|
hashkdf.Write(account.Key) |
|
v.behaviorSeed = crc64.Update(v.behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil)) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// Del a Shadowsocks user with a non-empty Email. |
|
func (v *Validator) Del(email string) error { |
|
if email == "" { |
|
return newError("Email must not be empty.") |
|
} |
|
|
|
v.Lock() |
|
defer v.Unlock() |
|
|
|
email = strings.ToLower(email) |
|
idx := -1 |
|
for i, u := range v.users { |
|
if strings.EqualFold(u.Email, email) { |
|
idx = i |
|
break |
|
} |
|
} |
|
|
|
if idx == -1 { |
|
return newError("User ", email, " not found.") |
|
} |
|
ulen := len(v.users) |
|
|
|
v.users[idx] = v.users[ulen-1] |
|
v.users[ulen-1] = nil |
|
v.users = v.users[:ulen-1] |
|
|
|
return nil |
|
} |
|
|
|
// Get a Shadowsocks user. |
|
func (v *Validator) Get(bs []byte, command protocol.RequestCommand) (u *protocol.MemoryUser, aead cipher.AEAD, ret []byte, ivLen int32, err error) { |
|
v.RLock() |
|
defer v.RUnlock() |
|
|
|
for _, user := range v.users { |
|
if account := user.Account.(*MemoryAccount); account.Cipher.IsAEAD() { |
|
// AEAD payload decoding requires the payload to be over 32 bytes |
|
if len(bs) < 32 { |
|
continue |
|
} |
|
|
|
aeadCipher := account.Cipher.(*AEADCipher) |
|
ivLen = aeadCipher.IVSize() |
|
iv := bs[:ivLen] |
|
subkey := make([]byte, 32) |
|
subkey = subkey[:aeadCipher.KeyBytes] |
|
hkdfSHA1(account.Key, iv, subkey) |
|
aead = aeadCipher.AEADAuthCreator(subkey) |
|
|
|
var matchErr error |
|
switch command { |
|
case protocol.RequestCommandTCP: |
|
data := make([]byte, 4+aead.NonceSize()) |
|
ret, matchErr = aead.Open(data[:0], data[4:], bs[ivLen:ivLen+18], nil) |
|
case protocol.RequestCommandUDP: |
|
data := make([]byte, 8192) |
|
ret, matchErr = aead.Open(data[:0], data[8192-aead.NonceSize():8192], bs[ivLen:], nil) |
|
} |
|
|
|
if matchErr == nil { |
|
u = user |
|
err = account.CheckIV(iv) |
|
return |
|
} |
|
} else { |
|
u = user |
|
ivLen = user.Account.(*MemoryAccount).Cipher.IVSize() |
|
// err = user.Account.(*MemoryAccount).CheckIV(bs[:ivLen]) // The IV size of None Cipher is 0. |
|
return |
|
} |
|
} |
|
|
|
return nil, nil, nil, 0, ErrNotFound |
|
} |
|
|
|
func (v *Validator) GetBehaviorSeed() uint64 { |
|
v.Lock() |
|
defer v.Unlock() |
|
|
|
v.behaviorFused = true |
|
if v.behaviorSeed == 0 { |
|
v.behaviorSeed = dice.RollUint64() |
|
} |
|
return v.behaviorSeed |
|
}
|
|
|