@roflsec/fail2scan 0.0.4 → 0.0.5

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 +93 -10
  2. package/package.json +1 -1
package/bin/daemon.js CHANGED
@@ -48,23 +48,106 @@ const STATE=loadState();
48
48
  // -------------------- IP extraction --------------------
49
49
  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;}
50
50
 
51
+ // -------------------- nmap helpers (parallel support) --------------------
52
+ function spawnOneNmap(args, outFile) {
53
+ return new Promise((resolve, reject) => {
54
+ const proc = spawn('nmap', args, { stdio: ['ignore', 'pipe', 'pipe'] });
55
+ const outStream = fs.createWriteStream(outFile, { flags: 'w' });
56
+ proc.stdout.pipe(outStream);
57
+ let stderr = '';
58
+ proc.stderr.on('data', c => { stderr += c.toString(); });
59
+ proc.on('close', code => {
60
+ if (stderr) {
61
+ try { fs.appendFileSync(outFile, '\n\nSTDERR:\n' + stderr); } catch (e) {}
62
+ }
63
+ resolve({ code, ok: code === 0 });
64
+ });
65
+ proc.on('error', err => reject(err));
66
+ });
67
+ }
68
+
69
+ async function spawnNmapParallel(ip, outDir, requestedArgs, parts) {
70
+ // requestedArgs: array of args
71
+ // if -p- not present, run single nmap
72
+ if (!requestedArgs.includes('-p-')) {
73
+ const outNmap = path.join(outDir, 'nmap.txt');
74
+ await spawnOneNmap([...requestedArgs, ip], outNmap);
75
+ return;
76
+ }
77
+
78
+ // decide number of parts
79
+ const cpuCount = os.cpus() ? os.cpus().length : 1;
80
+ const numParts = Math.max(1, parts || CORE_OVERRIDE || cpuCount);
81
+
82
+ const portsPer = Math.floor(65535 / numParts);
83
+ const jobs = [];
84
+ for (let i = 0; i < numParts; i++) {
85
+ const start = 1 + i * portsPer;
86
+ const end = (i === numParts - 1) ? 65535 : ((i + 1) * portsPer);
87
+ const subdir = path.join(outDir, `part-${i}`);
88
+ try { fs.mkdirSync(subdir, { recursive: true, mode: 0o750 }); } catch (e) {}
89
+ const outNmap = path.join(subdir, 'nmap.txt');
90
+ const portArg = `-p${start}-${end}`;
91
+ // replace '-p-' with '-pstart-end' in requestedArgs
92
+ const args = requestedArgs.map(a => a === '-p-' ? portArg : a).concat([ip]);
93
+ jobs.push(spawnOneNmap(args, outNmap));
94
+ }
95
+
96
+ // await all parts
97
+ await Promise.all(jobs);
98
+
99
+ // merge outputs into single nmap.txt (preserve order)
100
+ const partsContent = [];
101
+ for (let i = 0; i < numParts; i++) {
102
+ const fn = path.join(outDir, `part-${i}`, 'nmap.txt');
103
+ try {
104
+ if (fs.existsSync(fn)) partsContent.push(fs.readFileSync(fn, 'utf8'));
105
+ } catch (e) {}
106
+ }
107
+ try { fs.writeFileSync(path.join(outDir, 'nmap.txt'), partsContent.join('\n\n--- PART ---\n\n')); } catch (e) {}
108
+ }
109
+
110
+ // wrapper used by performScan
111
+ async function runNmap(ip, outDir, requestedArgs) {
112
+ // adapt -sS -> -sT if not root
113
+ const isRoot = (typeof process.getuid === 'function' && process.getuid() === 0);
114
+ const args = requestedArgs.map(a => (!isRoot && a === '-sS') ? '-sT' : a);
115
+
116
+ // if user explicitly set a small host-timeout, keep it; otherwise let parallel jobs share same requested args
117
+ // decide parts from CORE_OVERRIDE or cpu
118
+ const cpuCount = os.cpus() ? os.cpus().length : 1;
119
+ const parts = CORE_OVERRIDE || cpuCount;
120
+
121
+ // if args include -p- -> use parallel split, else single
122
+ await spawnNmapParallel(ip, outDir, args, parts);
123
+ }
124
+
51
125
  // -------------------- scan --------------------
52
- function spawnNmap(ip,outDir,args){return new Promise((res,rej)=>{const outNmap=path.join(outDir,'nmap.txt');const proc=spawn('nmap',[...args,ip],{stdio:['ignore','pipe','pipe']});const outStream=fs.createWriteStream(outNmap,{flags:'w'});proc.stdout.pipe(outStream);let err='';proc.stderr.on('data',c=>{err+=c.toString();});proc.on('close',code=>{if(err)fs.appendFileSync(outNmap,'\n\nSTDERR:\n'+err);res({code,ok:code===0});});proc.on('error',e=>rej(e));});}
53
126
  async function performScan(ip){
54
127
  const now=new Date(),dateDir=now.toISOString().slice(0,10),safeIp=sanitizeFilename(ip);
55
128
  let outDir=path.join(OUT_ROOT,dateDir,`${safeIp}_${now.toISOString().replace(/[:.]/g,'-')}`);
56
129
  outDir=path.join(safeMkdirSyncWithFallback(path.dirname(outDir)),path.basename(outDir));
57
- fs.mkdirSync(outDir,{recursive:true,mode:0o750});
130
+ try { fs.mkdirSync(outDir,{recursive:true,mode:0o750}); } catch (e) {}
58
131
  const summary={ip,ts:now.toISOString(),cmds:{}};
132
+
59
133
  const requested=NMAP_ARGS_STR.trim().split(/\s+/).filter(Boolean);
60
- const isRoot=(typeof process.getuid==='function'&&process.getuid()===0);
61
- const nmapArgs=requested.map(a=>(!isRoot&&a==='-sS')?'-sT':a);
62
- try{log('Running nmap on',ip,'args:',nmapArgs.join(' '));await spawnNmap(ip,outDir,nmapArgs);summary.cmds.nmap={ok:true,args:nmapArgs.join(' '),path:'nmap.txt'}}catch(e){log('nmap failed for',ip,e.message);summary.cmds.nmap={ok:false,err:e.message};}
63
- 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.message};}
64
- 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.message};}
65
- 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=[];}
66
- fs.writeFileSync(path.join(outDir,'summary.json'),JSON.stringify(summary,null,2));
67
- try{fs.chmodSync(outDir,0o750);}catch{}
134
+
135
+ try{
136
+ log('Running nmap on',ip,'args:',requested.join(' '));
137
+ await runNmap(ip, outDir, requested);
138
+ summary.cmds.nmap = { ok: true, args: requested.join(' '), path: 'nmap.txt' };
139
+ } catch(e){
140
+ log('nmap failed for',ip, e && e.message ? e.message : e);
141
+ summary.cmds.nmap = { ok: false, err: e && e.message ? e.message : String(e) };
142
+ }
143
+
144
+ 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) }; }
145
+ 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) }; }
146
+
147
+ 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 = []; }
148
+
149
+ try{ fs.writeFileSync(path.join(outDir,'summary.json'),JSON.stringify(summary,null,2)); } catch (e) {}
150
+ try{ fs.chmodSync(outDir,0o750); } catch (e) {}
68
151
  log('Scan written for',ip,'->',outDir);
69
152
  }
70
153
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roflsec/fail2scan",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
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"