package main import ( "bytes" "crypto/rand" "encoding/hex" "encoding/json" "errors" "flag" "fmt" "io/ioutil" "log" "net" "net/http" "os" "os/exec" "os/user" "path/filepath" "regexp" "sort" "strconv" "strings" "sync" "syscall" "time" ) type Config struct { Listen string `json:"listen"` Username string `json:"username"` Password string `json:"password"` Token string `json:"token"` DataDir string `json:"data_dir"` } type Account struct { Username string `json:"username"` Password string `json:"password,omitempty"` MaxConnections int `json:"max_connections"` UUID string `json:"uuid,omitempty"` WithXray bool `json:"with_xray"` ExpiresAt *time.Time `json:"expires_at,omitempty"` CreatedAt time.Time `json:"created_at"` } type Store struct { mu sync.Mutex path string Accounts map[string]Account `json:"accounts"` } type App struct { cfg Config sessions map[string]time.Time sessMu sync.Mutex store *Store } var usernameRE = regexp.MustCompile(`^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,31}$`) func main() { cfgPath := flag.String("config", getenv("DRAGON_BRIDGE_CONFIG", "/etc/dragoncore-bridge/config.json"), "config file") flag.Parse() cfg, err := loadConfig(*cfgPath) if err != nil { log.Fatalf("config: %v", err) } if cfg.Listen == "" { cfg.Listen = ":6969" } if cfg.DataDir == "" { cfg.DataDir = "/etc/dragoncore-bridge" } if cfg.Username == "" { cfg.Username = "admin" } if cfg.Password == "" || cfg.Token == "" { log.Fatalf("username/password/token must be configured in %s", *cfgPath) } if err := os.MkdirAll(cfg.DataDir, 0700); err != nil { log.Fatalf("data dir: %v", err) } st, err := loadStore(filepath.Join(cfg.DataDir, "accounts.json")) if err != nil { log.Fatalf("store: %v", err) } app := &App{cfg: cfg, sessions: map[string]time.Time{}, store: st} go app.expiryLoop() mux := http.NewServeMux() mux.HandleFunc("/", app.handleLegacyCommand) mux.HandleFunc("/api/auth/login", app.handleLogin) mux.Handle("/api/auth/me", app.auth(http.HandlerFunc(app.handleMe))) mux.Handle("/api/users", app.auth(http.HandlerFunc(app.handleUsers))) mux.Handle("/api/users/create", app.auth(http.HandlerFunc(app.handleCreateUser))) mux.Handle("/api/users/delete", app.auth(http.HandlerFunc(app.handleDeleteUser))) mux.Handle("/api/dragonpanel/create", app.auth(http.HandlerFunc(app.handleDragonCreate))) mux.Handle("/api/dragonpanel/delete", app.auth(http.HandlerFunc(app.handleDragonDelete))) mux.Handle("/api/dragonpanel/sync", app.auth(http.HandlerFunc(app.handleDragonSync))) mux.Handle("/api/stats", app.auth(http.HandlerFunc(app.handleStats))) mux.Handle("/api/system/reboot", app.auth(http.HandlerFunc(app.handleReboot))) mux.Handle("/api/system/restart-ssh", app.auth(http.HandlerFunc(app.handleRestartSSH))) mux.Handle("/api/system/cleanup", app.auth(http.HandlerFunc(app.handleCleanup))) mux.Handle("/api/xray/fix", app.auth(http.HandlerFunc(app.handleXrayFix))) mux.Handle("/api/xray/inbounds", app.auth(http.HandlerFunc(app.handleXrayInbounds))) mux.Handle("/api/xray/clients/add", app.auth(http.HandlerFunc(app.handleXrayClientAdd))) mux.Handle("/api/xray/clients/remove", app.auth(http.HandlerFunc(app.handleXrayClientRemove))) log.Printf("DragonCore bridge listening on %s", cfg.Listen) srv := &http.Server{Addr: cfg.Listen, Handler: mux, ReadHeaderTimeout: 10 * time.Second} log.Fatal(srv.ListenAndServe()) } func getenv(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def } func loadConfig(path string) (Config, error) { b, err := ioutil.ReadFile(path) if err != nil { return Config{}, err } var c Config if err := json.Unmarshal(b, &c); err != nil { return Config{}, err } return c, nil } func loadStore(path string) (*Store, error) { st := &Store{path: path, Accounts: map[string]Account{}} b, err := ioutil.ReadFile(path) if errors.Is(err, os.ErrNotExist) { return st, nil } if err != nil { return nil, err } if len(bytes.TrimSpace(b)) == 0 { return st, nil } if err := json.Unmarshal(b, st); err != nil { return nil, err } if st.Accounts == nil { st.Accounts = map[string]Account{} } st.path = path return st, nil } func (s *Store) saveLocked() error { tmp := s.path + ".tmp" b, _ := json.MarshalIndent(s, "", " ") if err := ioutil.WriteFile(tmp, b, 0600); err != nil { return err } return os.Rename(tmp, s.path) } func randToken(n int) string { b := make([]byte, n); _, _ = rand.Read(b); return hex.EncodeToString(b) } func writeJSON(w http.ResponseWriter, v interface{}) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(v) } func errText(w http.ResponseWriter, code int, msg string) { http.Error(w, msg, code) } func (a *App) handleLogin(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var req struct{ Username, Password string } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { errText(w, 400, "invalid json") return } if req.Username != a.cfg.Username || req.Password != a.cfg.Password { errText(w, 401, "invalid credentials") return } tok := randToken(24) a.sessMu.Lock() a.sessions[tok] = time.Now().Add(12 * time.Hour) a.sessMu.Unlock() writeJSON(w, map[string]interface{}{"token": tok, "username": a.cfg.Username, "role": "admin"}) } func (a *App) auth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Senha") == a.cfg.Token || strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") == a.cfg.Token { next.ServeHTTP(w, r) return } tok := r.Header.Get("X-Session-Token") a.sessMu.Lock() exp, ok := a.sessions[tok] if ok && time.Now().After(exp) { delete(a.sessions, tok) ok = false } a.sessMu.Unlock() if !ok { errText(w, 401, "unauthorized") return } next.ServeHTTP(w, r) }) } func (a *App) handleMe(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]interface{}{"username": a.cfg.Username, "role": "admin"}) } func (a *App) handleUsers(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } users := listSystemUsers(a.store) writeJSON(w, users) } func (a *App) handleCreateUser(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var p struct { Username string `json:"username"` Password *string `json:"password"` MaxConnections int `json:"max_connections"` ExpiresAt string `json:"expires_at"` LimitUpMbps int `json:"limit_mbps_up"` LimitDownMbps int `json:"limit_mbps_down"` } if err := json.NewDecoder(r.Body).Decode(&p); err != nil { errText(w, 400, "invalid json") return } pass := "" if p.Password != nil { pass = *p.Password } exp, _ := parseTimeMaybe(p.ExpiresAt) if err := a.createSSH(p.Username, pass, p.MaxConnections, exp, "", false); err != nil { errText(w, 400, err.Error()) return } w.WriteHeader(http.StatusCreated) } func (a *App) handleDeleteUser(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { w.WriteHeader(http.StatusMethodNotAllowed) return } u := r.URL.Query().Get("username") if u == "" { errText(w, 400, "username required") return } if err := a.deleteSSH(u, ""); err != nil { errText(w, 400, err.Error()) return } w.WriteHeader(http.StatusNoContent) } func (a *App) handleDragonCreate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var p struct { Username string `json:"username"` Password string `json:"password"` UUID string `json:"uuid"` Days int `json:"days"` Minutes int `json:"minutes"` MaxConnections int `json:"max_connections"` WithXray bool `json:"with_xray"` } if err := json.NewDecoder(r.Body).Decode(&p); err != nil { errText(w, 400, "invalid json") return } var exp *time.Time if p.Minutes > 0 { t := time.Now().Add(time.Duration(p.Minutes) * time.Minute) exp = &t } else if p.Days > 0 { t := time.Now().AddDate(0, 0, p.Days) exp = &t } if err := a.createSSH(p.Username, p.Password, p.MaxConnections, exp, p.UUID, p.WithXray); err != nil { errText(w, 400, err.Error()) return } writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleDragonDelete(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost && r.Method != http.MethodDelete { w.WriteHeader(http.StatusMethodNotAllowed) return } var p struct{ Username, UUID string } if r.Method == http.MethodDelete { p.Username = r.URL.Query().Get("username") p.UUID = r.URL.Query().Get("uuid") } else { _ = json.NewDecoder(r.Body).Decode(&p) } if err := a.deleteSSH(p.Username, p.UUID); err != nil { errText(w, 400, err.Error()) return } writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleDragonSync(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var req struct { Accounts []struct { Username string `json:"username"` Password string `json:"password"` ExpiresAt string `json:"expires_at"` UUID string `json:"uuid"` MaxConnections int `json:"max_connections"` } `json:"accounts"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { errText(w, 400, "invalid json") return } ok, fail := 0, []string{} for _, ac := range req.Accounts { exp, _ := parseTimeMaybe(ac.ExpiresAt) if err := a.createSSH(ac.Username, ac.Password, ac.MaxConnections, exp, ac.UUID, ac.UUID != ""); err != nil { fail = append(fail, ac.Username+":"+err.Error()) } else { ok++ } } writeJSON(w, map[string]interface{}{"ok": ok, "fail": fail}) } func (a *App) createSSH(username, password string, limit int, expiresAt *time.Time, uuid string, withXray bool) error { if !usernameRE.MatchString(username) { return fmt.Errorf("invalid username") } if password == "" { return fmt.Errorf("password required") } _ = a.deleteSSH(username, "") hash, err := passwordHash(password) if err != nil { return err } args := []string{"-M", "-s", "/bin/false", "-p", hash} if expiresAt != nil { linuxExpiry := *expiresAt if linuxExpiry.Before(time.Now().Add(24 * time.Hour)) { linuxExpiry = time.Now().AddDate(0, 0, 2) } args = append(args, "-e", linuxExpiry.Format("2006-01-02")) } args = append(args, username) if out, err := exec.Command("useradd", args...).CombinedOutput(); err != nil { return fmt.Errorf("useradd: %v: %s", err, strings.TrimSpace(string(out))) } if err := writeCompatUserFiles(username, password, limit); err != nil { log.Printf("compat files: %v", err) } if withXray && uuid != "" { if err := addXrayClientAll(uuid, username); err != nil { return err } } a.store.mu.Lock() defer a.store.mu.Unlock() a.store.Accounts[username] = Account{Username: username, Password: password, MaxConnections: limit, UUID: uuid, WithXray: withXray, ExpiresAt: expiresAt, CreatedAt: time.Now()} return a.store.saveLocked() } func (a *App) deleteSSH(username, uuid string) error { if username == "" { return fmt.Errorf("username required") } if username == "root" { return fmt.Errorf("refusing to delete root") } if !usernameRE.MatchString(username) { return fmt.Errorf("invalid username") } if uuid == "" { a.store.mu.Lock() if ac, ok := a.store.Accounts[username]; ok { uuid = ac.UUID } a.store.mu.Unlock() } if uuid != "" { _ = removeXrayClientAll(uuid) } if _, err := user.Lookup(username); err == nil { dummy, _ := passwordHash("disabled-dragoncore") _ = exec.Command("usermod", "-p", dummy, username).Run() _ = exec.Command("pkill", "-u", username).Run() _ = exec.Command("userdel", "--force", username).Run() } _ = removeCompatUserFiles(username) a.store.mu.Lock() delete(a.store.Accounts, username) err := a.store.saveLocked() a.store.mu.Unlock() return err } func passwordHash(password string) (string, error) { out, err := exec.Command("openssl", "passwd", "-1", password).Output() if err != nil { return "", fmt.Errorf("openssl passwd failed: %w", err) } return strings.TrimSpace(string(out)), nil } func writeCompatUserFiles(username, password string, limit int) error { _ = os.MkdirAll("/etc/SSHPlus/senha", 0755) _ = ioutil.WriteFile("/etc/SSHPlus/senha/"+username, []byte(password+"\n"), 0600) upsertLine("/root/usuarios.db", username, fmt.Sprintf("%s %d", username, limit)) if _, err := os.Stat("/opt/DragonCore/menu.php"); err == nil { _ = exec.Command("php", "/opt/DragonCore/menu.php", "deleteData", username).Run() _ = exec.Command("php", "/opt/DragonCore/menu.php", "insertData", username, password, strconv.Itoa(limit)).Run() } return nil } func removeCompatUserFiles(username string) error { _ = os.Remove("/etc/SSHPlus/senha/" + username) removeLinePrefix("/root/usuarios.db", username) if _, err := os.Stat("/opt/DragonCore/menu.php"); err == nil { _ = exec.Command("php", "/opt/DragonCore/menu.php", "deleteData", username).Run() } return nil } func upsertLine(path, prefix, line string) { removeLinePrefix(path, prefix) f, _ := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if f != nil { defer f.Close() _, _ = f.WriteString(line + "\n") } } func removeLinePrefix(path, prefix string) { b, err := ioutil.ReadFile(path) if err != nil { return } var out []string for _, l := range strings.Split(string(b), "\n") { if l == "" { continue } if strings.HasPrefix(l, prefix+" ") || l == prefix { continue } out = append(out, l) } _ = ioutil.WriteFile(path, []byte(strings.Join(out, "\n")+"\n"), 0644) } func (a *App) expiryLoop() { for { time.Sleep(60 * time.Second) now := time.Now() var expired []Account a.store.mu.Lock() for _, ac := range a.store.Accounts { if ac.ExpiresAt != nil && now.After(*ac.ExpiresAt) { expired = append(expired, ac) } } a.store.mu.Unlock() for _, ac := range expired { log.Printf("expiring %s", ac.Username) _ = a.deleteSSH(ac.Username, ac.UUID) } } } func listSystemUsers(st *Store) []map[string]interface{} { st.mu.Lock() defer st.mu.Unlock() out := make([]map[string]interface{}, 0, len(st.Accounts)) for _, ac := range st.Accounts { out = append(out, map[string]interface{}{"username": ac.Username, "active_conns": activeProcCount(ac.Username), "max_connections": ac.MaxConnections, "expires_at": ac.ExpiresAt, "uuid": ac.UUID}) } sort.Slice(out, func(i, j int) bool { return fmt.Sprint(out[i]["username"]) < fmt.Sprint(out[j]["username"]) }) return out } func activeProcCount(username string) int { out, err := exec.Command("pgrep", "-u", username).Output() if err != nil { return 0 } n := 0 for _, l := range strings.Split(strings.TrimSpace(string(out)), "\n") { if l != "" { n++ } } return n } func parseTimeMaybe(s string) (*time.Time, error) { s = strings.TrimSpace(s) if s == "" { return nil, nil } layouts := []string{time.RFC3339, "2006-01-02 15:04:05", "2006-01-02"} for _, l := range layouts { if t, err := time.Parse(l, s); err == nil { return &t, nil } } return nil, fmt.Errorf("invalid time") } func xrayConfigPaths() []string { return []string{"/usr/local/etc/xray/config.json", "/etc/xray/config.json", "/etc/v2ray/config.json"} } func readJSONFile(path string) (map[string]interface{}, error) { b, err := ioutil.ReadFile(path) if err != nil { return nil, err } var m map[string]interface{} err = json.Unmarshal(b, &m) return m, err } func writeJSONFile(path string, m map[string]interface{}) error { b, err := json.MarshalIndent(m, "", " ") if err != nil { return err } tmp := path + ".tmp" if err := ioutil.WriteFile(tmp, b, 0644); err != nil { return err } return os.Rename(tmp, path) } func addXrayClientAll(uuid, email string) error { changed := false for _, path := range xrayConfigPaths() { if _, err := os.Stat(path); err != nil { continue } cfg, err := readJSONFile(path) if err != nil { return err } inb, _ := cfg["inbounds"].([]interface{}) fileChanged := false for _, it := range inb { im, ok := it.(map[string]interface{}) if !ok || fmt.Sprint(im["protocol"]) != "vless" { continue } settings, _ := im["settings"].(map[string]interface{}) if settings == nil { settings = map[string]interface{}{} im["settings"] = settings } clients, _ := settings["clients"].([]interface{}) exists := false for _, c := range clients { cm, _ := c.(map[string]interface{}) if fmt.Sprint(cm["id"]) == uuid || fmt.Sprint(cm["email"]) == email { exists = true } } if !exists { settings["clients"] = append(clients, map[string]interface{}{"id": uuid, "alterId": 0, "email": email}) fileChanged = true } } if fileChanged { if err := writeJSONFile(path, cfg); err != nil { return err } changed = true } } if changed { restartXray() } return nil } func removeXrayClientAll(uuid string) error { changed := false for _, path := range xrayConfigPaths() { if _, err := os.Stat(path); err != nil { continue } cfg, err := readJSONFile(path) if err != nil { return err } inb, _ := cfg["inbounds"].([]interface{}) fileChanged := false for _, it := range inb { im, ok := it.(map[string]interface{}) if !ok || fmt.Sprint(im["protocol"]) != "vless" { continue } settings, _ := im["settings"].(map[string]interface{}) if settings == nil { continue } clients, _ := settings["clients"].([]interface{}) out := make([]interface{}, 0, len(clients)) for _, c := range clients { cm, _ := c.(map[string]interface{}) if fmt.Sprint(cm["id"]) == uuid { fileChanged = true continue } out = append(out, c) } settings["clients"] = out } if fileChanged { if err := writeJSONFile(path, cfg); err != nil { return err } changed = true } } if changed { restartXray() } return nil } func restartXray() { _ = exec.Command("systemctl", "restart", "xray").Run() _ = exec.Command("systemctl", "restart", "v2ray").Run() } func (a *App) handleXrayInbounds(w http.ResponseWriter, r *http.Request) { type Inb struct { Tag string `json:"tag"` Protocol string `json:"protocol"` Clients []map[string]interface{} `json:"clients"` } var out []Inb for _, path := range xrayConfigPaths() { cfg, err := readJSONFile(path) if err != nil { continue } inb, _ := cfg["inbounds"].([]interface{}) for i, it := range inb { im, _ := it.(map[string]interface{}) proto := fmt.Sprint(im["protocol"]) tag := fmt.Sprint(im["tag"]) if tag == "" || tag == "" { tag = fmt.Sprintf("%s-%d", proto, i) } settings, _ := im["settings"].(map[string]interface{}) clientsAny, _ := settings["clients"].([]interface{}) clients := []map[string]interface{}{} for _, c := range clientsAny { if cm, ok := c.(map[string]interface{}); ok { clients = append(clients, cm) } } out = append(out, Inb{Tag: tag, Protocol: proto, Clients: clients}) } } writeJSON(w, out) } func (a *App) handleXrayClientAdd(w http.ResponseWriter, r *http.Request) { var p struct { UUID string `json:"uuid"` Email string `json:"email"` Name string `json:"name"` } _ = json.NewDecoder(r.Body).Decode(&p) if p.UUID == "" { errText(w, 400, "uuid required") return } email := p.Email if email == "" { email = p.Name } if email == "" { email = p.UUID } if err := addXrayClientAll(p.UUID, email); err != nil { errText(w, 400, err.Error()) return } w.WriteHeader(200) } func (a *App) handleXrayClientRemove(w http.ResponseWriter, r *http.Request) { uuid := r.URL.Query().Get("uuid") if uuid == "" { errText(w, 400, "uuid required") return } if err := removeXrayClientAll(uuid); err != nil { errText(w, 400, err.Error()) return } w.WriteHeader(204) } func (a *App) handleStats(w http.ResponseWriter, r *http.Request) { writeJSON(w, collectStats()) } type cpuSample struct{ idle, total uint64 } func readCPU() cpuSample { b, _ := ioutil.ReadFile("/proc/stat") fields := strings.Fields(strings.SplitN(string(b), "\n", 2)[0]) var vals []uint64 for _, f := range fields[1:] { v, _ := strconv.ParseUint(f, 10, 64) vals = append(vals, v) } var total uint64 for _, v := range vals { total += v } idle := uint64(0) if len(vals) > 3 { idle = vals[3] } return cpuSample{idle, total} } func collectStats() map[string]interface{} { c1 := readCPU() time.Sleep(150 * time.Millisecond) c2 := readCPU() cpu := 0.0 if c2.total > c1.total { cpu = 100 * (1 - float64(c2.idle-c1.idle)/float64(c2.total-c1.total)) } mem := readMem() return map[string]interface{}{"cpu_percent": cpu, "mem_total_bytes": mem["total"], "mem_used_bytes": mem["used"], "mem_avail_bytes": mem["avail"], "mem_percent": mem["percent"], "interfaces": readNet()} } func readMem() map[string]interface{} { b, _ := ioutil.ReadFile("/proc/meminfo") vals := map[string]uint64{} for _, l := range strings.Split(string(b), "\n") { f := strings.Fields(l) if len(f) >= 2 { v, _ := strconv.ParseUint(f[1], 10, 64) vals[strings.TrimSuffix(f[0], ":")] = v * 1024 } } total := vals["MemTotal"] avail := vals["MemAvailable"] used := uint64(0) pct := 0.0 if total > 0 { used = total - avail pct = 100 * float64(used) / float64(total) } return map[string]interface{}{"total": total, "used": used, "avail": avail, "percent": pct} } func readNet() []map[string]interface{} { b, _ := ioutil.ReadFile("/proc/net/dev") var out []map[string]interface{} for _, l := range strings.Split(string(b), "\n") { if !strings.Contains(l, ":") { continue } parts := strings.SplitN(l, ":", 2) name := strings.TrimSpace(parts[0]) if name == "lo" || name == "" { continue } f := strings.Fields(parts[1]) if len(f) >= 16 { rx, _ := strconv.ParseUint(f[0], 10, 64) tx, _ := strconv.ParseUint(f[8], 10, 64) out = append(out, map[string]interface{}{"name": name, "rx_bytes": rx, "tx_bytes": tx}) } } return out } func (a *App) handleReboot(w http.ResponseWriter, r *http.Request) { go exec.Command("shutdown", "-r", "+1", "DragonCore bridge reboot requested").Run() writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleRestartSSH(w http.ResponseWriter, r *http.Request) { _ = exec.Command("systemctl", "restart", "ssh").Run() _ = exec.Command("systemctl", "restart", "sshd").Run() writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleCleanup(w http.ResponseWriter, r *http.Request) { _ = exec.Command("sh", "-c", "apt-get clean 2>/dev/null; journalctl --vacuum-time=3d 2>/dev/null; rm -rf /tmp/* 2>/dev/null").Run() writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleXrayFix(w http.ResponseWriter, r *http.Request) { restartXray() writeJSON(w, map[string]interface{}{"ok": true}) } func (a *App) handleLegacyCommand(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } if r.Method != http.MethodPost { _, _ = w.Write([]byte("DragonCore Bridge API")) return } if r.Header.Get("Senha") != a.cfg.Token { errText(w, 401, "Nao autorizado") return } _ = r.ParseForm() cmd := r.FormValue("comando") msg, err := a.runLegacyDragonCommand(cmd) if err != nil { _, _ = w.Write([]byte(err.Error())) return } _, _ = w.Write([]byte(msg)) } func shellFields(s string) []string { var out []string var cur strings.Builder quote := rune(0) esc := false for _, r := range s { if esc { cur.WriteRune(r) esc = false continue } if r == '\\' { esc = true continue } if quote != 0 { if r == quote { quote = 0 } else { cur.WriteRune(r) } continue } if r == '\'' || r == '"' { quote = r continue } if r == ' ' || r == '\t' || r == '\n' { if cur.Len() > 0 { out = append(out, cur.String()) cur.Reset() } continue } cur.WriteRune(r) } if cur.Len() > 0 { out = append(out, cur.String()) } return out } func (a *App) runLegacyDragonCommand(cmd string) (string, error) { f := shellFields(cmd) if len(f) > 0 && strings.Contains(f[0], "dragonmodule") { f = f[1:] } if len(f) == 0 { return "", fmt.Errorf("empty command") } switch f[0] { case "createssh": if len(f) < 5 { return "", fmt.Errorf("bad createssh") } days, _ := strconv.Atoi(f[3]) lim, _ := strconv.Atoi(f[4]) t := time.Now().AddDate(0, 0, days) return "CRIADOCOMSUCESSO", a.createSSH(f[1], f[2], lim, &t, "", false) case "createsshteste": if len(f) < 5 { return "", fmt.Errorf("bad createsshteste") } mins, _ := strconv.Atoi(f[3]) lim, _ := strconv.Atoi(f[4]) t := time.Now().Add(time.Duration(mins) * time.Minute) return "CRIADOCOMSUCESSO", a.createSSH(f[1], f[2], lim, &t, "", false) case "v2rayadd": if len(f) < 6 { return "", fmt.Errorf("bad v2rayadd") } days, _ := strconv.Atoi(f[4]) lim, _ := strconv.Atoi(f[5]) t := time.Now().AddDate(0, 0, days) return "1\nCRIADOCOMSUCESSO", a.createSSH(f[2], f[3], lim, &t, f[1], true) case "v2rayaddteste": if len(f) < 6 { return "", fmt.Errorf("bad v2rayaddteste") } mins, _ := strconv.Atoi(f[4]) lim, _ := strconv.Atoi(f[5]) t := time.Now().Add(time.Duration(mins) * time.Minute) return "1\nCRIADOCOMSUCESSO", a.createSSH(f[2], f[3], lim, &t, f[1], true) case "removessh": if len(f) < 2 { return "", fmt.Errorf("bad removessh") } return "90Cbp1PK1ExPingu", a.deleteSSH(f[1], "") case "v2raydel": if len(f) < 3 { return "", fmt.Errorf("bad v2raydel") } return "90Cbp1PK1ExPingu", a.deleteSSH(f[2], f[1]) default: return "", fmt.Errorf("unsupported command") } } func localIP() string { ifaces, _ := net.Interfaces() for _, i := range ifaces { addrs, _ := i.Addrs() for _, a := range addrs { ipnet, ok := a.(*net.IPNet) if ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil { return ipnet.IP.String() } } } return "127.0.0.1" } func dropPrivilegesNotUsed() { _ = syscall.Getuid() }