check_client_ip_job.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. package job
  2. import (
  3. "encoding/json"
  4. "log"
  5. "os"
  6. "regexp"
  7. "x-ui/database"
  8. "x-ui/database/model"
  9. "x-ui/logger"
  10. "x-ui/web/service"
  11. "x-ui/xray"
  12. "sort"
  13. "strings"
  14. "time"
  15. )
  16. type CheckClientIpJob struct {
  17. xrayService service.XrayService
  18. }
  19. var job *CheckClientIpJob
  20. var disAllowedIps []string
  21. func NewCheckClientIpJob() *CheckClientIpJob {
  22. job = new(CheckClientIpJob)
  23. return job
  24. }
  25. func (j *CheckClientIpJob) Run() {
  26. logger.Debug("Check Client IP Job...")
  27. if hasLimitIp() {
  28. //create log file for Fail2ban IP Limit
  29. logIpFile, err := os.OpenFile("/var/log/3xipl.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
  30. checkError(err)
  31. defer logIpFile.Close()
  32. log.SetOutput(logIpFile)
  33. log.SetFlags(log.LstdFlags)
  34. //create file to collect access.log to another file accessp.log (p=persistent)
  35. logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
  36. checkError(err)
  37. defer logAccessP.Close()
  38. processLogFile()
  39. }
  40. blockedIps := []byte(strings.Join(disAllowedIps, ","))
  41. // check if file exists, if not create one
  42. _, err := os.Stat(xray.GetBlockedIPsPath())
  43. if os.IsNotExist(err) {
  44. _, err = os.OpenFile(xray.GetBlockedIPsPath(), os.O_RDWR|os.O_CREATE, 0755)
  45. checkError(err)
  46. }
  47. err = os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
  48. checkError(err)
  49. }
  50. func hasLimitIp() bool {
  51. db := database.GetDB()
  52. var inbounds []*model.Inbound
  53. err := db.Model(model.Inbound{}).Find(&inbounds).Error
  54. if err != nil {
  55. return false
  56. }
  57. for _, inbound := range inbounds {
  58. if inbound.Settings == "" {
  59. continue
  60. }
  61. settings := map[string][]model.Client{}
  62. json.Unmarshal([]byte(inbound.Settings), &settings)
  63. clients := settings["clients"]
  64. for _, client := range clients {
  65. limitIp := client.LimitIP
  66. if limitIp > 0 {
  67. return true
  68. }
  69. }
  70. }
  71. return false
  72. }
  73. func processLogFile() {
  74. accessLogPath := GetAccessLogPath()
  75. if accessLogPath == "" {
  76. logger.Warning("access.log doesn't exist in your config.json")
  77. return
  78. }
  79. data, err := os.ReadFile(accessLogPath)
  80. InboundClientIps := make(map[string][]string)
  81. checkError(err)
  82. lines := strings.Split(string(data), "\n")
  83. for _, line := range lines {
  84. ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
  85. emailRegx, _ := regexp.Compile(`email:.+`)
  86. matchesIp := ipRegx.FindString(line)
  87. if len(matchesIp) > 0 {
  88. ip := string(matchesIp)
  89. if ip == "127.0.0.1" || ip == "1.1.1.1" {
  90. continue
  91. }
  92. matchesEmail := emailRegx.FindString(line)
  93. if matchesEmail == "" {
  94. continue
  95. }
  96. matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
  97. if InboundClientIps[matchesEmail] != nil {
  98. if contains(InboundClientIps[matchesEmail], ip) {
  99. continue
  100. }
  101. InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
  102. } else {
  103. InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
  104. }
  105. }
  106. }
  107. disAllowedIps = []string{}
  108. shouldCleanLog := false
  109. for clientEmail, ips := range InboundClientIps {
  110. inboundClientIps, err := GetInboundClientIps(clientEmail)
  111. sort.Strings(ips)
  112. if err != nil {
  113. addInboundClientIps(clientEmail, ips)
  114. } else {
  115. shouldCleanLog = updateInboundClientIps(inboundClientIps, clientEmail, ips)
  116. }
  117. }
  118. time.Sleep(time.Second * 3)
  119. //added 3 seconds delay before cleaning logs to reduce chance of logging IP that already has been banned
  120. if shouldCleanLog {
  121. //copy log
  122. logAccessP, err := os.OpenFile("/usr/local/x-ui/accessp.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
  123. checkError(err)
  124. input, err := os.ReadFile(accessLogPath)
  125. checkError(err)
  126. if _, err := logAccessP.Write(input); err != nil {
  127. checkError(err)
  128. }
  129. defer logAccessP.Close()
  130. // clean log
  131. if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
  132. checkError(err)
  133. }
  134. }
  135. }
  136. func GetAccessLogPath() string {
  137. config, err := os.ReadFile(xray.GetConfigPath())
  138. checkError(err)
  139. jsonConfig := map[string]interface{}{}
  140. err = json.Unmarshal([]byte(config), &jsonConfig)
  141. checkError(err)
  142. if jsonConfig["log"] != nil {
  143. jsonLog := jsonConfig["log"].(map[string]interface{})
  144. if jsonLog["access"] != nil {
  145. accessLogPath := jsonLog["access"].(string)
  146. return accessLogPath
  147. }
  148. }
  149. return ""
  150. }
  151. func checkError(e error) {
  152. if e != nil {
  153. logger.Warning("client ip job err:", e)
  154. }
  155. }
  156. func contains(s []string, str string) bool {
  157. for _, v := range s {
  158. if v == str {
  159. return true
  160. }
  161. }
  162. return false
  163. }
  164. func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
  165. db := database.GetDB()
  166. InboundClientIps := &model.InboundClientIps{}
  167. err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
  168. if err != nil {
  169. return nil, err
  170. }
  171. return InboundClientIps, nil
  172. }
  173. func addInboundClientIps(clientEmail string, ips []string) error {
  174. inboundClientIps := &model.InboundClientIps{}
  175. jsonIps, err := json.Marshal(ips)
  176. checkError(err)
  177. inboundClientIps.ClientEmail = clientEmail
  178. inboundClientIps.Ips = string(jsonIps)
  179. db := database.GetDB()
  180. tx := db.Begin()
  181. defer func() {
  182. if err == nil {
  183. tx.Commit()
  184. } else {
  185. tx.Rollback()
  186. }
  187. }()
  188. err = tx.Save(inboundClientIps).Error
  189. if err != nil {
  190. return err
  191. }
  192. return nil
  193. }
  194. func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
  195. jsonIps, err := json.Marshal(ips)
  196. checkError(err)
  197. inboundClientIps.ClientEmail = clientEmail
  198. inboundClientIps.Ips = string(jsonIps)
  199. // check inbound limitation
  200. inbound, err := GetInboundByEmail(clientEmail)
  201. checkError(err)
  202. if inbound.Settings == "" {
  203. logger.Debug("wrong data ", inbound)
  204. return false
  205. }
  206. settings := map[string][]model.Client{}
  207. json.Unmarshal([]byte(inbound.Settings), &settings)
  208. clients := settings["clients"]
  209. shouldCleanLog := false
  210. for _, client := range clients {
  211. if client.Email == clientEmail {
  212. limitIp := client.LimitIP
  213. if limitIp != 0 {
  214. shouldCleanLog = true
  215. if limitIp < len(ips) && inbound.Enable {
  216. disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
  217. for i := limitIp; i < len(ips); i++ {
  218. log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
  219. }
  220. }
  221. }
  222. }
  223. }
  224. logger.Debug("disAllowedIps ", disAllowedIps)
  225. sort.Strings(disAllowedIps)
  226. db := database.GetDB()
  227. err = db.Save(inboundClientIps).Error
  228. if err != nil {
  229. return shouldCleanLog
  230. }
  231. return shouldCleanLog
  232. }
  233. func DisableInbound(id int) error {
  234. db := database.GetDB()
  235. result := db.Model(model.Inbound{}).
  236. Where("id = ? and enable = ?", id, true).
  237. Update("enable", false)
  238. err := result.Error
  239. logger.Warning("disable inbound with id:", id)
  240. if err == nil {
  241. job.xrayService.SetToNeedRestart()
  242. }
  243. return err
  244. }
  245. func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
  246. db := database.GetDB()
  247. var inbounds *model.Inbound
  248. err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
  249. if err != nil {
  250. return nil, err
  251. }
  252. return inbounds, nil
  253. }