155 lines
4.2 KiB
Go
155 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"chfs-ng/internal/api"
|
|
"chfs-ng/internal/auth"
|
|
"chfs-ng/internal/config"
|
|
"chfs-ng/internal/opslog"
|
|
"chfs-ng/internal/storage"
|
|
)
|
|
|
|
func main() {
|
|
file := flag.String("file", "", "configuration file")
|
|
pathFlag := flag.String("path", "/data", "shared path")
|
|
port := flag.Int("port", 80, "listen port")
|
|
allow := flag.String("allow", "", "allowed IPv4 addresses")
|
|
var rules multiFlag
|
|
flag.Var(&rules, "rule", "access rule (repeatable)")
|
|
flag.Parse()
|
|
|
|
cfg := config.Default()
|
|
if *file != "" {
|
|
loaded, err := config.Load(*file)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
log.Fatal(err)
|
|
}
|
|
if err == nil {
|
|
cfg = loaded
|
|
}
|
|
}
|
|
flag.CommandLine.Visit(func(f *flag.Flag) {
|
|
switch f.Name {
|
|
case "path":
|
|
cfg.Path = splitPath(*pathFlag)
|
|
case "port":
|
|
cfg.Port = *port
|
|
case "allow":
|
|
cfg.Allow = *allow
|
|
case "rule":
|
|
cfg.Rules = append([]string(nil), rules...)
|
|
}
|
|
})
|
|
if len(cfg.Path) == 0 {
|
|
cfg.Path = []string{"/data"}
|
|
}
|
|
if err := cfg.Validate(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
root := cfg.Path[0]
|
|
for _, p := range cfg.Path {
|
|
if err := os.MkdirAll(p, 0755); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
mgr, err := auth.ParseRules(cfg.Rules)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for _, ac := range cfg.Accounts {
|
|
a := &auth.Account{Username: ac.Username, Password: ac.Password, Mask: auth.ParseMask(ac.Default)}
|
|
for _, d := range ac.None {
|
|
a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Deny: true})
|
|
}
|
|
for _, d := range ac.Read {
|
|
a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Mask: auth.Read, ChildrenOnly: true})
|
|
}
|
|
for _, d := range ac.Write {
|
|
a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Mask: auth.ParseMask("w"), ChildrenOnly: true})
|
|
}
|
|
for _, d := range ac.Delete {
|
|
a.Rules = append(a.Rules, auth.PathRule{Dir: accountRulePath(d, cfg.Path), Mask: auth.ParseMask("d"), ChildrenOnly: true})
|
|
}
|
|
if ac.Username == "guest" {
|
|
mgr.Guest = a
|
|
} else {
|
|
mgr.Accounts[ac.Username] = a
|
|
}
|
|
}
|
|
multi := storage.NewMulti(cfg.Path)
|
|
store := storage.Storage(multi)
|
|
if len(cfg.Path) == 1 {
|
|
store = storage.Local{Root: root}
|
|
}
|
|
srv := &api.Server{Store: store, Auth: mgr, Sessions: map[string]*auth.Account{}, SessionExpiry: map[string]time.Time{}, SessionTimeout: cfg.SessionTimeout, WebDAVRoot: root, Allow: cfg.Allow, MultiRoot: len(cfg.Path) > 1, RemoveMode: cfg.FileRemove, ImagePreview: cfg.ImagePreview, FolderDownload: cfg.FolderDownload, HTMLTitle: cfg.HTMLTitle, HTMLNotice: cfg.HTMLNotice, Log: opslog.New(cfg.Log)}
|
|
addr := ":" + itoa(cfg.Port)
|
|
log.Printf("chfs-ng listening on %s root=%s removeMode=%d", addr, filepath.Clean(root), cfg.FileRemove)
|
|
if cfg.SSLCert != "" && cfg.SSLKey != "" {
|
|
log.Fatal(http.ListenAndServeTLS(addr, cfg.SSLCert, cfg.SSLKey, srv.Handler()))
|
|
}
|
|
log.Fatal(http.ListenAndServe(addr, srv.Handler()))
|
|
}
|
|
|
|
type multiFlag []string
|
|
|
|
func (m *multiFlag) String() string { return strings.Join(*m, "|") }
|
|
func (m *multiFlag) Set(v string) error { *m = append(*m, v); return nil }
|
|
|
|
func accountRulePath(rule string, roots []string) string {
|
|
rule = strings.TrimSpace(strings.Trim(rule, "\\\""))
|
|
if rule == "" {
|
|
return rule
|
|
}
|
|
rule = filepath.ToSlash(rule)
|
|
if !filepath.IsAbs(rule) {
|
|
// CHFS section rules are filesystem paths. Do not silently rebase a
|
|
// relative rule onto --path; the original parser treats such a value
|
|
// as a literal path and it therefore does not match a virtual path.
|
|
return rule
|
|
}
|
|
for _, root := range roots {
|
|
r := filepath.Clean(root)
|
|
rel, err := filepath.Rel(r, rule)
|
|
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
continue
|
|
}
|
|
if rel == "." {
|
|
return ""
|
|
}
|
|
return filepath.ToSlash(rel)
|
|
}
|
|
return rule
|
|
}
|
|
|
|
func itoa(n int) string {
|
|
const d = "0123456789"
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
b := make([]byte, 0, 6)
|
|
for n > 0 {
|
|
b = append([]byte{d[n%10]}, b...)
|
|
n /= 10
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func splitPath(v string) []string {
|
|
var out []string
|
|
for _, p := range strings.Split(v, "|") {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|