Skip to content

Commit

Permalink
v1.6.0: Enhanced service detection, improved tests and code quality
Browse files Browse the repository at this point in the history
- Added service detection with banner grabbing
- Added type hints and docstrings
- Improved error handling
- Fixed ASCII banner and socket issues
- Increased test coverage to 48%
  • Loading branch information
melihcanndemir committed Jan 3, 2025
1 parent 38870e8 commit 60e8942
Show file tree
Hide file tree
Showing 12 changed files with 516 additions and 49 deletions.
Binary file added .coverage
Binary file not shown.
6 changes: 5 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ jobs:
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Install package
run: |
pip install -e .
- name: Run tests
run: |
pytest tests/ -v --cov=./ --cov-report=xml
PYTHONPATH=$PYTHONPATH:$(pwd) pytest tests/ -v --cov=m_map --cov-report=xml
- name: Upload coverage reports
uses: codecov/codecov-action@v3
Expand Down
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Changelog
All notable changes to M-MAP will be documented in this file.

## [1.6.0] - 2025-01-04

### ✨ New Features
- Added service detection with banner grabbing
- Added type hints for better code quality
- Added new validation functions:
- `validate_ip()` for IP address validation
- `validate_subnet()` for subnet validation
- `parse_port_range()` for port range parsing
- Added docstrings for better documentation
- Added new service name resolution function
- Added host resolution function

### 🐛 Bug Fixes
- Fixed invalid escape sequence in ASCII banner
- Fixed socket timeout issues in UDP scanning
- Fixed banner encoding issues in service detection
- Fixed thread management in multi-port scanning
- Fixed progress bar calculation

### 🔨 Improvements
- Increased test coverage from 30% to 48%
- Added new unit tests:
- Network scanning tests
- Service detection tests
- Port range parsing tests
- IP validation tests
- Improved code organization
- Enhanced service detection accuracy
- Better error handling and user feedback
- Optimized thread management
- Improved progress reporting

### 🔒 Security
- Added input validation for all user inputs
- Improved error handling for network operations
- Added timeout controls for all network operations

### 📚 Documentation
- Added detailed docstrings
- Improved help messages
- Added type hints
- Updated README.md

## [1.5.0] - 2021-08-21

### Features
- Initial release
- Basic port scanning
- Multi-threading support
- Quick scan option
- Export functionality
21 changes: 21 additions & 0 deletions m_map/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from .m_map import (
get_service,
ping_scan,
get_optimal_thread_count,
scan_port,
network_scan,
udp_scan,
get_service_banner,
detect_os
)

__all__ = [
'get_service',
'ping_scan',
'get_optimal_thread_count',
'scan_port',
'network_scan',
'udp_scan',
'get_service_banner',
'detect_os'
]
239 changes: 216 additions & 23 deletions m_map/m_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,6 @@
# Version 1.6.0
# Date 21/08/2021

__all__ = [
'get_service',
'ping_scan',
'get_optimal_thread_count',
'scan_port',
'network_scan',
'udp_scan',
'get_service_banner',
'detect_os'
]

from colorama import Fore
from datetime import datetime
import colorama
Expand Down Expand Up @@ -364,6 +353,220 @@ def get_font_path():
except:
return None

def validate_ip(ip_address: str) -> bool:
"""IP adresinin geçerli olup olmadığını kontrol eder."""
try:
parts = ip_address.split('.')
if len(parts) != 4:
return False
return all(0 <= int(part) <= 255 for part in parts)
except (AttributeError, TypeError, ValueError):
return False

def validate_subnet(subnet: str) -> bool:
"""Subnet'in geçerli olup olmadığını kontrol eder."""
try:
if '/' not in subnet:
return False
ip, mask = subnet.split('/')
if not validate_ip(ip):
return False
mask = int(mask)
return 0 <= mask <= 32
except (ValueError, AttributeError):
return False

def get_service_name(port: int) -> str:
"""Port numarasına göre servis ismini döndürür."""
common_ports = {
20: "ftp-data",
21: "ftp",
22: "ssh",
23: "telnet",
25: "smtp",
53: "dns",
80: "http",
110: "pop3",
143: "imap",
443: "https",
3306: "mysql",
5432: "postgresql"
}
return common_ports.get(port, "unknown")

def parse_port_range(port_range: str) -> list:
"""Port aralığını parse eder ve port listesi döndürür."""
try:
ports = []
for part in port_range.split(','):
if '-' in part:
start, end = map(int, part.split('-'))
ports.extend(range(start, end + 1))
else:
ports.append(int(part))
return sorted(ports)
except ValueError:
raise ValueError("Geçersiz port aralığı formatı")

