master
js 276 lines 10.2 KB
Raw
1
2 var promise = require('promise');
3
4 function dataHandler(data) { this.str += data.toString(); }
5
6 function av()
7 {
8 var result = [];
9 var child, version, dbDate, running;
10
11 // Check if clamscan binary exists
12 var clamBinaries = ['/usr/bin/clamscan', '/usr/local/bin/clamscan', '/bin/clamscan'];
13 var clamFound = false;
14 var fs = require('fs');
15 for (var i = 0; i < clamBinaries.length; ++i) {
16 try { if (fs.existsSync(clamBinaries[i])) { clamFound = true; break; } } catch (ex) { }
17 }
18 if (!clamFound) { return ([]); }
19
20 // Get ClamAV version
21 try {
22 child = require('child_process').execFile('/bin/sh', ['sh']);
23 child.stdout.str = ''; child.stdout.on('data', dataHandler);
24 child.stdin.write('clamscan --version 2>/dev/null | head -1\nexit\n');
25 child.waitExit();
26 version = child.stdout.str.trim();
27 } catch (ex) { version = ''; }
28
29 // Get virus database date from freshclam/clamd dat files
30 try {
31 child = require('child_process').execFile('/bin/sh', ['sh']);
32 child.stdout.str = ''; child.stdout.on('data', dataHandler);
33 child.stdin.write('stat -c "%y" /var/lib/clamav/main.cvd /var/lib/clamav/main.cld 2>/dev/null | sort -r | head -1 | cut -d" " -f1\nexit\n');
34 child.waitExit();
35 dbDate = child.stdout.str.trim();
36 } catch (ex) { dbDate = ''; }
37
38 // Check if clamd service is running (systemd, OpenRC, or process scan fallback)
39 try {
40 child = require('child_process').execFile('/bin/sh', ['sh']);
41 child.stdout.str = ''; child.stdout.on('data', dataHandler);
42 child.stdin.write(
43 'if command -v systemctl >/dev/null 2>&1; then ' +
44 'systemctl is-active clamav-daemon 2>/dev/null || systemctl is-active clamd 2>/dev/null || echo inactive; ' +
45 'elif command -v rc-service >/dev/null 2>&1; then ' +
46 'rc-service clamd status 2>/dev/null | grep -q started && echo active || echo inactive; ' +
47 'else ' +
48 'pgrep -x clamd >/dev/null 2>&1 && echo active || echo inactive; ' +
49 'fi\nexit\n'
50 );
51 child.waitExit();
52 running = child.stdout.str.trim() === 'active';
53 } catch (ex) { running = false; }
54
55 var status = {};
56 status.product = version || 'ClamAV';
57 status.enabled = running;
58 status.updated = dbDate !== '';
59 if (dbDate) { status.definitionDate = dbDate; }
60 result.push(status);
61
62 return (result);
63 }
64
65 function firewall()
66 {
67 var child, status, output;
68
69 // Check if ufw binary exists
70 var ufwBinaries = ['/usr/sbin/ufw', '/sbin/ufw', '/usr/bin/ufw'];
71 var ufwFound = false;
72 var fs = require('fs');
73 for (var i = 0; i < ufwBinaries.length; ++i) {
74 try { if (fs.existsSync(ufwBinaries[i])) { ufwFound = true; break; } } catch (ex) { }
75 }
76 if (!ufwFound) { return (null); }
77
78 // Get ufw status
79 try {
80 child = require('child_process').execFile('/bin/sh', ['sh']);
81 child.stdout.str = ''; child.stdout.on('data', dataHandler);
82 child.stdin.write('ufw status 2>/dev/null | head -1\nexit\n');
83 child.waitExit();
84 output = child.stdout.str.trim();
85 } catch (ex) { output = ''; }
86
87 return ({
88 product: 'UFW',
89 installed: true,
90 enabled: /^status:\s+active$/i.test(output)
91 });
92 }
93
94 function packages() {
95 var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
96 var fs = require('fs');
97
98 function runSh(script) {
99 try {
100 var child = require('child_process').execFile('/bin/sh', ['sh']);
101 child.stdout.str = '';
102 child.stdout.on('data', function (d) { child.stdout.str += d; });
103 child.stdin.write(script + '\nexit\n');
104 child.waitExit();
105 return child.stdout.str.trim();
106 } catch (ex) { return ''; }
107 }
108
109 function dirExists(path) {
110 try { return fs.existsSync(path); } catch (ex) { return false; }
111 }
112
113 function binExists(paths) {
114 for (var i = 0; i < paths.length; ++i) {
115 try { if (fs.existsSync(paths[i])) return true; } catch (ex) { }
116 }
117 return false;
118 }
119
120 // ---------- dpkg (Debian/Ubuntu) ----------
121 function collectDpkg() {
122 if (!binExists(['/usr/bin/dpkg-query', '/bin/dpkg-query'])) return [];
123 if (!dirExists('/var/lib/dpkg/info')) return [];
124
125 var out = runSh('dpkg-query -W -f=\'${binary:Package}\\t${Version}\\t${Architecture}\\t${db:Status-Abbrev}\\t${Maintainer}\\n\' 2>/dev/null');
126 if (!out) return [];
127
128 var dateMap = {};
129 var dateOut = runSh('find /var/lib/dpkg/info -name \'*.list\' -printf \'%f\\t%TY-%Tm-%Td\\n\' 2>/dev/null');
130 if (dateOut) {
131 dateOut.split('\n').forEach(function (line) {
132 var p = line.split('\t');
133 if (p.length >= 2) { dateMap[p[0].replace(/\.list$/, '')] = p[1]; }
134 });
135 }
136
137 var results = [];
138 out.split('\n').forEach(function (line) {
139 var p = line.split('\t');
140 if (p.length < 4) return;
141 if (p[3].indexOf('ii') !== 0) return;
142 results.push({ name: p[0], version: p[1], arch: p[2], publisher: p[4] || '', date: dateMap[p[0]] || '', location: 'dpkg' });
143 });
144 return results;
145 }
146
147 // ---------- rpm (RHEL/Fedora/CentOS/SUSE) ----------
148 function collectRpm() {
149 if (!binExists(['/usr/bin/rpm', '/bin/rpm'])) return [];
150 if (!dirExists('/var/lib/rpm') && !dirExists('/usr/lib/sysimage/rpm')) return [];
151
152 var out = runSh('rpm -qa --qf \'%{NAME}\\t%{VERSION}\\t%{RELEASE}\\t%{ARCH}\\t%{VENDOR}\\t%{INSTALLTIME:date}\\n\' 2>/dev/null');
153 if (!out) return [];
154
155 var results = [];
156 out.split('\n').forEach(function (line) {
157 var p = line.split('\t');
158 if (p.length < 5 || !p[0]) return;
159 results.push({ name: p[0], version: p[1] + (p[2] ? '-' + p[2] : ''), arch: p[3], publisher: p[4] || '', date: p[5] || '', location: 'rpm' });
160 });
161 return results;
162 }
163
164 // ---------- pacman (Arch/Manjaro) ----------
165 function collectPacman() {
166 if (!binExists(['/usr/bin/pacman', '/bin/pacman'])) return [];
167 if (!dirExists('/var/lib/pacman/local')) return [];
168
169 var out = runSh('pacman -Qi 2>/dev/null');
170 if (!out) return [];
171
172 var results = [];
173 var current = {};
174 out.split('\n').forEach(function (line) {
175 var m = line.match(/^([A-Za-z ]+?)\s*:\s(.+)$/);
176 if (m) {
177 current[m[1].trim()] = m[2].trim();
178 } else if (!line.trim() && current['Name']) {
179 results.push({ name: current['Name'], version: current['Version'] || '', arch: current['Architecture'] || '', publisher: current['Packager'] || '', date: current['Install Date'] || '', location: 'pacman' });
180 current = {};
181 }
182 });
183 if (current['Name']) {
184 results.push({ name: current['Name'], version: current['Version'] || '', arch: current['Architecture'] || '', publisher: current['Packager'] || '', date: current['Install Date'] || '', location: 'pacman' });
185 }
186 return results;
187 }
188
189 // ---------- apk (Alpine) ----------
190 function collectApk() {
191 if (!binExists(['/sbin/apk', '/usr/bin/apk'])) return [];
192 if (!dirExists('/lib/apk/db') && !dirExists('/var/lib/apk/db')) return [];
193
194 var out = runSh('apk info -v 2>/dev/null');
195 if (!out) return [];
196
197 var results = [];
198 out.split('\n').forEach(function (line) {
199 line = line.trim();
200 if (!line) return;
201 var match = line.match(/^(.+)-(\d[^-]*)(-r\d+)?$/);
202 if (match) {
203 results.push({ name: match[1], version: match[2] + (match[3] || ''), arch: '', publisher: '', location: 'apk' });
204 } else {
205 results.push({ name: line, version: '', arch: '', publisher: '', location: 'apk' });
206 }
207 });
208 return results;
209 }
210
211 // ---------- flatpak ----------
212 function collectFlatpak() {
213 if (!binExists(['/usr/bin/flatpak', '/bin/flatpak'])) return [];
214 if (!dirExists('/var/lib/flatpak')) return [];
215
216 var results = [];
217
218 var sys = runSh('flatpak list --system --columns=application,version,origin 2>/dev/null');
219 sys.split('\n').forEach(function (line) {
220 var p = line.split('\t');
221 if (!p[0] || !p[0].trim()) return;
222 results.push({ name: p[0].trim(), version: (p[1] || '').trim(), arch: '', publisher: (p[2] || '').trim(), location: 'flatpak', scope: 'system' });
223 });
224
225 var usr = runSh('flatpak list --user --columns=application,version,origin 2>/dev/null');
226 usr.split('\n').forEach(function (line) {
227 var p = line.split('\t');
228 if (!p[0] || !p[0].trim()) return;
229 results.push({ name: p[0].trim(), version: (p[1] || '').trim(), arch: '', publisher: (p[2] || '').trim(), location: 'flatpak', scope: 'user' });
230 });
231
232 return results;
233 }
234
235 // ---------- snap ----------
236 function collectSnap() {
237 if (!binExists(['/usr/bin/snap', '/bin/snap'])) return [];
238 if (!dirExists('/var/lib/snapd/snaps')) return [];
239
240 var out = runSh('snap list --all 2>/dev/null');
241 if (!out) return [];
242
243 var results = [];
244 out.split('\n').forEach(function (line) {
245 if (!line || line.indexOf('Name') === 0) return;
246 var p = line.trim().split(/\s+/);
247 if (p.length < 5 || !p[0]) return;
248 results.push({ name: p[0], version: p[1], arch: '', publisher: p[4] || '', location: 'snap' });
249 });
250 return results;
251 }
252
253 try {
254 var all = [].concat(
255 collectDpkg(),
256 collectRpm(),
257 collectPacman(),
258 collectApk(),
259 collectFlatpak(),
260 collectSnap()
261 );
262 ret._res(all);
263 } catch (e) {
264 ret._rej(e);
265 }
266 return (ret);
267 }
268
269 if (process.platform == 'linux') {
270 module.exports = { av: av, firewall: firewall, packages: packages };
271 }
272 else
273 {
274 var not_supported = function () { throw (process.platform + ' not supported'); };
275 module.exports = { av: not_supported, firewall: not_supported, packages: not_supported };
276 }