@roflsec/fail2scan 0.0.13 → 0.0.17

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.
Files changed (2) hide show
  1. package/bin/daemon.js +13 -43
  2. package/package.json +1 -1
package/bin/daemon.js CHANGED
@@ -6,11 +6,9 @@ const { execFile, spawn } = require('child_process');
6
6
  const { promisify } = require('util');
7
7
  const execFileP = promisify(execFile);
8
8
 
9
- // === Géolocalisation ===
10
9
  let getGeo = async (ip) => null;
11
10
  if (process.env.IPGEO_API_KEY) {
12
- const IPGeolocationAPI = require("ip-geolocation-api-javascript-sdk");
13
- const GeolocationParams = require("ip-geolocation-api-javascript-sdk/GeolocationParams.js");
11
+ const { default: IPGeolocationAPI, GeolocationParams } = require("ip-geolocation-api-javascript-sdk");
14
12
  const ipGeo = new IPGeolocationAPI(process.env.IPGEO_API_KEY, true);
15
13
  getGeo = (ip) => {
16
14
  return new Promise((resolve) => {
@@ -22,7 +20,6 @@ if (process.env.IPGEO_API_KEY) {
22
20
  };
23
21
  }
24
22
 
25
- // === Arguments CLI ===
26
23
  const argv = process.argv.slice(2);
27
24
  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; };
28
25
  if(argv.includes('--help')||argv.includes('-h')){console.log(`Fail2Scan optimized daemon
@@ -46,27 +43,21 @@ const RESCAN_TTL_SEC = 60*60;
46
43
  const STATE_FILE = path.join(os.homedir(),'.fail2scan_state.json');
47
44
  const LOG_FILE = path.join(os.homedir(),'.fail2scan.log');
48
45
 
49
- // === Logging ===
50
46
  const log=(...a)=>{if(!QUIET)console.log(new Date().toISOString(),...a); try{fs.appendFileSync(LOG_FILE,new Date().toISOString()+' '+a.join(' ')+'\n');}catch{}};
51
47
 
52
- // === Utilitaires ===
53
48
  const sanitizeFilename=s=>String(s).replace(/[:\/\\<>?"|* ]+/g,'_');
54
49
  async function which(bin){try{await execFileP('which',[bin]);return true;}catch{return false;}}
55
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)+''};}}
56
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;}}}
57
52
 
58
- // === Vérification prérequis ===
59
53
  (async()=>{for(const t of ['nmap','dig','whois','which']){if(t==='which')continue;if(!(await which(t))){console.error(`Missing required binary: ${t}`);process.exit(2);}}})().catch(e=>{console.error('Prereq check failed',e);process.exit(2);});
60
54
 
61
- // === Etat ===
62
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:{}};}
63
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{}}
64
57
  const STATE=loadState();
65
58
 
66
- // === Extraction IP ===
67
59
  function extractIpFromLine(line){const v4=line.match(/\b(?:\d{1,3}\.){3}\d{1,3}\b/);if(v4&&v4[0])return v4[0];const v6=line.match(/\b([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}\b/);if(v6&&v6[0])return v6[0];return null;}
68
60
 
69
- // === Nmap spawn ===
70
61
  function spawnOneNmap(args, outFile) {
71
62
  return new Promise((resolve, reject) => {
72
63
  const proc = spawn('nmap', args, { stdio: ['ignore', 'pipe', 'pipe'] });
@@ -127,7 +118,6 @@ async function runNmap(ip, outDir, requestedArgs) {
127
118
  await spawnNmapParallel(ip, outDir, args, parts);
128
119
  }
129
120
 
130
- // === Scan d’une IP ===
131
121
  async function performScan(ip){
132
122
  const now=new Date(),dateDir=now.toISOString().slice(0,10),safeIp=sanitizeFilename(ip);
133
123
  let outDir=path.join(OUT_ROOT,dateDir,`${safeIp}_${now.toISOString().replace(/[:.]/g,'-')}`);
@@ -146,41 +136,23 @@ async function performScan(ip){
146
136
  summary.cmds.nmap = { ok: false, err: e && e.message ? e.message : String(e) };
147
137
  }
148
138
 
149
- try{
150
- const dig = await runCmdCapture('dig',['-x',ip,'+short']);
151
- fs.writeFileSync(path.join(outDir,'dig.txt'),(dig.stdout||'') + (dig.stderr?'\n\nSTDERR:\n'+dig.stderr:''));
152
- summary.cmds.dig = { ok: dig.ok, path: 'dig.txt' };
153
- } catch(e){ summary.cmds.dig = { ok:false, err: e && e.message ? e.message : String(e) }; }
154
-
155
- try{
156
- const who = await runCmdCapture('whois',[ip]);
157
- fs.writeFileSync(path.join(outDir,'whois.txt'),(who.stdout||'') + (who.stderr?'\n\nSTDERR:\n'+who.stderr:''));
158
- summary.cmds.whois = { ok: who.ok, path: 'whois.txt' };
159
- } catch(e){ summary.cmds.whois = { ok:false, err: e && e.message ? e.message : String(e) }; }
160
-
161
- try{
162
- const nmapTxt = fs.readFileSync(path.join(outDir,'nmap.txt'),'utf8');
163
- summary.open_ports = nmapTxt.split(/\r?\n/).filter(l=>/^\d+\/tcp\s+open/.test(l)).map(l=>l.trim());
164
- } catch(e){ summary.open_ports = []; }
165
-
166
- // === Géolocalisation ===
167
- if(getGeo){
168
- try{
169
- const geo = await getGeo(ip);
170
- fs.writeFileSync(path.join(outDir,'geo.json'), JSON.stringify(geo,null,2));
171
- summary.geo = geo;
172
- }catch(e){
173
- log('Geo lookup failed for',ip,e.message||e);
174
- summary.geo = null;
175
- }
176
- }
139
+ try{ const dig = await runCmdCapture('dig',['-x',ip,'+short']); fs.writeFileSync(path.join(outDir,'dig.txt'),(dig.stdout||'') + (dig.stderr?'\n\nSTDERR:\n'+dig.stderr:'')); summary.cmds.dig = { ok: dig.ok, path: 'dig.txt' }; } catch(e){ summary.cmds.dig = { ok:false, err: e && e.message ? e.message : String(e) }; }
140
+ try{ const who = await runCmdCapture('whois',[ip]); fs.writeFileSync(path.join(outDir,'whois.txt'),(who.stdout||'') + (who.stderr?'\n\nSTDERR:\n'+who.stderr:'')); summary.cmds.whois = { ok: who.ok, path: 'whois.txt' }; } catch(e){ summary.cmds.whois = { ok:false, err: e && e.message ? e.message : String(e) }; }
141
+
142
+ // Geolocation
143
+ try {
144
+ const geo = await getGeo(ip);
145
+ if (geo) fs.writeFileSync(path.join(outDir,'geo.json'),JSON.stringify(geo,null,2));
146
+ summary.cmds.geo = { ok: !!geo, path: 'geo.json' };
147
+ } catch(e){ summary.cmds.geo = { ok:false, err: e && e.message ? e.message : String(e) }; }
148
+
149
+ try{ const nmapTxt = fs.readFileSync(path.join(outDir,'nmap.txt'),'utf8'); summary.open_ports = nmapTxt.split(/\r?\n/).filter(l=>/^\d+\/tcp\s+open/.test(l)).map(l=>l.trim()); } catch(e){ summary.open_ports = []; }
177
150
 
178
151
  try{ fs.writeFileSync(path.join(outDir,'summary.json'),JSON.stringify(summary,null,2)); } catch (e) {}
179
152
  try{ fs.chmodSync(outDir,0o750); } catch (e) {}
180
153
  log('Scan written for',ip,'->',outDir);
181
154
  }
182
155
 
183
- // === Queue ===
184
156
  class ScanQueue{
185
157
  constructor(concurrency=1){this.concurrency=concurrency;this.running=0;this.q=[];this.set=STATE.seen;this.tmpCache=new Set();}
186
158
  push(ip){
@@ -208,10 +180,7 @@ class ScanQueue{
208
180
  }
209
181
  }
210
182
 
211
- // === Single IP mode ===
212
183
  if(SINGLE_IP){(async()=>{const q=new ScanQueue(CORE_OVERRIDE||USER_CONCURRENCY||1);q.push(SINGLE_IP);})();return;}
213
-
214
- // === Lancement daemon ===
215
184
  const concurrency = CORE_OVERRIDE||USER_CONCURRENCY||os.cpus().length||1;
216
185
  const q = new ScanQueue(concurrency);
217
186
  log(`Fail2Scan started. Watching ${LOG_PATH} -> output ${OUT_ROOT}, concurrency ${concurrency}`);
@@ -229,3 +198,4 @@ const tail=new FileTail(LOG_PATH,line=>{try{if(!BAN_RE.test(line))return;const i
229
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();}
230
199
  process.on('SIGINT',shutdown);
231
200
  process.on('SIGTERM',shutdown);
201
+ //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roflsec/fail2scan",
3
- "version": "0.0.13",
3
+ "version": "0.0.17",
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"