feat: port user group management

This commit is contained in:
SunBK201 2025-11-27 19:17:39 +08:00
parent bdd13b0b86
commit a7a9f45145
4 changed files with 158 additions and 76 deletions

View File

@ -6,71 +6,6 @@ START=99
NAME="ua3f" NAME="ua3f"
PROG="/usr/bin/$NAME" 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() { start_service() {
config_load "$NAME" config_load "$NAME"
@ -83,10 +18,10 @@ start_service() {
config_get_bool enabled "enabled" "enabled" "0" config_get_bool enabled "enabled" "enabled" "0"
[ "$enabled" -eq "1" ] || return 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 rewrite_mode rewrite_rules
local set_ttl set_ipid del_tcpts 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 port "main" "port" "1080"
config_get bind "main" "bind" "127.0.0.1" config_get bind "main" "bind" "127.0.0.1"
config_get ua "main" "ua" "FFF" config_get ua "main" "ua" "FFF"
@ -99,21 +34,16 @@ start_service() {
config_get_bool set_ipid "main" "set_ipid" 0 config_get_bool set_ipid "main" "set_ipid" 0
config_get_bool del_tcpts "main" "del_tcpts" 0 config_get_bool del_tcpts "main" "del_tcpts" 0
# compose set_ttl set_ipid del_tcpts with others ",ttl,ipid,tcpts,"
local others="," local others=","
[ "$set_ipid" -eq "1" ] && others="${others}ipid," [ "$set_ipid" -eq "1" ] && others="${others}ipid,"
[ "$del_tcpts" -eq "1" ] && others="${others}tcpts," [ "$del_tcpts" -eq "1" ] && others="${others}tcpts,"
[ "$set_ttl" -eq "1" ] && others="${others}ttl," [ "$set_ttl" -eq "1" ] && others="${others}ttl,"
SERVER_MODE="$(echo "$SERVER_MODE" | tr '[:lower:]' '[:upper:]')"
set_ua3f_group set_ua3f_group
LOG "Server Mode: $SERVER_MODE"
LOG "Group: $UA3F_GROUP"
procd_open_instance "$NAME" procd_open_instance "$NAME"
procd_set_param command "$PROG" 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 -p "$port"
procd_append_param command -b "$bind" procd_append_param command -b "$bind"
procd_append_param command -f "$ua" procd_append_param command -f "$ua"
@ -128,11 +58,8 @@ start_service() {
procd_set_param stdout 1 procd_set_param stdout 1
procd_set_param stderr 1 procd_set_param stderr 1
procd_set_param limits nproc="unlimited" as="unlimited" memlock="unlimited" nofile="65535 65535" procd_set_param limits nproc="unlimited" as="unlimited" memlock="unlimited" nofile="65535 65535"
procd_set_param group $UA3F_GROUP
procd_close_instance procd_close_instance
LOG "$NAME service started"
} }
service_triggers() { service_triggers() {

View File

@ -0,0 +1,11 @@
//go:build !linux
package usergroup
import (
"github.com/sunbk201/ua3f/internal/config"
)
func SetUserGroup(cfg *config.Config) error {
return nil
}

View File

@ -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)
}

View File

@ -14,6 +14,7 @@ import (
"github.com/sunbk201/ua3f/internal/server" "github.com/sunbk201/ua3f/internal/server"
"github.com/sunbk201/ua3f/internal/server/netlink" "github.com/sunbk201/ua3f/internal/server/netlink"
"github.com/sunbk201/ua3f/internal/statistics" "github.com/sunbk201/ua3f/internal/statistics"
"github.com/sunbk201/ua3f/internal/usergroup"
) )
var appVersion = "Development" var appVersion = "Development"
@ -30,6 +31,11 @@ func main() {
log.LogHeader(appVersion, cfg) 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) rw, err := rewrite.New(cfg)
if err != nil { if err != nil {
slog.Error("rewrite.New", slog.Any("error", err)) slog.Error("rewrite.New", slog.Any("error", err))