mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
c3a32c8343
When every client of a Shadowsocks-2022 inbound was disabled the panel handed the core an empty client list, from which xray-core builds a single-user server keyed by the inbound's server key alone. That key is the first half of every link the panel ever issued, so a client cut off for quota, expiry or by the operator connected again with it. An inbound left without clients now gets a placeholder user whose key is random and held only in memory, in the config and in the running core alike. The core stays multi-user, and disabling the last real client takes the same remove-and-restart path as any other client, so its live sessions end as well.
67 lines
2.0 KiB
Go
67 lines
2.0 KiB
Go
package xray
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"sync"
|
|
|
|
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
|
)
|
|
|
|
// Shadowsocks2022KeyBytes is the key size a Shadowsocks-2022 method takes, or 0
|
|
// for any other method, whose password is free-form.
|
|
func Shadowsocks2022KeyBytes(method string) int {
|
|
cipher, err := shadowsocks_2022.GetCipherMethod(method)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return cipher.KeySaltLength
|
|
}
|
|
|
|
// ValidShadowsocks2022Key reads key the way the core does: base64, else the raw
|
|
// string, and either way exactly the method's key size.
|
|
func ValidShadowsocks2022Key(method, key string) bool {
|
|
_, err := shadowsocks_2022.ParseKey(key, Shadowsocks2022KeyBytes(method))
|
|
return err == nil
|
|
}
|
|
|
|
// Shadowsocks2022NoMultiUser reports a 2022 method xray-core refuses on an
|
|
// inbound with users: the multi-user identity header is AES-only.
|
|
func Shadowsocks2022NoMultiUser(method string) bool {
|
|
cipher, err := shadowsocks_2022.GetCipherMethod(method)
|
|
return err == nil && cipher.IsChaCha
|
|
}
|
|
|
|
// LockedShadowsocks2022Email names the placeholder user; the panel refuses "/" in a
|
|
// client email, so it never collides with a real one.
|
|
const LockedShadowsocks2022Email = "/locked"
|
|
|
|
var lockedShadowsocks2022Key = sync.OnceValue(func() []byte {
|
|
key := make([]byte, 32)
|
|
_, _ = rand.Read(key)
|
|
return key
|
|
})
|
|
|
|
// LockShadowsocks2022 gives a Shadowsocks-2022 inbound left with no client a user whose key
|
|
// no link carries: with none, the core serves the inbound to its server key alone.
|
|
func LockShadowsocks2022(settings map[string]any) bool {
|
|
method, _ := settings["method"].(string)
|
|
n := Shadowsocks2022KeyBytes(method)
|
|
if n == 0 {
|
|
return false
|
|
}
|
|
// The core reads clients over users, so users count only when clients is absent.
|
|
users, hasClients := settings["clients"]
|
|
if !hasClients {
|
|
users = settings["users"]
|
|
}
|
|
if list, _ := users.([]any); len(list) > 0 {
|
|
return false
|
|
}
|
|
settings["clients"] = []any{map[string]any{
|
|
"email": LockedShadowsocks2022Email,
|
|
"password": base64.StdEncoding.EncodeToString(lockedShadowsocks2022Key()[:n]),
|
|
}}
|
|
return true
|
|
}
|