<commit_before>package ozinit

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"

	"github.com/subgraph/oz"
	"github.com/subgraph/oz/fs"
	"github.com/subgraph/oz/ipc"
	"github.com/subgraph/oz/network"
	"github.com/subgraph/oz/xpra"

	"github.com/kr/pty"
	"github.com/op/go-logging"
)

const EnvPrefix = "INIT_ENV_"

type initState struct {
	log       *logging.Logger
	profile   *oz.Profile
	config    *oz.Config
	sockaddr  string
	launchEnv []string
	lock      sync.Mutex
	children  map[int]*exec.Cmd
	uid       int
	gid       int
	user      *user.User
	display   int
	fs        *fs.Filesystem
	ipcServer *ipc.MsgServer
	xpra      *xpra.Xpra
	xpraReady sync.WaitGroup
	network   *network.SandboxNetwork
}

// By convention oz-init writes log messages to stderr with a single character
// prefix indicating the logging level.  These messages are read one line at a time
// over a pipe by oz-daemon and translated into appropriate log events.
func createLogger() *logging.Logger {
	l := logging.MustGetLogger("oz-init")
	be := logging.NewLogBackend(os.Stderr, "", 0)
	f := logging.MustStringFormatter("%{level:.1s} %{message}")
	fbe := logging.NewBackendFormatter(be, f)
	logging.SetBackend(fbe)
	return l
}

func Main() {
	parseArgs().runInit()
}

func parseArgs() *initState {
	log := createLogger()

	if os.Getuid() != 0 {
		log.Error("oz-init must run as root\n")
		os.Exit(1)
	}

	if os.Getpid() != 1 {
		log.Error("oz-init must be launched in new pid namespace.")
		os.Exit(1)
	}

	getvar := func(name string) string {
		val := os.Getenv(name)
		if val == "" {
			log.Error("Error: missing required '%s' argument", name)
			os.Exit(1)
		}
		return val
	}
	pname := getvar("INIT_PROFILE")
	sockaddr := getvar("INIT_SOCKET")
	uidval := getvar("INIT_UID")
	dispval := os.Getenv("INIT_DISPLAY")

	stnip := os.Getenv("INIT_ADDR")
	stnvhost := os.Getenv("INIT_VHOST")
	stnvguest := os.Getenv("INIT_VGUEST")
	stngateway := os.Getenv("INIT_GATEWAY")

	var config *oz.Config
	config, err := oz.LoadConfig(oz.DefaultConfigPath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Info("Configuration file (%s) is missing, using defaults.", oz.DefaultConfigPath)
			config = oz.NewDefaultConfig()
		} else {
			log.Error("Could not load configuration: %s", oz.DefaultConfigPath, err)
			os.Exit(1)
		}
	}

	p, err := loadProfile(config.ProfileDir, pname)
	if err != nil {
		log.Error("Could not load profile %s: %v", pname, err)
		os.Exit(1)
	}
	uid, err := strconv.Atoi(uidval)
	if err != nil {
		log.Error("Could not parse INIT_UID argument (%s) into an integer: %v", uidval, err)
		os.Exit(1)
	}
	u, err := user.LookupId(uidval)
	if err != nil {
		log.Error("Failed to look up user with uid=%s: %v", uidval, err)
		os.Exit(1)
	}
	gid, err := strconv.Atoi(u.Gid)
	if err != nil {
		log.Error("Failed to parse gid value (%s) from user struct: %v", u.Gid, err)
		os.Exit(1)
	}
	display := 0
	if dispval != "" {
		d, err := strconv.Atoi(dispval)
		if err != nil {
			log.Error("Unable to parse display (%s) into an integer: %v", dispval, err)
			os.Exit(1)
		}
		display = d
	}

	stn := new(network.SandboxNetwork)
	if stnip != "" {
		gateway, _, err := net.ParseCIDR(stngateway)
		if err != nil {
			log.Error("Unable to parse network configuration gateway (%s): %v", stngateway, err)
			os.Exit(1)
		}

		stn.Ip = stnip
		stn.VethHost = stnvhost
		stn.VethGuest = stnvguest
		stn.Gateway = gateway
	}

	env := []string{}
	for _, e := range os.Environ() {
		if strings.HasPrefix(e, EnvPrefix) {
			e = e[len(EnvPrefix):]
			log.Debug("Adding (%s) to launch environment", e)
			env = append(env, e)
		}
	}

	env = append(env, "PATH=/usr/bin:/bin")

	if p.XServer.Enabled {
		env = append(env, "DISPLAY=:"+strconv.Itoa(display))
	}

	return &initState{
		log:       log,
		config:    config,
		sockaddr:  sockaddr,
		launchEnv: env,
		profile:   p,
		children:  make(map[int]*exec.Cmd),
		uid:       uid,
		gid:       gid,
		user:      u,
		display:   display,
		fs:        fs.NewFilesystem(config, log),
		network:   stn,
	}
}

