From ff8ef5f7243819abf154be386de37cf8d45eff1b Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Fri, 5 Apr 2019 09:48:43 +0200 Subject: [PATCH] * Fixed path canonicalize logic. * Added option to follow symbolic links (default: don't). * Fixed default value for -f FQDN option. * Renamed -d option to -r. * Replaced os.File.Readdir() by ioutil.ReadDir() to get a sorted list. * Removed dot-dot entry from top level menu. * Removed HOME reference from directory listings. * Several small fixes and minor improvements. --- .gitignore | 1 - gogopherd.go | 97 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index e4e58c6..e656622 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ gogopherd -stuff diff --git a/gogopherd.go b/gogopherd.go index 1523026..7f303aa 100644 --- a/gogopherd.go +++ b/gogopherd.go @@ -41,25 +41,34 @@ var cfg = struct { port string fqdn string docRoot string - header string + message string + fsymln bool verbose bool }{ iface: "localhost", port: "7070", fqdn: "localhost", docRoot: ".", - header: "igogopherd on localhost \r\n", + message: "", + fsymln: false, verbose: false, } func initialize() { + help := false flag.StringVar(&cfg.iface, "i", cfg.iface, "interface to bind to") flag.StringVar(&cfg.port, "p", cfg.port, "TCP port to listen on") - flag.StringVar(&cfg.fqdn, "f", cfg.port, "fully qualified domain name") - flag.StringVar(&cfg.docRoot, "d", cfg.docRoot, "document root directory") - flag.StringVar(&cfg.header, "H", cfg.header, "index header") + flag.StringVar(&cfg.fqdn, "f", cfg.fqdn, "fully qualified domain name") + flag.StringVar(&cfg.docRoot, "r", cfg.docRoot, "document root directory") + flag.StringVar(&cfg.message, "M", cfg.message, "index greeter message") + flag.BoolVar(&help, "h", help, "show this help page") + flag.BoolVar(&cfg.fsymln, "s", cfg.fsymln, "follow symbolic links") flag.BoolVar(&cfg.verbose, "v", cfg.verbose, "produce verbose output") flag.Parse() + if help { + flag.Usage() + os.Exit(1) + } if 0 != len(flag.Args()) { fmt.Println("unrecognized options: ", flag.Args()) flag.Usage() @@ -78,11 +87,16 @@ func initialize() { cfg.docRoot, err = canonicalizePath(cfg.docRoot) checkFatal(err, "canonicalizePath "+cfg.docRoot) + if cfg.message != "" { + cfg.message = fmt.Sprintf("i%s\t\t\t\r\n", cfg.message) + } + tracer.Print("interface: ", cfg.iface) tracer.Print("TCP port: ", cfg.port) tracer.Print("doc root: ", cfg.docRoot) + tracer.Print("fsymlinks: ", cfg.fsymln) tracer.Print("fqdn: ", cfg.fqdn) - tracer.Print("header: ", cfg.header) + tracer.Print("message: ", cfg.message) tracer.Print("verbose: ", cfg.verbose) } @@ -101,26 +115,22 @@ func check(err error, msg string) error { } func canonicalizePath(path string) (string, error) { - evp, err := filepath.EvalSymlinks(path) - if check(err, "EvalSymlinks "+path) != nil { - return path, err - } - abs, err := filepath.Abs(evp) - if check(err, "Abs "+evp) != nil { + abs, err := filepath.Abs(path) + if check(err, "Abs "+path) != nil { return path, err } return abs, err } -func validatePath(relpath string) (string, error) { - path, err := canonicalizePath(cfg.docRoot + pathSep + relpath) - if check(err, "canonicalizePath "+relpath) != nil { +func validatePath(root string, path string) (string, error) { + cpath, err := canonicalizePath(path) + if check(err, "canonicalizePath "+path) != nil { return path, err } - if len(path) < len(cfg.docRoot) || path[:len(cfg.docRoot)] != cfg.docRoot { - return path, errors.New("Path outside docRoot") + if len(cpath) < len(root) || cpath[:len(root)] != root { + return cpath, errors.New("Path outside root") } - return path, err + return cpath, err } func guessFiletype(path string) (string, error) { @@ -133,7 +143,7 @@ func guessFiletype(path string) (string, error) { // Get the content buffer := make([]byte, 512) n, err := f.Read(buffer) - if check(err, "Read "+path) != nil { + if err != io.EOF && check(err, "Read "+path) != nil { return "i", err } buffer = buffer[:n] @@ -172,19 +182,16 @@ func humanSize(bytes int64) string { func createIndex(selector string) (string, error) { dirname := cfg.docRoot + selector - d, err := os.Open(dirname) - defer d.Close() - if check(err, "Open "+dirname) != nil { - return "", err - } - fi, err := d.Readdir(-1) + fi, err := ioutil.ReadDir(dirname) if check(err, "Readdir "+dirname) != nil { return "", err } loc := "\t" + cfg.fqdn + "\t" + cfg.port + "\r\n" - list := "1HOME\t/" + loc + list := "" updir, _ := filepath.Split(selector) - list += "1..\t" + updir + loc + if dirname != cfg.docRoot { + list += "1..\t" + updir + loc + } for _, fi := range fi { fmode := fi.Mode() if fmode.IsDir() { @@ -194,12 +201,14 @@ func createIndex(selector string) (string, error) { // create a file reference ftype, _ := guessFiletype(dirname + pathSep + fi.Name()) list += ftype + fi.Name() + " (" + humanSize(fi.Size()) + ")\t" + selector + pathSep + fi.Name() + loc - } else if fmode&os.ModeSymlink != 0 { - // create a reference according to link target + } else if cfg.fsymln == true && fmode&os.ModeSymlink != 0 { + // create a reference with a type matching the link target linktarget, _ := os.Readlink(dirname + pathSep + fi.Name()) - path, err := validatePath(linktarget) - if check(err, "validatePath "+path) == nil { - tracer.Print("path: '", path, "'") + if linktarget[:1] != pathSep { + linktarget = dirname + pathSep + linktarget + } + path, err := canonicalizePath(linktarget) + if check(err, "canonicalizePath "+path) == nil { lfi, err := os.Stat(path) if check(err, "Stat "+path) == nil { if lfi.IsDir() { @@ -228,14 +237,25 @@ func handleRequest(conn net.Conn) { } req = strings.TrimSpace(req) tracer.Print("request: '", req, "'") - // evaluate, canonicalize, and validate referenced path - path, err := validatePath(req) + // canonicalize, and validate referenced path + path, err := validatePath(cfg.docRoot, cfg.docRoot+pathSep+req) if check(err, "validatePath "+path) != nil { return } - tracer.Print("path: '", path, "'") + tracer.Print("request path: '", path, "'") + // check for symbolic link + if cfg.fsymln == false { + fi, err := os.Lstat(path) + if check(err, "request path Lstat "+path) != nil { + return + } + if fi.Mode()&os.ModeSymlink != 0 { + return + } + } + // stat the file fi, err := os.Stat(path) - if check(err, "Stat "+path) != nil { + if check(err, "request path Stat "+path) != nil { return } fmode := fi.Mode() @@ -248,7 +268,7 @@ func handleRequest(conn net.Conn) { if check(err, "makeIndex "+path) != nil { return } - nb, err := conn.Write([]byte(cfg.header + diridx)) + nb, err := conn.Write([]byte(cfg.message + diridx)) if check(err, "Write") != nil { return } @@ -264,8 +284,7 @@ func handleRequest(conn net.Conn) { return } } else { - // TODO: handle other cases? - nbytes = -1 + return } tracer.Print(strconv.FormatInt(nbytes, 10) + " bytes sent.") return -- 2.30.2