diff --git a/chfs-ng/cmd/chfs/main.go b/chfs-ng/cmd/chfs/main.go index c6092c2..cef5734 100644 --- a/chfs-ng/cmd/chfs/main.go +++ b/chfs-ng/cmd/chfs/main.go @@ -63,7 +63,7 @@ func main() { if err != nil { log.Fatal(err) } - srv := &api.Server{Store: storage.Local{Root: root}, Auth: mgr, Sessions: map[string]*auth.Account{}, WebDAVRoot: root} + srv := &api.Server{Store: storage.Local{Root: root}, Auth: mgr, Sessions: map[string]*auth.Account{}, WebDAVRoot: root, Allow: cfg.Allow} addr := ":" + itoa(cfg.Port) log.Printf("chfs-ng listening on %s root=%s", addr, filepath.Clean(root)) if cfg.SSLCert != "" && cfg.SSLKey != "" { diff --git a/chfs-ng/internal/api/server.go b/chfs-ng/internal/api/server.go index 4f1bf73..b58c0fa 100644 --- a/chfs-ng/internal/api/server.go +++ b/chfs-ng/internal/api/server.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "mime/multipart" + "net" "net/http" "path" "path/filepath" @@ -22,6 +23,7 @@ type Server struct { Auth *auth.Manager Sessions map[string]*auth.Account WebDAVRoot string + Allow string } type fileInfo struct { Name string `json:"name"` @@ -53,7 +55,7 @@ func (s *Server) Handler() http.Handler { mux.HandleFunc("/chfs/shared", s.shared) mux.HandleFunc("/chfs/preview", s.preview) mux.HandleFunc("/", s.fileHandler) - return mux + return s.ipFilter(mux) } func account(r *http.Request, s *Server) *auth.Account { if c, e := r.Cookie("chfs_token"); e == nil { @@ -509,3 +511,78 @@ func (s *Server) webdav(w http.ResponseWriter, r *http.Request) { h := &webdav.Handler{FileSystem: webdav.LocalFileSystem(s.WebDAVRoot)} h.ServeHTTP(w, r) } + +func (s *Server) ipFilter(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if s.Allow != "" && !allowedIP(s.Allow, clientIP(r)) { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + next.ServeHTTP(w, r) + }) +} + +func clientIP(r *http.Request) net.IP { + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err == nil { + return net.ParseIP(host) + } + return net.ParseIP(r.RemoteAddr) +} + +func allowedIP(rule string, ip net.IP) bool { + if ip == nil { + return false + } + rule = strings.TrimSpace(rule) + black := false + if strings.HasPrefix(strings.ToLower(rule), "not(") && strings.HasSuffix(rule, ")") { + black = true + rule = strings.TrimSpace(rule[4 : len(rule)-1]) + } + matched := false + for _, item := range strings.Split(rule, ",") { + item = strings.TrimSpace(item) + if item == "" { + continue + } + if ipInItem(ip, item) { + matched = true + break + } + } + if black { + return !matched + } + return matched +} + +func ipInItem(ip net.IP, item string) bool { + if strings.Contains(item, "-") { + parts := strings.SplitN(item, "-", 2) + lo, hi := net.ParseIP(strings.TrimSpace(parts[0])), net.ParseIP(strings.TrimSpace(parts[1])) + if lo == nil || hi == nil { + return false + } + v := ip.To4() + l := lo.To4() + h := hi.To4() + if v == nil || l == nil || h == nil { + return false + } + return bytesCompare(v, l) >= 0 && bytesCompare(v, h) <= 0 + } + return ip.Equal(net.ParseIP(item)) +} + +func bytesCompare(a, b net.IP) int { + for i := range a { + if a[i] < b[i] { + return -1 + } + if a[i] > b[i] { + return 1 + } + } + return 0 +}