func (st *initState) runInit() {
	st.log.Info("Starting oz-init for profile: %s", st.profile.Name)
	sigs := make(chan os.Signal)
	signal.Notify(sigs, syscall.SIGTERM, os.Interrupt)

	s, err := ipc.NewServer(st.sockaddr, messageFactory, st.log,
		handlePing,
		st.handleRunProgram,
		st.handleRunShell,
	)
	if err != nil {
		st.log.Error("NewServer failed: %v", err)
		os.Exit(1)
	}

	if err := os.Chown(st.sockaddr, st.uid, st.gid); err != nil {
		st.log.Warning("Failed to chown oz-init control socket: %v", err)
	}

	if err := st.setupFilesystem(nil); err != nil {
		st.log.Error("Failed to setup filesytem: %v", err)
		os.Exit(1)
	}

	if st.user != nil && st.user.HomeDir != "" {
		st.launchEnv = append(st.launchEnv, "HOME="+st.user.HomeDir)
	}

	if st.profile.Networking.Nettype != network.TYPE_HOST {
		err := network.NetSetup(st.network)
		if err != nil {
			st.log.Error("Unable to setup networking: %+v", err)
			os.Exit(1)
		}
	}
	network.NetPrint(st.log)

	if syscall.Sethostname([]byte(st.profile.Name)) != nil {
		st.log.Error("Failed to set hostname to (%s)", st.profile.Name)
	}
	if syscall.Setdomainname([]byte("local")) != nil {
		st.log.Error("Failed to set domainname")
	}
	st.log.Info("Hostname set to (%s.local)", st.profile.Name)

	oz.ReapChildProcs(st.log, st.handleChildExit)

	if st.profile.XServer.Enabled {
		st.xpraReady.Add(1)
		st.startXpraServer()
	}
	st.xpraReady.Wait()
	st.log.Info("XPRA started")

	fsbx := path.Join("/tmp", "oz-sandbox")
	err = ioutil.WriteFile(fsbx, []byte(st.profile.Name), 0644)

	os.Stderr.WriteString("OK\n")

	go st.processSignals(sigs, s)

	st.ipcServer = s

	if err := s.Run(); err != nil {
		st.log.Warning("MsgServer.Run() return err: %v", err)
	}
	st.log.Info("oz-init exiting...")
}

func (st *initState) startXpraServer() {
	if st.user == nil {
		st.log.Warning("Cannot start xpra server because no user is set")
		return
	}
	workdir := path.Join(st.user.HomeDir, ".Xoz", st.profile.Name)
	st.log.Info("xpra work dir is %s", workdir)
	xpra := xpra.NewServer(&st.profile.XServer, uint64(st.display), workdir)
	p, err := xpra.Process.StderrPipe()
	if err != nil {
		st.log.Warning("Error creating stderr pipe for xpra output: %v", err)
		os.Exit(1)
	}
	go st.readXpraOutput(p)
	xpra.Process.Env = []string{
		"HOME=" + st.user.HomeDir,
	}
	xpra.Process.SysProcAttr = &syscall.SysProcAttr{}
	xpra.Process.SysProcAttr.Credential = &syscall.Credential{
		Uid: uint32(st.uid),
		Gid: uint32(st.gid),
	}
	st.log.Info("Starting xpra server")
	if err := xpra.Process.Start(); err != nil {
		st.log.Warning("Failed to start xpra server: %v", err)
		st.xpraReady.Done()
	}
	st.xpra = xpra
}

