diff --git a/cmd_serve.go b/cmd_serve.go new file mode 100644 index 0000000..cec3265 --- /dev/null +++ b/cmd_serve.go @@ -0,0 +1,102 @@ +// Copyright (c) 2020, Andreas Schneider +// 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 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 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. + +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "strings" + + "golang.org/x/crypto/bcrypt" + "golang.org/x/net/webdav" +) + +type CmdServe struct { + ListenAddress string `name:"listen-address" default:":3000" help:"Address to listen on for HTTP requests."` +} + +func (cmd *CmdServe) Run(app *app) error { + h := &webdav.Handler{} + h.Prefix = "/dav/" + h.LockSystem = webdav.NewMemLS() + h.FileSystem = BaseDir(app.DataDirectory) + + authenticatedWebdavHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeUnauthorized := func() { + w.Header().Set("WWW-Authenticate", `Basic realm="ShareDAV"`) + http.Error(w, "unauthorized", http.StatusUnauthorized) + } + + username, password, ok := r.BasicAuth() + if !ok { + writeUnauthorized() + return + } + + usernameParts := strings.Split(username, "/") + if len(usernameParts) != 2 { + http.Error(w, "invalid username pattern", http.StatusBadRequest) + return + } + + username = usernameParts[0] + loginName := usernameParts[1] + + share, err := app.shareStore.FindShareByLogin(username, loginName) + if err == ErrShareNotFound { + writeUnauthorized() + return + } else if err != nil { + log.Printf("Cannot process login request: %v", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + if err := bcrypt.CompareHashAndPassword([]byte(share.Password), []byte(password)); err != nil { + writeUnauthorized() + return + } + + // TODO determine permissions + + directoryMapping := DirectoryMapping{ + DataDirName: share.UUID.String(), + } + + // Use the WebDAV handler to actually serve the request. Also enhance the context + // to contain the subdirectory (of the base directory) which contains the data for + // the authenticated user. + h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "mapping", &directoryMapping))) + }) + + fmt.Printf("Listening on %s\n", cmd.ListenAddress) + if err := http.ListenAndServe(cmd.ListenAddress, authenticatedWebdavHandler); err != http.ErrServerClosed && err != nil { + return fmt.Errorf("cannot listen: %w", err) + } + return nil +} diff --git a/config.go b/config.go deleted file mode 100644 index b7f7674..0000000 --- a/config.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2020, Andreas Schneider -// 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 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 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. - -package main - -type config struct { - ListenAddress string `name:"listen-address" default:":3000" help:"Address to listen on for HTTP requests."` - DataDirectory string `name:"data-dir" default:"data" help:"Directory to store all files in."` - Db string `name:"db" default:"ShareDAV.db" help:"Database file to use."` -} diff --git a/filesystem.go b/filesystem.go index d3f1edb..9f1fc70 100644 --- a/filesystem.go +++ b/filesystem.go @@ -44,8 +44,7 @@ import ( ) type DirectoryMapping struct { - VirtualName string - RealName string + DataDirName string } // slashClean is equivalent to but slightly more efficient than @@ -61,23 +60,17 @@ type BaseDir string func (d BaseDir) resolve(ctx context.Context, name string) string { directoryMapping := ctx.Value("mapping").(*DirectoryMapping) - prefix := directoryMapping.VirtualName + "/" - if !strings.HasPrefix(name, prefix) { - // something does not add up - return "" - } - nameWithoutPrefix := strings.TrimPrefix(name, prefix) // This implementation is based on Dir.Open's code in the standard net/http package. if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || - strings.Contains(nameWithoutPrefix, "\x00") { + strings.Contains(name, "\x00") { return "" } dir := string(d) if dir == "" { dir = "." } - return filepath.Join(dir, directoryMapping.RealName, filepath.FromSlash(slashClean(nameWithoutPrefix))) + return filepath.Join(dir, directoryMapping.DataDirName, filepath.FromSlash(slashClean(name))) } func (d BaseDir) Mkdir(ctx context.Context, name string, perm os.FileMode) error { diff --git a/main.go b/main.go index 948e21b..297a70e 100644 --- a/main.go +++ b/main.go @@ -34,10 +34,16 @@ import ( "github.com/alecthomas/kong" ) +type config struct { + DataDirectory string `name:"data-dir" default:"data" help:"Directory to store all files in."` + Db string `name:"db" default:"ShareDAV.db" help:"Database file to use."` +} + type app struct { config CmdUser CmdUser `cmd:"" name:"user" help:"Manage users."` CmdShare CmdShare `cmd:"" name:"share" help:"Manage shares."` + CmdServe CmdServe `cmd:"" name:"serve" help:"Serve HTTP requests." default:"1"` userStore UserStore shareStore ShareStore @@ -63,40 +69,4 @@ func main() { app.shareStore = store ctx.FatalIfErrorf(ctx.Run(&app)) - - //c := LoadConfig(*configFile) - // - //h := &webdav.Handler{} - //h.Prefix = "/share/" - //h.LockSystem = webdav.NewMemLS() - //h.FileSystem = BaseDir(c.BaseDirectory) - // - //authenticatedWebdavHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // username, password, ok := r.BasicAuth() - // directory := "" - // if ok { - // ok, directory = c.ValidateDAVUser(username, password) - // } - // if !ok { - // w.Header().Set("WWW-Authenticate", `Basic realm="ShareDAV"`) - // http.Error(w, "unauthorized", http.StatusUnauthorized) - // return - // } - // - // shareName := chi.URLParam(r, "share") - // directoryMapping := DirectoryMapping{ - // VirtualName: shareName, - // RealName: directory, - // } - // - // // Use the WebDAV handler to actually serve the request. Also enhance the context - // // to contain the subdirectory (of the base directory) which contains the data for - // // the authenticated user. - // h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), "mapping", &directoryMapping))) - //}) - // - //r := chi.NewRouter() - //r.Handle("/share/{share}/*", authenticatedWebdavHandler) - // - //http.ListenAndServe(c.ListenAddress, r) }