@fanboynz/network-scanner 1.0.35

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.
@@ -0,0 +1,1200 @@
1
+ /**
2
+ * Network tools module for whois and dig lookups - COMPLETE FIXED VERSION
3
+ * Provides domain analysis capabilities with proper timeout handling, custom whois servers, and retry logic
4
+ */
5
+
6
+ const { exec } = require('child_process');
7
+ const util = require('util');
8
+ const { formatLogMessage, messageColors } = require('./colorize');
9
+ const execPromise = util.promisify(exec);
10
+
11
+ /**
12
+ * Strips ANSI color codes from a string for clean file logging
13
+ * @param {string} text - Text that may contain ANSI codes
14
+ * @returns {string} Text with ANSI codes removed
15
+ */
16
+ function stripAnsiColors(text) {
17
+ // Remove ANSI escape sequences (color codes)
18
+ return text.replace(/\x1b\[[0-9;]*m/g, '');
19
+ }
20
+
21
+ /**
22
+ * Validates if whois command is available on the system
23
+ * @returns {Object} Object with isAvailable boolean and version/error info
24
+ */
25
+ function validateWhoisAvailability() {
26
+ try {
27
+ const result = require('child_process').execSync('whois --version 2>&1', { encoding: 'utf8' });
28
+ return {
29
+ isAvailable: true,
30
+ version: result.trim()
31
+ };
32
+ } catch (error) {
33
+ // Some systems don't have --version, try just whois
34
+ try {
35
+ require('child_process').execSync('which whois', { encoding: 'utf8' });
36
+ return {
37
+ isAvailable: true,
38
+ version: 'whois (version unknown)'
39
+ };
40
+ } catch (e) {
41
+ return {
42
+ isAvailable: false,
43
+ error: 'whois command not found'
44
+ };
45
+ }
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Validates if dig command is available on the system
51
+ * @returns {Object} Object with isAvailable boolean and version/error info
52
+ */
53
+ function validateDigAvailability() {
54
+ try {
55
+ const result = require('child_process').execSync('dig -v 2>&1', { encoding: 'utf8' });
56
+ return {
57
+ isAvailable: true,
58
+ version: result.split('\n')[0].trim()
59
+ };
60
+ } catch (error) {
61
+ return {
62
+ isAvailable: false,
63
+ error: 'dig command not found'
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Executes a command with proper timeout handling
70
+ * @param {string} command - Command to execute
71
+ * @param {number} timeout - Timeout in milliseconds
72
+ * @returns {Promise<Object>} Promise that resolves with stdout/stderr or rejects on timeout/error
73
+ */
74
+ function execWithTimeout(command, timeout = 10000) {
75
+ return new Promise((resolve, reject) => {
76
+ const child = exec(command, { encoding: 'utf8' }, (error, stdout, stderr) => {
77
+ if (timer) clearTimeout(timer);
78
+
79
+ if (error) {
80
+ reject(error);
81
+ } else {
82
+ resolve({ stdout, stderr });
83
+ }
84
+ });
85
+
86
+ // Set up timeout
87
+ const timer = setTimeout(() => {
88
+ child.kill('SIGTERM');
89
+
90
+ // Force kill after 2 seconds if SIGTERM doesn't work
91
+ setTimeout(() => {
92
+ if (!child.killed) {
93
+ child.kill('SIGKILL');
94
+ }
95
+ }, 2000);
96
+
97
+ reject(new Error(`Command timeout after ${timeout}ms: ${command}`));
98
+ }, timeout);
99
+
100
+ // Handle child process errors
101
+ child.on('error', (err) => {
102
+ if (timer) clearTimeout(timer);
103
+ reject(err);
104
+ });
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Selects a whois server from the configuration
110
+ * @param {string|Array<string>} whoisServer - Single server string or array of servers
111
+ * @param {string} mode - Selection mode: 'random' (default) or 'cycle'
112
+ * @returns {string|null} Selected whois server or null if none specified
113
+ */
114
+ function selectWhoisServer(whoisServer, mode = 'random'){
115
+ if (!whoisServer) {
116
+ return null; // Use default whois behavior
117
+ }
118
+
119
+ if (typeof whoisServer === 'string') {
120
+ return whoisServer;
121
+ }
122
+
123
+ if (Array.isArray(whoisServer) && whoisServer.length > 0) {
124
+ if (mode === 'cycle') {
125
+ // Use global cycling index
126
+ if (typeof global.globalWhoisServerIndex === 'undefined') {
127
+ global.globalWhoisServerIndex = 0;
128
+ }
129
+
130
+ const selectedServer = whoisServer[global.globalWhoisServerIndex % whoisServer.length];
131
+ global.globalWhoisServerIndex = (global.globalWhoisServerIndex + 1) % whoisServer.length;
132
+
133
+ return selectedServer;
134
+ } else {
135
+ // Random selection (default behavior)
136
+ const randomIndex = Math.floor(Math.random() * whoisServer.length);
137
+ return whoisServer[randomIndex];
138
+ }
139
+ }
140
+
141
+ return null;
142
+ }
143
+
144
+ /**
145
+ * Gets common whois servers for debugging/fallback suggestions
146
+ * @returns {Array<string>} List of common whois servers
147
+ */
148
+ function getCommonWhoisServers() {
149
+ return [
150
+ 'whois.iana.org',
151
+ 'whois.internic.net',
152
+ 'whois.verisign-grs.com',
153
+ 'whois.markmonitor.com',
154
+ 'whois.godaddy.com',
155
+ 'whois.namecheap.com',
156
+ 'whois.1and1.com'
157
+ ];
158
+ }
159
+
160
+ /**
161
+ * Suggests alternative whois servers based on domain TLD
162
+ * @param {string} domain - Domain to get suggestions for
163
+ * @param {string} failedServer - Server that failed (to exclude from suggestions)
164
+ * @returns {Array<string>} Suggested whois servers
165
+ */
166
+ function suggestWhoisServers(domain, failedServer = null) {
167
+ const tld = domain.split('.').pop().toLowerCase();
168
+ const suggestions = [];
169
+
170
+ // TLD-specific servers
171
+ const tldServers = {
172
+ 'com': ['whois.verisign-grs.com', 'whois.internic.net'],
173
+ 'net': ['whois.verisign-grs.com', 'whois.internic.net'],
174
+ 'org': ['whois.pir.org'],
175
+ 'info': ['whois.afilias.net'],
176
+ 'biz': ['whois.neulevel.biz'],
177
+ 'uk': ['whois.nominet.uk'],
178
+ 'de': ['whois.denic.de'],
179
+ 'fr': ['whois.afnic.fr'],
180
+ 'it': ['whois.nic.it'],
181
+ 'nl': ['whois.domain-registry.nl']
182
+ };
183
+
184
+ if (tldServers[tld]) {
185
+ suggestions.push(...tldServers[tld]);
186
+ }
187
+
188
+ // Add common servers
189
+ suggestions.push(...getCommonWhoisServers());
190
+
191
+ // Remove duplicates and failed server
192
+ const uniqueSuggestions = [...new Set(suggestions)];
193
+ return failedServer ? uniqueSuggestions.filter(s => s !== failedServer) : uniqueSuggestions;
194
+ }
195
+
196
+ /**
197
+ * Performs a whois lookup on a domain with proper timeout handling and custom server support (basic version)
198
+ * @param {string} domain - Domain to lookup
199
+ * @param {number} timeout - Timeout in milliseconds (default: 10000)
200
+ * @param {string|Array<string>} whoisServer - Custom whois server(s) to use
201
+ * @param {boolean} debugMode - Enable debug logging (default: false)
202
+ * @returns {Promise<Object>} Object with success status and output/error
203
+ */
204
+ async function whoisLookup(domain, timeout = 10000, whoisServer = null, debugMode = false, logFunc = null) {
205
+ const startTime = Date.now();
206
+ let cleanDomain, selectedServer, whoisCommand;
207
+
208
+ try {
209
+ // Clean domain (remove protocol, path, etc)
210
+ cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:\d+$/, '');
211
+
212
+ // Select whois server if provided
213
+ selectedServer = selectWhoisServer(whoisServer);
214
+
215
+ // Build whois command
216
+ if (selectedServer) {
217
+ // Use custom whois server with -h flag
218
+ whoisCommand = `whois -h "${selectedServer}" -- "${cleanDomain}"`;
219
+ } else {
220
+ // Use default whois behavior
221
+ whoisCommand = `whois -- "${cleanDomain}"`;
222
+ }
223
+
224
+ if (debugMode) {
225
+ if (logFunc) {
226
+ logFunc(`${messageColors.highlight('[whois]')} Starting lookup for ${cleanDomain} (timeout: ${timeout}ms)`);
227
+ logFunc(`${messageColors.highlight('[whois]')} Command: ${whoisCommand}`);
228
+ } else {
229
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Starting lookup for ${cleanDomain} (timeout: ${timeout}ms)`));
230
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Command: ${whoisCommand}`));
231
+ }
232
+ }
233
+
234
+ const { stdout, stderr } = await execWithTimeout(whoisCommand, timeout);
235
+ const duration = Date.now() - startTime;
236
+
237
+ if (stderr && stderr.trim()) {
238
+ if (debugMode) {
239
+ if (logFunc) {
240
+ logFunc(`${messageColors.highlight('[whois]')} Lookup failed for ${cleanDomain} after ${duration}ms`);
241
+ logFunc(`${messageColors.highlight('[whois]')} Server: ${selectedServer || 'default'}`);
242
+ logFunc(`${messageColors.highlight('[whois]')} Error: ${stderr.trim()}`);
243
+ logFunc(`${messageColors.highlight('[whois]')} Command executed: ${whoisCommand}`);
244
+ } else {
245
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Lookup failed for ${cleanDomain} after ${duration}ms`));
246
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Server: ${selectedServer || 'default'}`));
247
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Error: ${stderr.trim()}`));
248
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Command executed: ${whoisCommand}`));
249
+ }
250
+ if (selectedServer) {
251
+ if (logFunc) {
252
+ logFunc(`${messageColors.highlight('[whois]')} Custom server used: ${selectedServer}`);
253
+ } else {
254
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Custom server used: ${selectedServer}`));
255
+ }
256
+ }
257
+ }
258
+
259
+ return {
260
+ success: false,
261
+ error: stderr.trim(),
262
+ domain: cleanDomain,
263
+ whoisServer: selectedServer,
264
+ duration: duration,
265
+ command: whoisCommand
266
+ };
267
+ }
268
+
269
+ if (debugMode) {
270
+ if (logFunc) {
271
+ logFunc(`${messageColors.highlight('[whois]')} Lookup successful for ${cleanDomain} after ${duration}ms`);
272
+ logFunc(`${messageColors.highlight('[whois]')} Server: ${selectedServer || 'default'}`);
273
+ logFunc(`${messageColors.highlight('[whois]')} Output length: ${stdout.length} characters`);
274
+ } else {
275
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Lookup successful for ${cleanDomain} after ${duration}ms`));
276
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Server: ${selectedServer || 'default'}`));
277
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Output length: ${stdout.length} characters`));
278
+ }
279
+ }
280
+
281
+ return {
282
+ success: true,
283
+ output: stdout,
284
+ domain: cleanDomain,
285
+ whoisServer: selectedServer,
286
+ duration: duration,
287
+ command: whoisCommand
288
+ };
289
+ } catch (error) {
290
+ const duration = Date.now() - startTime;
291
+ const isTimeout = error.message.includes('timeout') || error.message.includes('Command timeout');
292
+ const errorType = isTimeout ? 'timeout' : 'error';
293
+
294
+ if (debugMode) {
295
+ if (logFunc) {
296
+ logFunc(`${messageColors.highlight('[whois]')} Lookup ${errorType} for ${cleanDomain || domain} after ${duration}ms`);
297
+ logFunc(`${messageColors.highlight('[whois]')} Server: ${selectedServer || 'default'}`);
298
+ logFunc(`${messageColors.highlight('[whois]')} Command: ${whoisCommand || 'command not built'}`);
299
+ logFunc(`${messageColors.highlight('[whois]')} ${errorType === 'timeout' ? 'Timeout' : 'Error'}: ${error.message}`);
300
+ } else {
301
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Lookup ${errorType} for ${cleanDomain || domain} after ${duration}ms`));
302
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Server: ${selectedServer || 'default'}`));
303
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Command: ${whoisCommand || 'command not built'}`));
304
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} ${errorType === 'timeout' ? 'Timeout' : 'Error'}: ${error.message}`));
305
+ }
306
+
307
+ if (selectedServer) {
308
+ if (logFunc) {
309
+ logFunc(`${messageColors.highlight('[whois]')} Failed server: ${selectedServer} (custom)`);
310
+ } else {
311
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Failed server: ${selectedServer} (custom)`));
312
+ }
313
+ } else {
314
+ if (logFunc) {
315
+ logFunc(`${messageColors.highlight('[whois]')} Failed server: system default whois server`);
316
+ } else {
317
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Failed server: system default whois server`));
318
+ }
319
+ }
320
+
321
+ if (isTimeout) {
322
+ if (logFunc) {
323
+ logFunc(`${messageColors.highlight('[whois]')} Timeout exceeded ${timeout}ms limit`);
324
+ } else {
325
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Timeout exceeded ${timeout}ms limit`));
326
+ }
327
+ if (selectedServer) {
328
+ if (logFunc) {
329
+ logFunc(`${messageColors.highlight('[whois]')} Consider using a different whois server or increasing timeout`);
330
+ } else {
331
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois]')} Consider using a different whois server or increasing timeout`));
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ return {
338
+ success: false,
339
+ error: error.message,
340
+ domain: cleanDomain || domain,
341
+ whoisServer: selectedServer,
342
+ duration: duration,
343
+ command: whoisCommand,
344
+ isTimeout: isTimeout,
345
+ errorType: errorType
346
+ };
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Performs a whois lookup with retry logic and fallback servers
352
+ * @param {string} domain - Domain to lookup
353
+ * @param {number} timeout - Timeout in milliseconds (default: 10000)
354
+ * @param {string|Array<string>} whoisServer - Custom whois server(s) to use
355
+ * @param {boolean} debugMode - Enable debug logging (default: false)
356
+ * @param {Object} retryOptions - Retry configuration options
357
+ * @param {number} whoisDelay - Delay in milliseconds before whois requests (default: 2000)
358
+ * @returns {Promise<Object>} Object with success status and output/error
359
+ */
360
+ async function whoisLookupWithRetry(domain, timeout = 10000, whoisServer = null, debugMode = false, retryOptions = {}, whoisDelay = 2000, logFunc = null) {
361
+ const {
362
+ maxRetries = 2,
363
+ timeoutMultiplier = 1.5,
364
+ useFallbackServers = true,
365
+ retryOnTimeout = true,
366
+ retryOnError = false
367
+ } = retryOptions;
368
+
369
+ let serversToTry = [];
370
+ let currentTimeout = timeout;
371
+
372
+ // Build list of servers to try
373
+ if (whoisServer) {
374
+ if (Array.isArray(whoisServer)) {
375
+ serversToTry = [...whoisServer]; // Copy array to avoid modifying original
376
+ } else {
377
+ serversToTry = [whoisServer];
378
+ }
379
+ } else {
380
+ serversToTry = [null]; // Default server
381
+ }
382
+
383
+ // Add fallback servers if enabled and we have custom servers
384
+ if (useFallbackServers && whoisServer) {
385
+ const fallbacks = suggestWhoisServers(domain).slice(0, 3);
386
+ // Only add fallbacks that aren't already in our list
387
+ const existingServers = serversToTry.filter(s => s !== null);
388
+ const newFallbacks = fallbacks.filter(fb => !existingServers.includes(fb));
389
+ serversToTry.push(...newFallbacks);
390
+ }
391
+
392
+ let lastError = null;
393
+ let attemptCount = 0;
394
+
395
+ if (debugMode) {
396
+ if (logFunc) {
397
+ logFunc(`${messageColors.highlight('[whois-retry]')} Starting whois lookup for ${domain} with ${serversToTry.length} server(s) to try`);
398
+ logFunc(`${messageColors.highlight('[whois-retry]')} Servers: [${serversToTry.map(s => s || 'default').join(', ')}]`);
399
+ logFunc(`${messageColors.highlight('[whois-retry]')} Retry settings: maxRetries=${maxRetries}, timeoutMultiplier=${timeoutMultiplier}, retryOnTimeout=${retryOnTimeout}, retryOnError=${retryOnError}`);
400
+ } else {
401
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Starting whois lookup for ${domain} with ${serversToTry.length} server(s) to try`));
402
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Servers: [${serversToTry.map(s => s || 'default').join(', ')}]`));
403
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Retry settings: maxRetries=${maxRetries}, timeoutMultiplier=${timeoutMultiplier}, retryOnTimeout=${retryOnTimeout}, retryOnError=${retryOnError}`));
404
+ }
405
+ }
406
+
407
+ for (const server of serversToTry) {
408
+ attemptCount++;
409
+
410
+ if (debugMode) {
411
+ const serverName = server || 'default';
412
+ if (logFunc) {
413
+ logFunc(`${messageColors.highlight('[whois-retry]')} Attempt ${attemptCount}/${serversToTry.length}: trying server ${serverName} (timeout: ${currentTimeout}ms)`);
414
+ } else {
415
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Attempt ${attemptCount}/${serversToTry.length}: trying server ${serverName} (timeout: ${currentTimeout}ms)`));
416
+ }
417
+ }
418
+
419
+ // Add delay between retry attempts to prevent rate limiting
420
+ if (attemptCount > 1) {
421
+ if (debugMode) {
422
+ if (logFunc) {
423
+ logFunc(`${messageColors.highlight('[whois-retry]')} Adding ${whoisDelay}ms delay before retry attempt...`);
424
+ } else {
425
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Adding ${whoisDelay}ms delay before retry attempt...`));
426
+ }
427
+ }
428
+
429
+ await new Promise(resolve => setTimeout(resolve, whoisDelay));
430
+ } else if (whoisDelay > 0) {
431
+ // Add initial delay on first attempt if configured
432
+ if (debugMode) {
433
+ if (logFunc) {
434
+ logFunc(`${messageColors.highlight('[whois-retry]')} Adding ${whoisDelay}ms delay to prevent rate limiting...`);
435
+ } else {
436
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Adding ${whoisDelay}ms delay to prevent rate limiting...`));
437
+ }
438
+ }
439
+ await new Promise(resolve => setTimeout(resolve, whoisDelay));
440
+ }
441
+
442
+ try {
443
+ const result = await whoisLookup(domain, currentTimeout, server, debugMode, logFunc);
444
+
445
+ if (result.success) {
446
+ if (debugMode) {
447
+ if (logFunc) {
448
+ logFunc(`${messageColors.highlight('[whois-retry]')} SUCCESS on attempt ${attemptCount}/${serversToTry.length} using server ${result.whoisServer || 'default'}`);
449
+ } else {
450
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} SUCCESS on attempt ${attemptCount}/${serversToTry.length} using server ${result.whoisServer || 'default'}`));
451
+ }
452
+ }
453
+
454
+ // Add retry info to result
455
+ return {
456
+ ...result,
457
+ retryInfo: {
458
+ totalAttempts: attemptCount,
459
+ maxAttempts: serversToTry.length,
460
+ serversAttempted: serversToTry.slice(0, attemptCount),
461
+ finalServer: result.whoisServer,
462
+ retriedAfterFailure: attemptCount > 1
463
+ }
464
+ };
465
+ } else {
466
+ // Determine if we should retry based on error type
467
+ const shouldRetry = (result.isTimeout && retryOnTimeout) || (!result.isTimeout && retryOnError);
468
+
469
+ if (debugMode) {
470
+ const serverName = result.whoisServer || 'default';
471
+ const errorType = result.isTimeout ? 'TIMEOUT' : 'ERROR';
472
+ if (logFunc) {
473
+ logFunc(`${messageColors.highlight('[whois-retry]')} ${errorType} on attempt ${attemptCount}/${serversToTry.length} with server ${serverName}: ${result.error}`);
474
+ } else {
475
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} ${errorType} on attempt ${attemptCount}/${serversToTry.length} with server ${serverName}: ${result.error}`));
476
+ }
477
+
478
+ if (attemptCount < serversToTry.length) {
479
+ if (shouldRetry) {
480
+ if (logFunc) {
481
+ logFunc(`${messageColors.highlight('[whois-retry]')} Will retry with next server...`);
482
+ } else {
483
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Will retry with next server...`));
484
+ }
485
+ } else {
486
+ if (logFunc) {
487
+ logFunc(`${messageColors.highlight('[whois-retry]')} Skipping retry (retryOn${result.isTimeout ? 'Timeout' : 'Error'}=${shouldRetry})`);
488
+ } else {
489
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Skipping retry (retryOn${result.isTimeout ? 'Timeout' : 'Error'}=${shouldRetry})`));
490
+ }
491
+ }
492
+ }
493
+ }
494
+
495
+ lastError = result;
496
+
497
+ // If this is the last server or we shouldn't retry this error type, break
498
+ if (attemptCount >= serversToTry.length || !shouldRetry) {
499
+ break;
500
+ }
501
+
502
+ // Increase timeout for next attempt
503
+ currentTimeout = Math.round(currentTimeout * timeoutMultiplier);
504
+ }
505
+ } catch (error) {
506
+ if (debugMode) {
507
+ const serverName = server || 'default';
508
+ if (logFunc) {
509
+ logFunc(`${messageColors.highlight('[whois-retry]')} EXCEPTION on attempt ${attemptCount}/${serversToTry.length} with server ${serverName}: ${error.message}`);
510
+ } else {
511
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} EXCEPTION on attempt ${attemptCount}/${serversToTry.length} with server ${serverName}: ${error.message}`));
512
+ }
513
+ }
514
+
515
+ lastError = {
516
+ success: false,
517
+ error: error.message,
518
+ domain: domain,
519
+ whoisServer: server,
520
+ isTimeout: error.message.includes('timeout'),
521
+ duration: 0
522
+ };
523
+
524
+ // Continue to next server unless this is the last one
525
+ if (attemptCount >= serversToTry.length) {
526
+ break;
527
+ }
528
+
529
+ currentTimeout = Math.round(currentTimeout * timeoutMultiplier);
530
+ }
531
+ }
532
+
533
+ // All attempts failed
534
+ if (debugMode) {
535
+ if (logFunc) {
536
+ logFunc(`${messageColors.highlight('[whois-retry]')} FINAL FAILURE: All ${attemptCount} attempts failed for ${domain}`);
537
+ } else {
538
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} FINAL FAILURE: All ${attemptCount} attempts failed for ${domain}`));
539
+ }
540
+ if (lastError) {
541
+ if (logFunc) {
542
+ logFunc(`${messageColors.highlight('[whois-retry]')} Last error: ${lastError.error} (${lastError.isTimeout ? 'timeout' : 'error'})`);
543
+ } else {
544
+ console.log(formatLogMessage('debug', `${messageColors.highlight('[whois-retry]')} Last error: ${lastError.error} (${lastError.isTimeout ? 'timeout' : 'error'})`));
545
+ }
546
+ }
547
+ }
548
+
549
+ // Return the last error with retry info
550
+ return {
551
+ ...lastError,
552
+ retryInfo: {
553
+ totalAttempts: attemptCount,
554
+ maxAttempts: serversToTry.length,
555
+ serversAttempted: serversToTry.slice(0, attemptCount),
556
+ finalServer: lastError?.whoisServer || null,
557
+ retriedAfterFailure: attemptCount > 1,
558
+ allAttemptsFailed: true
559
+ }
560
+ };
561
+ }
562
+
563
+ /**
564
+ * Performs a dig lookup on a domain with proper timeout handling
565
+ * @param {string} domain - Domain to lookup
566
+ * @param {string} recordType - DNS record type (A, AAAA, MX, TXT, etc.) default: 'A'
567
+ * @param {number} timeout - Timeout in milliseconds (default: 5000)
568
+ * @returns {Promise<Object>} Object with success status and output/error
569
+ */
570
+ async function digLookup(domain, recordType = 'A', timeout = 5000) {
571
+ try {
572
+ // Clean domain
573
+ const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/:\d+$/, '');
574
+
575
+ // Get short output first
576
+ const { stdout, stderr } = await execWithTimeout(`dig +short "${cleanDomain}" ${recordType}`, timeout);
577
+
578
+ if (stderr && stderr.trim()) {
579
+ return {
580
+ success: false,
581
+ error: stderr.trim(),
582
+ domain: cleanDomain,
583
+ recordType
584
+ };
585
+ }
586
+
587
+ // Also get full dig output for detailed analysis
588
+ const { stdout: fullOutput } = await execWithTimeout(`dig "${cleanDomain}" ${recordType}`, timeout);
589
+
590
+ return {
591
+ success: true,
592
+ output: fullOutput,
593
+ shortOutput: stdout.trim(),
594
+ domain: cleanDomain,
595
+ recordType
596
+ };
597
+ } catch (error) {
598
+ return {
599
+ success: false,
600
+ error: error.message,
601
+ domain: domain,
602
+ recordType
603
+ };
604
+ }
605
+ }
606
+
607
+ /**
608
+ * Checks if whois output contains all specified search terms (AND logic)
609
+ * @param {string} whoisOutput - The whois lookup output
610
+ * @param {Array<string>} searchTerms - Array of terms that must all be present
611
+ * @returns {boolean} True if all terms are found
612
+ */
613
+ function checkWhoisTerms(whoisOutput, searchTerms) {
614
+ if (!searchTerms || !Array.isArray(searchTerms) || searchTerms.length === 0) {
615
+ return false;
616
+ }
617
+
618
+ const lowerOutput = whoisOutput.toLowerCase();
619
+ return searchTerms.every(term => lowerOutput.includes(term.toLowerCase()));
620
+ }
621
+
622
+ /**
623
+ * Checks if whois output contains any of the specified search terms (OR logic)
624
+ * @param {string} whoisOutput - The whois lookup output
625
+ * @param {Array<string>} searchTerms - Array of terms where at least one must be present
626
+ * @returns {boolean} True if any term is found
627
+ */
628
+ function checkWhoisTermsOr(whoisOutput, searchTerms) {
629
+ if (!searchTerms || !Array.isArray(searchTerms) || searchTerms.length === 0) {
630
+ return false;
631
+ }
632
+
633
+ const lowerOutput = whoisOutput.toLowerCase();
634
+ return searchTerms.some(term => lowerOutput.includes(term.toLowerCase()));
635
+ }
636
+
637
+ /**
638
+ * Checks if dig output contains all specified search terms (AND logic)
639
+ * @param {string} digOutput - The dig lookup output
640
+ * @param {Array<string>} searchTerms - Array of terms that must all be present
641
+ * @returns {boolean} True if all terms are found
642
+ */
643
+ function checkDigTerms(digOutput, searchTerms) {
644
+ if (!searchTerms || !Array.isArray(searchTerms) || searchTerms.length === 0) {
645
+ return false;
646
+ }
647
+
648
+ const lowerOutput = digOutput.toLowerCase();
649
+ return searchTerms.every(term => lowerOutput.includes(term.toLowerCase()));
650
+ }
651
+
652
+ /**
653
+ * Checks if dig output contains any of the specified search terms (OR logic)
654
+ * @param {string} digOutput - The dig lookup output
655
+ * @param {Array<string>} searchTerms - Array of terms where at least one must be present
656
+ * @returns {boolean} True if any term is found
657
+ */
658
+ function checkDigTermsOr(digOutput, searchTerms) {
659
+ if (!searchTerms || !Array.isArray(searchTerms) || searchTerms.length === 0) {
660
+ return false;
661
+ }
662
+
663
+ const lowerOutput = digOutput.toLowerCase();
664
+ return searchTerms.some(term => lowerOutput.includes(term.toLowerCase()));
665
+ }
666
+
667
+ /**
668
+ * Enhanced dry run callback factory for better nettools reporting
669
+ * @param {Map} matchedDomains - The matched domains collection
670
+ * @param {boolean} forceDebug - Debug logging flag
671
+ * @returns {Function} Enhanced dry run callback
672
+ */
673
+ function createEnhancedDryRunCallback(matchedDomains, forceDebug) {
674
+ return (domain, tool, matchType, matchedTerm, details, additionalInfo = {}) => {
675
+ const result = {
676
+ domain,
677
+ tool,
678
+ matchType,
679
+ matchedTerm,
680
+ details,
681
+ ...additionalInfo
682
+ };
683
+
684
+ matchedDomains.get('dryRunNetTools').push(result);
685
+
686
+ if (forceDebug) {
687
+ const serverInfo = additionalInfo.server ? ` (server: ${additionalInfo.server})` : '';
688
+ const timingInfo = additionalInfo.duration ? ` [${additionalInfo.duration}ms]` : '';
689
+ console.log(formatLogMessage('debug', `[DRY RUN] NetTools match: ${domain} via ${tool.toUpperCase()} (${matchType})${serverInfo}${timingInfo}`));
690
+ }
691
+ };
692
+ }
693
+
694
+ /**
695
+ * Creates a handler for network tools checks with enhanced error handling
696
+ * @param {Object} config - Configuration object
697
+ * @returns {Function} Async function that handles network tool lookups
698
+ */
699
+ function createNetToolsHandler(config) {
700
+ const {
701
+ whoisTerms,
702
+ whoisOrTerms,
703
+ whoisDelay = 2000,
704
+ whoisServer,
705
+ whoisServerMode = 'random',
706
+ debugLogFile = null,
707
+ digTerms,
708
+ digOrTerms,
709
+ digRecordType = 'A',
710
+ digSubdomain = false,
711
+ dryRunCallback = null,
712
+ matchedDomains,
713
+ addMatchedDomain,
714
+ currentUrl,
715
+ getRootDomain,
716
+ siteConfig,
717
+ dumpUrls,
718
+ matchedUrlsLogFile,
719
+ forceDebug,
720
+ fs
721
+ } = config;
722
+
723
+ const hasWhois = whoisTerms && Array.isArray(whoisTerms) && whoisTerms.length > 0;
724
+ const hasWhoisOr = whoisOrTerms && Array.isArray(whoisOrTerms) && whoisOrTerms.length > 0;
725
+ const hasDig = digTerms && Array.isArray(digTerms) && digTerms.length > 0;
726
+ const hasDigOr = digOrTerms && Array.isArray(digOrTerms) && digOrTerms.length > 0;
727
+
728
+ // Add deduplication cache for nettools lookups
729
+ const processedDomains = new Set();
730
+
731
+
732
+ return async function handleNetToolsCheck(domain, originalDomain) {
733
+ // Helper function to log to BOTH console and debug file
734
+
735
+ // NOTE: The logToConsoleAndFile function needs to be declared INSIDE this function
736
+ // so it has access to the closure variables (forceDebug, debugLogFile, fs) from the
737
+ // createNetToolsHandler config. This function was being called but not declared
738
+ // within the scope where whoisLookup and whoisLookupWithRetry try to use it.
739
+ // This is why we were getting "logToConsoleAndFile is not defined" errors.
740
+
741
+ // Move the logToConsoleAndFile function declaration from later in the file to here:
742
+ function logToConsoleAndFile(message) {
743
+ // Note: This function needs access to forceDebug, debugLogFile, and fs from the parent scope
744
+ // These are passed in via the config object to createNetToolsHandler
745
+ // forceDebug, debugLogFile, and fs are available in this closure
746
+
747
+ // Always log to console when in debug mode
748
+ if (forceDebug) {
749
+ console.log(formatLogMessage('debug', message));
750
+ }
751
+
752
+ // Also log to file if debug file logging is enabled
753
+ if (debugLogFile && fs) {
754
+ try {
755
+ const timestamp = new Date().toISOString();
756
+ const cleanMessage = stripAnsiColors(message);
757
+ fs.appendFileSync(debugLogFile, `${timestamp} [debug nettools] ${cleanMessage}\n`);
758
+ } catch (logErr) {
759
+ // Silently fail file logging to avoid disrupting whois operations
760
+ }
761
+ }
762
+ }
763
+
764
+ // Skip if we've already processed this domain
765
+ if (processedDomains.has(domain)) {
766
+ if (forceDebug) {
767
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Skipping duplicate lookup for ${domain}`);
768
+ }
769
+ return;
770
+ }
771
+
772
+ // Mark domain as being processed
773
+ processedDomains.add(domain);
774
+ if (forceDebug) {
775
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Processing new domain: ${domain} (${processedDomains.size} total processed)`);
776
+ }
777
+
778
+ // Log site-specific whois delay if different from default
779
+ if (forceDebug && whoisDelay !== 3000) {
780
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Using site-specific whois delay: ${whoisDelay}ms`);
781
+ }
782
+
783
+ // Add overall timeout for the entire nettools check
784
+ const netlookupTimeout = setTimeout(() => {
785
+ if (forceDebug) {
786
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Overall timeout for domain ${domain}, continuing with next...`);
787
+ }
788
+ }, 30000); // 30 second overall timeout
789
+
790
+ // Wrap entire function in timeout protection
791
+ return Promise.race([
792
+ (async () => {
793
+ try {
794
+ return await executeNetToolsLookup();
795
+ } finally {
796
+ clearTimeout(netlookupTimeout);
797
+ }
798
+ })(),
799
+ new Promise((_, reject) => setTimeout(() => reject(new Error('NetTools overall timeout')), 30000))
800
+ ]).catch(err => {
801
+ if (forceDebug) {
802
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} ${err.message} for ${domain}, continuing...`);
803
+ }
804
+ });
805
+
806
+ async function executeNetToolsLookup() {
807
+
808
+ try {
809
+ let whoisMatched = false;
810
+ let whoisOrMatched = false;
811
+ let digMatched = false;
812
+ let digOrMatched = false;
813
+
814
+ // Debug logging for digSubdomain logic
815
+ if (forceDebug) {
816
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} digSubdomain setting: ${digSubdomain}`);
817
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} domain parameter: ${domain}`);
818
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} originalDomain parameter: ${originalDomain}`);
819
+ if (whoisServer) {
820
+ const serverInfo = Array.isArray(whoisServer)
821
+ ? `randomized from [${whoisServer.join(', ')}]`
822
+ : whoisServer;
823
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Custom whois server: ${serverInfo}`);
824
+ }
825
+ }
826
+
827
+ // Determine which domain to use for dig lookup
828
+ const digDomain = digSubdomain && originalDomain ? originalDomain : domain;
829
+
830
+ if (forceDebug) {
831
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Final digDomain will be: ${digDomain}`);
832
+ }
833
+
834
+ // Enhanced dry run logging
835
+ if (dryRunCallback && forceDebug) {
836
+ logToConsoleAndFile(`${messageColors.highlight('[nettools-dryrun]')} Processing ${domain} (original: ${originalDomain})`);
837
+
838
+ // Show what checks will be performed
839
+ const checksToPerform = [];
840
+ if (hasWhois) checksToPerform.push('whois-and');
841
+ if (hasWhoisOr) checksToPerform.push('whois-or');
842
+ if (hasDig) checksToPerform.push('dig-and');
843
+ if (hasDigOr) checksToPerform.push('dig-or');
844
+ logToConsoleAndFile(`${messageColors.highlight('[nettools-dryrun]')} Will perform: ${checksToPerform.join(', ')}`);
845
+
846
+ // Show which domain will be used for dig
847
+ if (hasDig || hasDigOr) {
848
+ logToConsoleAndFile(`${messageColors.highlight('[dig-dryrun]')} Will check ${digDomain} (${digSubdomain ? 'subdomain mode' : 'root domain mode'})`);
849
+ }
850
+
851
+ // Show whois server selection
852
+ if (hasWhois || hasWhoisOr) {
853
+ const selectedServer = selectWhoisServer(whoisServer, whoisServerMode);
854
+ const serverInfo = selectedServer ? selectedServer : 'system default';
855
+ logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Will use server: ${serverInfo}`);
856
+ }
857
+
858
+ // Show retry configuration in dry-run
859
+ if (hasWhois || hasWhoisOr) {
860
+ const maxRetries = siteConfig.whois_max_retries || 2;
861
+ logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Max retries: ${maxRetries}, timeout multiplier: ${siteConfig.whois_timeout_multiplier || 1.5}`);
862
+ }
863
+ }
864
+
865
+ // Perform whois lookup if either whois or whois-or is configured
866
+ if (hasWhois || hasWhoisOr) {
867
+ const selectedServer = selectWhoisServer(whoisServer, whoisServerMode);
868
+
869
+ if (forceDebug) {
870
+ const serverInfo = selectedServer ? ` using server ${selectedServer}` : ' using default server';
871
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Performing whois lookup for ${domain}${serverInfo}`);
872
+ }
873
+
874
+ try {
875
+ // Configure retry options based on site config or use defaults
876
+ const retryOptions = {
877
+ maxRetries: siteConfig.whois_max_retries || 2,
878
+ timeoutMultiplier: siteConfig.whois_timeout_multiplier || 1.5,
879
+ useFallbackServers: siteConfig.whois_use_fallback !== false, // Default true
880
+ retryOnTimeout: siteConfig.whois_retry_on_timeout !== false, // Default true
881
+ retryOnError: siteConfig.whois_retry_on_error === true // Default false
882
+ };
883
+
884
+ const whoisResult = await whoisLookupWithRetry(domain, 8000, whoisServer, forceDebug, retryOptions, whoisDelay, logToConsoleAndFile);
885
+
886
+ if (whoisResult.success) {
887
+ // Check AND terms if configured
888
+ if (hasWhois) {
889
+ whoisMatched = checkWhoisTerms(whoisResult.output, whoisTerms);
890
+ if (whoisMatched && dryRunCallback) {
891
+ dryRunCallback(domain, 'whois', 'AND logic', whoisTerms.join(', '), 'All terms found in whois data', {
892
+ server: whoisResult.whoisServer || 'default',
893
+ duration: whoisResult.duration,
894
+ retryAttempts: whoisResult.retryInfo?.totalAttempts || 1
895
+ });
896
+ }
897
+ if (forceDebug && siteConfig.verbose === 1) {
898
+ logToConsoleAndFile(`${messageColors.highlight('[whois-and]')} Terms checked: ${whoisTerms.join(' AND ')}, matched: ${whoisMatched}`);
899
+ }
900
+
901
+ }
902
+
903
+ // Check OR terms if configured
904
+ if (hasWhoisOr) {
905
+ whoisOrMatched = checkWhoisTermsOr(whoisResult.output, whoisOrTerms);
906
+ if (whoisOrMatched && dryRunCallback) {
907
+ const matchedTerm = whoisOrTerms.find(term => whoisResult.output.toLowerCase().includes(term.toLowerCase()));
908
+ dryRunCallback(domain, 'whois', 'OR logic', matchedTerm, 'Term found in whois data', {
909
+ server: whoisResult.whoisServer || 'default',
910
+ duration: whoisResult.duration,
911
+ retryAttempts: whoisResult.retryInfo?.totalAttempts || 1
912
+ });
913
+ }
914
+
915
+ if (forceDebug && siteConfig.verbose === 1) {
916
+ logToConsoleAndFile(`${messageColors.highlight('[whois-or]')} Terms checked: ${whoisOrTerms.join(' OR ')}, matched: ${whoisOrMatched}`);
917
+ }
918
+ }
919
+
920
+ if (forceDebug) {
921
+ const serverUsed = whoisResult.whoisServer ? ` (server: ${whoisResult.whoisServer})` : ' (default server)';
922
+ const retryInfo = whoisResult.retryInfo ? ` [${whoisResult.retryInfo.totalAttempts}/${whoisResult.retryInfo.maxAttempts} attempts]` : '';
923
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Lookup completed for ${domain}${serverUsed} in ${whoisResult.duration}ms${retryInfo}`);
924
+
925
+ if (whoisResult.retryInfo && whoisResult.retryInfo.retriedAfterFailure) {
926
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Success after retry - servers attempted: [${whoisResult.retryInfo.serversAttempted.map(s => s || 'default').join(', ')}]`);
927
+ }
928
+ }
929
+ } else {
930
+ // Enhanced error logging for failed whois lookups
931
+ if (forceDebug) {
932
+ const serverUsed = whoisResult.whoisServer ? ` (server: ${whoisResult.whoisServer})` : ' (default server)';
933
+ const errorContext = whoisResult.isTimeout ? 'TIMEOUT' : 'ERROR';
934
+ const retryInfo = whoisResult.retryInfo ? ` [${whoisResult.retryInfo.totalAttempts}/${whoisResult.retryInfo.maxAttempts} attempts]` : '';
935
+
936
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} ${errorContext}: Lookup failed for ${domain}${serverUsed} after ${whoisResult.duration}ms${retryInfo}`);
937
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Command executed: ${whoisResult.command || 'unknown'}`);
938
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Error details: ${whoisResult.error}`);
939
+
940
+ // Enhanced server debugging for failures
941
+ if (whoisResult.whoisServer) {
942
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Failed server: ${whoisResult.whoisServer} (custom)`);
943
+ } else {
944
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Failed server: system default whois server`);
945
+ }
946
+
947
+
948
+ if (whoisResult.retryInfo) {
949
+ if (whoisResult.retryInfo.allAttemptsFailed) {
950
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} All retry attempts failed. Servers tried: [${whoisResult.retryInfo.serversAttempted.map(s => s || 'default').join(', ')}]`);
951
+ }
952
+
953
+ if (whoisResult.retryInfo.retriedAfterFailure) {
954
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Retries were attempted but ultimately failed`);
955
+ }
956
+ }
957
+
958
+ if (whoisResult.isTimeout) {
959
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Timeout exceeded limit after all retry attempts`);
960
+ if (Array.isArray(whoisServer) && whoisServer.length > 1) {
961
+ const remainingServers = whoisServer.filter(s => !whoisResult.retryInfo?.serversAttempted.includes(s));
962
+ if (remainingServers.length > 0) {
963
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Unused servers from config: ${remainingServers.join(', ')}`);
964
+ }
965
+ } else {
966
+ // Suggest alternative servers based on domain TLD
967
+ const suggestions = suggestWhoisServers(domain, whoisResult.whoisServer).slice(0, 3);
968
+ if (suggestions.length > 0) {
969
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Suggested alternative servers: ${suggestions.join(', ')}`);
970
+ }
971
+ }
972
+ // Show specific rate limiting advice
973
+ if (whoisResult.error.toLowerCase().includes('too fast') || whoisResult.error.toLowerCase().includes('rate limit')) {
974
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Rate limiting detected - consider increasing delays or using different servers`);
975
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Current server: ${whoisResult.whoisServer || 'default'} may be overloaded`);
976
+ }
977
+ }
978
+
979
+ // Log specific error patterns
980
+ if (whoisResult.error.toLowerCase().includes('connection refused')) {
981
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Connection refused - server may be down or blocking requests`);
982
+ } else if (whoisResult.error.toLowerCase().includes('no route to host')) {
983
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Network connectivity issue to whois server`);
984
+ } else if (whoisResult.error.toLowerCase().includes('name or service not known')) {
985
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} DNS resolution failed for whois server`);
986
+ }
987
+ }
988
+
989
+ // Log whois failures in dry run mode
990
+ if (dryRunCallback && forceDebug) {
991
+ const errorType = whoisResult.isTimeout ? 'TIMEOUT' : 'ERROR';
992
+ logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} ${errorType}: ${whoisResult.error}`);
993
+ if (whoisResult.retryInfo?.allAttemptsFailed) {
994
+ logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} All ${whoisResult.retryInfo.totalAttempts} retry attempts failed`);
995
+ }
996
+ }
997
+ // Don't return early - continue with dig if configured
998
+ }
999
+ } catch (whoisError) {
1000
+ if (forceDebug) {
1001
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception during lookup for ${domain}: ${whoisError.message}`);
1002
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Exception type: ${whoisError.constructor.name}`);
1003
+ if (whoisError.stack) {
1004
+ logToConsoleAndFile(`${messageColors.highlight('[whois]')} Stack trace: ${whoisError.stack.split('\n').slice(0, 3).join(' -> ')}`);
1005
+ }
1006
+ }
1007
+
1008
+ // Log whois exceptions in dry run mode
1009
+ if (dryRunCallback && forceDebug) {
1010
+ logToConsoleAndFile(`${messageColors.highlight('[whois-dryrun]')} Exception: ${whoisError.message}`);
1011
+ }
1012
+ // Continue with dig if configured
1013
+ }
1014
+ }
1015
+
1016
+ // Perform dig lookup if configured
1017
+ if (hasDig || hasDigOr) {
1018
+ if (forceDebug) {
1019
+ const digTypes = [];
1020
+ if (hasDig) digTypes.push('dig-and');
1021
+ if (hasDigOr) digTypes.push('dig-or');
1022
+ logToConsoleAndFile(`${messageColors.highlight('[dig]')} Performing dig lookup for ${digDomain} (${digRecordType}) [${digTypes.join(' + ')}]${digSubdomain ? ' [subdomain mode]' : ''}`);
1023
+ }
1024
+
1025
+ try {
1026
+ const digResult = await digLookup(digDomain, digRecordType, 5000); // 5 second timeout for dig
1027
+
1028
+ if (digResult.success) {
1029
+ // Check AND terms if configured
1030
+ if (hasDig) {
1031
+ digMatched = checkDigTerms(digResult.output, digTerms);
1032
+ if (digMatched && dryRunCallback) {
1033
+ dryRunCallback(domain, 'dig', 'AND logic', digTerms.join(', '), `All terms found in ${digRecordType} records`, {
1034
+ queriedDomain: digDomain,
1035
+ recordType: digRecordType,
1036
+ subdomainMode: digSubdomain
1037
+ });
1038
+ }
1039
+ }
1040
+
1041
+ // Check OR terms if configured
1042
+ if (hasDigOr) {
1043
+ digOrMatched = checkDigTermsOr(digResult.output, digOrTerms);
1044
+ if (digOrMatched && dryRunCallback) {
1045
+ const matchedTerm = digOrTerms.find(term => digResult.output.toLowerCase().includes(term.toLowerCase()));
1046
+ dryRunCallback(domain, 'dig', 'OR logic', matchedTerm, `Term found in ${digRecordType} records`, {
1047
+ queriedDomain: digDomain,
1048
+ recordType: digRecordType,
1049
+ subdomainMode: digSubdomain
1050
+ });
1051
+ }
1052
+ }
1053
+
1054
+ if (forceDebug) {
1055
+ if (siteConfig.verbose === 1) {
1056
+ if (hasDig) logToConsoleAndFile(`${messageColors.highlight('[dig-and]')} Terms checked: ${digTerms.join(' AND ')}, matched: ${digMatched}`);
1057
+ if (hasDigOr) logToConsoleAndFile(`${messageColors.highlight('[dig-or]')} Terms checked: ${digOrTerms.join(' OR ')}, matched: ${digOrMatched}`);
1058
+ }
1059
+ logToConsoleAndFile(`${messageColors.highlight('[dig]')} Lookup completed for ${digDomain}, dig-and: ${digMatched}, dig-or: ${digOrMatched}`);
1060
+ if (siteConfig.verbose === 1) {
1061
+ if (hasDig) logToConsoleAndFile(`${messageColors.highlight('[dig]')} AND terms: ${digTerms.join(', ')}`);
1062
+ if (hasDigOr) logToConsoleAndFile(`${messageColors.highlight('[dig]')} OR terms: ${digOrTerms.join(', ')}`);
1063
+ logToConsoleAndFile(`${messageColors.highlight('[dig]')} Short output: ${digResult.shortOutput}`);
1064
+ }
1065
+ }
1066
+ } else {
1067
+ if (forceDebug) {
1068
+ logToConsoleAndFile(`${messageColors.highlight('[dig]')} Lookup failed for ${digDomain}: ${digResult.error}`);
1069
+ }
1070
+
1071
+ // Log dig failures in dry run mode
1072
+ if (dryRunCallback && forceDebug) {
1073
+ logToConsoleAndFile(`${messageColors.highlight('[dig-dryrun]')} Failed: ${digResult.error}`);
1074
+ }
1075
+ }
1076
+ } catch (digError) {
1077
+ if (forceDebug) {
1078
+ logToConsoleAndFile(`${messageColors.highlight('[dig]')} Exception during lookup for ${digDomain}: ${digError.message}`);
1079
+ }
1080
+
1081
+ // Log dig exceptions in dry run mode
1082
+ if (dryRunCallback && forceDebug) {
1083
+ logToConsoleAndFile(`${messageColors.highlight('[dig-dryrun]')} Exception: ${digError.message}`);
1084
+ }
1085
+ }
1086
+ }
1087
+
1088
+ // Domain matches if any of these conditions are true:
1089
+ let shouldMatch = false;
1090
+
1091
+ if (hasWhois && !hasWhoisOr && !hasDig && !hasDigOr) {
1092
+ shouldMatch = whoisMatched;
1093
+ } else if (!hasWhois && hasWhoisOr && !hasDig && !hasDigOr) {
1094
+ shouldMatch = whoisOrMatched;
1095
+ } else if (!hasWhois && !hasWhoisOr && hasDig && !hasDigOr) {
1096
+ shouldMatch = digMatched;
1097
+ } else if (!hasWhois && !hasWhoisOr && !hasDig && hasDigOr) {
1098
+ shouldMatch = digOrMatched;
1099
+ } else {
1100
+ // Multiple checks configured - ALL must pass
1101
+ shouldMatch = true;
1102
+ if (hasWhois) shouldMatch = shouldMatch && whoisMatched;
1103
+ if (hasWhoisOr) shouldMatch = shouldMatch && whoisOrMatched;
1104
+ if (hasDig) shouldMatch = shouldMatch && digMatched;
1105
+ if (hasDigOr) shouldMatch = shouldMatch && digOrMatched;
1106
+ }
1107
+
1108
+ if (shouldMatch) {
1109
+ // Add to matched domains only if not in dry run mode
1110
+ if (dryRunCallback) {
1111
+ // In dry run mode, the callback has already been called above
1112
+ // Add comprehensive dry run logging
1113
+ if (forceDebug) {
1114
+ const matchType = [];
1115
+ if (hasWhois && whoisMatched) matchType.push('whois-and');
1116
+ if (hasWhoisOr && whoisOrMatched) matchType.push('whois-or');
1117
+ if (hasDig && digMatched) matchType.push(digSubdomain ? 'dig-and-subdomain' : 'dig-and');
1118
+ if (hasDigOr && digOrMatched) matchType.push(digSubdomain ? 'dig-or-subdomain' : 'dig-or');
1119
+ logToConsoleAndFile(`${messageColors.highlight('[nettools-dryrun]')} ${domain} would match via ${matchType.join(' + ')}`);
1120
+ }
1121
+
1122
+ // Show what adblock rule would be generated
1123
+ if (forceDebug) {
1124
+ const adblockRule = `||${domain}^`;
1125
+ logToConsoleAndFile(`${messageColors.highlight('[nettools-dryrun]')} Would generate adblock rule: ${adblockRule}`);
1126
+ }
1127
+ // No need to add to matched domains
1128
+ } else {
1129
+ if (typeof addMatchedDomain === 'function') {
1130
+ addMatchedDomain(domain);
1131
+ } else {
1132
+ matchedDomains.add(domain);
1133
+ }
1134
+ }
1135
+
1136
+ const simplifiedUrl = currentUrl ? getRootDomain(currentUrl) : 'unknown';
1137
+
1138
+ if (siteConfig.verbose === 1) {
1139
+ const matchType = [];
1140
+ if (hasWhois && whoisMatched) matchType.push('whois-and');
1141
+ if (hasWhoisOr && whoisOrMatched) matchType.push('whois-or');
1142
+ if (hasDig && digMatched) matchType.push(digSubdomain ? 'dig-and-subdomain' : 'dig-and');
1143
+ if (hasDigOr && digOrMatched) matchType.push(digSubdomain ? 'dig-or-subdomain' : 'dig-or');
1144
+ logToConsoleAndFile(`[${simplifiedUrl}] ${domain} matched via ${matchType.join(' + ')}`);
1145
+ }
1146
+
1147
+ if (dumpUrls && matchedUrlsLogFile && fs) {
1148
+ const timestamp = new Date().toISOString();
1149
+ const matchType = [];
1150
+ if (hasWhois && whoisMatched) matchType.push('whois-and');
1151
+ if (hasWhoisOr && whoisOrMatched) matchType.push('whois-or');
1152
+ if (hasDig && digMatched) matchType.push(digSubdomain ? 'dig-and-subdomain' : 'dig-and');
1153
+ if (hasDigOr && digOrMatched) matchType.push(digSubdomain ? 'dig-or-subdomain' : 'dig-or');
1154
+
1155
+ // Add whois server info to log if custom server was used
1156
+ const serverInfo = whoisServer ? ` (whois-server: ${selectWhoisServer(whoisServer)})` : '';
1157
+ fs.appendFileSync(matchedUrlsLogFile, `${timestamp} [match][${simplifiedUrl}] ${domain} (${matchType.join(' + ')})${serverInfo}\n`);
1158
+ }
1159
+ }
1160
+
1161
+ } catch (timeoutError) {
1162
+ if (timeoutError.message.includes('NetTools overall timeout')) {
1163
+ if (forceDebug) {
1164
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Overall timeout for domain ${domain}: ${timeoutError.message}`);
1165
+ }
1166
+ // Don't rethrow - continue processing other domains
1167
+ return;
1168
+ }
1169
+ try {
1170
+ throw timeoutError; // Re-throw other errors
1171
+ } catch (error) {
1172
+ if (forceDebug) {
1173
+ logToConsoleAndFile(`${messageColors.highlight('[nettools]')} Error processing ${domain}: ${error.message}`);
1174
+ }
1175
+ // Silently fail and continue - don't block other processing
1176
+ }
1177
+ }
1178
+
1179
+
1180
+ } // End of executeNetToolsLookup function
1181
+ };
1182
+ }
1183
+
1184
+ module.exports = {
1185
+ validateWhoisAvailability,
1186
+ validateDigAvailability,
1187
+ whoisLookup,
1188
+ whoisLookupWithRetry,
1189
+ digLookup,
1190
+ checkWhoisTerms,
1191
+ checkWhoisTermsOr,
1192
+ checkDigTerms,
1193
+ checkDigTermsOr,
1194
+ createNetToolsHandler,
1195
+ createEnhancedDryRunCallback,
1196
+ selectWhoisServer,
1197
+ getCommonWhoisServers,
1198
+ suggestWhoisServers,
1199
+ execWithTimeout // Export for testing
1200
+ };