mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +03:00
fix(outbound): keep imported links to remote-server settings
A share link describes a remote server, but extra=, fm= and the vmess JSON copy provider objects verbatim, so a link could carry keys that only make sense on the importing host. Both link parsers now drop dial-redirect and host file keys (matched case-folded, as the core binds them). Outbound subscriptions also refuse outbounds that dial this host or its network without allowPrivate, any that target the core's own API or metrics port, and any that take their server from DNS records. Rows stored earlier are filtered the same way when the config is built.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"math"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -118,7 +119,15 @@ func splitLines(s string) []string {
|
||||
// - hysteria2:// (also hy2://)
|
||||
// - wireguard:// (also wg://)
|
||||
func ParseLink(link string) (*ParseResult, error) {
|
||||
link = strings.TrimSpace(link)
|
||||
res, err := parseLinkByScheme(strings.TrimSpace(link))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dropHostLocalKeys(res.Outbound)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func parseLinkByScheme(link string) (*ParseResult, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(link, "vmess://"):
|
||||
return parseVmess(link)
|
||||
@@ -137,6 +146,29 @@ func ParseLink(link string) (*ParseResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// hostLocalKeys redirect the core's dial or name files on the importing host; extra=, fm= and
|
||||
// vmess JSON copy provider keys verbatim, and the core binds keys case-folded.
|
||||
var hostLocalKeys = []string{"dialerProxy", "certificateFile", "keyFile", "masterKeyLog"}
|
||||
|
||||
func dropHostLocalKeys(v any) {
|
||||
switch v := v.(type) {
|
||||
case map[string]any:
|
||||
for key, child := range v {
|
||||
if slices.ContainsFunc(hostLocalKeys, func(k string) bool { return strings.EqualFold(k, key) }) {
|
||||
delete(v, key)
|
||||
continue
|
||||
}
|
||||
dropHostLocalKeys(child)
|
||||
}
|
||||
case Outbound:
|
||||
dropHostLocalKeys(map[string]any(v))
|
||||
case []any:
|
||||
for _, child := range v {
|
||||
dropHostLocalKeys(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- vmess ---
|
||||
|
||||
func parseVmess(link string) (*ParseResult, error) {
|
||||
|
||||
@@ -612,3 +612,48 @@ func TestParseLink_PutsUDPHopMaskLast(t *testing.T) {
|
||||
t.Fatalf("imported finalmask\n got: %s\nwant: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A share link describes a remote server; keys that redirect the dial or name files on
|
||||
// the importing host must not survive, however the core's case-folding decoder spells them.
|
||||
func TestParseLinkDropsHostLocalKeys(t *testing.T) {
|
||||
extra := url.QueryEscape(`{"downloadSettings":{"address":"8.8.8.8","port":443,"network":"xhttp",` +
|
||||
`"sockopt":{"dialerProxy":"api","tcpFastOpen":true},` +
|
||||
`"tlsSettings":{"serverName":"cdn.example.com","CERTIFICATES":[{"certificateFile":"/dev/zero","keyFile":"/etc/shadow"}],"maſterKeyLog":"/root/keys"}}}`)
|
||||
fm := url.QueryEscape(`{"tcp":[{"type":"header-custom","settings":{"DialerProxy":"api"}}]}`)
|
||||
res, err := ParseLink("vless://uuid@8.8.8.8:443?type=xhttp&security=tls&sni=cdn.example.com&extra=" + extra + "&fm=" + fm + "#node")
|
||||
if err != nil {
|
||||
t.Fatalf("parse vless: %v", err)
|
||||
}
|
||||
raw, err := json.Marshal(res.Outbound)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var hostLocal []string
|
||||
var walk func(v any)
|
||||
walk = func(v any) {
|
||||
switch v := v.(type) {
|
||||
case map[string]any:
|
||||
for k, child := range v {
|
||||
for _, banned := range []string{"dialerProxy", "certificateFile", "keyFile", "masterKeyLog"} {
|
||||
if strings.EqualFold(k, banned) {
|
||||
hostLocal = append(hostLocal, k)
|
||||
}
|
||||
}
|
||||
walk(child)
|
||||
}
|
||||
case []any:
|
||||
for _, child := range v {
|
||||
walk(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
var decoded any
|
||||
_ = json.Unmarshal(raw, &decoded)
|
||||
walk(decoded)
|
||||
if len(hostLocal) > 0 {
|
||||
t.Fatalf("outbound keeps host-local keys %v: %s", hostLocal, raw)
|
||||
}
|
||||
if !strings.Contains(string(raw), `"tcpFastOpen":true`) || !strings.Contains(string(raw), `"serverName":"cdn.example.com"`) {
|
||||
t.Fatalf("remote-server settings were dropped too: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user