Xray-core/proxy/shadowsocks/validator.go
yuhan6665 85a1c33709
API: Add new Get Inbound User (#3644)
* Add GetInboundUser in proto

* Add get user logic for all existing inbounds

* Add inbounduser command

* Add option to get all users

* Fix shadowsocks2022 config

* Fix init users in shadowsocks2022

* Fix copy

* Add inbound user count command

This api costs much less than get inbound user, could be useful in some case

* Update from latest main
2024-11-03 00:25:23 -04:00

166 lines
3.6 KiB
Go

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/errors"
"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 = errors.New("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 errors.New("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 errors.New("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 errors.New("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
}
// GetByEmail Get a Shadowsocks user with a non-empty Email.
func (v *Validator) GetByEmail(email string) *protocol.MemoryUser {
if email == "" {
return nil
}
v.Lock()
defer v.Unlock()
email = strings.ToLower(email)
for _, u := range v.users {
if strings.EqualFold(u.Email, email) {
return u
}
}
return nil
}
// GetAll get all users
func (v *Validator) GetAll() []*protocol.MemoryUser {
v.Lock()
defer v.Unlock()
dst := make([]*protocol.MemoryUser, len(v.users))
copy(dst, v.users)
return dst
}
// GetCount get users count
func (v *Validator) GetCount() int64 {
v.Lock()
defer v.Unlock()
return int64(len(v.users))
}
// 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
}