Skip to content

Commit 239a955

Browse files
committed
updated
1 parent 18b2b98 commit 239a955

6 files changed

Lines changed: 949 additions & 0 deletions

File tree

bin/apps/aspiesoft-clamav-scanner/linux-clamav-download-scanner/LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module github.com/AspieSoft/linux-clamav-download-scanner
2+
3+
go 1.20
4+
5+
require (
6+
github.com/AspieSoft/go-regex/v4 v4.1.2
7+
github.com/AspieSoft/goutil/v5 v5.0.3
8+
github.com/alphadose/haxmap v1.2.0
9+
)
10+
11+
require (
12+
github.com/AspieSoft/go-syncterval v1.0.4 // indirect
13+
github.com/AspieSoft/go-ttlcache v1.2.1 // indirect
14+
github.com/GRbit/go-pcre v1.0.0 // indirect
15+
github.com/andybalholm/brotli v1.0.5 // indirect
16+
github.com/cespare/go-smaz v1.0.0 // indirect
17+
github.com/fsnotify/fsnotify v1.6.0 // indirect
18+
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
19+
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
20+
golang.org/x/sys v0.8.0 // indirect
21+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
github.com/AspieSoft/go-regex/v4 v4.1.2 h1:7RNJwKwL2HrWJtibpr4Br5w43/SG/RbexAQ9wLZpKSs=
2+
github.com/AspieSoft/go-regex/v4 v4.1.2/go.mod h1:vU1ZsmQXyVwM6zBrGwj0lL+kYZv/tnUP0ZZv9Wbh+qY=
3+
github.com/AspieSoft/go-syncterval v1.0.4 h1:K3x89HY2JZT88l7YDIuoEpx8L8RggSmsIqHdsjD3Yfs=
4+
github.com/AspieSoft/go-syncterval v1.0.4/go.mod h1:BET28sFgAaUPdHvQZrW1Mce8uI2jqsIv91o44PUeQf0=
5+
github.com/AspieSoft/go-ttlcache v1.2.1 h1:hPRRIFOloMBUmkXvb7hWAGtR6Bg1N4VciNWDjh5+kpg=
6+
github.com/AspieSoft/go-ttlcache v1.2.1/go.mod h1:wOPoCh1yrJiTieRoZcg5mSSSZHqi0gE8kGqGltKd75o=
7+
github.com/AspieSoft/goutil/v5 v5.0.3 h1:BCiG1yHqFHKgKsoTDfjeNbnRq1W1alo+uJrQJw1YV/U=
8+
github.com/AspieSoft/goutil/v5 v5.0.3/go.mod h1:nLhRdEfkRq9xPM/XfUEaQYjPbc4omzhWTy8lFjcaPjM=
9+
github.com/GRbit/go-pcre v1.0.0 h1:Qv/YZ/tr436mFgep3Y0WAzKOZvAKGOGD0cAMwUcsEJo=
10+
github.com/GRbit/go-pcre v1.0.0/go.mod h1:OuMGyux1WcDrKNwDn9MyQLa2kzxQS/xFyVqXFZ/ay0I=
11+
github.com/alphadose/haxmap v1.2.0 h1:noGrAmCE+gNheZ4KpW+sYj9W5uMcO1UAjbAq9XBOAfM=
12+
github.com/alphadose/haxmap v1.2.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
13+
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
14+
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
15+
github.com/cespare/go-smaz v1.0.0 h1:CUrrqIzakjINfWkdyNrVhtDKcGmdKkdB9AW7ke5nJ+M=
16+
github.com/cespare/go-smaz v1.0.0/go.mod h1:h77Hd4Dz/EPofhYvhkSuEyM0+6vyP3ckmSoBxTcKyxk=
17+
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
18+
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
19+
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
20+
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
21+
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
22+
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
23+
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
25+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Binary file not shown.
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log"
7+
"os"
8+
"os/exec"
9+
"strconv"
10+
"strings"
11+
"time"
12+
13+
"github.com/AspieSoft/go-regex/v4"
14+
"github.com/AspieSoft/goutil/v5"
15+
"github.com/alphadose/haxmap"
16+
)
17+
18+
func main(){
19+
newFiles := haxmap.New[string, uint]()
20+
hasFiles := haxmap.New[string, uint]()
21+
lastNotify := uint(0)
22+
notifyDelay := uint(3000)
23+
24+
scanDirList := []string{
25+
"Downloads",
26+
"Desktop",
27+
"Documents",
28+
"Pictures",
29+
"Videos",
30+
"Music",
31+
}
32+
33+
34+
homeDir, err := os.UserHomeDir()
35+
if err != nil {
36+
log.Fatal(err)
37+
}
38+
39+
for _, arg := range os.Args[1:] {
40+
if dir := string(regex.Comp(`[^\w_-]+`).RepStr([]byte(arg), []byte{})); dir != "" {
41+
scanDirList = append(scanDirList, dir)
42+
}
43+
}
44+
45+
46+
// create quarantine directory if it does not exist
47+
if _, err := os.Stat("/VirusScan/quarantine"); err == nil || !strings.HasSuffix(err.Error(), "permission denied") {
48+
exec.Command(`sudo`, `mkdir`, `-p`, `/VirusScan/quarantine`, `&&`, `sudo`, `chmod`, `0664`, `/VirusScan`, `&&`, `sudo`, `chmod`, `2660`, `/VirusScan/quarantine`, `&&`, `sudo`, `chmod`, `-R`, `2660`, `/VirusScan/quarantine`).Run()
49+
}
50+
51+
52+
cmd := exec.Command(`find`, homeDir+"/.config", `-type`, `d`, `-name`, `*xtensions`)
53+
if stdout, err := cmd.StdoutPipe(); err == nil {
54+
go func(){
55+
for {
56+
b := make([]byte, 1024)
57+
_, err := stdout.Read(b)
58+
if err != nil {
59+
break
60+
}
61+
62+
list := bytes.Split(b, []byte{'\n'})
63+
if len(list) == 0 {
64+
continue
65+
}
66+
if list[len(list)-1][0] == 0 {
67+
list = list[:len(list)-1]
68+
}
69+
70+
for _, dir := range list {
71+
dir = regex.Comp(`[\r\n\t ]+`).RepStrRef(&dir, []byte{})
72+
if !bytes.Contains(dir, []byte("/tmp/")) {
73+
scanDirList = append(scanDirList, string(dir[len([]byte(homeDir))+1:]))
74+
}
75+
}
76+
}
77+
}()
78+
}
79+
cmd.Run()
80+
81+
82+
watcher := goutil.FS.FileWatcher()
83+
defer watcher.CloseWatcher("*")
84+
85+
var downloadDir string
86+
87+
for _, dir := range scanDirList {
88+
if path, err := goutil.FS.JoinPath(homeDir, dir); err == nil {
89+
watcher.WatchDir(path)
90+
if downloadDir == "" && dir == "Downloads" {
91+
downloadDir = path
92+
}
93+
}
94+
}
95+
96+
watcher.OnFileChange = func(path, op string) {
97+
newFiles.Set(path, uint(time.Now().UnixMilli()))
98+
hasFiles.Set(path, uint(time.Now().UnixMilli()))
99+
}
100+
101+
watcher.OnRemove = func(path, op string) (removeWatcher bool) {
102+
newFiles.Del(path)
103+
hasFiles.Del(path)
104+
return true
105+
}
106+
107+
scanFile := make(chan string)
108+
109+
running := true
110+
111+
go func(){
112+
for {
113+
if !running {
114+
break
115+
}
116+
117+
now := uint(time.Now().UnixMilli())
118+
newFiles.ForEach(func(path string, modified uint) bool {
119+
if now - modified > 1000 {
120+
scanFile <- path
121+
newFiles.Del(path)
122+
}
123+
return true
124+
})
125+
}
126+
}()
127+
128+
go func(){
129+
for {
130+
file := <- scanFile
131+
132+
if file == "" {
133+
break
134+
}
135+
136+
// prevent removed or recently changed files from staying at the begining of the queue
137+
now := uint(time.Now().UnixMilli())
138+
if modified, ok := hasFiles.Get(file); !ok || now - modified < 1000 {
139+
continue
140+
}
141+
hasFiles.Del(file)
142+
143+
cmd := exec.Command(`sudo`, `nice`, `-n`, `15`, `clamscan`, `&&`, `sudo`, `clamscan`, `-r`, `--bell`, `--move=/VirusScan/quarantine`, `--exclude-dir=/VirusScan/quarantine`, file)
144+
145+
success := false
146+
147+
if stdout, err := cmd.StdoutPipe(); err == nil {
148+
go func(){
149+
onSummary := false
150+
for {
151+
b := make([]byte, 1024)
152+
_, err := stdout.Read(b)
153+
if err != nil {
154+
break
155+
}
156+
157+
if !onSummary && regex.Comp(`(?i)-+\s*scan\s+summ?[ae]ry\s*-+`).MatchRef(&b) {
158+
onSummary = true
159+
success = true
160+
}
161+
162+
if onSummary && regex.Comp(`(?i)infected\s+files:?\s*([0-9]+)`).MatchRef(&b) {
163+
inf := 0
164+
regex.Comp(`(?i)infected\s+files:?\s*([0-9]+)`).RepFuncRef(&b, func(data func(int) []byte) []byte {
165+
if i, err := strconv.Atoi(string(data(1))); err == nil && i > inf {
166+
inf = i
167+
}
168+
return nil
169+
}, true)
170+
171+
fmt.Println("\nFile/Dir:", file, "\n Infected files:", inf)
172+
173+
if inf == 0 && downloadDir != "" && strings.HasPrefix(file, downloadDir) {
174+
now := uint(time.Now().UnixMilli())
175+
if now - lastNotify > notifyDelay {
176+
lastNotify = now
177+
exec.Command(`notify-send`, `-i`, `/etc/aspiesoft-clamav-scanner/icon-green.png`, `-t`, `3`, `File Is Safe`, file).Run()
178+
}
179+
}else if inf != 0 {
180+
now := uint(time.Now().UnixMilli())
181+
if now - lastNotify > notifyDelay {
182+
lastNotify = now
183+
exec.Command(`notify-send`, `-i`, `/etc/aspiesoft-clamav-scanner/icon-red.png`, `-t`, `3`, `Warning: File Has Been Moved To Quarantine`, file).Run()
184+
}
185+
}
186+
187+
break
188+
}
189+
}
190+
}()
191+
}
192+
193+
if downloadDir != "" && strings.HasPrefix(file, downloadDir) {
194+
now := uint(time.Now().UnixMilli())
195+
if now - lastNotify > notifyDelay {
196+
lastNotify = now
197+
exec.Command(`notify-send`, `-i`, `/etc/aspiesoft-clamav-scanner/icon.png`, `-t`, `3`, `Started Scanning File`, file).Run()
198+
}
199+
}
200+
201+
err := cmd.Run()
202+
if err != nil && !success {
203+
fmt.Println(err)
204+
205+
if downloadDir != "" && strings.HasPrefix(file, downloadDir) {
206+
now := uint(time.Now().UnixMilli())
207+
if now - lastNotify > notifyDelay {
208+
lastNotify = now
209+
exec.Command(`notify-send`, `-i`, `/etc/aspiesoft-clamav-scanner/icon.png`, `-t`, `3`, `Error: Failed To Scan File`, file).Run()
210+
}
211+
}
212+
}
213+
214+
time.Sleep(250 * time.Millisecond)
215+
}
216+
}()
217+
218+
watcher.Wait()
219+
running = false
220+
scanFile <- ""
221+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Linux ClamAV Download Scanner
2+
3+
[![donation link](https://img.shields.io/badge/buy%20me%20a%20coffee-paypal-blue)](https://paypal.me/shaynejrtaylor?country.x=US&locale.x=en_US)
4+
5+
> This module is currently in beta.
6+
7+
Automatically scan your linux home directory when you download something new.
8+
9+
By default, this module only scans common directoried (Downloads, Desktop, etc.) and searches for extension directories like your chrome extensions.
10+
I may add additional directories in future updates.
11+
Currently, this module only uses the active users home directories, and does not touch the root directory.
12+
13+
## Installation
14+
15+
```shell script
16+
# add this file to your startup script
17+
./linux-clamav-download-scanner
18+
19+
# optional: add additional directories to watch for downloads
20+
./linux-clamav-download-scanner MyDir1 MyDir2 MyDir...
21+
```

0 commit comments

Comments
 (0)