diff --git a/internal/agent/nfs/filter.go b/internal/agent/nfs/filter.go new file mode 100644 index 0000000..f9be0ab --- /dev/null +++ b/internal/agent/nfs/filter.go @@ -0,0 +1,85 @@ +package nfs + +import ( + "regexp" + "strings" +) + +func wildcardToRegex(pattern string) string { + // Escape backslashes and convert path to regex-friendly format + escapedPattern := regexp.QuoteMeta(pattern) + + // Replace double-star wildcard ** with regex equivalent (any directory depth) + escapedPattern = strings.ReplaceAll(escapedPattern, `\*\*`, `.*`) + + // Replace single-star wildcard * with regex equivalent (any single directory level) + escapedPattern = strings.ReplaceAll(escapedPattern, `\*`, `[^\/]*`) + + if strings.HasPrefix(escapedPattern, "/") { + escapedPattern = "^" + escapedPattern + } + + // Ensure the regex matches paths that start with the pattern and allows for subdirectories + return escapedPattern + `(\/|$)` +} + +func skipPath(path string) bool { + excludedPaths := []string{ + "$RECYCLE.BIN", + "$WinREAgent", + "pagefile.sys", + "swapfile.sys", + "hiberfil.sys", + "System Volume Information", + `Config.Msi`, + `/Documents and Settings`, + `MSOCache`, + `PerfLogs`, + `/Program Files`, + `/Program Files (x86)`, + `/ProgramData`, + `/Recovery`, + `/Users/Default`, + `/Users/Public`, + `/Windows`, + `/Users/*/AppData/Local/Temp`, + `/Users/*/AppData/Local/Microsoft/Windows/INetCache`, + `/Users/*/AppData/Local/Microsoft/Windows/History`, + `/Users/*/AppData/Local/Microsoft/Edge`, + `/Users/*/AppData/Local/Google/Chrome/User Data/Default/Cache`, + `/Users/*/AppData/Local/Packages`, + `/Users/*/AppData/Roaming/Microsoft/Windows/Recent`, + `/Users/*/AppData/Local/Mozilla/Firefox/Profiles/*/cache2`, + `/Users/*/AppData/Local/Mozilla/Firefox/Profiles/*/offlineCache`, + `/Users/*/AppData/Local/Mozilla/Firefox/Profiles/*/startupCache`, + `/Users/*/AppData/Local/Thunderbird/Profiles/*/cache2`, + `/Users/*/AppData/Local/Thunderbird/Profiles/*/offlineCache`, + `/Users/*/AppData/Roaming/Thunderbird/Crash Reports`, + `/Users/*/AppData/Roaming/Mozilla/Firefox/Crash Reports`, + `/Users/*/AppData/Local/Microsoft/OneDrive/Temp`, + `/Users/*/AppData/Local/Microsoft/OneDrive/logs`, + `/Users/*/AppData/Local/Spotify/Storage`, + `/Users/*/AppData/Local/Spotify/Data`, + `/Users/*/AppData/Local/Slack/Cache`, + `/Users/*/AppData/Local/Slack/Code Cache`, + `/Users/*/AppData/Local/Slack/GPUCache`, + `/Users/*/AppData/Roaming/Zoom/bin`, + `/Users/*/AppData/Roaming/Zoom/data`, + `/Users/*/AppData/Roaming/Zoom/logs`, + `/Users/*/AppData/Local/BraveSoftware`, + `/Users/*/AppData/**log**`, + } + + normalizedPath := strings.ToUpper(path) + + for _, excludePath := range excludedPaths { + regexPattern := wildcardToRegex(excludePath) + regex := regexp.MustCompile("(?i)" + regexPattern) + + if regex.MatchString(normalizedPath) { + return true + } + } + + return false +} diff --git a/internal/agent/nfs/handler.go b/internal/agent/nfs/handler.go index f567787..6e9c778 100644 --- a/internal/agent/nfs/handler.go +++ b/internal/agent/nfs/handler.go @@ -1,13 +1,12 @@ package nfs import ( - "errors" "os" "github.com/go-git/go-billy/v5" ) -var errReadOnly = errors.New("read-only file system") +var errReadOnly = billy.ErrReadOnly type ReadOnlyFS struct { fs billy.Filesystem @@ -26,6 +25,11 @@ func (ro *ReadOnlyFS) OpenFile(filename string, flag int, perm os.FileMode) (bil if flag != os.O_RDONLY { return nil, errReadOnly } + + if skipPath(filename) { + return nil, billy.ErrNotSupported + } + return ro.fs.OpenFile(filename, flag, perm) } @@ -40,10 +44,18 @@ func (ro *ReadOnlyFS) Remove(filename string) error { // Delegate read-only operations to the underlying fs func (ro *ReadOnlyFS) Open(filename string) (billy.File, error) { + if skipPath(filename) { + return nil, billy.ErrNotSupported + } + return ro.fs.Open(filename) } func (ro *ReadOnlyFS) Stat(filename string) (os.FileInfo, error) { + if skipPath(filename) { + return nil, billy.ErrNotSupported + } + return ro.fs.Stat(filename) } @@ -56,6 +68,10 @@ func (ro *ReadOnlyFS) TempFile(dir, prefix string) (billy.File, error) { } func (ro *ReadOnlyFS) ReadDir(path string) ([]os.FileInfo, error) { + if skipPath(path) { + return nil, billy.ErrNotSupported + } + return ro.fs.ReadDir(path) } @@ -72,10 +88,18 @@ func (ro *ReadOnlyFS) Symlink(target, link string) error { } func (ro *ReadOnlyFS) Readlink(link string) (string, error) { + if skipPath(link) { + return "", billy.ErrNotSupported + } + return ro.fs.Readlink(link) } func (ro *ReadOnlyFS) Chroot(path string) (billy.Filesystem, error) { + if skipPath(path) { + return nil, billy.ErrNotSupported + } + fs, err := ro.fs.Chroot(path) if err != nil { return nil, err diff --git a/internal/agent/nfs/nfs.go b/internal/agent/nfs/nfs.go index a988152..fe2e076 100644 --- a/internal/agent/nfs/nfs.go +++ b/internal/agent/nfs/nfs.go @@ -6,6 +6,7 @@ package nfs import ( "context" "fmt" + "log" "net" "net/url" "time" @@ -17,51 +18,45 @@ import ( "golang.org/x/sys/windows/registry" ) -func Serve(ctx context.Context, errChan chan string, address, port string, driveLetter string) { +func Serve(ctx context.Context, address, port string, driveLetter string) error { baseKey, _, err := registry.CreateKey(registry.LOCAL_MACHINE, "Software\\PBSPlus\\Config", registry.QUERY_VALUE) if err != nil { - errChan <- fmt.Sprintf("Unable to create registry key -> %v", err) - return + return fmt.Errorf("Unable to create registry key -> %v", err) } defer baseKey.Close() var server string if server, _, err = baseKey.GetStringValue("ServerURL"); err != nil { - errChan <- fmt.Sprintf("Unable to get server url -> %v", err) - return + return fmt.Errorf("Unable to get server url -> %v", err) } serverUrl, err := url.Parse(server) if err != nil { - errChan <- fmt.Sprintf("failed to parse server IP: %v", err) - return + return fmt.Errorf("failed to parse server IP: %v", err) } var listener net.Listener - listening := false - listen := func() { + listen := func() error { var err error listenAt := fmt.Sprintf("%s:%s", address, port) listener, err = net.Listen("tcp", listenAt) if err != nil { - errChan <- fmt.Sprintf("Port is already in use! Failed to listen on %s: %v", listenAt, err) - return + return fmt.Errorf("Port is already in use! Failed to listen on %s: %v", listenAt, err) } listener = &FilteredListener{Listener: listener, allowedIP: serverUrl.Hostname()} - listening = true + return nil } - listen() - - for !listening { + err = listen() + for err != nil { select { case <-ctx.Done(): - return + return nil case <-time.After(time.Second * 5): - listen() + err = listen() } } @@ -69,8 +64,7 @@ func Serve(ctx context.Context, errChan chan string, address, port string, drive snapshot, err := snapshots.Snapshot(driveLetter) if err != nil { - errChan <- fmt.Sprintf("failed to initialize snapshot: %v", err) - return + return fmt.Errorf("failed to initialize snapshot: %v", err) } defer snapshot.Close() @@ -83,7 +77,7 @@ func Serve(ctx context.Context, errChan chan string, address, port string, drive go func() { err := nfs.Serve(listener, nfsHandler) if err != nil { - errChan <- fmt.Sprintf("NFS server error: %v", err) + log.Printf("NFS server error: %v\n", err) } close(done) }() @@ -91,7 +85,7 @@ func Serve(ctx context.Context, errChan chan string, address, port string, drive select { case <-ctx.Done(): listener.Close() - return + return nil case <-done: } }