# File: src/main.py """Main CLI interface for the reconnaissance tool.""" import click import json import sys import logging from pathlib import Path from .config import Config from .reconnaissance import ReconnaissanceEngine from .report_generator import ReportGenerator from .web_app import create_app # Module logger logger = logging.getLogger(__name__) @click.command() @click.argument('target', required=False) @click.option('--web', is_flag=True, help='Start web interface instead of CLI') @click.option('--shodan-key', help='Shodan API key') @click.option('--virustotal-key', help='VirusTotal API key') @click.option('--max-depth', default=2, help='Maximum recursion depth (default: 2)') @click.option('--output', '-o', help='Output file prefix (will create .json and .txt files)') @click.option('--json-only', is_flag=True, help='Only output JSON') @click.option('--text-only', is_flag=True, help='Only output text report') @click.option('--port', default=5000, help='Port for web interface (default: 5000)') @click.option('--verbose', '-v', is_flag=True, help='Enable verbose logging (DEBUG level)') @click.option('--quiet', '-q', is_flag=True, help='Quiet mode (WARNING level only)') def main(target, web, shodan_key, virustotal_key, max_depth, output, json_only, text_only, port, verbose, quiet): """DNS Reconnaissance Tool Examples: recon example.com # Scan example.com recon example # Try example.* for all TLDs recon example.com --max-depth 3 # Deeper recursion recon example.com -v # Verbose logging recon --web # Start web interface """ # Determine log level if verbose: log_level = "DEBUG" elif quiet: log_level = "WARNING" else: log_level = "INFO" # Create configuration and setup logging config = Config.from_args(shodan_key, virustotal_key, max_depth, log_level) config.setup_logging(cli_mode=True) if web: # Start web interface logger.info("🌐 Starting web interface...") app = create_app(config) logger.info(f"šŸš€ Web interface starting on http://0.0.0.0:{port}") app.run(host='0.0.0.0', port=port, debug=False) # Changed debug to False to reduce noise return if not target: click.echo("āŒ Error: TARGET is required for CLI mode. Use --web for web interface.") sys.exit(1) # Initialize reconnaissance engine logger.info("šŸ”§ Initializing reconnaissance engine...") engine = ReconnaissanceEngine(config) # Set up progress callback for CLI def progress_callback(message, percentage=None): if percentage is not None: click.echo(f"[{percentage:3d}%] {message}") else: click.echo(f" {message}") engine.set_progress_callback(progress_callback) # Display startup information click.echo("=" * 60) click.echo("šŸ” DNS RECONNAISSANCE TOOL") click.echo("=" * 60) click.echo(f"šŸŽÆ Target: {target}") click.echo(f"šŸ“Š Max recursion depth: {max_depth}") click.echo(f"🌐 DNS servers: {', '.join(config.DNS_SERVERS[:3])}{'...' if len(config.DNS_SERVERS) > 3 else ''}") click.echo(f"⚔ DNS rate limit: {config.DNS_RATE_LIMIT}/s") if shodan_key: click.echo("āœ… Shodan integration enabled") logger.info(f"šŸ•µļø Shodan API key provided (ends with: ...{shodan_key[-4:] if len(shodan_key) > 4 else shodan_key})") else: click.echo("āš ļø Shodan integration disabled (no API key)") if virustotal_key: click.echo("āœ… VirusTotal integration enabled") logger.info(f"šŸ›”ļø VirusTotal API key provided (ends with: ...{virustotal_key[-4:] if len(virustotal_key) > 4 else virustotal_key})") else: click.echo("āš ļø VirusTotal integration disabled (no API key)") click.echo("") # Run reconnaissance try: logger.info(f"šŸš€ Starting reconnaissance for target: {target}") data = engine.run_reconnaissance(target) # Display final statistics stats = data.get_stats() click.echo("") click.echo("=" * 60) click.echo("šŸ“Š RECONNAISSANCE COMPLETE") click.echo("=" * 60) click.echo(f"šŸ  Hostnames discovered: {stats['hostnames']}") click.echo(f"🌐 IP addresses found: {stats['ip_addresses']}") click.echo(f"šŸ“‹ DNS records collected: {stats['dns_records']}") click.echo(f"šŸ“œ Certificates found: {stats['certificates']}") click.echo(f"šŸ•µļø Shodan results: {stats['shodan_results']}") click.echo(f"šŸ›”ļø VirusTotal results: {stats['virustotal_results']}") # Calculate and display timing if data.end_time and data.start_time: duration = data.end_time - data.start_time click.echo(f"ā±ļø Total time: {duration}") click.echo("") # Generate reports logger.info("šŸ“„ Generating reports...") report_gen = ReportGenerator(data) if output: # Save to files saved_files = [] if not text_only: json_file = f"{output}.json" try: json_content = data.to_json() with open(json_file, 'w', encoding='utf-8') as f: f.write(json_content) saved_files.append(json_file) logger.info(f"šŸ’¾ JSON report saved: {json_file}") except Exception as e: logger.error(f"āŒ Failed to save JSON report: {e}") if not json_only: text_file = f"{output}.txt" try: with open(text_file, 'w', encoding='utf-8') as f: f.write(report_gen.generate_text_report()) saved_files.append(text_file) logger.info(f"šŸ’¾ Text report saved: {text_file}") except Exception as e: logger.error(f"āŒ Failed to save text report: {e}") if saved_files: click.echo(f"šŸ’¾ Reports saved:") for file in saved_files: click.echo(f" šŸ“„ {file}") else: # Output to stdout if json_only: try: click.echo(data.to_json()) except Exception as e: logger.error(f"āŒ Failed to generate JSON output: {e}") click.echo(f"Error generating JSON: {e}") elif text_only: try: click.echo(report_gen.generate_text_report()) except Exception as e: logger.error(f"āŒ Failed to generate text report: {e}") click.echo(f"Error generating text report: {e}") else: # Default: show text report try: click.echo(report_gen.generate_text_report()) click.echo(f"\nšŸ’” To get JSON output, use: --json-only") click.echo(f"šŸ’” To save reports, use: --output filename") except Exception as e: logger.error(f"āŒ Failed to generate report: {e}") click.echo(f"Error generating report: {e}") except KeyboardInterrupt: logger.warning("āš ļø Reconnaissance interrupted by user") click.echo("\nāš ļø Reconnaissance interrupted by user.") sys.exit(1) except Exception as e: logger.error(f"āŒ Error during reconnaissance: {e}", exc_info=True) click.echo(f"āŒ Error during reconnaissance: {e}") if verbose: raise # Re-raise in verbose mode to show full traceback sys.exit(1) if __name__ == '__main__': main()