fix(listener): strip incomplete jls-config before Mihomo validate
Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation. fix(listener): strip incomplete jls-config before Mihomo validate Empty or half-filled jls-config (missing dest/users) causes mihomo -t to fail. Require complete JLS on the form; sanitize on save and at config generation.
This commit is contained in:
1 parent
5b8e42dfc1
commit
8f9ffb1a08
4 files changed
+144
-11
No files matched your search
@@ -119,6 +119,44 @@ func sanitizeServerConfig(cfg map[string]interface{}) {
|
||||
delete(raw, "public_key")
|
||||
cfg["reality-config"] = raw
|
||||
}
|
||||
// Incomplete JLS blocks make Mihomo reject the whole config:
|
||||
// "jls-config has unset fields: dest, users".
|
||||
if jls, ok := cfg["jls-config"].(map[string]interface{}); ok {
|
||||
dest, _ := jls["dest"].(string)
|
||||
if strings.TrimSpace(dest) == "" || !jlsHasUsers(jls) {
|
||||
delete(cfg, "jls-config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func jlsHasUsers(m map[string]interface{}) bool {
|
||||
raw, ok := m["users"]
|
||||
if !ok || raw == nil {
|
||||
return false
|
||||
}
|
||||
switch users := raw.(type) {
|
||||
case []interface{}:
|
||||
for _, item := range users {
|
||||
u, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
user, _ := u["username"].(string)
|
||||
pass, _ := u["password"].(string)
|
||||
if strings.TrimSpace(user) != "" && strings.TrimSpace(pass) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []map[string]interface{}:
|
||||
for _, u := range users {
|
||||
user, _ := u["username"].(string)
|
||||
pass, _ := u["password"].(string)
|
||||
if strings.TrimSpace(user) != "" && strings.TrimSpace(pass) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func needsReality(cfg map[string]interface{}) bool {
|
||||
|
||||
@@ -201,6 +201,8 @@ func generateListeners(listeners []models.Listener, creds map[uint][]Credential)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listener %q: %w", l.Name, err)
|
||||
}
|
||||
// Drop half-filled wrappers first so TLS cert ensure is not skipped.
|
||||
sanitizeIncompleteTLSWrappers(configMap)
|
||||
if err := ensureListenerTLSMaterial(protocolName, configMap); err != nil {
|
||||
return nil, fmt.Errorf("listener %q: %w", l.Name, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeIncompleteTLSWrappers drops TLS-wrapper blocks that Mihomo would
|
||||
// reject for missing required nested fields (e.g. jls-config without dest/users).
|
||||
// Incomplete blocks often come from panel toggles that were saved without filling
|
||||
// the required sub-fields.
|
||||
func sanitizeIncompleteTLSWrappers(cfg map[string]interface{}) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
if jls, ok := cfg["jls-config"].(map[string]interface{}); ok {
|
||||
if !wrapperEnabled(jls) || !hasNonEmptyString(jls, "dest") || !hasJLSUsers(jls) {
|
||||
delete(cfg, "jls-config")
|
||||
}
|
||||
}
|
||||
if st, ok := cfg["shadow-tls"].(map[string]interface{}); ok {
|
||||
// Minimal: if explicitly disabled or empty object, drop.
|
||||
if !wrapperEnabled(st) {
|
||||
delete(cfg, "shadow-tls")
|
||||
}
|
||||
}
|
||||
if rt, ok := cfg["res-tls"].(map[string]interface{}); ok {
|
||||
if !wrapperEnabled(rt) || !hasNonEmptyString(rt, "dest") {
|
||||
delete(cfg, "res-tls")
|
||||
}
|
||||
}
|
||||
// allow-insecure alone without cert is valid for anytls only when intentional;
|
||||
// do not strip it here.
|
||||
}
|
||||
|
||||
func wrapperEnabled(m map[string]interface{}) bool {
|
||||
if m == nil || len(m) == 0 {
|
||||
return false
|
||||
}
|
||||
if en, ok := m["enable"].(bool); ok {
|
||||
return en
|
||||
}
|
||||
if en, ok := m["enabled"].(bool); ok {
|
||||
return en
|
||||
}
|
||||
// Present without enable flag: treat as enabled only if it has meaningful keys.
|
||||
for k := range m {
|
||||
if k == "enable" || k == "enabled" {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasNonEmptyString(m map[string]interface{}, key string) bool {
|
||||
v, ok := m[key]
|
||||
if !ok || v == nil {
|
||||
return false
|
||||
}
|
||||
s, ok := v.(string)
|
||||
return ok && strings.TrimSpace(s) != ""
|
||||
}
|
||||
|
||||
func hasJLSUsers(m map[string]interface{}) bool {
|
||||
raw, ok := m["users"]
|
||||
if !ok || raw == nil {
|
||||
return false
|
||||
}
|
||||
switch users := raw.(type) {
|
||||
case []interface{}:
|
||||
for _, item := range users {
|
||||
u, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
user, _ := u["username"].(string)
|
||||
pass, _ := u["password"].(string)
|
||||
if strings.TrimSpace(user) != "" && strings.TrimSpace(pass) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []map[string]interface{}:
|
||||
for _, u := range users {
|
||||
user, _ := u["username"].(string)
|
||||
pass, _ := u["password"].(string)
|
||||
if strings.TrimSpace(user) != "" && strings.TrimSpace(pass) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -574,21 +574,24 @@ export function formValuesToConfig(
|
||||
}) || { enable: true };
|
||||
}
|
||||
|
||||
// jls-config
|
||||
// jls-config — Mihomo requires dest + users; never emit a half-filled block.
|
||||
if (WRAPPER_TLS_PROTOCOLS.has(protocol) && values.jls_enabled) {
|
||||
const users = asArray(values.jls_users)
|
||||
.filter((u: any) => u?.username || u?.password)
|
||||
.filter((u: any) => u?.username && u?.password)
|
||||
.map((u: any) => cleanObj({ username: u.username, password: u.password }))
|
||||
.filter(Boolean);
|
||||
cfg['jls-config'] = cleanObj({
|
||||
enable: true,
|
||||
dest: values.jls_dest,
|
||||
sni: values.jls_sni,
|
||||
alpn: values.jls_alpn,
|
||||
proxy: values.jls_proxy,
|
||||
'rate-limit': values.jls_rate_limit,
|
||||
users: users.length ? users : undefined,
|
||||
}) || { enable: true };
|
||||
const dest = typeof values.jls_dest === 'string' ? values.jls_dest.trim() : '';
|
||||
if (dest && users.length > 0) {
|
||||
cfg['jls-config'] = cleanObj({
|
||||
enable: true,
|
||||
dest,
|
||||
sni: values.jls_sni,
|
||||
alpn: values.jls_alpn,
|
||||
proxy: values.jls_proxy,
|
||||
'rate-limit': values.jls_rate_limit,
|
||||
users,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// mux-option (listener-side: only padding + brutal per official docs)
|
||||
|
||||
Reference in new issue
Block a user