func (st *initState) readXpraOutput(r io.ReadCloser) {
	sc := bufio.NewScanner(r)
	seenReady := false
	for sc.Scan() {
		line := sc.Text()
		if len(line) > 0 {
			if strings.Contains(line, "xpra is ready.") && !seenReady {
				seenReady = true
				st.xpraReady.Done()
				if !st.config.LogXpra {
					r.Close()
					return
				}
			}
			if st.config.LogXpra {
				st.log.Debug("(xpra) %s", line)
			}
		}
	}
}

func (st *initState) launchApplication(cpath, pwd string, cmdArgs []string) (*exec.Cmd, error) {
	if cpath == "" {
		cpath = st.profile.Path
	}
	if st.config.DivertSuffix != "" {
		cpath += "." + st.config.DivertSuffix
	}

	if st.profile.Seccomp.Mode == oz.PROFILE_SECCOMP_WHITELIST {
		st.log.Notice("Enabling seccomp whitelist for: %s", cpath)
		cmdArgs = append([]string{"-w",cpath}, cmdArgs...)
		cpath = path.Join(st.config.PrefixPath, "bin", "oz-seccomp")
	}
	cmd := exec.Command(cpath)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		st.log.Warning("Failed to create stdout pipe: %v", err)
		return nil, err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		st.log.Warning("Failed to create stderr pipe: %v", err)
		return nil, err
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.SysProcAttr.Credential = &syscall.Credential{
		Uid: uint32(st.uid),
		Gid: uint32(st.gid),
	}
	cmd.Env = append(cmd.Env, st.launchEnv...)

	if st.profile.Seccomp.Mode == oz.PROFILE_SECCOMP_WHITELIST {
		cmd.Env = append(cmd.Env, "_OZ_PROFILE="+st.profile.Name)
	}

	cmd.Args = append(cmd.Args, cmdArgs...)

	if _, err := os.Stat(pwd); err == nil {<commit_msg>Blacklist support in oz-init.<commit_after>package ozinit

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"

	"github.com/subgraph/oz"
	"github.com/subgraph/oz/fs"
	"github.com/subgraph/oz/ipc"
	"github.com/subgraph/oz/network"
	"github.com/subgraph/oz/xpra"

	"github.com/kr/pty"
	"github.com/op/go-logging"
)

const EnvPrefix = "INIT_ENV_"

type initState struct {
	log       *logging.Logger
	profile   *oz.Profile
	config    *oz.Config
	sockaddr  string
	launchEnv []string
	lock      sync.Mutex
	children  map[int]*exec.Cmd
	uid       int
	gid       int
	user      *user.User
	display   int
	fs        *fs.Filesystem
	ipcServer *ipc.MsgServer
	xpra      *xpra.Xpra
	xpraReady sync.WaitGroup
	network   *network.SandboxNetwork
}

// By convention oz-init writes log messages to stderr with a single character
// prefix indicating the logging level.  These messages are read one line at a time
// over a pipe by oz-daemon and translated into appropriate log events.
func createLogger() *logging.Logger {
	l := logging.MustGetLogger("oz-init")
	be := logging.NewLogBackend(os.Stderr, "", 0)
	f := logging.MustStringFormatter("%{level:.1s} %{message}")
	fbe := logging.NewBackendFormatter(be, f)
	logging.SetBackend(fbe)
	return l
}

func Main() {
	parseArgs().runInit()
}

func parseArgs() *initState {
	log := createLogger()

	if os.Getuid() != 0 {
		log.Error("oz-init must run as root\n")
		os.Exit(1)
	}

	if os.Getpid() != 1 {
		log.Error("oz-init must be launched in new pid namespace.")
		os.Exit(1)
	}

	getvar := func(name string) string {
		val := os.Getenv(name)
		if val == "" {
			log.Error("Error: missing required '%s' argument", name)
			os.Exit(1)
		}
		return val
	}
	pname := getvar("INIT_PROFILE")
	sockaddr := getvar("INIT_SOCKET")
	uidval := getvar("INIT_UID")
	dispval := os.Getenv("INIT_DISPLAY")

	stnip := os.Getenv("INIT_ADDR")
	stnvhost := os.Getenv("INIT_VHOST")
	stnvguest := os.Getenv("INIT_VGUEST")
	stngateway := os.Getenv("INIT_GATEWAY")

	var config *oz.Config
	config, err := oz.LoadConfig(oz.DefaultConfigPath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Info("Configuration file (%s) is missing, using defaults.", oz.DefaultConfigPath)
			config = oz.NewDefaultConfig()
		} else {
			log.Error("Could not load configuration: %s", oz.DefaultConfigPath, err)
			os.Exit(1)
		}
	}

	p, err := loadProfile(config.ProfileDir, pname)
	if err != nil {
		log.Error("Could not load profile %s: %v", pname, err)
		os.Exit(1)
	}
	uid, err := strconv.Atoi(uidval)
	if err != nil {
		log.Error("Could not parse INIT_UID argument (%s) into an integer: %v", uidval, err)
		os.Exit(1)
	}
	u, err := user.LookupId(uidval)
	if err != nil {
		log.Error("Failed to look up user with uid=%s: %v", uidval, err)
		os.Exit(1)
	}
	gid, err := strconv.Atoi(u.Gid)
	if err != nil {
		log.Error("Failed to parse gid value (%s) from user struct: %v", u.Gid, err)
		os.Exit(1)
	}
	display := 0
	if dispval != "" {
		d, err := strconv.Atoi(dispval)
		if err != nil {
			log.Error("Unable to parse display (%s) into an integer: %v", dispval, err)
			os.Exit(1)
		}
		display = d
	}

	stn := new(network.SandboxNetwork)
	if stnip != "" {
		gateway, _, err := net.ParseCIDR(stngateway)
		if err != nil {
			log.Error("Unable to parse network configuration gateway (%s): %v", stngateway, err)
			os.Exit(1)
		}

		stn.Ip = stnip
		stn.VethHost = stnvhost
		stn.VethGuest = stnvguest
		stn.Gateway = gateway
	}

	env := []string{}
	for _, e := range os.Environ() {
		if strings.HasPrefix(e, EnvPrefix) {
			e = e[len(EnvPrefix):]
			log.Debug("Adding (%s) to launch environment", e)
			env = append(env, e)
		}
	}

	env = append(env, "PATH=/usr/bin:/bin")

	if p.XServer.Enabled {
		env = append(env, "DISPLAY=:"+strconv.Itoa(display))
	}

	return &initState{
		log:       log,
		config:    config,
		sockaddr:  sockaddr,
		launchEnv: env,
		profile:   p,
		children:  make(map[int]*exec.Cmd),
		uid:       uid,
		gid:       gid,
		user:      u,
		display:   display,
		fs:        fs.NewFilesystem(config, log),
		network:   stn,
	}
}

