## Usage
-The `gogopherd` executable recognizes the following command line
-options:
+`Gogopherd` recognizes the following command line options:
```
+
-D
allow generated directory indexes
- -F string
- footer file to append to generated pages
+ -F file
+ footer: append contents of file to generated pages
- -H string
- header file to prepend to generated pages
+ -H file
+ header: prepend contents of file to generated pages
- -I string
- directory index page (default "index.goph")
+ -I file
+ directory index file name (default "index.goph")
- -L string
+ -L file
write log to file; empty for stderr
- -c string
- configuration file
+ -b user
+ restart the process in the background as user
+
+ -c file
+ read configuration from file
-d
allow access to dotfiles
fully qualified domain name (default "localhost")
-h
- show this help page
+ display this help message
-i string
interface to bind to; empty for any (default "localhost")
- -lb int
- set connection burst limit (default 50)
+ -lb num
+ set connection burst limit to num (default 50)
- -lc int
- set connections per second rate limit (default 5)
+ -lc num
+ set connections per second rate limit to num (default 5)
- -lj int
- set worker job queue size (default 50)
+ -lj num
+ set worker job queue size to num (default 50)
- -lw int
- set number of worker threads (default 6)
+ -lw num
+ set number of worker threads to num (default 6)
- -p string
+ -p port
TCP port to listen on (default "7070")
- -r string
+ -r directory
document root directory (default ".")
-s
follow symbolic links
- -t int
+ -t seconds
connection read/write timeout in seconds (default 60)
-v
produce verbose output
+
```
A commented example configuration file, `gogopherd.cfg.example`, is
A small sample gopherhole is included in this project. You can test it
by simply copying `gogopherd.cfg.example` to `gogopherd.cfg` and
-starting gogopherd. Now point your gopher client to localhost port 70
+starting gogopherd. Now point your gopher client to localhost port 7070
and you are good to go.
njobs int
nworker int
logfile string
+ bkguser string
verbose bool
}{
iface: "localhost",
nworker: 6,
njobs: 50,
logfile: "",
+ bkguser: "",
verbose: false,
}
cfg.njobs, _ = strconv.Atoi(val)
case "logfile":
cfg.logfile = val
+ case "bkguser":
+ cfg.bkguser = val
case "verbose":
cfg.verbose = strToBool(val)
default:
flag.IntVar(&cfg.nworker, "lw", cfg.nworker, "set number of worker threads to `num`")
flag.IntVar(&cfg.njobs, "lj", cfg.njobs, "set worker job queue size to `num`")
flag.StringVar(&cfg.logfile, "L", cfg.logfile, "write log to `file`; empty for stderr")
+ flag.StringVar(&cfg.bkguser, "b", cfg.bkguser, "restart the process in the background as `user`")
flag.BoolVar(&cfg.verbose, "v", cfg.verbose, "produce verbose output")
flag.Parse()
if help {
tracer.Print("cburst: ", cfg.cburst)
tracer.Print("nworker: ", cfg.nworker)
tracer.Print("njobs: ", cfg.njobs)
+ tracer.Print("logfile: ", cfg.logfile)
+ tracer.Print("bkguser: ", cfg.bkguser)
tracer.Print("verbose: ", cfg.verbose)
tracer.Print("gogopherd version ", version)
}
import (
"bufio"
+ "fmt"
"io"
"io/ioutil"
"net"
"os"
+ "os/exec"
"os/signal"
+ "os/user"
"path"
"path/filepath"
+ "strconv"
"strings"
"syscall"
"time"
return
}
req = strings.TrimSpace(req)
- tracer.Print("request: '", req, "'")
+ logger.Print("[", conn.RemoteAddr(),"] '", req, "'")
// canonicalize and validate referenced path
rpath, err := validatePath(cfg.docroot, filepath.Join(cfg.docroot, filepath.FromSlash(req)))
if check(err, "validatePath "+rpath) != nil {
return
}
-func serveTCP(listener *net.TCPListener) {
+func serveTCP(listener net.Listener) {
// connection rate limiter
tick := time.NewTicker(time.Second / time.Duration(cfg.crate))
defer tick.Stop()
defer listener.Close()
logger.Print("listening on TCP ", listener.Addr())
for {
- conn, err := listener.AcceptTCP()
+ conn, err := listener.Accept()
if shutting_down {
return
}
if checkFatal(err, "Accept") == nil {
- logger.Print("connect from ", conn.RemoteAddr())
+ logger.Print("[", conn.RemoteAddr(),"] connect")
jobs <- conn
}
<-throttle
}
}
+func daemonize(asuser string, stdfiles []*os.File, xfiles []*os.File, env []string) (int, error) {
+ userCurrent, err := user.Current()
+ if err != nil {
+ return -1, err
+ }
+ userRequired, err := user.Lookup(asuser)
+ if err != nil {
+ return -1, err
+ }
+ executable, err := filepath.Abs(os.Args[0])
+ if err != nil {
+ return -1, err
+ }
+ cmd := exec.Command(executable)
+ cmd.Args = append([]string{executable}, os.Args[1:]...)
+ cmd.Env = append(env, []string{
+ fmt.Sprintf("USER=%s", userRequired.Username),
+ fmt.Sprintf("HOME=%s", userRequired.HomeDir),
+ fmt.Sprintf("NUMXFD=%d", len(xfiles)),
+ }...)
+ if userCurrent.Uid != userRequired.Uid {
+ uid, _ := strconv.Atoi(userRequired.Uid)
+ gid, _ := strconv.Atoi(userRequired.Gid)
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)},
+ }
+ }
+ if len(stdfiles) > 0 {
+ cmd.Stdin = stdfiles[0]
+ }
+ if len(stdfiles) > 1 {
+ cmd.Stdout = stdfiles[1]
+ }
+ if len(stdfiles) > 2 {
+ cmd.Stderr = stdfiles[2]
+ }
+ cmd.ExtraFiles = xfiles
+ err = cmd.Start()
+ if err != nil {
+ return -1, err
+ }
+ return cmd.Process.Pid, nil
+}
+
func main() {
initialize()
- // listen on interface:port
- service := cfg.iface + ":" + cfg.port
- address, err := net.ResolveTCPAddr("tcp", service)
- checkFatal(err, "ResolveTCPAddr")
- listener, err := net.ListenTCP("tcp", address)
- checkFatal(err, "ListenTCP")
- go serveTCP(listener)
+ if cfg.bkguser == "" {
+ // run as foreground process
+ address := cfg.iface + ":" + cfg.port
+ listener, err := net.Listen("tcp", address)
+ checkFatal(err, "Listen")
+ go serveTCP(listener)
+ } else {
+ const isdaemon = "_ISDAEMON_"
+ if os.Getenv(isdaemon) != "1" {
+ // parent
+ service := cfg.iface + ":" + cfg.port
+ address, err := net.ResolveTCPAddr("tcp", service)
+ checkFatal(err, "ResolveTCPAddr")
+ listener, err := net.ListenTCP("tcp", address)
+ checkFatal(err, "ListenTCP")
+ lf, err := listener.File()
+ checkFatal(err, "listener.File")
+ fxtra := []*os.File{
+ lf,
+ }
+ fstd := []*os.File{
+ nil,
+ nil,
+ os.Stderr, // inherit our logging fd
+ }
+ env := []string{
+ "PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"",
+ fmt.Sprintf("%s=1", isdaemon),
+ }
+ cpid, err := daemonize(cfg.bkguser, fstd, fxtra, env)
+ checkFatal(err, "daemonize")
+ tracer.Print("background process spawned, child pid=", cpid, ", bye.")
+ os.Exit(0)
+ } else {
+ // child
+ pid := os.Getpid()
+ ppid := os.Getppid()
+ user, _ := user.Current()
+ tracer.Print("daemon pid=", pid, ", ppid=", ppid, ", user=", user)
+ tracer.Print("daemon args=", os.Args)
+ tracer.Print("daemon env=", os.Environ())
+ nxfd, err := strconv.Atoi(os.Getenv("NUMXFD"))
+ check(err, "Atoi(Getenv())")
+ if nxfd < 1 {
+ fmt.Println("too few extra files", err)
+ os.Exit(1)
+ }
+ listener, err := net.FileListener(os.NewFile(3, "listener"))
+ checkFatal(err, "FileListener")
+ go serveTCP(listener)
+ }
+ }
// wait for signal
sigchan := make(chan os.Signal)