From 85b0d73faa6eab2eaf57d17976aa8d1f99371e2a Mon Sep 17 00:00:00 2001 From: hybris Date: Mon, 22 Oct 2018 16:59:55 +0200 Subject: [PATCH] initial commit --- LICENSE.md | 29 ++++++++++++++++++ serve.go | 72 +++++++++++++++++++++++++++++++++++++++++++ validate.go | 71 +++++++++++++++++++++++++++++++++++++++++++ validate_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 LICENSE.md create mode 100644 serve.go create mode 100644 validate.go create mode 100644 validate_test.go diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..bcda400 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2018, hybris +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/serve.go b/serve.go new file mode 100644 index 0000000..8e685ef --- /dev/null +++ b/serve.go @@ -0,0 +1,72 @@ +// Copyright 2018 hybris. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +// simple wrapper around http.Fileserver and http.ServeFile +package serve + +import ( + "os" + "strconv" + "strings" + "net/http" + "path/filepath" + "github.com/kpango/glg" +) + + +func Serve(path_tainted string, ip_tainted string, port_tainted int) { + if (os.Geteuid() == 0) { + glg.Warn("running as root") + } + + path, err_path := validatePath(path_tainted) + ip, err_ip := validateAddress(ip_tainted) + port, err_port := validatePort(port_tainted) + + listen := strings.Join([]string{ip,strconv.Itoa(port)}, ":") + + if (err_path == nil && err_ip == nil && err_port == nil) { + fi, _ := os.Stat(path) + switch mode := fi.Mode(); { + case mode.IsDir(): + serveDirectory(path,listen) + break + case mode.IsRegular(): + serveFile(path,listen) + break + } + } +} + +// ServeDirectory will start a http.FileServer for the given path on the given listen address +// expects `path` to be a valid directory +func serveDirectory(path,listen string) { + glg.Infof("serving directory %s on %s",path,listen) + if (strings.Compare(path,"/") == 0) { + glg.Warn("you are serving your whole filesystem right now, use with care") + } + panic(http.ListenAndServe(listen, loggingHandler(http.FileServer(http.Dir(path))))) +} + +// custom handler to achieve access logging for http.FileServer +func loggingHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + glg.Infof("Access: from: %s method: %s full_path: %s%s",r.RemoteAddr,r.Method,r.Host,r.URL.Path) + h.ServeHTTP(w, r) + }) +} + +// ServeFile will start http.ListenAndServe and answer request on "/" with the file from the given path +// expects `path` to be a valid file +func serveFile(path,listen string) { + glg.Infof("serving file '%s' on '%s'",path,listen) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){ + glg.Infof("Access: from: %s method: %s full_path: %s%s",r.RemoteAddr,r.Method,r.Host,r.URL.Path) + var header_string = "attachment; filename=" + header_string += filepath.Base(path) + w.Header().Set("Content-Disposition", header_string) + http.ServeFile(w, r, path) + }) + panic(http.ListenAndServe(listen,nil)) +} diff --git a/validate.go b/validate.go new file mode 100644 index 0000000..316dc63 --- /dev/null +++ b/validate.go @@ -0,0 +1,71 @@ +// Copyright 2018 hybris. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE.md file. + +package serve + +import ( + "os" + "net" + "errors" + "strings" + "path/filepath" + "github.com/kpango/glg" +) + +// ValidatePath validates if the path exists and returns the full absolute path of the given path. +// if no path was provided (e.g. empty string), it will return the current working directory. +// exit with error if path is not valid or does not exist +func validatePath(path_tainted string) (string, error) { + if path_tainted == "" { + glg.Info("no directory specified, serving current working directory") + return filepath.Abs(filepath.Dir(os.Args[0])) + } + + if _, err := os.Stat(path_tainted); os.IsNotExist(err) { + glg.Errorf("file or directory does not exist: %s",path_tainted) + return "", err + } + + if filepath.IsAbs(path_tainted) { + return path_tainted, nil + } + + return filepath.Abs(path_tainted) +} + +// ValidateAddress will check if a provided string is a valid ipv4/6 address and return it as string. +// if will return ipv6 addresses with surrounding brackets.(Example: "::" => "[::]") +func validateAddress(address_tainted string) (string, error) { + if address_tainted == "" { + return "", nil + } + + var address = net.ParseIP(address_tainted) + if address == nil { + glg.Errorf("%s is not a valid ipv4 or ipv6 address",address_tainted) + return "", errors.New("not a valid ipv4 or ipv6 address") + } + + if (address.To4() == nil) { + return strings.Join([]string{"[","]"},address.String()), nil + } + + return address.String(), nil +} + +// ValidatePort will check if a given port is in valid port range. +// returns the input if port is valid +// exits with errror if port is out of range, or port <1024 is called without priviliges +func validatePort(port_tainted int) (int, error) { + if(port_tainted < 0 || port_tainted > 65535) { + glg.Errorf("port %d is invalid, please provide a port in range 0-65535",port_tainted) + return 0, errors.New("Port is out of range, please provide a port in range 0-65535") + } + if(os.Geteuid() != 0 && port_tainted < 1024) { + glg.Errorf("cannot bind to port %d without root priviliges",port_tainted) + return 0, errors.New("Ports below 1024 can only be bind by root. You are not root.") + } + + return port_tainted, nil +} diff --git a/validate_test.go b/validate_test.go new file mode 100644 index 0000000..c873e6f --- /dev/null +++ b/validate_test.go @@ -0,0 +1,79 @@ +package serve + +import ( + "testing" + "fmt" +) + +func TestValidatePath(t *testing.T) { + invalidPaths := []string{"://","/'.'/"} + validPaths := []string{".","./","/",""} + + for _, path := range invalidPaths { + _, err := validatePath(path) + if err == nil { + t.Errorf("Path '%s' is supposed to be invalid.", path) + } + } + + for _, path := range validPaths { + _, err := validatePath(path) + if err != nil { + fmt.Println(err) + t.Errorf("Path '%s' is supposed to be valid.", path) + } + } +} + +func TestValidateAddress(t *testing.T) { + invalidIPs := []string{"string",":::::1::::","1.2.3.4.5","300.200.100.000","localhost"} + validIPs := []string{"::","0.0.0.0","","::1"} + + for _, ip := range invalidIPs { + _, err := validateAddress(ip) + if err == nil { + t.Errorf("IP '%s' is supposed to be invalid.", ip) + } + } + + for _, ip := range validIPs { + _, err := validateAddress(ip) + if err != nil { + t.Errorf("IP '%s' is supposed to be valid.", ip) + } + } +} + +func TestValidatePort(t *testing.T) { + // test valid ports + for i := 1024; i < 65536; i++ { + _, err := validatePort(i) + if err != nil { + t.Errorf("Port %d should be valid",i) + } + } + + // test admin ports + for j := 1; j < 1024; j++ { + _, err := validatePort(j) + if err == nil { + t.Errorf("Port %d should not be valid",j) + } + } + + // test negative ports + for k := 0; k > -65536; k-- { + _, err := validatePort(k) + if err == nil { + t.Errorf("Port %d should not be valid",k) + } + } + + // test ports above 65536 + for l := 65536; l < 131072; l++ { + _, err := validatePort(l) + if err == nil { + t.Errorf("Port %d should not be valid",l) + } + } +}