func (st *initState) runInit() {
	st.log.Info("Starting oz-init for profile: %s", st.profile.Name)
	sigs := make(chan os.Signal)
	signal.Notify(sigs, syscall.SIGTERM, os.Interrupt)

	s, err := ipc.NewServer(st.sockaddr, messageFactory, st.log,
		handlePing,
		st.handleRunProgram,
		st.handleRunShell,
	)
	if err != nil {
		st.log.Error("NewServer failed: %v", err)
		os.Exit(1)
	}

	if err := os.Chown(st.sockaddr, st.uid, st.gid); err != nil {
		st.log.Warning("Failed to chown oz-init control socket: %v", err)
	}

	if err := st.setupFilesystem(nil); err != nil {
		st.log.Error("Failed to setup filesytem: %v", err)
		os.Exit(1)
	}

	if st.user != nil && st.user.HomeDir != "" {
		st.launchEnv = append(st.launchEnv, "HOME="+st.user.HomeDir)
	}

	if st.profile.Networking.Nettype != network.TYPE_HOST {
		err := network.NetSetup(st.network)
		if err != nil {
			st.log.Error("Unable to setup networking: %+v", err)
			os.Exit(1)
		}
	}
	network.NetPrint(st.log)

	if syscall.Sethostname([]byte(st.profile.Name)) != nil {
		st.log.Error("Failed to set hostname to (%s)", st.profile.Name)
	}
	if syscall.Setdomainname([]byte("local")) != nil {
		st.log.Error("Failed to set domainname")
	}
	st.log.Info("Hostname set to (%s.local)", st.profile.Name)

	oz.ReapChildProcs(st.log, st.handleChildExit)

	if st.profile.XServer.Enabled {
		st.xpraReady.Add(1)
		st.startXpraServer()
	}
	st.xpraReady.Wait()
	st.log.Info("XPRA started")

	fsbx := path.Join("/tmp", "oz-sandbox")
	err = ioutil.WriteFile(fsbx, []byte(st.profile.Name), 0644)

	os.Stderr.WriteString("OK\n")

	go st.processSignals(sigs, s)

	st.ipcServer = s

	if err := s.Run(); err != nil {
		st.log.Warning("MsgServer.Run() return err: %v", err)
	}
	st.log.Info("oz-init exiting...")
}

