@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.
- package/bin/daemon.js +13 -43
- 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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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