You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gophi/core/config.go

295 lines
9.0 KiB
Go

package core
import (
"fmt"
"os"
"os/signal"
"os/user"
"strconv"
"sync"
"syscall"
"time"
"github.com/grufwub/go-bufpools"
"github.com/grufwub/go-config"
"github.com/grufwub/go-filecache"
log "github.com/grufwub/go-logger"
)
func usage(code int) {
fmt.Printf("Usage: %s [-v|--version] [-c|--config $file]\n", os.Args[0])
os.Exit(code)
}
// ParseConfigAndSetup parses necessary core server config from file (and any others defined), and sets up the core ready for Start() to be called
func ParseConfigAndSetup(tree config.Tree, proto string, defaultPort uint, newListener func() (*Listener, error), fileContent func(*Path) FileContent, dirHandler func(*Client, *os.File, *Path) error, largeHandler func(*Client, *os.File, *Path) error, appendCgi func(*Client, *Request, []string) []string) {
// Default configuration file location
configFile := "/etc/gophi." + proto + ".conf"
// If we have arguments to handle, do so!
if len(os.Args) > 1 {
switch os.Args[1] {
case "-c", "--config":
if len(os.Args) != 3 {
usage(1)
}
configFile = os.Args[2]
case "-v", "--version":
fmt.Printf("Gophi (%s) %s\n", proto, Version)
os.Exit(0)
default:
usage(1)
}
}
// Core configuration
tree.StringVar(&Root, "root", "/var/"+proto)
tree.StringVar(&Bind, "listen", "")
tree.StringVar(&Hostname, "hostname", "")
port := tree.Uint64("port", uint64(defaultPort))
chroot := tree.String("chroot", "")
username := tree.String("user", "")
groupname := tree.String("group", "")
// Filesystem configuration
fReadBuf := tree.Uint64("filesystem.read-buf", 1024)
tree.DurationVar(&monitorSleepTime, "filesystem.cache.monitor-freq", time.Second*60)
cacheMax := tree.Float64("filesystem.cache.file-max", 1.0)
cacheSize := tree.Uint64("filesystem.cache.size", 100)
cacheAgeMax := tree.Duration("filesystem.cache.age-max", time.Minute*5)
// Request mapping, hiding, restricting
restrictedPathsList := tree.StringArray("requests.restrict", []string{})
hiddenPathsList := tree.StringArray("requests.hidden", []string{})
remapRequestsList := tree.StringArray("requests.remap", []string{})
// Logging configuration
sysLog := tree.String("log.system", "stdout")
accLog := tree.String("log.access", "stdout")
// Connection configuration
tree.DurationVar(&connReadDeadline, "connection.read-timeout", time.Second*5)
tree.DurationVar(&connWriteDeadline, "connection.write-timeout", time.Second*15)
cWriteBuf := tree.Uint64("connection.write-buf", 1024)
cReadMax := tree.Uint64("connection.read-max", 1024)
// CGI configuration
cgiDir := tree.String("cgi.directory", "")
safePath := tree.String("cgi.safe-path", "/bin:/usr/bin")
// User space configuration
userSpacesEnabled := tree.Bool("user-spaces", false)
// Parse provided config file
tree.Parse(configFile)
// Setup loggers
SystemLog = setupLogger(*sysLog)
if *sysLog == *accLog {
AccessLog = SystemLog
} else {
AccessLog = setupLogger(*accLog)
}
// Check valid values for BindAddr and Hostname
if Hostname == "" {
if Bind == "" {
SystemLog.Fatal("At least one of 'hostname' or 'listen' must be non-empty!")
}
Hostname = Bind
}
// Check valid root (i.e. not empty!)
if Root == "" {
SystemLog.Fatal("No server root directory supplied!")
}
// Set port info
Port = strconv.Itoa(int(*port))
// Set protocol string (only really used by CGI and one call to SystemLog)
protocol = proto
// Setup listener BEFORE entering chroot
// in case TLS cert+key needs to be read
var err error
serverListener, err = newListener()
if err != nil {
SystemLog.Fatalf("Failed to start listener on %s://%s:%s (%s:%s) - %s", protocol, Hostname, Port, Bind, Port, err.Error())
}
// Setup the sync pools
// NOTE:
// - this must be done early on before Root is used so we can
// sanitize it first
// - cReadMax is +2 because it accounts for \r\n line-end
connRequestBufferPool = bufpools.NewBufferPool(int(*cReadMax + 2))
connBufferedWriterPool = bufpools.NewBufferedWriterPool(int(*cWriteBuf))
fileBufferedReaderPool = bufpools.NewBufferedReaderPool(int(*fReadBuf))
fileBufferPool = bufpools.NewBufferPool(int(*fReadBuf))
pathBuilderPool = &sync.Pool{
New: func() interface{} {
return newPathBuilder()
},
}
// - username supplied, lookup and set uid.
// - groupname supplied, lookup and set gid.
// - username but no groupname, we use the user primary gid
var uid, gid int
if *username != "" {
u, err := user.Lookup(*username)
if err != nil {
SystemLog.Fatalf("Error looking up user: %s", err.Error())
}
uid, _ = strconv.Atoi(u.Uid)
gid, _ = strconv.Atoi(u.Gid)
}
if *groupname != "" {
g, err := user.LookupGroup(*groupname)
if err != nil {
SystemLog.Fatalf("Error looking up group: %s", err.Error())
}
gid, _ = strconv.Atoi(g.Gid)
}
// If chroot provided, change to this!
if *chroot != "" {
err := syscall.Chroot(*chroot)
if err != nil {
SystemLog.Fatalf("Error chrooting into directory: %s", err.Error())
}
SystemLog.Infof("Chrooting into dir: %s", *chroot)
// Ensure we're at root of chroot
err = os.Chdir("/")
if err != nil {
SystemLog.Fatalf("Error entering server directory: %s", err.Error())
}
}
// Sanitize the root path! This function always returns
// a relative path, so we have to check beforehand whether
// we need to make sure its an absolute path
isAbs := (Root[0] == '/')
Root = sanitizePath(Root)
if isAbs {
Root = "/" + Root
}
// Change to server root
err = os.Chdir(Root)
if err != nil {
SystemLog.Fatalf("Error entering server directory: %s", err.Error())
}
SystemLog.Infof("Entered server dir: %s", Root)
// If been supplied a group, change to requested group
if *groupname != "" {
err := syscall.Setgid(gid)
if err != nil {
SystemLog.Fatalf("Error performing setgid: %s", err.Error())
}
SystemLog.Infof("Running as group: %s", *groupname)
}
// If been supplied a user, switch to requested user
if *username != "" {
err := syscall.Setuid(uid)
if err != nil {
SystemLog.Fatalf("Error performing setuid: %s", err.Error())
}
SystemLog.Infof("Running as user: %s", *username)
}
// Check not running as root
if syscall.Geteuid() == 0 || syscall.Getegid() == 0 {
SystemLog.Fatal("Gophi does not support running as root!")
}
// FileSystemObject (and related) setup
fileSizeMax = int64(1048576.0 * *cacheMax) // gets megabytes value in bytes
FileCache = filecache.NewFileCache(int(*cacheSize), *cacheAgeMax)
// If no restricted paths provided, set to the disabled function. Else, compile and enable
if len(*restrictedPathsList) == 0 {
SystemLog.Info("Path restrictions disabled")
IsRestrictedPath = isRestrictedPathDisabled
} else {
SystemLog.Info("Path restrictions enabled")
restrictedPaths = compileRestrictedPathsRegex(*restrictedPathsList)
IsRestrictedPath = isRestrictedPathEnabled
}
// If no hidden paths provided, set to the disabled function. Else, compile and enable
if len(*hiddenPathsList) == 0 {
SystemLog.Info("Path hiding disabled")
IsHiddenPath = isHiddenPathDisabled
} else {
SystemLog.Info("Path hiding enabled")
hiddenPaths = compileHiddenPathsRegex(*hiddenPathsList)
IsHiddenPath = isHiddenPathEnabled
}
// If no remapped paths provided, set to the disabled function. Else, compile and enable
if len(*remapRequestsList) == 0 {
SystemLog.Info("Request remapping disabled")
RemapRequest = remapRequestDisabled
} else {
SystemLog.Info("Request remapping enabled")
requestRemaps = compileRequestRemapRegex(*remapRequestsList)
RemapRequest = remapRequestEnabled
}
// If no CGI dir supplied, set to disabled function. Else, compile and enable
if *cgiDir == "" {
SystemLog.Info("CGI script support disabled")
WithinCGIDir = withinCGIDirDisabled
} else {
SystemLog.Info("CGI script support enabled")
SystemLog.Infof("CGI safe path: %s", *safePath)
cgiPath = NewSanitizedPathAtRoot(Root, *cgiDir)
cgiDirRegex = compileCGIRegex(cgiPath.Relative())
cgiEnv = setupInitialCGIEnv(*safePath)
WithinCGIDir = withinCGIDirEnabled
}
// Set appropriate Path builder function depending
// on whether user spaces enabled or disabled
if *userSpacesEnabled {
SystemLog.Info("User spaces support enabled")
BuildPath = buildPathUserSpacesEnabled
SystemLog.Info("User space directory: public_" + protocol)
} else {
SystemLog.Info("User spaces support disabled")
BuildPath = buildPathUserSpacesDisabled
}
// Set provided protocol specific functions
newFileContent = fileContent
handleDirectory = dirHandler
handleLargeFile = largeHandler
appendCgiEnv = appendCgi
// Setup signal channel
sigChannel = make(chan os.Signal)
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
}
func setupLogger(output string) *log.SLogger {
switch output {
case "stdout":
return log.NewSLogger(os.Stdout, true)
case "stderr":
return log.NewSLogger(os.Stderr, true)
case "null":
return log.NewSLogger(&log.NilWriter{}, true)
default:
file, err := os.OpenFile(output, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatalf("Error opening log output %s: %s", output, err.Error())
}
return log.NewSLogger(file, true)
}
}