def scan_port(target: str, port: int, timeout: int = 1) -> bool:
"""
Belirtilen portu tarar.
Args:
target: Hedef IP adresi
port: Taranacak port
timeout: Bağlantı zaman aşımı (saniye)
"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((target, port))
sock.close()
return result == 0
except:
return False

def resolve_host(hostname: str) -> str:
"""
Hostname'i IP adresine çözümler.
Args:
hostname: Çözümlenecek hostname
Returns:
str: IP adresi veya boş string (çözümlenemezse)
"""
try:
return socket.gethostbyname(hostname)
except socket.gaierror:
return ""

def get_banner(target: str, port: int, timeout: int = 2) -> str:
"""
Belirtilen port üzerinden banner bilgisini alır.
Args:
target: Hedef IP adresi
port: Bağlanılacak port
timeout: Zaman aşımı süresi (saniye)
Returns:
str: Banner bilgisi veya boş string
"""
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(timeout)
sock.connect((target, port))
banner = sock.recv(1024)
return banner.decode('utf-8', errors='ignore').strip()
except (socket.timeout, socket.error):
return ""

def detect_service(target: str, port: int) -> str:
"""
Port üzerinde çalışan servisi tespit eder.
Args:
target: Hedef IP adresi
port: Kontrol edilecek port
Returns:
str: Tespit edilen servis adı ve versiyonu
"""
banner = get_banner(target, port)

# HTTP/HTTPS kontrolü
if "HTTP" in banner:
service = "HTTP"
if "nginx" in banner.lower():
service += " (nginx)"
elif "apache" in banner.lower():
service += " (Apache)"
return service

# SSH kontrolü
if "SSH" in banner:
service = "SSH"
if "OpenSSH" in banner:
version = banner.split("OpenSSH_")[1].split()[0]
service += f" (OpenSSH {version})"
return service

# Bilinmeyen servis
return "Unknown"

def show_about():
"""Display program information and check for updates"""
version_info = {
'name': 'M-MAP',
'version': '1.6.0',
'author': 'Melih Can',
'email': '[email protected]',
'github': 'https://github.com/melihcan1376/m-map',
'license': 'MIT',
'year': '2025',
'description': 'Advanced Port Scanner and Network Mapping Tool'
}

# Stil için ANSI renk kodları
BLUE = Fore.LIGHTBLUE_EX
GREEN = Fore.LIGHTGREEN_EX
WHITE = Fore.LIGHTWHITE_EX
RESET = Fore.RESET

print(f"\n{BLUE}{'='*50}{RESET}")

# ASCII banner'ı ekle
try:
ascii_banner = pyfiglet.figlet_format("M - MAP")
except:
ascii_banner = r"""
__ __ __ __ _ ____
| \/ | | \/ | / \ | _ \
| |\/| | | |\/| |/ _ \ | |_) |
| | | | | | | / ___ \| __/
|_| |_| |_| |_/_/ \_\_|
"""
print(f"{GREEN}{ascii_banner}{RESET}")

print(f"{GREEN}{version_info['name']}{RESET} - {version_info['description']}")
print(f"\n{WHITE}Version Information:{RESET}")
print(f" • Version: {version_info['version']}")
print(f" • Release Year: {version_info['year']}")
print(f" • License: {version_info['license']}")

print(f"\n{WHITE}Developer:{RESET}")
print(f" • {version_info['author']}")
print(f" • {version_info['email']}")
print(f" • {version_info['github']}")

print(f"\n{WHITE}Features:{RESET}")
print(" • TCP/UDP Port Scanning")
print(" • Service Detection")
print(" • OS Detection")
print(" • Network Scanning")
print(" • Banner Grabbing")
print(" • Multi-threading Support")
print(" • Export (TXT/JSON/HTML)")

print(f"\n{WHITE}Checking for updates...{RESET}")
try:
current_version = version_info['version']
response = requests.get(
"https://api.github.com/repos/melihcan1376/m-map/releases/latest",
timeout=5
)
latest_version = response.json()["tag_name"]

if latest_version > current_version:
print(f"{GREEN}New version available!{RESET}")
print(f"Current: {current_version}")
print(f"Latest: {latest_version}")
print(f"Update at: {version_info['github']}/releases")
else:
print(f"{GREEN}You have the latest version!{RESET}")
except Exception as e:
print(f"{Fore.RED}Could not check for updates: Network error{RESET}")

print(f"\n{BLUE}{'='*50}{RESET}")

if __name__ == "__main__":
args = parse_arguments()

Expand Down Expand Up @@ -407,7 +610,7 @@ def get_font_path():
ascii_banner = pyfiglet.figlet_format("M - MAP")
except:
# Her şey başarısız olursa basit bir banner kullan
ascii_banner = """
ascii_banner = r"""
__ __ __ __ _ ____
| \/ | | \/ | / \ | _ \
| |\/| | | |\/| |/ _ \ | |_) |
Expand Down Expand Up @@ -738,17 +941,7 @@ def get_font_path():
print("10 - Network Scan: Scan local network")

elif choose == "12": # About
print("-"*50)
print("M - MAP is an easy port scan tool")
print(" ")
print("Created by Melih Can")
print("Version 1.5 Date 21/08/2021")
print("-"*50)
print("My E-mail: [email protected]")
print("-"*50)

# Check for updates
check_for_updates()
show_about()

elif choose == "13": # Exit
print("\nExiting program...")
Expand Down
14 changes: 14 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from setuptools import setup, find_packages

setup(
name="m_map",
version="1.6.0",
packages=find_packages(),
install_requires=[
'colorama',
'pyfiglet',
'python-nmap',
'requests'
],
python_requires='>=3.6',
)
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test package
6 changes: 1 addition & 5 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import pytest
from m_map import get_service, ping_scan, get_optimal_thread_count
from m_map.m_map import get_service, ping_scan, get_optimal_thread_count

def test_get_service():
# Test known ports
Expand Down
Loading

0 comments on commit 60e8942

Please sign in to comment.