func (st *initState) startXpraServer() {
	if st.user == nil {
		st.log.Warning("Cannot start xpra server because no user is set")
		return
	}
	workdir := path.Join(st.user.HomeDir, ".Xoz", st.profile.Name)
	st.log.Info("xpra work dir is %s", workdir)
	xpra := xpra.NewServer(&st.profile.XServer, uint64(st.display), workdir)
	p, err := xpra.Process.StderrPipe()
	if err != nil {
		st.log.Warning("Error creating stderr pipe for xpra output: %v", err)
		os.Exit(1)
	}
	go st.readXpraOutput(p)
	xpra.Process.Env = []string{
		"HOME=" + st.user.HomeDir,
	}
	xpra.Process.SysProcAttr = &syscall.SysProcAttr{}
	xpra.Process.SysProcAttr.Credential = &syscall.Credential{
		Uid: uint32(st.uid),
		Gid: uint32(st.gid),
	}
	st.log.Info("Starting xpra server")
	if err := xpra.Process.Start(); err != nil {
		st.log.Warning("Failed to start xpra server: %v", err)
		st.xpraReady.Done()
	}
	st.xpra = xpra
}

func (st *initState) readXpraOutput(r io.ReadCloser) {
	sc := bufio.NewScanner(r)
	seenReady := false
	for sc.Scan() {
		line := sc.Text()
		if len(line) > 0 {
			if strings.Contains(line, "xpra is ready.") && !seenReady {
				seenReady = true
				st.xpraReady.Done()
				if !st.config.LogXpra {
					r.Close()
					return
				}
			}
			if st.config.LogXpra {
				st.log.Debug("(xpra) %s", line)
			}
		}
	}
}

func (st *initState) launchApplication(cpath, pwd string, cmdArgs []string) (*exec.Cmd, error) {
	if cpath == "" {
		cpath = st.profile.Path
	}
	if st.config.DivertSuffix != "" {
		cpath += "." + st.config.DivertSuffix
	}

	if st.profile.Seccomp.Mode == oz.PROFILE_SECCOMP_WHITELIST {
		st.log.Notice("Enabling seccomp whitelist for: %s", cpath)
		cmdArgs = append([]string{"-w", cpath}, cmdArgs...)
		cpath = path.Join(st.config.PrefixPath, "bin", "oz-seccomp")
	} else if st.profile.Seccomp.Mode == oz.PROFILE_SECCOMP_BLACKLIST {
		st.log.Notice("Enabling seccomp blacklist for: %s", cpath)
		cmdArgs = append([]string{"-b", cpath}, cmdArgs...)
		cpath = path.Join(st.config.PrefixPath, "bin", "oz-seccomp")
	}
	cmd := exec.Command(cpath)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		st.log.Warning("Failed to create stdout pipe: %v", err)
		return nil, err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		st.log.Warning("Failed to create stderr pipe: %v", err)
		return nil, err
	}
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.SysProcAttr.Credential = &syscall.Credential{
		Uid: uint32(st.uid),
		Gid: uint32(st.gid),
	}
	cmd.Env = append(cmd.Env, st.launchEnv...)

	if st.profile.Seccomp.Mode == oz.PROFILE_SECCOMP_WHITELIST ||
		st.profile.Seccomp.Mode == oz.PROFILE_SECCOMP_BLACKLIST {
		cmd.Env = append(cmd.Env, "_OZ_PROFILE="+st.profile.Name)
	}

	cmd.Args = append(cmd.Args, cmdArgs...)

	if _, err := os.Stat(pwd); err == nil {
{'fraction_non_alphanumeric': 0.16524300441826215, 'fraction_numerical': 0.004123711340206186, 'mean_word_length': 3.5041124966834705, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6563027', 'n_tokens_mistral': 7184, 'n_tokens_neox': 6724, 'n_words': 1486}