diff options
Diffstat (limited to 'web/service')
| -rw-r--r-- | web/service/inbound.go | 167 | ||||
| -rw-r--r-- | web/service/server.go | 26 | ||||
| -rw-r--r-- | web/service/sub.go | 70 |
3 files changed, 188 insertions, 75 deletions
diff --git a/web/service/inbound.go b/web/service/inbound.go index b7eb6789..c3f92e5a 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -64,28 +64,45 @@ func (s *InboundService) getClients(inbound *model.Inbound) ([]model.Client, err return clients, nil } -func (s *InboundService) checkEmailsExist(emails map[string]bool, ignoreId int) (string, error) { +func (s *InboundService) getAllEmails() ([]string, error) { db := database.GetDB() - var inbounds []*model.Inbound - db = db.Model(model.Inbound{}).Where("Protocol in ?", []model.Protocol{model.VMess, model.VLESS, model.Trojan}) - if ignoreId > 0 { - db = db.Where("id != ?", ignoreId) - } - db = db.Find(&inbounds) - if db.Error != nil { - return "", db.Error + var emails []string + err := db.Raw(` + SELECT JSON_EXTRACT(client.value, '$.email') + FROM inbounds, + JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client + `).Scan(&emails).Error + + if err != nil { + return nil, err } + return emails, nil +} - for _, inbound := range inbounds { - clients, err := s.getClients(inbound) - if err != nil { - return "", err +func (s *InboundService) contains(slice []string, str string) bool { + for _, s := range slice { + if s == str { + return true } + } + return false +} - for _, client := range clients { - if emails[client.Email] { +func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) { + allEmails, err := s.getAllEmails() + if err != nil { + return "", err + } + var emails []string + for _, client := range clients { + if client.Email != "" { + if s.contains(emails, client.Email) { + return client.Email, nil + } + if s.contains(allEmails, client.Email) { return client.Email, nil } + emails = append(emails, client.Email) } } return "", nil @@ -96,16 +113,23 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri if err != nil { return "", err } - emails := make(map[string]bool) + allEmails, err := s.getAllEmails() + if err != nil { + return "", err + } + var emails []string for _, client := range clients { if client.Email != "" { - if emails[client.Email] { + if s.contains(emails, client.Email) { + return client.Email, nil + } + if s.contains(allEmails, client.Email) { return client.Email, nil } - emails[client.Email] = true + emails = append(emails, client.Email) } } - return s.checkEmailsExist(emails, inbound.Id) + return "", nil } func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, error) { @@ -215,14 +239,6 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, return inbound, common.NewError("Port already exists:", inbound.Port) } - existEmail, err := s.checkEmailExistForInbound(inbound) - if err != nil { - return inbound, err - } - if existEmail != "" { - return inbound, common.NewError("Duplicate email:", existEmail) - } - oldInbound, err := s.GetInbound(inbound.Id) if err != nil { return inbound, err @@ -245,8 +261,12 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, return inbound, db.Save(oldInbound).Error } -func (s *InboundService) AddInboundClient(inbound *model.Inbound) error { - existEmail, err := s.checkEmailExistForInbound(inbound) +func (s *InboundService) AddInboundClient(data *model.Inbound) error { + clients, err := s.getClients(data) + if err != nil { + return err + } + existEmail, err := s.checkEmailsExistForClients(clients) if err != nil { return err } @@ -255,29 +275,35 @@ func (s *InboundService) AddInboundClient(inbound *model.Inbound) error { return common.NewError("Duplicate email:", existEmail) } - clients, err := s.getClients(inbound) + oldInbound, err := s.GetInbound(data.Id) if err != nil { return err } - oldInbound, err := s.GetInbound(inbound.Id) + var settings map[string]interface{} + err = json.Unmarshal([]byte(oldInbound.Settings), &settings) if err != nil { return err } - oldClients, err := s.getClients(oldInbound) + oldClients := settings["clients"].([]interface{}) + var newClients []interface{} + for _, client := range clients { + newClients = append(newClients, client) + } + + settings["clients"] = append(oldClients, newClients...) + + newSettings, err := json.MarshalIndent(settings, "", " ") if err != nil { return err } - oldInbound.Settings = inbound.Settings + oldInbound.Settings = string(newSettings) - if len(clients[len(clients)-1].Email) > 0 { - s.AddClientStat(inbound.Id, &clients[len(clients)-1]) - } - for i := len(oldClients); i < len(clients); i++ { - if len(clients[i].Email) > 0 { - s.AddClientStat(inbound.Id, &clients[i]) + for _, client := range clients { + if len(client.Email) > 0 { + s.AddClientStat(data.Id, &client) } } db := database.GetDB() @@ -309,37 +335,56 @@ func (s *InboundService) DelInboundClient(inbound *model.Inbound, email string) return db.Save(oldInbound).Error } -func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error { - existEmail, err := s.checkEmailExistForInbound(inbound) +func (s *InboundService) UpdateInboundClient(data *model.Inbound, index int) error { + clients, err := s.getClients(data) if err != nil { return err } - if existEmail != "" { - return common.NewError("Duplicate email:", existEmail) - } - clients, err := s.getClients(inbound) + oldInbound, err := s.GetInbound(data.Id) if err != nil { return err } - oldInbound, err := s.GetInbound(inbound.Id) + oldClients, err := s.getClients(oldInbound) if err != nil { return err } - oldClients, err := s.getClients(oldInbound) + if len(clients[0].Email) > 0 && clients[0].Email != oldClients[index].Email { + existEmail, err := s.checkEmailsExistForClients(clients) + if err != nil { + return err + } + if existEmail != "" { + return common.NewError("Duplicate email:", existEmail) + } + } + + var settings map[string]interface{} + err = json.Unmarshal([]byte(oldInbound.Settings), &settings) if err != nil { return err } - oldInbound.Settings = inbound.Settings + settingsClients := settings["clients"].([]interface{}) + var newClients []interface{} + newClients = append(newClients, clients[0]) + settingsClients[index] = newClients[0] + + settings["clients"] = settingsClients + + newSettings, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return err + } + oldInbound.Settings = string(newSettings) db := database.GetDB() - if len(clients[index].Email) > 0 { + if len(clients[0].Email) > 0 { if len(oldClients[index].Email) > 0 { - err = s.UpdateClientStat(oldClients[index].Email, &clients[index]) + err = s.UpdateClientStat(oldClients[index].Email, &clients[0]) if err != nil { return err } @@ -348,7 +393,7 @@ func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) return err } } else { - s.AddClientStat(inbound.Id, &clients[index]) + s.AddClientStat(data.Id, &clients[0]) } } else { err = s.DelClientStat(db, oldClients[index].Email) @@ -507,6 +552,16 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) { count := result.RowsAffected return count, err } +func (s *InboundService) DisableInvalidClients() (int64, error) { + db := database.GetDB() + now := time.Now().Unix() * 1000 + result := db.Model(xray.ClientTraffic{}). + Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). + Update("enable", false) + err := result.Error + count := result.RowsAffected + return count, err +} func (s *InboundService) RemoveOrphanedTraffics() { db := database.GetDB() db.Exec(` @@ -518,16 +573,6 @@ func (s *InboundService) RemoveOrphanedTraffics() { ) `) } -func (s *InboundService) DisableInvalidClients() (int64, error) { - db := database.GetDB() - now := time.Now().Unix() * 1000 - result := db.Model(xray.ClientTraffic{}). - Where("((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?)) and enable = ?", now, true). - Update("enable", false) - err := result.Error - count := result.RowsAffected - return count, err -} func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error { db := database.GetDB() diff --git a/web/service/server.go b/web/service/server.go index c73fce57..f2540252 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -390,3 +390,29 @@ func (s *ServerService) GetDb() ([]byte, error) { return fileContents, nil } + +func (s *ServerService) GetNewX25519Cert() (interface{}, error) { + // Run the command + cmd := exec.Command(xray.GetBinaryPath(), "x25519") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return nil, err + } + + lines := strings.Split(out.String(), "\n") + + privateKeyLine := strings.Split(lines[0], ":") + publicKeyLine := strings.Split(lines[1], ":") + + privateKey := strings.TrimSpace(privateKeyLine[1]) + publicKey := strings.TrimSpace(publicKeyLine[1]) + + keyPair := map[string]interface{}{ + "privateKey": privateKey, + "publicKey": publicKey, + } + + return keyPair, nil +} diff --git a/web/service/sub.go b/web/service/sub.go index 06c597e8..3041b721 100644 --- a/web/service/sub.go +++ b/web/service/sub.go @@ -8,6 +8,7 @@ import ( "x-ui/database" "x-ui/database/model" "x-ui/logger" + "x-ui/xray" "github.com/goccy/go-json" "gorm.io/gorm" @@ -18,12 +19,15 @@ type SubService struct { inboundService InboundService } -func (s *SubService) GetSubs(subId string, host string) ([]string, error) { +func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) { s.address = host var result []string + var header string + var traffic xray.ClientTraffic + var clientTraffics []xray.ClientTraffic inbounds, err := s.getInboundsBySubId(subId) if err != nil { - return nil, err + return nil, "", err } for _, inbound := range inbounds { clients, err := s.inboundService.getClients(inbound) @@ -37,22 +41,60 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, error) { if client.SubID == subId { link := s.getLink(inbound, client.Email) result = append(result, link) + clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email)) + } + } + } + for index, clientTraffic := range clientTraffics { + if index == 0 { + traffic.Up = clientTraffic.Up + traffic.Down = clientTraffic.Down + traffic.Total = clientTraffic.Total + if clientTraffic.ExpiryTime > 0 { + traffic.ExpiryTime = clientTraffic.ExpiryTime + } + } else { + traffic.Up += clientTraffic.Up + traffic.Down += clientTraffic.Down + if traffic.Total == 0 || clientTraffic.Total == 0 { + traffic.Total = 0 + } else { + traffic.Total += clientTraffic.Total + } + if clientTraffic.ExpiryTime != traffic.ExpiryTime { + traffic.ExpiryTime = 0 } } } - return result, nil + header = fmt.Sprintf("upload=%d;download=%d", traffic.Up, traffic.Down) + if traffic.Total > 0 { + header = header + fmt.Sprintf(";total=%d", traffic.Total) + } + if traffic.ExpiryTime > 0 { + header = header + fmt.Sprintf(";expire=%d", traffic.ExpiryTime) + } + return result, header, nil } func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound - err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error + err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } return inbounds, nil } +func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic { + for _, traffic := range traffics { + if traffic.Email == email { + return traffic + } + } + return xray.ClientTraffic{} +} + func (s *SubService) getLink(inbound *model.Inbound, email string) string { switch inbound.Protocol { case "vmess": @@ -296,7 +338,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { if security == "xtls" { params["security"] = "xtls" - xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{}) + xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{}) alpns, _ := xtlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { @@ -306,15 +348,15 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { params["alpn"] = strings.Join(alpn, ",") } - XTLSSettings, _ := searchKey(xtlsSetting, "settings") + xtlsSettings, _ := searchKey(xtlsSetting, "settings") if xtlsSetting != nil { - if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok { + if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok { params["sni"], _ = sniValue.(string) } - if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok { + if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } - if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok { + if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } @@ -465,7 +507,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string if security == "xtls" { params["security"] = "xtls" - xtlsSetting, _ := stream["XTLSSettings"].(map[string]interface{}) + xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{}) alpns, _ := xtlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { @@ -475,15 +517,15 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string params["alpn"] = strings.Join(alpn, ",") } - XTLSSettings, _ := searchKey(xtlsSetting, "settings") + xtlsSettings, _ := searchKey(xtlsSetting, "settings") if xtlsSetting != nil { - if sniValue, ok := searchKey(XTLSSettings, "serverName"); ok { + if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok { params["sni"], _ = sniValue.(string) } - if fpValue, ok := searchKey(XTLSSettings, "fingerprint"); ok { + if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } - if insecure, ok := searchKey(XTLSSettings, "allowInsecure"); ok { + if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } |
