package main import ( "encoding/csv" "flag" "fmt" "log" "net" "net/http" "os" "path/filepath" "time" "golang.org/x/crypto/bcrypt" "golang.org/x/net/webdav" "suah.dev/gavin/pu" ) var ( davDir string listen string passPath string davPath string staticDir string users map[string]string ) func init() { users = make(map[string]string) dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatalln(err) } flag.StringVar(&davDir, "davdir", dir, "Directory to serve over WebDAV.") flag.StringVar(&listen, "http", ":8080", "Listen on") flag.StringVar(&passPath, "htpass", fmt.Sprintf("%s/.htpasswd", dir), "Path to .htpasswd file..") flag.StringVar(&davPath, "davpath", "/dav/", "Directory containing files to serve over WebDAV.") flag.StringVar(&staticDir, "static", dir, "Directory to serve static resources from. Served at '/'.") flag.Parse() // These are OpenBSD specific protections used to prevent un-necesary file access. pu.Unveil(staticDir, "r") pu.Unveil(passPath, "r") pu.Unveil(davDir, "rwc") err = pu.UnveilBlock() if err != nil { log.Fatal(err) } p, err := os.Open(passPath) if err != nil { log.Fatal(err) } defer p.Close() ht := csv.NewReader(p) ht.Comma = ':' ht.Comment = '#' ht.TrimLeadingSpace = true entries, err := ht.ReadAll() if err != nil { log.Fatal(err) } for _, parts := range entries { users[parts[0]] = parts[1] } } func validate(user string, pass string) bool { htpass, exists := users[user] if !exists { return false } err := bcrypt.CompareHashAndPassword([]byte(htpass), []byte(pass)) return err == nil } func main() { wdav := &webdav.Handler{ Prefix: davPath, LockSystem: webdav.NewMemLS(), FileSystem: webdav.Dir(davDir), Logger: func(r *http.Request, err error) { n := time.Now() fmt.Printf("%s [%s] \"%s %s %s\" %03d\n", r.RemoteAddr, n.Format(time.RFC822Z), r.Method, r.URL.Path, r.Proto, r.ContentLength, ) }, } mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.Dir(staticDir))) mux.HandleFunc(davPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !(ok && validate(user, pass)) { w.Header().Set("WWW-Authenticate", `Basic realm="davfs"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } wdav.ServeHTTP(w, r) })) lis, err := net.Listen("tcp", listen) if err != nil { log.Panic(err) } s := http.Server{Handler: mux} log.Printf("Listening on '%s'", listen) log.Panic(s.Serve(lis)) }