dnsrecon/app.py
overcuriousity ce0e11cf0b progress
2025-09-10 15:17:17 +02:00

339 lines
9.9 KiB
Python

"""
Flask application entry point for DNSRecon web interface.
Provides REST API endpoints and serves the web interface.
"""
import json
import traceback
from flask import Flask, render_template, request, jsonify, send_file
from datetime import datetime, timezone
import io
from core.scanner import scanner
from config import config
app = Flask(__name__)
app.config['SECRET_KEY'] = 'dnsrecon-dev-key-change-in-production'
@app.route('/')
def index():
"""Serve the main web interface."""
return render_template('index.html')
@app.route('/api/scan/start', methods=['POST'])
def start_scan():
"""
Start a new reconnaissance scan.
Expects JSON payload:
{
"target_domain": "example.com",
"max_depth": 2
}
"""
print("=== API: /api/scan/start called ===")
try:
print("Getting JSON data from request...")
data = request.get_json()
print(f"Request data: {data}")
if not data or 'target_domain' not in data:
print("ERROR: Missing target_domain in request")
return jsonify({
'success': False,
'error': 'Missing target_domain in request'
}), 400
target_domain = data['target_domain'].strip()
max_depth = data.get('max_depth', config.default_recursion_depth)
print(f"Parsed - target_domain: '{target_domain}', max_depth: {max_depth}")
# Validation
if not target_domain:
print("ERROR: Target domain cannot be empty")
return jsonify({
'success': False,
'error': 'Target domain cannot be empty'
}), 400
if not isinstance(max_depth, int) or max_depth < 1 or max_depth > 5:
print(f"ERROR: Invalid max_depth: {max_depth}")
return jsonify({
'success': False,
'error': 'Max depth must be an integer between 1 and 5'
}), 400
print("Validation passed, calling scanner.start_scan...")
# Start scan
success = scanner.start_scan(target_domain, max_depth)
print(f"scanner.start_scan returned: {success}")
if success:
session_id = scanner.logger.session_id
print(f"Scan started successfully with session ID: {session_id}")
return jsonify({
'success': True,
'message': 'Scan started successfully',
'scan_id': session_id
})
else:
print("ERROR: Scanner returned False")
return jsonify({
'success': False,
'error': 'Failed to start scan (scan may already be running)'
}), 409
except Exception as e:
print(f"ERROR: Exception in start_scan endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Internal server error: {str(e)}'
}), 500
@app.route('/api/scan/stop', methods=['POST'])
def stop_scan():
"""Stop the current scan."""
print("=== API: /api/scan/stop called ===")
try:
success = scanner.stop_scan()
if success:
return jsonify({
'success': True,
'message': 'Scan stop requested'
})
else:
return jsonify({
'success': False,
'error': 'No active scan to stop'
}), 400
except Exception as e:
print(f"ERROR: Exception in stop_scan endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Internal server error: {str(e)}'
}), 500
@app.route('/api/scan/status', methods=['GET'])
def get_scan_status():
"""Get current scan status and progress."""
try:
status = scanner.get_scan_status()
return jsonify({
'success': True,
'status': status
})
except Exception as e:
print(f"ERROR: Exception in get_scan_status endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Internal server error: {str(e)}'
}), 500
@app.route('/api/graph', methods=['GET'])
def get_graph_data():
"""Get current graph data for visualization."""
try:
graph_data = scanner.get_graph_data()
return jsonify({
'success': True,
'graph': graph_data
})
except Exception as e:
print(f"ERROR: Exception in get_graph_data endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Internal server error: {str(e)}'
}), 500
@app.route('/api/export', methods=['GET'])
def export_results():
"""Export complete scan results as downloadable JSON."""
try:
# Get complete results
results = scanner.export_results()
# Create filename with timestamp
timestamp = datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
target = scanner.current_target or 'unknown'
filename = f"dnsrecon_{target}_{timestamp}.json"
# Create in-memory file
json_data = json.dumps(results, indent=2, ensure_ascii=False)
file_obj = io.BytesIO(json_data.encode('utf-8'))
return send_file(
file_obj,
as_attachment=True,
download_name=filename,
mimetype='application/json'
)
except Exception as e:
print(f"ERROR: Exception in export_results endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Export failed: {str(e)}'
}), 500
@app.route('/api/providers', methods=['GET'])
def get_providers():
"""Get information about available providers."""
print("=== API: /api/providers called ===")
try:
provider_stats = scanner.get_provider_statistics()
# Add configuration information
provider_info = {}
for provider_name, stats in provider_stats.items():
provider_info[provider_name] = {
'statistics': stats,
'enabled': config.is_provider_enabled(provider_name),
'rate_limit': config.get_rate_limit(provider_name),
'requires_api_key': provider_name in ['shodan', 'virustotal']
}
print(f"Returning provider info: {list(provider_info.keys())}")
return jsonify({
'success': True,
'providers': provider_info
})
except Exception as e:
print(f"ERROR: Exception in get_providers endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Internal server error: {str(e)}'
}), 500
@app.route('/api/config/api-keys', methods=['POST'])
def set_api_keys():
"""
Set API keys for providers (stored in memory only).
Expects JSON payload:
{
"shodan": "api_key_here",
"virustotal": "api_key_here"
}
"""
try:
data = request.get_json()
if not data:
return jsonify({
'success': False,
'error': 'No API keys provided'
}), 400
updated_providers = []
for provider, api_key in data.items():
if provider in ['shodan', 'virustotal'] and api_key.strip():
success = config.set_api_key(provider, api_key.strip())
if success:
updated_providers.append(provider)
if updated_providers:
# Reinitialize scanner providers
scanner._initialize_providers()
return jsonify({
'success': True,
'message': f'API keys updated for: {", ".join(updated_providers)}',
'updated_providers': updated_providers
})
else:
return jsonify({
'success': False,
'error': 'No valid API keys were provided'
}), 400
except Exception as e:
print(f"ERROR: Exception in set_api_keys endpoint: {e}")
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Internal server error: {str(e)}'
}), 500
@app.route('/api/health', methods=['GET'])
def health_check():
"""Health check endpoint with enhanced Phase 2 information."""
return jsonify({
'success': True,
'status': 'healthy',
'timestamp': datetime.now(datetime.UTC).isoformat(),
'version': '1.0.0-phase2',
'phase': 2,
'features': {
'multi_provider': True,
'concurrent_processing': True,
'real_time_updates': True,
'api_key_management': True,
'enhanced_visualization': True,
'retry_logic': True
},
'providers_available': len(scanner.providers) if hasattr(scanner, 'providers') else 0
})
@app.errorhandler(404)
def not_found(error):
"""Handle 404 errors."""
return jsonify({
'success': False,
'error': 'Endpoint not found'
}), 404
@app.errorhandler(500)
def internal_error(error):
"""Handle 500 errors."""
print(f"ERROR: 500 Internal Server Error: {error}")
traceback.print_exc()
return jsonify({
'success': False,
'error': 'Internal server error'
}), 500
if __name__ == '__main__':
print("Starting DNSRecon Flask application...")
# Load configuration from environment
config.load_from_env()
# Start Flask application
print(f"Starting server on {config.flask_host}:{config.flask_port}")
app.run(
host=config.flask_host,
port=config.flask_port,
debug=config.flask_debug,
threaded=True
)