weblug/cmd/weblug/child.go
Felix Niederwanger 7721f4fcb9
WIP: Add Privilege droppings
This is a work in progress commit. Do not merge!

Adds privilege droppings, such that the main webserver process must not
run as root anymore.
2023-06-05 16:45:41 +02:00

169 lines
3.1 KiB
Go

package main
/* Child process handling */
import (
"fmt"
"os"
"syscall"
)
type Process struct {
pid int // Child PID or 0, if not running
cmd string // Command
argv []string // Arguments
pipeIn int // stdin pipe file descriptor
pipeOut int // stdout pipe file descriptor
pipeErr int // stderr pipe file descriptor
env []string
}
type ProcessPool struct {
Processes []Process
}
// Create a Unix pipe
func pipe() (int, int, error) {
fd := make([]int, 2)
err := syscall.Pipe(fd)
if err != nil {
return 0, 0, err
}
return fd[0], fd[1], nil
}
func CreateProcess(cmd string, argv []string) Process {
child := Process{cmd: cmd, argv: argv}
return child
}
func (c *Process) SetEnv(env []string) {
c.env = env
}
func (c *Process) Close() {
// Close all pipes
if c.pipeIn != 0 {
syscall.Close(c.pipeIn)
c.pipeIn = 0
}
if c.pipeOut != 0 {
syscall.Close(c.pipeOut)
c.pipeOut = 0
}
if c.pipeErr != 0 {
syscall.Close(c.pipeErr)
c.pipeErr = 0
}
}
func (c *Process) Exec() error {
var err error
var (
pInIn, pOutOut, pOutErr int
)
// Create in- and output pipe
pInIn, c.pipeIn, err = pipe()
if err != nil {
return err
}
c.pipeOut, pOutOut, err = pipe()
if err != nil {
return err
}
c.pipeErr, pOutErr, err = pipe()
if err != nil {
return err
}
attr := syscall.ProcAttr{
// Use pipes as stdin, stdout and stderr
Files: []uintptr{uintptr(pInIn), uintptr(pOutOut), uintptr(pOutErr)},
Env: c.env,
}
c.pid, err = syscall.ForkExec(c.cmd, c.argv, &attr)
// Close pipe ends which go to the process
syscall.Close(pInIn)
syscall.Close(pOutOut)
syscall.Close(pOutErr)
if err != nil {
return err
}
return err
}
// Wait waits for the process to complete, returning the return value
func (c *Process) Wait() (int, error) {
if c.pid == 0 {
return 0, fmt.Errorf("no process")
}
proc, err := os.FindProcess(c.pid)
if err != nil {
return 0, err
}
state, err := proc.Wait()
if err != nil {
return 0, err
}
c.pid = 0
return state.ExitCode(), err
}
// Write writes a given buffer to the stdin of the child process
func (c *Process) Write(p []byte) (int, error) {
if c.pid == 0 {
return 0, fmt.Errorf("no process")
}
if c.pipeIn == 0 {
return 0, fmt.Errorf("no pipe")
}
return syscall.Write(c.pipeIn, p)
}
// ReadStdout reads from the stdout of the child process
func (c *Process) ReadStdout(p []byte) (int, error) {
if c.pid == 0 {
return 0, fmt.Errorf("no process")
}
if c.pipeOut == 0 {
return 0, fmt.Errorf("no pipe")
}
return syscall.Read(c.pipeOut, p)
}
// ReadStderr reads from the stderr of the child process
func (c *Process) ReadStderr(p []byte) (int, error) {
if c.pid == 0 {
return 0, fmt.Errorf("no process")
}
if c.pipeErr == 0 {
return 0, fmt.Errorf("no pipe")
}
return syscall.Read(c.pipeErr, p)
}
func (c *Process) Terminate() {
if c.pid != 0 {
c.Close()
syscall.Kill(c.pid, syscall.SIGTERM)
c.pid = 0
}
}
func (c *Process) Abort() {
if c.pid != 0 {
c.Close()
syscall.Kill(c.pid, syscall.SIGABRT)
c.pid = 0
}
}
func (c *Process) Kill() {
if c.pid != 0 {
c.Close()
syscall.Kill(c.pid, syscall.SIGKILL)
c.pid = 0
}
}