From a7a9f45145cb59ad9ee5d5c96f3f2491f73331ba Mon Sep 17 00:00:00 2001 From: SunBK201 Date: Thu, 27 Nov 2025 19:17:39 +0800 Subject: [PATCH] feat: port user group management --- openwrt/files/ua3f.init | 79 +----------- src/internal/usergroup/usergroup_others.go | 11 ++ src/internal/usergroup/usergroup_unix.go | 138 +++++++++++++++++++++ src/main.go | 6 + 4 files changed, 158 insertions(+), 76 deletions(-) create mode 100644 src/internal/usergroup/usergroup_others.go create mode 100644 src/internal/usergroup/usergroup_unix.go diff --git a/openwrt/files/ua3f.init b/openwrt/files/ua3f.init index 491f49a..d337ec3 100755 --- a/openwrt/files/ua3f.init +++ b/openwrt/files/ua3f.init @@ -6,71 +6,6 @@ START=99 NAME="ua3f" PROG="/usr/bin/$NAME" -SERVER_MODE="" -UA3F_GROUP="nogroup" -LOG_FILE="/var/log/ua3f/ua3f.log" - -LOG() { - if [ -n "${1}" ]; then - printf '[%s] %s\n' "$(date "+%Y-%m-%d %H:%M:%S")" "$1" >>"$LOG_FILE" - fi -} - -opkg_available() { command -v opkg >/dev/null 2>&1; } - -openclash_exists() { - if opkg_available; then - if opkg list-installed luci-app-openclash | grep -q 'luci-app-openclash'; then - return 0 - fi - fi - return 1 -} - -openclash_running() { - if pgrep -f "openclash" >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -shellclash_exists() { - if id -u shellclash >/dev/null 2>&1; then - return 0 - fi - if id -u shellcrash >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -shellclash_running() { - if pgrep -f "ShellCrash" >/dev/null 2>&1; then - return 0 - fi - return 1 -} - -set_ua3f_group() { - if [ "$SERVER_MODE" = "REDIRECT" ]; then - UA3F_GROUP="root" - return - elif [ "$SERVER_MODE" = "NFQUEUE" ]; then - UA3F_GROUP="root" - return - fi - if openclash_running; then - UA3F_GROUP="nogroup" - elif shellclash_running; then - UA3F_GROUP="shellcrash" - elif openclash_exists; then - UA3F_GROUP="nogroup" - elif shellclash_exists; then - UA3F_GROUP="shellcrash" - else - UA3F_GROUP="nogroup" - fi -} start_service() { config_load "$NAME" @@ -83,10 +18,10 @@ start_service() { config_get_bool enabled "enabled" "enabled" "0" [ "$enabled" -eq "1" ] || return 0 - local port bind ua log_level ua_regex partial_replace + local server_mode port bind ua log_level ua_regex partial_replace local rewrite_mode rewrite_rules local set_ttl set_ipid del_tcpts - config_get SERVER_MODE "main" "server_mode" "TPROXY" + config_get server_mode "main" "server_mode" "TPROXY" config_get port "main" "port" "1080" config_get bind "main" "bind" "127.0.0.1" config_get ua "main" "ua" "FFF" @@ -99,21 +34,16 @@ start_service() { config_get_bool set_ipid "main" "set_ipid" 0 config_get_bool del_tcpts "main" "del_tcpts" 0 - # compose set_ttl set_ipid del_tcpts with others ",ttl,ipid,tcpts," local others="," [ "$set_ipid" -eq "1" ] && others="${others}ipid," [ "$del_tcpts" -eq "1" ] && others="${others}tcpts," [ "$set_ttl" -eq "1" ] && others="${others}ttl," - SERVER_MODE="$(echo "$SERVER_MODE" | tr '[:lower:]' '[:upper:]')" - set_ua3f_group - LOG "Server Mode: $SERVER_MODE" - LOG "Group: $UA3F_GROUP" procd_open_instance "$NAME" procd_set_param command "$PROG" - procd_append_param command -m "$SERVER_MODE" + procd_append_param command -m "$server_mode" procd_append_param command -p "$port" procd_append_param command -b "$bind" procd_append_param command -f "$ua" @@ -128,11 +58,8 @@ start_service() { procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param limits nproc="unlimited" as="unlimited" memlock="unlimited" nofile="65535 65535" - procd_set_param group $UA3F_GROUP procd_close_instance - - LOG "$NAME service started" } service_triggers() { diff --git a/src/internal/usergroup/usergroup_others.go b/src/internal/usergroup/usergroup_others.go new file mode 100644 index 0000000..57e7333 --- /dev/null +++ b/src/internal/usergroup/usergroup_others.go @@ -0,0 +1,11 @@ +//go:build !linux + +package usergroup + +import ( + "github.com/sunbk201/ua3f/internal/config" +) + +func SetUserGroup(cfg *config.Config) error { + return nil +} diff --git a/src/internal/usergroup/usergroup_unix.go b/src/internal/usergroup/usergroup_unix.go new file mode 100644 index 0000000..c908daa --- /dev/null +++ b/src/internal/usergroup/usergroup_unix.go @@ -0,0 +1,138 @@ +//go:build linux + +package usergroup + +import ( + "bufio" + "fmt" + "log/slog" + "os" + "os/exec" + "strconv" + "strings" + "syscall" + + "github.com/sunbk201/ua3f/internal/config" +) + +func SetUserGroup(cfg *config.Config) error { + groupName := determineGroup(cfg.ServerMode) + if groupName == "" { + return nil + } + + gid, err := getGroupID(groupName) + if err != nil { + slog.Warn("getGroupID", slog.String("group", groupName), slog.Any("error", err)) + return nil + } + + if err := syscall.Setgid(gid); err != nil { + if err == syscall.EPERM { + slog.Warn("syscall.Setgid", slog.String("group", groupName), slog.Int("gid", gid), slog.Any("error", err)) + return nil + } + return fmt.Errorf("syscall.Setgid: %w", err) + } + + slog.Info("Setup user group", slog.String("group", groupName), slog.Int("gid", gid)) + return nil +} + +func determineGroup(serverMode config.ServerMode) string { + if serverMode == config.ServerModeRedirect || serverMode == config.ServerModeNFQueue { + return "root" + } + + if processRunning("openclash") { + return "nogroup" + } + + if processRunning("ShellCrash") { + return "shellcrash" + } + + if openClashExists() { + return "nogroup" + } + + if shellClashExists() { + return "shellcrash" + } + + return "nogroup" +} + +func openClashExists() bool { + if !opkgAvailable() { + return false + } + + cmd := exec.Command("opkg", "list-installed", "luci-app-openclash") + output, err := cmd.Output() + if err != nil { + return false + } + + return strings.Contains(string(output), "luci-app-openclash") +} + +func shellClashExists() bool { + if userExists("shellclash") { + return true + } + if userExists("shellcrash") { + return true + } + return false +} + +func opkgAvailable() bool { + _, err := exec.LookPath("opkg") + return err == nil +} + +func processRunning(name string) bool { + cmd := exec.Command("pgrep", "-f", name) + err := cmd.Run() + return err == nil +} + +func userExists(username string) bool { + cmd := exec.Command("id", "-u", username) + err := cmd.Run() + return err == nil +} + +func getGroupID(groupName string) (int, error) { + if groupName == "root" { + return 0, nil + } + + file, err := os.Open("/etc/group") + if err != nil { + return 0, fmt.Errorf("failed to open /etc/group: %w", err) + } + defer func() { + _ = file.Close() + }() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) >= 3 && parts[0] == groupName { + gid, err := strconv.Atoi(parts[2]) + if err != nil { + return 0, fmt.Errorf("failed to parse GID for group %s: %w", groupName, err) + } + return gid, nil + } + } + + if err := scanner.Err(); err != nil { + return 0, fmt.Errorf("error reading /etc/group: %w", err) + } + + return 0, fmt.Errorf("group %s not found", groupName) +} diff --git a/src/main.go b/src/main.go index d848355..8229034 100644 --- a/src/main.go +++ b/src/main.go @@ -14,6 +14,7 @@ import ( "github.com/sunbk201/ua3f/internal/server" "github.com/sunbk201/ua3f/internal/server/netlink" "github.com/sunbk201/ua3f/internal/statistics" + "github.com/sunbk201/ua3f/internal/usergroup" ) var appVersion = "Development" @@ -30,6 +31,11 @@ func main() { log.LogHeader(appVersion, cfg) + if err := usergroup.SetUserGroup(cfg); err != nil { + slog.Error("usergroup.SetUserGroup", slog.Any("error", err)) + return + } + rw, err := rewrite.New(cfg) if err != nil { slog.Error("rewrite.New", slog.Any("error", err))