mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-06-29 04:01:23 +00:00
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
//go:build unix
|
|
|
|
package tailssh
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"syscall"
|
|
|
|
C "github.com/sagernet/sing-box/constant"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
"github.com/creack/pty"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func StartPtyProcess(shell string, args, env []string, dir string, uid, gid int, groups []int, rows, cols uint16) (*os.File, *os.Process, error) {
|
|
cmd := exec.Command(shell)
|
|
cmd.Args = args
|
|
cmd.Dir = dir
|
|
cmd.Env = env
|
|
attrs := &syscall.SysProcAttr{
|
|
Setsid: true,
|
|
Setctty: true,
|
|
Ctty: 0,
|
|
}
|
|
setCredential(attrs, uid, gid, groups)
|
|
var size *pty.Winsize
|
|
if rows > 0 && cols > 0 {
|
|
size = &pty.Winsize{Rows: rows, Cols: cols}
|
|
}
|
|
master, err := pty.StartWithAttrs(cmd, size, attrs)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return master, cmd.Process, nil
|
|
}
|
|
|
|
func StartSocketpairProcess(shell string, args, env []string, dir string, uid, gid int, groups []int) (*os.File, *os.Process, error) {
|
|
fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0)
|
|
if err != nil {
|
|
return nil, nil, E.Cause(err, "socketpair")
|
|
}
|
|
syscall.CloseOnExec(fds[0])
|
|
syscall.CloseOnExec(fds[1])
|
|
childFile := os.NewFile(uintptr(fds[1]), "socketpair-child")
|
|
cmd := exec.Command(shell)
|
|
cmd.Args = args
|
|
cmd.Dir = dir
|
|
cmd.Env = env
|
|
cmd.Stdin = childFile
|
|
cmd.Stdout = childFile
|
|
cmd.Stderr = childFile
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Setsid: true,
|
|
}
|
|
setCredential(cmd.SysProcAttr, uid, gid, groups)
|
|
err = cmd.Start()
|
|
childFile.Close()
|
|
if err != nil {
|
|
syscall.Close(fds[0])
|
|
return nil, nil, err
|
|
}
|
|
return os.NewFile(uintptr(fds[0]), "socketpair-parent"), cmd.Process, nil
|
|
}
|
|
|
|
func setCredential(attr *syscall.SysProcAttr, uid, gid int, groups []int) {
|
|
if uid < 0 {
|
|
return
|
|
}
|
|
// Skip only when the target identity already matches the server: a non-root
|
|
// server cannot setgroups/setgid, so attempting it would only fail the exec.
|
|
// When the gid differs (a privileged server dropping to another group) we
|
|
// still apply the credential so supplementary groups are reset.
|
|
if uid == os.Getuid() && gid == os.Getgid() {
|
|
return
|
|
}
|
|
// macOS and iOS reject setgroups with more than 16 groups (EINVAL), which
|
|
// fails the exec; cap to the first 16.
|
|
if C.IsDarwin && len(groups) > 16 {
|
|
groups = groups[:16]
|
|
}
|
|
cred := &syscall.Credential{
|
|
Uid: uint32(uid),
|
|
Gid: uint32(gid),
|
|
}
|
|
// Always call setgroups when dropping privileges: an empty slice clears the
|
|
// parent's supplementary groups. Leaving NoSetGroups set here would make a
|
|
// child dropped from root retain root's supplementary groups (wheel/sudo/...).
|
|
cred.Groups = make([]uint32, len(groups))
|
|
for i, g := range groups {
|
|
cred.Groups[i] = uint32(g)
|
|
}
|
|
attr.Credential = cred
|
|
}
|
|
|
|
func SetWinsize(fd int, rows, cols uint16) error {
|
|
return unix.IoctlSetWinsize(fd, unix.TIOCSWINSZ, &unix.Winsize{Row: rows, Col: cols})
|
|
}
|