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.
295 lines
9.0 KiB
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)
|
|
}
|
|
}
|