iplimit - accept all email format

This commit is contained in:
mhsanaei 2024-10-28 20:13:42 +01:00
parent ac84553a68
commit 569d99512c
No known key found for this signature in database
GPG key ID: 4DACC0663B5986F5

View file

@ -9,7 +9,6 @@ import (
"os/exec" "os/exec"
"regexp" "regexp"
"sort" "sort"
"strings"
"time" "time"
"x-ui/database" "x-ui/database"
@ -37,11 +36,17 @@ func (j *CheckClientIpJob) Run() {
shouldClearAccessLog := false shouldClearAccessLog := false
iplimitActive := j.hasLimitIp() iplimitActive := j.hasLimitIp()
f2bInstalled := j.checkFail2BanInstalled(iplimitActive) f2bInstalled := j.checkFail2BanInstalled()
isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive) isAccessLogAvailable := j.checkAccessLogAvailable(iplimitActive)
if iplimitActive && f2bInstalled && isAccessLogAvailable { if iplimitActive {
shouldClearAccessLog = j.processLogFile() if f2bInstalled && isAccessLogAvailable {
shouldClearAccessLog = j.processLogFile()
} else {
if !f2bInstalled {
logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.")
}
}
} }
if shouldClearAccessLog || (isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600) { if shouldClearAccessLog || (isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600) {
@ -53,23 +58,18 @@ func (j *CheckClientIpJob) clearAccessLog() {
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
j.checkError(err) j.checkError(err)
// get access log path to open it
accessLogPath, err := xray.GetAccessLogPath() accessLogPath, err := xray.GetAccessLogPath()
j.checkError(err) j.checkError(err)
// reopen the access log file for reading
file, err := os.Open(accessLogPath) file, err := os.Open(accessLogPath)
j.checkError(err) j.checkError(err)
// copy access log content to persistent file
_, err = io.Copy(logAccessP, file) _, err = io.Copy(logAccessP, file)
j.checkError(err) j.checkError(err)
// close the file after copying content
logAccessP.Close() logAccessP.Close()
file.Close() file.Close()
// clean access log
err = os.Truncate(accessLogPath, 0) err = os.Truncate(accessLogPath, 0)
j.checkError(err) j.checkError(err)
j.lastClear = time.Now().Unix() j.lastClear = time.Now().Unix()
@ -105,74 +105,69 @@ func (j *CheckClientIpJob) hasLimitIp() bool {
} }
func (j *CheckClientIpJob) processLogFile() bool { func (j *CheckClientIpJob) processLogFile() bool {
accessLogPath, err := xray.GetAccessLogPath()
j.checkError(err)
file, err := os.Open(accessLogPath) ipRegex := regexp.MustCompile(`from \[?([0-9a-fA-F:.]+)\]?:\d+ accepted`)
j.checkError(err) emailRegex := regexp.MustCompile(`email: (.+)$`)
InboundClientIps := make(map[string][]string) accessLogPath, _ := xray.GetAccessLogPath()
file, _ := os.Open(accessLogPath)
defer file.Close()
inboundClientIps := make(map[string]map[string]struct{}, 100)
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
ipRegx, _ := regexp.Compile(`from \[?([0-9a-fA-F:.]+)\]?:\d+ accepted`) ipMatches := ipRegex.FindStringSubmatch(line)
emailRegx, _ := regexp.Compile(`email: (\S+)$`) if len(ipMatches) < 2 {
continue
matches := ipRegx.FindStringSubmatch(line)
if len(matches) > 1 {
ip := matches[1]
if ip == "127.0.0.1" || ip == "::1" {
continue
}
matchesEmail := emailRegx.FindString(line)
if matchesEmail == "" {
continue
}
matchesEmail = strings.Split(matchesEmail, "email: ")[1]
if InboundClientIps[matchesEmail] != nil {
if j.contains(InboundClientIps[matchesEmail], ip) {
continue
}
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
} else {
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
}
} }
ip := ipMatches[1]
if ip == "127.0.0.1" || ip == "::1" {
continue
}
emailMatches := emailRegex.FindStringSubmatch(line)
if len(emailMatches) < 2 {
continue
}
email := emailMatches[1]
if _, exists := inboundClientIps[email]; !exists {
inboundClientIps[email] = make(map[string]struct{})
}
inboundClientIps[email][ip] = struct{}{}
} }
j.checkError(scanner.Err())
file.Close()
shouldCleanLog := false shouldCleanLog := false
for email, uniqueIps := range inboundClientIps {
for clientEmail, ips := range InboundClientIps { ips := make([]string, 0, len(uniqueIps))
inboundClientIps, err := j.getInboundClientIps(clientEmail) for ip := range uniqueIps {
sort.Strings(ips) ips = append(ips, ip)
if err != nil {
j.addInboundClientIps(clientEmail, ips)
} else {
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
} }
sort.Strings(ips)
inboundClientIps, err := j.getInboundClientIps(email)
if err != nil {
j.addInboundClientIps(email, ips)
continue
}
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, email, ips) || shouldCleanLog
} }
return shouldCleanLog return shouldCleanLog
} }
func (j *CheckClientIpJob) checkFail2BanInstalled(iplimitActive bool) bool { func (j *CheckClientIpJob) checkFail2BanInstalled() bool {
cmd := "fail2ban-client" cmd := "fail2ban-client"
args := []string{"-h"} args := []string{"-h"}
err := exec.Command(cmd, args...).Run() err := exec.Command(cmd, args...).Run()
return err == nil
if iplimitActive && err != nil {
logger.Warning("[LimitIP] Fail2Ban is not installed, Please install Fail2Ban from the x-ui bash menu.")
return false
}
return true
} }
func (j *CheckClientIpJob) checkAccessLogAvailable(iplimitActive bool) bool { func (j *CheckClientIpJob) checkAccessLogAvailable(iplimitActive bool) bool {
@ -253,7 +248,6 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
inboundClientIps.ClientEmail = clientEmail inboundClientIps.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps) inboundClientIps.Ips = string(jsonIps)
// Fetch inbound settings by client email
inbound, err := j.getInboundByEmail(clientEmail) inbound, err := j.getInboundByEmail(clientEmail)
if err != nil { if err != nil {
logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err) logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err)
@ -265,14 +259,12 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
return false return false
} }
// Unmarshal settings to get client limits
settings := map[string][]model.Client{} settings := map[string][]model.Client{}
json.Unmarshal([]byte(inbound.Settings), &settings) json.Unmarshal([]byte(inbound.Settings), &settings)
clients := settings["clients"] clients := settings["clients"]
shouldCleanLog := false shouldCleanLog := false
j.disAllowedIps = []string{} j.disAllowedIps = []string{}
// Open log file for IP limits
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil { if err != nil {
logger.Errorf("failed to open IP limit log file: %s", err) logger.Errorf("failed to open IP limit log file: %s", err)
@ -282,7 +274,6 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
log.SetOutput(logIpFile) log.SetOutput(logIpFile)
log.SetFlags(log.LstdFlags) log.SetFlags(log.LstdFlags)
// Check client IP limits
for _, client := range clients { for _, client := range clients {
if client.Email == clientEmail { if client.Email == clientEmail {
limitIp := client.LimitIP limitIp := client.LimitIP