mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-02-28 21:23:01 +00:00
Compare commits
3 commits
d8fb09faae
...
25f64738e4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25f64738e4 | ||
|
|
5bb87fd3d4 | ||
|
|
491e3f9f8b |
4 changed files with 43 additions and 55 deletions
|
|
@ -30,7 +30,8 @@ RUN apk add --no-cache --update \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban \
|
fail2ban \
|
||||||
bash \
|
bash \
|
||||||
curl
|
curl \
|
||||||
|
openssl
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
COPY --from=builder /app/build/ /app/
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
|
|
|
||||||
|
|
@ -182,10 +182,24 @@ func (a *SUBController) ApplyCommonHeaders(
|
||||||
) {
|
) {
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
|
c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
|
||||||
c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
|
|
||||||
c.Writer.Header().Set("Support-Url", profileSupportUrl)
|
//Basics
|
||||||
c.Writer.Header().Set("Profile-Web-Page-Url", profileUrl)
|
if profileTitle != "" {
|
||||||
c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce)))
|
c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
|
||||||
|
}
|
||||||
|
if profileSupportUrl != "" {
|
||||||
|
c.Writer.Header().Set("Support-Url", profileSupportUrl)
|
||||||
|
}
|
||||||
|
if profileUrl != "" {
|
||||||
|
c.Writer.Header().Set("Profile-Web-Page-Url", profileUrl)
|
||||||
|
}
|
||||||
|
if profileAnnounce != "" {
|
||||||
|
c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Advanced (Happ)
|
||||||
c.Writer.Header().Set("Routing-Enable", strconv.FormatBool(profileEnableRouting))
|
c.Writer.Header().Set("Routing-Enable", strconv.FormatBool(profileEnableRouting))
|
||||||
c.Writer.Header().Set("Routing", profileRoutingRules)
|
if profileRoutingRules != "" {
|
||||||
|
c.Writer.Header().Set("Routing", profileRoutingRules)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package job
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -388,7 +387,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||||
// disconnectClientTemporarily removes and re-adds a client to force disconnect old connections
|
// disconnectClientTemporarily removes and re-adds a client to force disconnect old connections
|
||||||
func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, clientEmail string, clients []model.Client) {
|
func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, clientEmail string, clients []model.Client) {
|
||||||
var xrayAPI xray.XrayAPI
|
var xrayAPI xray.XrayAPI
|
||||||
|
|
||||||
// Get panel settings for API port
|
// Get panel settings for API port
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var apiPort int
|
var apiPort int
|
||||||
|
|
@ -396,7 +395,7 @@ func (j *CheckClientIpJob) disconnectClientTemporarily(inbound *model.Inbound, c
|
||||||
if err := db.Where("key = ?", "xrayApiPort").First(&apiPortSetting).Error; err == nil {
|
if err := db.Where("key = ?", "xrayApiPort").First(&apiPortSetting).Error; err == nil {
|
||||||
apiPort, _ = strconv.Atoi(apiPortSetting.Value)
|
apiPort, _ = strconv.Atoi(apiPortSetting.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if apiPort == 0 {
|
if apiPort == 0 {
|
||||||
apiPort = 10085 // Default API port
|
apiPort = 10085 // Default API port
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1056,35 +1056,23 @@ func (s *ServerService) IsValidGeofileName(filename string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) UpdateGeofile(fileName string) error {
|
func (s *ServerService) UpdateGeofile(fileName string) error {
|
||||||
files := []struct {
|
type geofileEntry struct {
|
||||||
URL string
|
URL string
|
||||||
FileName string
|
FileName string
|
||||||
}{
|
}
|
||||||
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
|
geofileAllowlist := map[string]geofileEntry{
|
||||||
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
|
"geoip.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
|
||||||
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
|
"geosite.dat": {"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
|
||||||
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
|
"geoip_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
|
||||||
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
|
"geosite_IR.dat": {"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
|
||||||
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
|
"geoip_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
|
||||||
|
"geosite_RU.dat": {"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strict allowlist check to avoid writing uncontrolled files
|
// Strict allowlist check to avoid writing uncontrolled files
|
||||||
if fileName != "" {
|
if fileName != "" {
|
||||||
// Use the centralized validation function
|
if _, ok := geofileAllowlist[fileName]; !ok {
|
||||||
if !s.IsValidGeofileName(fileName) {
|
return common.NewErrorf("Invalid geofile name: %q not in allowlist", fileName)
|
||||||
return common.NewErrorf("Invalid geofile name: contains unsafe path characters: %s", fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the filename matches exactly one from our allowlist
|
|
||||||
isAllowed := false
|
|
||||||
for _, file := range files {
|
|
||||||
if fileName == file.FileName {
|
|
||||||
isAllowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isAllowed {
|
|
||||||
return common.NewErrorf("Invalid geofile name: %s not in allowlist", fileName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1159,32 +1147,18 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
|
||||||
var errorMessages []string
|
var errorMessages []string
|
||||||
|
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
for _, file := range files {
|
// Download all geofiles
|
||||||
// Sanitize the filename from our allowlist as an extra precaution
|
for _, entry := range geofileAllowlist {
|
||||||
destPath := filepath.Join(config.GetBinFolderPath(), filepath.Base(file.FileName))
|
destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
|
||||||
if err := downloadFile(file.URL, destPath); err != nil {
|
if err := downloadFile(entry.URL, destPath); err != nil {
|
||||||
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", file.FileName, err))
|
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use filepath.Base to ensure we only get the filename component, no path traversal
|
entry := geofileAllowlist[fileName]
|
||||||
safeName := filepath.Base(fileName)
|
destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
|
||||||
destPath := filepath.Join(config.GetBinFolderPath(), safeName)
|
if err := downloadFile(entry.URL, destPath); err != nil {
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
|
||||||
var fileURL string
|
|
||||||
for _, file := range files {
|
|
||||||
if file.FileName == fileName {
|
|
||||||
fileURL = file.URL
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileURL == "" {
|
|
||||||
errorMessages = append(errorMessages, fmt.Sprintf("File '%s' not found in the list of Geofiles", fileName))
|
|
||||||
} else {
|
|
||||||
if err := downloadFile(fileURL, destPath); err != nil {
|
|
||||||
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", fileName, err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue