From b28b7c2c3be33954ccf3ef5b221a6a848b79aa79 Mon Sep 17 00:00:00 2001 From: Patrick Hener Date: Tue, 12 Oct 2021 08:54:54 +0200 Subject: [PATCH] added read only and upload only mode, also made basic auth so one can use custom user and password --- README.md | 44 ++++++----- internal/myhttp/fileserver.go | 52 +++++++++---- main.go | 133 +++++++++++++++++++++++++--------- 3 files changed, 159 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index d8b1805..88d444e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Version](https://img.shields.io/badge/Version-v0.1.3-green) +![Version](https://img.shields.io/badge/Version-v0.1.4-green) [![GitHub](https://img.shields.io/github/license/patrickhener/goshs)](https://github.com/patrickhener/goshs/blob/master/LICENSE) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/patrickhener/goshs) [![GitHub issues](https://img.shields.io/github/issues-raw/patrickhener/goshs)](https://github.com/patrickhener/goshs/issues) @@ -46,33 +46,37 @@ make build # Usage ```bash -Usage: goshs [options] +goshs v0.1.4 +Usage: ./goshs [options] Web server options: - -i The ip/if-name to listen on (default: 0.0.0.0) - -p The port to listen on (default: 8000) - -d The web root directory (default: current working path) - -w Also serve using webdav protocol (default: false) - -wp The port to listen on for webdav (default: 8001) + -i, --ip The ip/if-name to listen on (default: 0.0.0.0) + -p, --port The port to listen on (default: 8000) + -d, --dir The web root directory (default: current working path) + -w, --webdav Also serve using webdav protocol (default: false) + -wp, --webdav-port The port to listen on for webdav (default: 8001) + -ro, --read-only Read only mode, no upload possible (default: false) + -uo, --upload-only Upload only mode, no download possible (default: false) TLS options: - -s Use TLS - -ss Use a self-signed certificate - -sk Path to server key - -sc Path to server certificate + -s, --ssl Use TLS + -ss, --self-signed Use a self-signed certificate + -sk, --server-key Path to server key + -sc, --server-cert Path to server certificate Authentication options: - -P Use basic authentication password (user: gopher) + -b, --basic-auth Use basic authentication (user:pass) Misc options: - -v Print the current goshs version + -v Print the current goshs version Usage examples: - Start with default values: ./goshs - Start with different port: ./goshs -p 8080 - Start with self-signed cert: ./goshs -s -ss - Start with custom cert: ./goshs -s -sk -sc - Start with basic auth: ./goshs -P $up3r$3cur3 + Start with default values: ./goshs + Start with wevdav support: ./goshs -w + Start with different port: ./goshs -p 8080 + Start with self-signed cert: ./goshs -s -ss + Start with custom cert: ./goshs -s -sk -sc + Start with basic auth: ./goshs -b secret-user:$up3r$3cur3 ``` # Examples @@ -95,9 +99,9 @@ Usage examples: **Password protect the service** -`goshs -P VeryS3cureP4$$w0rd` +`goshs -b secret-user:VeryS3cureP4$$w0rd` -*Please note:* goshs uses HTTP basic authentication. It is recommended to use SSL option with basic authentication to prevent from credentials beeing transfered in cleartext over the line. User is `gopher`. +*Please note:* goshs uses HTTP basic authentication. It is recommended to use SSL option with basic authentication to prevent from credentials beeing transfered in cleartext over the line. **Use TLS connection** diff --git a/internal/myhttp/fileserver.go b/internal/myhttp/fileserver.go index ba36b71..17b0de7 100644 --- a/internal/myhttp/fileserver.go +++ b/internal/myhttp/fileserver.go @@ -60,20 +60,23 @@ type item struct { // FileServer holds the fileserver information type FileServer struct { - IP string - Port int - WebdavPort int - Webroot string - SSL bool - SelfSigned bool - MyKey string - MyCert string - BasicAuth string - Version string + IP string + Port int + WebdavPort int + Webroot string + SSL bool + SelfSigned bool + MyKey string + MyCert string + User string + Pass string + Version string Fingerprint256 string - Fingerprint1 string - Hub *mysock.Hub - Clipboard *myclipboard.Clipboard + Fingerprint1 string + UploadOnly bool + ReadOnly bool + Hub *mysock.Hub + Clipboard *myclipboard.Clipboard } type httperror struct { @@ -94,7 +97,7 @@ func (fs *FileServer) BasicAuthMiddleware(next http.Handler) http.Handler { return } - if username != "gopher" || password != fs.BasicAuth { + if username != fs.User || password != fs.Pass { http.Error(w, "Not authorized", http.StatusUnauthorized) return } @@ -162,11 +165,11 @@ func (fs *FileServer) Start(what string) { go fs.Hub.Run() // Check BasicAuth and use middleware - if fs.BasicAuth != "" && what == "web" { + if fs.User != "" && what == "web" { if !fs.SSL { log.Printf("WARNING!: You are using basic auth without SSL. Your credentials will be transferred in cleartext. Consider using -s, too.\n") } - log.Printf("Using 'gopher:%+v' as basic auth\n", fs.BasicAuth) + log.Printf("Using basic auth with user '%s' and password '%s'\n", fs.User, fs.Pass) // Use middleware mux.Use(fs.BasicAuthMiddleware) } @@ -300,6 +303,10 @@ func (fs *FileServer) handler(w http.ResponseWriter, req *http.Request) { // upload handles the POST request to upload files func (fs *FileServer) upload(w http.ResponseWriter, req *http.Request) { + if fs.ReadOnly { + fs.handleError(w, req, fmt.Errorf("%s", "Upload not allowed due to 'read only' option"), http.StatusForbidden) + return + } // Get url so you can extract Headline and title upath := req.URL.Path @@ -366,6 +373,10 @@ func (fs *FileServer) upload(w http.ResponseWriter, req *http.Request) { // bulkDownload will provide zip archived download bundle of multiple selected files func (fs *FileServer) bulkDownload(w http.ResponseWriter, req *http.Request) { + if fs.UploadOnly { + fs.handleError(w, req, fmt.Errorf("%s", "Bulk download not allowed due to 'upload only' option"), http.StatusForbidden) + return + } // make slice and query files from request var filesCleaned []string files := req.URL.Query()["file"] @@ -533,6 +544,11 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file d.IsSubdirectory = false } + // upload only mode empty directory + if fs.UploadOnly { + d = &directory{} + } + // Construct template tem := &indexTemplate{ Directory: d, @@ -550,6 +566,10 @@ func (fs *FileServer) processDir(w http.ResponseWriter, req *http.Request, file } func (fs *FileServer) sendFile(w http.ResponseWriter, req *http.Request, file *os.File) { + if fs.UploadOnly { + fs.handleError(w, req, fmt.Errorf("%s", "Download not allowed due to 'upload only' option"), http.StatusForbidden) + return + } // Extract download parameter download := req.URL.Query() if _, ok := download["download"]; ok { diff --git a/main.go b/main.go index 05ca7fb..1e132b5 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "github.com/patrickhener/goshs/internal/myutils" ) -const goshsVersion = "v0.1.3" +const goshsVersion = "v0.1.4" var ( port = 8000 @@ -28,53 +28,83 @@ var ( basicAuth = "" webdav = false webdavPort = 8001 + uploadOnly = false + readOnly = false ) +// Man page +func usage() func() { + return func() { + + fmt.Printf(` +goshs %s +Usage: %s [options] + +Web server options: + -i, --ip The ip/if-name to listen on (default: 0.0.0.0) + -p, --port The port to listen on (default: 8000) + -d, --dir The web root directory (default: current working path) + -w, --webdav Also serve using webdav protocol (default: false) + -wp, --webdav-port The port to listen on for webdav (default: 8001) + -ro, --read-only Read only mode, no upload possible (default: false) + -uo, --upload-only Upload only mode, no download possible (default: false) + +TLS options: + -s, --ssl Use TLS + -ss, --self-signed Use a self-signed certificate + -sk, --server-key Path to server key + -sc, --server-cert Path to server certificate + +Authentication options: + -b, --basic-auth Use basic authentication (user:pass) + +Misc options: + -v Print the current goshs version + +Usage examples: + Start with default values: ./goshs + Start with wevdav support: ./goshs -w + Start with different port: ./goshs -p 8080 + Start with self-signed cert: ./goshs -s -ss + Start with custom cert: ./goshs -s -sk -sc + Start with basic auth: ./goshs -b secret-user:$up3r$3cur3 + +`, goshsVersion, os.Args[0]) + } +} + +// Flag handling func init() { wd, _ := os.Getwd() // flags flag.StringVar(&ip, "i", ip, "ip") + flag.StringVar(&ip, "ip", ip, "ip") flag.IntVar(&port, "p", port, "port") + flag.IntVar(&port, "port", port, "port") flag.StringVar(&webroot, "d", wd, "web root") + flag.StringVar(&webroot, "dir", wd, "web root") flag.BoolVar(&ssl, "s", ssl, "tls") + flag.BoolVar(&ssl, "ssl", ssl, "tls") flag.BoolVar(&selfsigned, "ss", selfsigned, "self-signed") + flag.BoolVar(&selfsigned, "self-signed", selfsigned, "self-signed") flag.StringVar(&myKey, "sk", myKey, "server key") + flag.StringVar(&myKey, "server-key", myKey, "server key") flag.StringVar(&myCert, "sc", myCert, "server cert") - flag.StringVar(&basicAuth, "P", basicAuth, "basic auth") + flag.StringVar(&myCert, "server-cert", myCert, "server cert") + flag.StringVar(&basicAuth, "b", basicAuth, "basic auth") + flag.StringVar(&basicAuth, "basic-auth", basicAuth, "basic auth") flag.BoolVar(&webdav, "w", webdav, "enable webdav") + flag.BoolVar(&webdav, "webdav", webdav, "enable webdav") flag.IntVar(&webdavPort, "wp", webdavPort, "webdav port") + flag.IntVar(&webdavPort, "webdav-port", webdavPort, "webdav port") + flag.BoolVar(&uploadOnly, "uo", uploadOnly, "upload only") + flag.BoolVar(&uploadOnly, "upload-only", uploadOnly, "upload only") + flag.BoolVar(&readOnly, "ro", readOnly, "read only") + flag.BoolVar(&readOnly, "read-only", readOnly, "read only") version := flag.Bool("v", false, "goshs version") - flag.Usage = func() { - fmt.Printf("goshs %s\n", goshsVersion) - fmt.Printf("Usage: %s [options]\n\n", os.Args[0]) - fmt.Println("Web server options:") - fmt.Println("\t-i\tThe ip/if-name to listen on\t\t(default: 0.0.0.0)") - fmt.Println("\t-p\tThe port to listen on\t\t\t(default: 8000)") - fmt.Println("\t-d\tThe web root directory\t\t\t(default: current working path)") - fmt.Println("\t-w\tAlso serve using webdav protocol\t(default: false)") - fmt.Println("\t-wp\tThe port to listen on for webdav\t(default: 8001)") - fmt.Println("") - fmt.Println("TLS options:") - fmt.Println("\t-s\tUse TLS") - fmt.Println("\t-ss\tUse a self-signed certificate") - fmt.Println("\t-sk\tPath to server key") - fmt.Println("\t-sc\tPath to server certificate") - fmt.Println("") - fmt.Println("Authentication options:") - fmt.Println("\t-P\tUse basic authentication password (user: gopher)") - fmt.Println("") - fmt.Println("Misc options:") - fmt.Println("\t-v\tPrint the current goshs version") - fmt.Println("") - fmt.Println("Usage examples:") - fmt.Println("\tStart with default values:\t./goshs") - fmt.Println("\tStart with different port:\t./goshs -p 8080") - fmt.Println("\tStart with self-signed cert:\t./goshs -s -ss") - fmt.Println("\tStart with custom cert:\t\t./goshs -s -sk -sc ") - fmt.Println("\tStart with basic auth:\t\t./goshs -P $up3r$3cur3") - } + flag.Usage = usage() flag.Parse() @@ -88,20 +118,52 @@ func init() { if !strings.Contains(ip, ".") { addr, err := myutils.GetInterfaceIpv4Addr(ip) if err != nil { - fmt.Println(err) + log.Fatal(err) os.Exit(-1) } if addr == "" { - fmt.Println("IP address cannot be found for provided interface") + log.Println("IP address cannot be found for provided interface") os.Exit(-1) } ip = addr } + + // Sanity check for upload only vs read only + if uploadOnly && readOnly { + log.Println("You can only select either 'upload only' or 'read only', not both.") + os.Exit(-1) + } + + if webdav { + log.Println("WARNING: upload/read-only mode deactivated due to use of 'webdav' mode") + uploadOnly = false + readOnly = false + } +} + +// Sanity checks if basic auth has the right format +func parseBasicAuth() (string, string) { + auth := strings.SplitN(basicAuth, ":", 2) + if len(auth) < 2 { + fmt.Println("Wrong basic auth format. Please provide user:password seperated by a colon") + os.Exit(-1) + } + user := auth[0] + pass := auth[1] + return user, pass + } func main() { + user := "" + pass := "" + // check for basic auth + if basicAuth != "" { + user, pass = parseBasicAuth() + } + done := make(chan os.Signal, 1) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) @@ -116,7 +178,10 @@ func main() { SelfSigned: selfsigned, MyCert: myCert, MyKey: myKey, - BasicAuth: basicAuth, + User: user, + Pass: pass, + UploadOnly: uploadOnly, + ReadOnly: readOnly, Version: goshsVersion, }