From: Urban Wallasch Date: Thu, 11 Apr 2019 13:44:21 +0000 (+0200) Subject: * Added capability to respawn as background process. X-Git-Url: https://git.packet-gain.de/?a=commitdiff_plain;h=0715224e42fe83c653a1b6f04a8bc46d3f431d92;p=gogopherd.git * Added capability to respawn as background process. * Made some (partially related) changes to logging. * Updated README.md. --- diff --git a/README.md b/README.md index c078f28..bcfe73d 100644 --- a/README.md +++ b/README.md @@ -31,27 +31,30 @@ packages are referenced. A Makefile is provided to regenerate the ## 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 @@ -60,37 +63,38 @@ options: 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 @@ -102,7 +106,7 @@ line will override the respective settings in the configuration file. 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. diff --git a/config.go b/config.go index 6e5ea1d..0403cb0 100644 --- a/config.go +++ b/config.go @@ -37,6 +37,7 @@ var cfg = struct { njobs int nworker int logfile string + bkguser string verbose bool }{ iface: "localhost", @@ -55,6 +56,7 @@ var cfg = struct { nworker: 6, njobs: 50, logfile: "", + bkguser: "", verbose: false, } @@ -107,6 +109,8 @@ func parseConfigFile(filename string) error { cfg.njobs, _ = strconv.Atoi(val) case "logfile": cfg.logfile = val + case "bkguser": + cfg.bkguser = val case "verbose": cfg.verbose = strToBool(val) default: @@ -138,6 +142,7 @@ func initialize() { 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 { @@ -187,6 +192,8 @@ func initialize() { 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) } diff --git a/gogopherd.cfg.example b/gogopherd.cfg.example index 8dfa9a8..14809aa 100644 --- a/gogopherd.cfg.example +++ b/gogopherd.cfg.example @@ -38,6 +38,9 @@ njobs = 50 # Log to file; leave empty for stderr logfile = "" +# Daemonize as this user; leave empty for foreground operation +bkguser = "" + # Produce verbose output verbose = false diff --git a/gogopherd.go b/gogopherd.go index 8b8b577..30773e3 100644 --- a/gogopherd.go +++ b/gogopherd.go @@ -14,13 +14,17 @@ package main import ( "bufio" + "fmt" "io" "io/ioutil" "net" "os" + "os/exec" "os/signal" + "os/user" "path" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -174,7 +178,7 @@ func handleRequest(conn net.Conn) { 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 { @@ -247,7 +251,7 @@ func handleRequest(conn net.Conn) { 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() @@ -274,28 +278,117 @@ func serveTCP(listener *net.TCPListener) { 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) diff --git a/logger.go b/logger.go index c5a896b..0088207 100644 --- a/logger.go +++ b/logger.go @@ -28,10 +28,11 @@ func initLogger(verbose bool, logfilename string) { if len(logfilename) > 0 { logfile, err := os.OpenFile(logfilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { - log.Fatalln("Failed to open log file", logfilename, ":", err) + log.Print("Failed to open log file", logfilename, ":", err) + } else { + os.Stderr.Close() + os.Stderr = logfile } - os.Stderr.Close() - os.Stderr = logfile } logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile) loggex = log.New(os.Stderr, "", log.Ldate|log.Ltime)