commit da559ee7ca19c2a6b19b1c735eb40fbb366b9907 Author: Vincent Batts Date: Thu Sep 22 10:19:43 2016 -0400 Initial commit Signed-off-by: Vincent Batts diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2082267 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Vincent Batts + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c588a2 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# srvdav + +dangerously simple webdav server for a local filesystem + +# Building + +```bash +go get github.com/vbatts/srvdav +``` + +# Basic use + +This daemon can serve up [WebDAV](https://en.wikipedia.org/wiki/WebDAV) for a local directory without any auth, nor encryption. +*DO NOT DO THIS* + + +# More proper use + +Produce an x.509 certificate and accompanying key. +For development use case use can use the generator in golang's stdlib. + +```bash +> go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -h +> go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -host="localhost,example.com" +2016/09/22 09:46:19 written cert.pem +2016/09/22 09:46:19 written key.pem +``` + +Produce a password list for users. +The `htpasswd(1)` utility creates the password file nicely. + +```bash +> htpasswd -bc srvdav.passwd vbatts topsecretpassword +``` + +Then launch `srvdav` with these credentials. + +```bash +> mkdir -p ./test/ +> srvdav -htpasswd ./srvdav.passwd -cert ./cert.pem -key ./key.pem +Serving HTTPS:// :9999 +[...] +``` + +# Accompanying Clients + +There are a number of webdav clients. +For my specific use case, I am working with ChromeOS and there is a [WebDAV Storage Provider](https://chrome.google.com/webstore/detail/webdav-file-system/hmckflbfniicjijmdoffagjkpnjgbieh?hl=en). + +For Linux hosts, there is a package commonly `davfs2`, that provides a `mount.davfs` command. +See `mount.davfs(8)` man page for more information. + diff --git a/main.go b/main.go new file mode 100644 index 0000000..afdd146 --- /dev/null +++ b/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + + "github.com/abbot/go-http-auth" + "golang.org/x/net/webdav" +) + +var ( + flPort = flag.Int("port", 9999, "server port") + flCert = flag.String("cert", "", "server SSL cert (both -cert and -key must be present to use SSL). See `go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -h` to generate development cert/key") + flKey = flag.String("key", "", "server SSL key") + flHtpasswd = flag.String("htpasswd", "", "htpasswd file for auth (must be present to use auth) See htpasswd(1) to create this file.") +) + +func main() { + flag.Parse() + + if flag.NArg() == 0 { + log.Fatal("One argument required. Please provide path to serve (you can use a special keyword of 'mem' to serve an in-memory filesystem)") + } + + var fs webdav.FileSystem + if flag.Args()[0] == "mem" { + fs = webdav.NewMemFS() + } else { + fs = NewPassThroughFS(flag.Args()[0]) + } + log.SetFlags(0) + h := &webdav.Handler{ + FileSystem: fs, + LockSystem: webdav.NewMemLS(), + Logger: func(r *http.Request, err error) { + switch r.Method { + case "COPY", "MOVE": + dst := "" + if u, err := url.Parse(r.Header.Get("Destination")); err == nil { + dst = u.Path + } + o := r.Header.Get("Overwrite") + log.Printf("%-20s%-20s%-10s%-30s%-30so=%-2s%v", r.Header.Get("Date"), r.RemoteAddr, r.Method, r.URL.Path, dst, o, err) + default: + log.Printf("%-20s%-20s%-10s%-30s%v", r.Header.Get("Date"), r.RemoteAddr, r.Method, r.URL.Path, err) + } + }, + } + if *flHtpasswd != "" { + secret := auth.HtpasswdFileProvider(*flHtpasswd) + authenticator := auth.NewBasicAuthenticator("", secret) + authHandlerFunc := func(w http.ResponseWriter, r *auth.AuthenticatedRequest) { + h.ServeHTTP(w, &r.Request) + } + http.HandleFunc("/", authenticator.Wrap(authHandlerFunc)) + + } else { + log.Println("WARNING: connections are not authenticated. STRONGLY consider using -htpasswd.") + http.Handle("/", h) + } + addr := fmt.Sprintf(":%d", *flPort) + if *flCert != "" && *flKey != "" { + log.Printf("Serving HTTPS:// %v", addr) + log.Fatal(http.ListenAndServeTLS(addr, *flCert, *flKey, nil)) + } else { + log.Println("WARNING: connections are not encrypted. STRONGLY consider using -cert/-key.") + log.Printf("Serving HTTP:// %v", addr) + log.Fatal(http.ListenAndServe(addr, nil)) + } +} + +func NewPassThroughFS(path string) webdav.FileSystem { + return &passThroughFS{root: path} +} + +type passThroughFS struct { + root string +} + +func (ptfs *passThroughFS) Mkdir(name string, perm os.FileMode) error { + // TODO(vbatts) check for escaping the root directory + return os.Mkdir(filepath.Join(ptfs.root, name), perm) +} +func (ptfs *passThroughFS) OpenFile(name string, flag int, perm os.FileMode) (webdav.File, error) { + // TODO(vbatts) check for escaping the root directory + return os.OpenFile(filepath.Join(ptfs.root, name), flag, perm) +} +func (ptfs *passThroughFS) RemoveAll(name string) error { + // TODO(vbatts) check for escaping the root directory + return os.RemoveAll(filepath.Join(ptfs.root, name)) +} +func (ptfs *passThroughFS) Rename(oldName, newName string) error { + // TODO(vbatts) check for escaping the root directory + return os.Rename(filepath.Join(ptfs.root, oldName), filepath.Join(ptfs.root, newName)) +} +func (ptfs *passThroughFS) Stat(name string) (os.FileInfo, error) { + // TODO(vbatts) check for escaping the root directory + return os.Stat(filepath.Join(ptfs.root, name)) +}