@roflsec/fail2scan 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/daemon.js +285 -61
- package/package.json +2 -3
package/bin/daemon.js
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
require('dotenv').config();
|
|
4
3
|
const fs = require('fs'), path = require('path'), os = require('os');
|
|
5
4
|
const { execFile, spawn } = require('child_process');
|
|
6
5
|
const { promisify } = require('util');
|
|
6
|
+
const https = require('https');
|
|
7
7
|
const execFileP = promisify(execFile);
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
if (process.env.IPGEO_API_KEY) {
|
|
11
|
-
const { default: IPGeolocationAPI, GeolocationParams } = require("ip-geolocation-api-javascript-sdk");
|
|
12
|
-
const ipGeo = new IPGeolocationAPI(process.env.IPGEO_API_KEY, true);
|
|
13
|
-
getGeo = (ip) => {
|
|
14
|
-
return new Promise((resolve) => {
|
|
15
|
-
const params = new GeolocationParams();
|
|
16
|
-
params.setIPAddress(ip);
|
|
17
|
-
params.setFields("geo,time_zone,currency,asn,security");
|
|
18
|
-
ipGeo.getGeolocation((res) => resolve(res), params);
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
9
|
+
// -------------------- CLI / CONFIG --------------------
|
|
23
10
|
const argv = process.argv.slice(2);
|
|
24
11
|
const getArg = (k,d) => { for(let i=0;i<argv.length;i++){const a=argv[i]; if(a===k&&argv[i+1]) return argv[++i]; if(a.startsWith(k+'=')) return a.split('=')[1]; } return d; };
|
|
25
|
-
|
|
12
|
+
|
|
13
|
+
if(argv.includes('--help')||argv.includes('-h')){
|
|
14
|
+
console.log(`Fail2Scan optimized daemon
|
|
26
15
|
--log PATH (default /var/log/fail2ban.log)
|
|
27
16
|
--out PATH (default /var/log/fail2scan)
|
|
28
17
|
--concurrency N (default 1)
|
|
@@ -30,7 +19,9 @@ if(argv.includes('--help')||argv.includes('-h')){console.log(`Fail2Scan optimize
|
|
|
30
19
|
--nmap-args "args" (default "-sS -Pn -p- -T4 -sV")
|
|
31
20
|
--scan-ip IP (do one scan and exit)
|
|
32
21
|
--quiet
|
|
33
|
-
`);
|
|
22
|
+
`);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
34
25
|
|
|
35
26
|
const LOG_PATH = getArg('--log','/var/log/fail2ban.log');
|
|
36
27
|
const OUT_ROOT = getArg('--out','/var/log/fail2scan');
|
|
@@ -43,21 +34,107 @@ const RESCAN_TTL_SEC = 60*60;
|
|
|
43
34
|
const STATE_FILE = path.join(os.homedir(),'.fail2scan_state.json');
|
|
44
35
|
const LOG_FILE = path.join(os.homedir(),'.fail2scan.log');
|
|
45
36
|
|
|
46
|
-
const log=(...a)=>{
|
|
37
|
+
const log=(...a)=>{
|
|
38
|
+
if(!QUIET) console.log(new Date().toISOString(),...a);
|
|
39
|
+
try{ fs.appendFileSync(LOG_FILE,new Date().toISOString()+' '+a.join(' ')+'\n'); }catch{}
|
|
40
|
+
};
|
|
47
41
|
|
|
42
|
+
// -------------------- utilities --------------------
|
|
48
43
|
const sanitizeFilename=s=>String(s).replace(/[:\/\\<>?"|* ]+/g,'_');
|
|
49
|
-
async function which(bin){try{await execFileP('which',[bin]);return true;}catch{return false;}}
|
|
50
|
-
async function runCmdCapture(cmd,args,opts={}){try{const {stdout,stderr}=await execFileP(cmd,args,{maxBuffer:1024*1024*32,...opts}); return {ok:true,stdout:stdout||'',stderr:stderr||''};}catch(e){return {ok:false,stdout:(e.stdout||'')+'',stderr:(e.stderr||e.message)+''};}}
|
|
51
|
-
function safeMkdirSyncWithFallback(p){try{return fs.mkdirSync(p,{recursive:true,mode:0o750})||p}catch(e){const f=path.join('/tmp','fail2scan');try{return fs.mkdirSync(f,{recursive:true,mode:0o750})||f}catch(ee){throw e;}}}
|
|
52
44
|
|
|
53
|
-
|
|
45
|
+
async function which(bin){
|
|
46
|
+
try{ await execFileP('which',[bin]); return true; }
|
|
47
|
+
catch{ return false; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runCmdCapture(cmd,args,opts={}){
|
|
51
|
+
try{
|
|
52
|
+
const {stdout,stderr}=await execFileP(cmd,args,{maxBuffer:1024*1024*32,...opts});
|
|
53
|
+
return {ok:true,stdout:stdout||'',stderr:stderr||''};
|
|
54
|
+
}catch(e){
|
|
55
|
+
return {ok:false,stdout:(e.stdout||'')+'',stderr:(e.stderr||e.message)+''};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function safeMkdirSyncWithFallback(p){
|
|
60
|
+
try{ return fs.mkdirSync(p,{recursive:true,mode:0o750})||p; }
|
|
61
|
+
catch(e){
|
|
62
|
+
const f=path.join('/tmp','fail2scan');
|
|
63
|
+
try{ return fs.mkdirSync(f,{recursive:true,mode:0o750})||f; }
|
|
64
|
+
catch(ee){ throw e; }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// -------------------- prerequisites --------------------
|
|
69
|
+
(async()=>{
|
|
70
|
+
for(const t of ['nmap','dig','whois','which']){
|
|
71
|
+
if(t==='which') continue;
|
|
72
|
+
if(!(await which(t))){
|
|
73
|
+
console.error(`Missing required binary: ${t}`);
|
|
74
|
+
process.exit(2);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})().catch(e=>{
|
|
78
|
+
console.error('Prereq check failed',e);
|
|
79
|
+
process.exit(2);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// -------------------- state --------------------
|
|
83
|
+
function loadState(){
|
|
84
|
+
try{
|
|
85
|
+
if(fs.existsSync(STATE_FILE)){
|
|
86
|
+
const j=JSON.parse(fs.readFileSync(STATE_FILE,'utf8'));
|
|
87
|
+
return {
|
|
88
|
+
seen:new Set(Array.isArray(j.seen)?j.seen:[]),
|
|
89
|
+
retryAfter:typeof j.retryAfter==='object'?j.retryAfter:{}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}catch{}
|
|
93
|
+
return {seen:new Set(),retryAfter:{}};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function saveState(s){
|
|
97
|
+
try{
|
|
98
|
+
fs.mkdirSync(path.dirname(STATE_FILE),{recursive:true,mode:0o700});
|
|
99
|
+
fs.writeFileSync(STATE_FILE,JSON.stringify({
|
|
100
|
+
seen:Array.from(s.seen||[]),
|
|
101
|
+
retryAfter:s.retryAfter||{}
|
|
102
|
+
},null,2));
|
|
103
|
+
}catch{}
|
|
104
|
+
}
|
|
54
105
|
|
|
55
|
-
function loadState(){try{if(fs.existsSync(STATE_FILE)){const j=JSON.parse(fs.readFileSync(STATE_FILE,'utf8'));return{seen:new Set(Array.isArray(j.seen)?j.seen:[]),retryAfter:typeof j.retryAfter==='object'?j.retryAfter:{}};}}catch{}return{seen:new Set(),retryAfter:{}};}
|
|
56
|
-
function saveState(s){try{fs.mkdirSync(path.dirname(STATE_FILE),{recursive:true,mode:0o700});fs.writeFileSync(STATE_FILE,JSON.stringify({seen:Array.from(s.seen||[]),retryAfter:s.retryAfter||{}},null,2));}catch{}}
|
|
57
106
|
const STATE=loadState();
|
|
58
107
|
|
|
59
|
-
|
|
108
|
+
// -------------------- IP extraction --------------------
|
|
109
|
+
function extractIpFromLine(line){
|
|
110
|
+
const v4=line.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/);
|
|
111
|
+
if(v4&&v4[0]) return v4[0];
|
|
112
|
+
const v6=line.match(/\b([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}\b/);
|
|
113
|
+
if(v6&&v6[0]) return v6[0];
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// -------------------- GEOLOCATION (Native HTTPS) --------------------
|
|
118
|
+
function fetchGeo(ip){
|
|
119
|
+
return new Promise((resolve,reject)=>{
|
|
120
|
+
const apiKey = process.env.IPGEO_API_KEY;
|
|
121
|
+
if(!apiKey) return resolve({ error:"Missing IPGEO_API_KEY" });
|
|
60
122
|
|
|
123
|
+
const fields = "geo,time_zone,currency,asn,security";
|
|
124
|
+
const url = `https://api.ipgeolocation.io/ipgeo?apiKey=${apiKey}&ip=${ip}&fields=${fields}`;
|
|
125
|
+
|
|
126
|
+
https.get(url,res=>{
|
|
127
|
+
let data='';
|
|
128
|
+
res.on('data',chunk=>data+=chunk);
|
|
129
|
+
res.on('end',()=>{
|
|
130
|
+
try{ resolve(JSON.parse(data)); }
|
|
131
|
+
catch(e){ resolve({error:"Invalid JSON",raw:data}); }
|
|
132
|
+
});
|
|
133
|
+
}).on('error',reject);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// -------------------- nmap helpers --------------------
|
|
61
138
|
function spawnOneNmap(args, outFile) {
|
|
62
139
|
return new Promise((resolve, reject) => {
|
|
63
140
|
const proc = spawn('nmap', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
@@ -84,8 +161,8 @@ async function spawnNmapParallel(ip, outDir, requestedArgs, parts) {
|
|
|
84
161
|
|
|
85
162
|
const cpuCount = os.cpus() ? os.cpus().length : 1;
|
|
86
163
|
const numParts = Math.max(1, parts || CORE_OVERRIDE || cpuCount);
|
|
87
|
-
|
|
88
164
|
const portsPer = Math.floor(65535 / numParts);
|
|
165
|
+
|
|
89
166
|
const jobs = [];
|
|
90
167
|
for (let i = 0; i < numParts; i++) {
|
|
91
168
|
const start = 1 + i * portsPer;
|
|
@@ -97,14 +174,13 @@ async function spawnNmapParallel(ip, outDir, requestedArgs, parts) {
|
|
|
97
174
|
const args = requestedArgs.map(a => a === '-p-' ? portArg : a).concat([ip]);
|
|
98
175
|
jobs.push(spawnOneNmap(args, outNmap));
|
|
99
176
|
}
|
|
100
|
-
|
|
101
177
|
await Promise.all(jobs);
|
|
102
178
|
|
|
103
179
|
const partsContent = [];
|
|
104
180
|
for (let i = 0; i < numParts; i++) {
|
|
105
181
|
const fn = path.join(outDir, `part-${i}`, 'nmap.txt');
|
|
106
182
|
try {
|
|
107
|
-
if (fs.existsSync(fn)) partsContent.push(fs.readFileSync(fn,'utf8'));
|
|
183
|
+
if (fs.existsSync(fn)) partsContent.push(fs.readFileSync(fn, 'utf8'));
|
|
108
184
|
} catch (e) {}
|
|
109
185
|
}
|
|
110
186
|
try { fs.writeFileSync(path.join(outDir, 'nmap.txt'), partsContent.join('\n\n--- PART ---\n\n')); } catch (e) {}
|
|
@@ -118,17 +194,23 @@ async function runNmap(ip, outDir, requestedArgs) {
|
|
|
118
194
|
await spawnNmapParallel(ip, outDir, args, parts);
|
|
119
195
|
}
|
|
120
196
|
|
|
197
|
+
// -------------------- scan --------------------
|
|
121
198
|
async function performScan(ip){
|
|
122
|
-
const now=new Date(),
|
|
199
|
+
const now=new Date(),
|
|
200
|
+
dateDir=now.toISOString().slice(0,10),
|
|
201
|
+
safeIp=sanitizeFilename(ip);
|
|
202
|
+
|
|
123
203
|
let outDir=path.join(OUT_ROOT,dateDir,`${safeIp}_${now.toISOString().replace(/[:.]/g,'-')}`);
|
|
124
204
|
outDir=path.join(safeMkdirSyncWithFallback(path.dirname(outDir)),path.basename(outDir));
|
|
125
205
|
try { fs.mkdirSync(outDir,{recursive:true,mode:0o750}); } catch (e) {}
|
|
206
|
+
|
|
126
207
|
const summary={ip,ts:now.toISOString(),cmds:{}};
|
|
127
208
|
|
|
128
209
|
const requested=NMAP_ARGS_STR.trim().split(/\s+/).filter(Boolean);
|
|
129
210
|
|
|
211
|
+
log('Running nmap on',ip,'args:',requested.join(' '));
|
|
212
|
+
|
|
130
213
|
try{
|
|
131
|
-
log('Running nmap on',ip,'args:',requested.join(' '));
|
|
132
214
|
await runNmap(ip, outDir, requested);
|
|
133
215
|
summary.cmds.nmap = { ok: true, args: requested.join(' '), path: 'nmap.txt' };
|
|
134
216
|
} catch(e){
|
|
@@ -136,66 +218,208 @@ async function performScan(ip){
|
|
|
136
218
|
summary.cmds.nmap = { ok: false, err: e && e.message ? e.message : String(e) };
|
|
137
219
|
}
|
|
138
220
|
|
|
139
|
-
try{
|
|
140
|
-
|
|
221
|
+
try{
|
|
222
|
+
const dig = await runCmdCapture('dig',['-x',ip,'+short']);
|
|
223
|
+
fs.writeFileSync(path.join(outDir,'dig.txt'),
|
|
224
|
+
(dig.stdout||'') + (dig.stderr?'\n\nSTDERR:\n'+dig.stderr:'')
|
|
225
|
+
);
|
|
226
|
+
summary.cmds.dig = { ok: dig.ok, path: 'dig.txt' };
|
|
227
|
+
} catch(e){
|
|
228
|
+
summary.cmds.dig = { ok:false, err: e.message||String(e) };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try{
|
|
232
|
+
const who = await runCmdCapture('whois',[ip]);
|
|
233
|
+
fs.writeFileSync(path.join(outDir,'whois.txt'),
|
|
234
|
+
(who.stdout||'') + (who.stderr?'\n\nSTDERR:\n'+who.stderr:'')
|
|
235
|
+
);
|
|
236
|
+
summary.cmds.whois = { ok: who.ok, path: 'whois.txt' };
|
|
237
|
+
} catch(e){
|
|
238
|
+
summary.cmds.whois = { ok:false, err:e.message||String(e) };
|
|
239
|
+
}
|
|
141
240
|
|
|
142
|
-
//
|
|
241
|
+
// ---- GEOLOCATION ----
|
|
143
242
|
try {
|
|
144
|
-
const geo = await
|
|
145
|
-
|
|
146
|
-
summary.cmds.geo = { ok:
|
|
147
|
-
} catch(e){
|
|
243
|
+
const geo = await fetchGeo(ip);
|
|
244
|
+
fs.writeFileSync(path.join(outDir,'geo.json'), JSON.stringify(geo,null,2));
|
|
245
|
+
summary.cmds.geo = { ok:true, path:"geo.json" };
|
|
246
|
+
} catch(e){
|
|
247
|
+
summary.cmds.geo = { ok:false, err:e.message||String(e) };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// open ports
|
|
251
|
+
try{
|
|
252
|
+
const nmapTxt = fs.readFileSync(path.join(outDir,'nmap.txt'),'utf8');
|
|
253
|
+
summary.open_ports = nmapTxt
|
|
254
|
+
.split(/\r?\n/)
|
|
255
|
+
.filter(l=>/^\d+\/tcp\s+open/.test(l))
|
|
256
|
+
.map(l=>l.trim());
|
|
257
|
+
} catch(e){
|
|
258
|
+
summary.open_ports = [];
|
|
259
|
+
}
|
|
148
260
|
|
|
149
|
-
try{
|
|
261
|
+
try{ fs.writeFileSync(path.join(outDir,'summary.json'),JSON.stringify(summary,null,2)); }catch{}
|
|
262
|
+
try{ fs.chmodSync(outDir,0o750); }catch{}
|
|
150
263
|
|
|
151
|
-
try{ fs.writeFileSync(path.join(outDir,'summary.json'),JSON.stringify(summary,null,2)); } catch (e) {}
|
|
152
|
-
try{ fs.chmodSync(outDir,0o750); } catch (e) {}
|
|
153
264
|
log('Scan written for',ip,'->',outDir);
|
|
154
265
|
}
|
|
155
266
|
|
|
267
|
+
// -------------------- queue optimized --------------------
|
|
156
268
|
class ScanQueue{
|
|
157
|
-
constructor(concurrency=1){
|
|
269
|
+
constructor(concurrency=1){
|
|
270
|
+
this.concurrency=concurrency;
|
|
271
|
+
this.running=0;
|
|
272
|
+
this.q=[];
|
|
273
|
+
this.set=STATE.seen;
|
|
274
|
+
this.tmpCache=new Set();
|
|
275
|
+
}
|
|
158
276
|
push(ip){
|
|
159
|
-
const now=Math.floor(Date.now()/1000),
|
|
160
|
-
|
|
161
|
-
if(this.set.has(ip)&&next
|
|
162
|
-
|
|
163
|
-
|
|
277
|
+
const now=Math.floor(Date.now()/1000),
|
|
278
|
+
next=STATE.retryAfter[ip]||0;
|
|
279
|
+
if((this.set.has(ip)&&next>now)||this.tmpCache.has(ip)){
|
|
280
|
+
log('IP already queued or running (TTL/cache):',ip);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if(this.set.has(ip)&&next<=now){
|
|
284
|
+
log('Re-queueing after TTL:',ip);
|
|
285
|
+
this.set.delete(ip);
|
|
286
|
+
}
|
|
287
|
+
this.set.add(ip);
|
|
288
|
+
STATE.seen=this.set;
|
|
289
|
+
saveState(STATE);
|
|
290
|
+
this.q.push(ip);
|
|
291
|
+
this.tmpCache.add(ip);
|
|
292
|
+
this._next();
|
|
164
293
|
}
|
|
165
294
|
_next(){
|
|
166
295
|
if(this.running>=this.concurrency)return;
|
|
167
|
-
const ip=this.q.shift();if(!ip)return;
|
|
296
|
+
const ip=this.q.shift(); if(!ip) return;
|
|
168
297
|
this.running++;
|
|
169
298
|
(async()=>{
|
|
170
|
-
try{
|
|
171
|
-
|
|
172
|
-
|
|
299
|
+
try{
|
|
300
|
+
log('Scanning',ip);
|
|
301
|
+
await performScan(ip);
|
|
302
|
+
log('Done',ip);
|
|
303
|
+
}catch(e){
|
|
304
|
+
log('Error scanning',ip,e.message||e);
|
|
305
|
+
}finally{
|
|
173
306
|
STATE.retryAfter[ip]=Math.floor(Date.now()/1000)+RESCAN_TTL_SEC;
|
|
174
307
|
saveState(STATE);
|
|
175
|
-
this.set.delete(ip);
|
|
176
|
-
this.
|
|
308
|
+
this.set.delete(ip);
|
|
309
|
+
this.tmpCache.delete(ip);
|
|
310
|
+
this.running--;
|
|
311
|
+
setImmediate(()=>this._next());
|
|
177
312
|
}
|
|
178
313
|
})();
|
|
179
314
|
setImmediate(()=>this._next());
|
|
180
315
|
}
|
|
181
316
|
}
|
|
182
317
|
|
|
183
|
-
|
|
318
|
+
// -------------------- main --------------------
|
|
319
|
+
if(SINGLE_IP){
|
|
320
|
+
(async()=>{
|
|
321
|
+
const q=new ScanQueue(CORE_OVERRIDE||USER_CONCURRENCY||1);
|
|
322
|
+
q.push(SINGLE_IP);
|
|
323
|
+
})();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
184
327
|
const concurrency = CORE_OVERRIDE||USER_CONCURRENCY||os.cpus().length||1;
|
|
185
328
|
const q = new ScanQueue(concurrency);
|
|
186
329
|
log(`Fail2Scan started. Watching ${LOG_PATH} -> output ${OUT_ROOT}, concurrency ${concurrency}`);
|
|
187
330
|
|
|
188
331
|
const BAN_RE = /\bBan\b/i;
|
|
332
|
+
|
|
189
333
|
class FileTail{
|
|
190
|
-
constructor(filePath,onLine){
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
334
|
+
constructor(filePath,onLine){
|
|
335
|
+
this.filePath=filePath;
|
|
336
|
+
this.onLine=onLine;
|
|
337
|
+
this.pos=0;
|
|
338
|
+
this.inode=null;
|
|
339
|
+
this.buf='';
|
|
340
|
+
this.watch=null;
|
|
341
|
+
this.start();
|
|
342
|
+
}
|
|
343
|
+
start(){
|
|
344
|
+
try{
|
|
345
|
+
const st=fs.statSync(this.filePath);
|
|
346
|
+
this.inode=st.ino;
|
|
347
|
+
this.pos=st.size;
|
|
348
|
+
}catch{
|
|
349
|
+
this.inode=null;
|
|
350
|
+
this.pos=0;
|
|
351
|
+
}
|
|
352
|
+
this._watch();
|
|
353
|
+
this._readNew().catch(()=>{});
|
|
354
|
+
}
|
|
355
|
+
_watch(){
|
|
356
|
+
try{
|
|
357
|
+
this.watch=fs.watch(this.filePath,{persistent:true},async()=>{
|
|
358
|
+
try{
|
|
359
|
+
const st=fs.statSync(this.filePath);
|
|
360
|
+
if(!st){ this.inode=null; this.pos=0; return; }
|
|
361
|
+
if(this.inode!==null && st.ino!==this.inode){
|
|
362
|
+
this.inode=st.ino; this.pos=0;
|
|
363
|
+
}else if(this.inode===null){
|
|
364
|
+
this.inode=st.ino; this.pos=0;
|
|
365
|
+
}
|
|
366
|
+
await this._readNew();
|
|
367
|
+
}catch{}
|
|
368
|
+
});
|
|
369
|
+
}catch(e){
|
|
370
|
+
log('fs.watch failed:',e.message);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async _readNew(){
|
|
374
|
+
try{
|
|
375
|
+
const st=fs.statSync(this.filePath);
|
|
376
|
+
if(st.size<this.pos) this.pos=0;
|
|
377
|
+
if(st.size===this.pos) return;
|
|
378
|
+
|
|
379
|
+
const stream=fs.createReadStream(this.filePath,{
|
|
380
|
+
start:this.pos,
|
|
381
|
+
end:st.size-1,
|
|
382
|
+
encoding:'utf8'
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
for await(const chunk of stream){
|
|
386
|
+
this.buf+=chunk;
|
|
387
|
+
let idx;
|
|
388
|
+
while((idx=this.buf.indexOf('\n'))>=0){
|
|
389
|
+
const line=this.buf.slice(0,idx);
|
|
390
|
+
this.buf=this.buf.slice(idx+1);
|
|
391
|
+
if(line.trim()) this.onLine(line);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
this.pos=st.size;
|
|
395
|
+
}catch{}
|
|
396
|
+
}
|
|
397
|
+
close(){
|
|
398
|
+
try{ this.watch?.close(); }catch{}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const tail=new FileTail(LOG_PATH,line=>{
|
|
403
|
+
try{
|
|
404
|
+
if(!BAN_RE.test(line)) return;
|
|
405
|
+
const ip=extractIpFromLine(line);
|
|
406
|
+
if(!ip) return;
|
|
407
|
+
q.push(ip);
|
|
408
|
+
}catch(e){
|
|
409
|
+
log('onLine handler error',e.message||e);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
function shutdown(){
|
|
414
|
+
log('Shutting down Fail2Scan...');
|
|
415
|
+
tail.close();
|
|
416
|
+
const start=Date.now();
|
|
417
|
+
const wait=()=>{
|
|
418
|
+
if(q.running===0||Date.now()-start>10000) process.exit(0);
|
|
419
|
+
setTimeout(wait,500);
|
|
420
|
+
};
|
|
421
|
+
wait();
|
|
195
422
|
}
|
|
196
|
-
const tail=new FileTail(LOG_PATH,line=>{try{if(!BAN_RE.test(line))return;const ip=extractIpFromLine(line);if(!ip)return;q.push(ip);}catch(e){log('onLine handler error',e.message||e);}});
|
|
197
423
|
|
|
198
|
-
function shutdown(){log('Shutting down Fail2Scan...');tail.close();const start=Date.now();const wait=()=>{if(q.running===0||Date.now()-start>10000)process.exit(0);setTimeout(wait,500);};wait();}
|
|
199
424
|
process.on('SIGINT',shutdown);
|
|
200
425
|
process.on('SIGTERM',shutdown);
|
|
201
|
-
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roflsec/fail2scan",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "Fail2Scan daemon - watches fail2ban logs and scans banned IPs using nmap, dig and whois.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fail2scan-daemon": "./bin/daemon.js"
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"dotenv": "^17.2.3"
|
|
25
|
-
"ip-geolocation-api-javascript-sdk": "^2.0.1"
|
|
24
|
+
"dotenv": "^17.2.3"
|
|
26
25
|
}
|
|
27
26
|
}
|