From 94c04ba53aafe8d85d95dfcdfd503fe4aa7c8ece Mon Sep 17 00:00:00 2001 From: HF Date: Mon, 19 Sep 2022 02:12:21 +0200 Subject: [PATCH] use own whois --- package-lock.json | 21 +----- package.json | 1 - src/core/config.js | 2 + src/utils/whois.js | 179 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 178 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8e73551..e6cf2b43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,6 @@ "three-trackballcontrols": "^0.9.0", "ttag": "^1.7.24", "url-search-params-polyfill": "^8.1.1", - "whoiser": "^1.13.1", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.5.5", "ws": "^8.4.0" @@ -8641,6 +8640,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, "engines": { "node": ">=6" } @@ -10874,14 +10874,6 @@ "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, - "node_modules/whoiser": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/whoiser/-/whoiser-1.13.1.tgz", - "integrity": "sha512-4MF0LoIsSdM7R9rs9A+PxbCXMDRmRdF7eZb8IC8pGethCrSizqMLcbJCXZO5iZGqOKovQlRhpSFGGUlwUPzoQA==", - "dependencies": { - "punycode": "^2.1.1" - } - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -17547,7 +17539,8 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "qs": { "version": "6.10.3", @@ -19175,14 +19168,6 @@ "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, - "whoiser": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/whoiser/-/whoiser-1.13.1.tgz", - "integrity": "sha512-4MF0LoIsSdM7R9rs9A+PxbCXMDRmRdF7eZb8IC8pGethCrSizqMLcbJCXZO5iZGqOKovQlRhpSFGGUlwUPzoQA==", - "requires": { - "punycode": "^2.1.1" - } - }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index f639be6d..2618cf2d 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "three-trackballcontrols": "^0.9.0", "ttag": "^1.7.24", "url-search-params-polyfill": "^8.1.1", - "whoiser": "^1.13.1", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.5.5", "ws": "^8.4.0" diff --git a/src/core/config.js b/src/core/config.js index ca172877..01590b27 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -25,6 +25,8 @@ export const USE_XREALIP = !!process.env.USE_XREALIP; export const BACKUP_URL = process.env.BACKUP_URL || null; export const BACKUP_DIR = process.env.BACKUP_DIR || null; +export const OUTGOING_ADDRESS = process.env.OUTGOING_ADDRESS || null; + // Proxycheck export const USE_PROXYCHECK = parseInt(process.env.USE_PROXYCHECK, 10) || false; export const { PROXYCHECK_KEY } = process.env; diff --git a/src/utils/whois.js b/src/utils/whois.js index 919d2e7f..15e84dab 100644 --- a/src/utils/whois.js +++ b/src/utils/whois.js @@ -2,10 +2,178 @@ * get information from ip */ -import whoiser from 'whoiser'; +import net from 'net'; import { isIPv6, ip4InRangeToCIDR } from './ip'; +import { OUTGOING_ADDRESS } from '../core/config'; +const WHOIS_PORT = 43; +const QUERY_SUFFIX = '\r\n'; +const WHOIS_TIMEOUT = 30000; + +function splitStringBy(string, by) { + return [string.slice(0, by), string.slice(by + 1)]; +} + +function parseSimpleWhois(whois) { + const data = {}; + const text = []; + + const renameLabels = { + NetRange: 'range', + inetnum: 'range', + CIDR: 'route', + origin: 'asn', + OriginAS: 'asn', + }; + const lineToGroup = { + contact: 'contact', + OrgName: 'organisation', + organisation: 'organisation', + OrgAbuseHandle: 'contactAbuse', + irt: 'contactAbuse', + RAbuseHandle: 'contactAbuse', + OrgTechHandle: 'contactTechnical', + RTechHandle: 'contactTechnical', + OrgNOCHandle: 'contactNoc', + RNOCHandle: 'contactNoc', + }; + + if (whois.includes('returned 0 objects') || whois.includes('No match found')) { + return data; + } + + let resultNum = 0; + const groups = [{}]; + let lastLabel; + + whois.split('\n').forEach((line) => { + // catch comment lines + if (line.startsWith('%') || line.startsWith('#')) { + // detect if an ASN or IP has multiple WHOIS results + if (line.includes('# start')) { + // nothing + } else if (line.includes('# end')) { + resultNum++; + } else { + text.push(line); + } + } else if (resultNum === 0) { + // for the moment, parse only first WHOIS result + + if (line) { + if (line.includes(':')) { + const [label, value] = splitStringBy(line, line.indexOf(':')).map((info) => info.trim()); + lastLabel = label; + + // 1) Filter out unnecessary info, 2) then detect if the label is already added to group + if (value.includes('---')) { + // do nothing with useless data + } else if (groups[groups.length - 1][label]) { + groups[groups.length - 1][label] += `\n${value}`; + } else { + groups[groups.length - 1][label] = value; + } + } else { + groups[groups.length - 1][lastLabel] += `\n${line.trim()}`; + } + } else if (Object.keys(groups[groups.length - 1]).length) { + // if empty line, means another info group starts + groups.push({}); + } + } + }); + + groups + .filter((group) => Object.keys(group).length) + .forEach((group) => { + const groupLabels = Object.keys(group); + let isGroup = false; + + // check if a label is marked as group + groupLabels.forEach((groupLabel) => { + if (!isGroup && Object.keys(lineToGroup).includes(groupLabel)) { + isGroup = lineToGroup[groupLabel]; + } + }); + + // check if a info group is a Contact in APNIC result + // @Link https://www.apnic.net/manage-ip/using-whois/guide/role/ + if (!isGroup && groupLabels.includes('role')) { + isGroup = `Contact ${group.role.split(' ')[1]}`; + } else if (!isGroup && groupLabels.includes('person')) { + isGroup = `Contact ${group['nic-hdl']}`; + } + + if (isGroup === 'contact') { + data.contacts = data.contacts || {}; + data.contacts[group.contact] = group; + } else if (isGroup) { + data[isGroup] = group; + } else { + groupLabels.forEach((key) => { + const label = renameLabels[key] || key; + data[label] = group[key]; + }); + } + }); + + // Append the WHOIS comments + data.text = text; + + return data; +} + +function whoisQuery( + host = null, + query = '', +) { + console.log('whois with query', query, 'for host', host); + return new Promise((resolve, reject) => { + let data = ''; + const socket = net.createConnection({ + host, + port: WHOIS_PORT, + localAddress: OUTGOING_ADDRESS, + timeout: WHOIS_TIMEOUT, + }, () => socket.write(query + QUERY_SUFFIX)); + socket.on('data', (chunk) => { data += chunk; }); + socket.on('close', () => resolve(data)); + socket.on('timeout', () => socket.destroy(new Error('Timeout'))); + socket.on('error', reject); + }); +} + +async function whoisIp(query) { + query = String(query); + + // find WHOIS server for IP + let whoisResult = await whoisQuery('whois.iana.org', query); + whoisResult = parseSimpleWhois(whoisResult); + const host = whoisResult.whois; + + if (!host) { + throw new Error(`No WHOIS server for "${query}"`); + } + + // hardcoded custom queries.. + console.log('HOST', host); + if (host === 'whois.arin.net') { + query = `+ n ${query}`; + } else if (host === 'whois.ripe.net') { + /* + * flag to not return personal informations, otherwise + * RIPE is gonne rate limit and ban + */ + query = `-r ${query}`; + } + + const rawResult = await whoisQuery(host, query); + console.log(rawResult); + const data = parseSimpleWhois(rawResult); + + return data; +} /* * get CIDR of ip from whois return @@ -72,8 +240,8 @@ function parseWhois(ip, whoisData) { }; } -async function whois(ip) { - const whoisData = await whoiser.ip(ip); +export default async function whoiser(ip) { + const whoisData = await whoisIp(ip); if (whoisData.ReferralServer) { let referral = whoisData.ReferralServer; const prot = referral.indexOf('://'); @@ -85,7 +253,7 @@ async function whois(ip) { * if referral whois server produces any error * fallback to initial one */ - const refWhoisData = await whoiser.ip(ip, { + const refWhoisData = await whoisIp(ip, { host: referral, }); const refParsedData = parseWhois(ip, refWhoisData); @@ -96,7 +264,6 @@ async function whois(ip) { // nothing } } + console.log(whoisData); return parseWhois(ip, whoisData); } - -export default whois;