package main

import (
	"golang.org/x/sys/unix"
	"log"
	"path/filepath"
)

// Restrict access to the files specified in config in an OS-dependent way.
// The OpenBSD implementation uses pledge(2) and unveil(2) to restrict the
// operations available to the molly brown executable. Please note that (S)CGI
// processes that molly brown spawns or communicates with are unrestricted
// and should pledge their own restrictions and unveil their own files.
func enableSecurityRestrictions(config SysConfig, ui userInfo) error {

	// Setuid to an unprivileged user
	err := DropPrivs(ui)
	if err != nil {
		return err
	}

	// Unveil the configured document base as readable.
	log.Println("Unveiling \"" + config.DocBase + "\" as readable.")
	err = unix.Unveil(config.DocBase, "r")
	if err != nil {
		log.Println("Could not unveil DocBase: " + err.Error())
		return err
	}

	// Unveil cgi path globs as executable.
	for _, cgiPath := range config.CGIPaths {
		cgiGlobbedPaths, err := filepath.Glob(cgiPath)
		for _, cgiGlobbedPath := range cgiGlobbedPaths {
			log.Println("Unveiling \"" + cgiGlobbedPath + "\" as executable.")
			err = unix.Unveil(cgiGlobbedPath, "rx")
			if err != nil {
				log.Println("Could not unveil CGIPaths: " + err.Error())
				return err
			}
		}
	}

	// Unveil scgi socket paths as readable and writeable.
	for _, scgiSocket := range config.SCGIPaths {
		log.Println("Unveiling \"" + scgiSocket + "\" as read/write.")
		err = unix.Unveil(scgiSocket, "rw")
		if err != nil {
			return err
		}
	}

	// Finalize the unveil list.
	// Any files not whitelisted above won't be accessible to molly brown.
	err = unix.UnveilBlock()
	if err != nil {
		log.Println("Could not block unveil: " + err.Error())
		return err
	}

	// Pledge to only use stdio, inet, and rpath syscalls.
	promises := "stdio inet rpath"
	if len(config.CGIPaths) > 0 {
		// If CGI paths have been specified, also allow exec syscalls.
		promises += " exec proc"
	}
	if len(config.SCGIPaths) > 0 {
		// If SCGI paths have been specified, also allow unix sockets.
		promises += " unix"
	}
	err = unix.PledgePromises(promises)
	if err != nil {
		log.Println("Could not pledge: " + err.Error())
		return err
	}

	return nil
}
