Skip to content

Instantly share code, notes, and snippets.

@jonaslejon
Created April 25, 2025 12:16
Show Gist options
  • Save jonaslejon/fef7aae5fd6c539ce408a1dbf9a2b0ba to your computer and use it in GitHub Desktop.
Save jonaslejon/fef7aae5fd6c539ce408a1dbf9a2b0ba to your computer and use it in GitHub Desktop.
OpenSSH EOL Linux dist checker
import argparse
import xml.etree.ElementTree as ET
import re
from datetime import datetime
from colorama import init, Fore, Style
# Initialize colorama
init(autoreset=True)
# EOL Dates
ubuntu_eol = {
'16.04': '2021-04-30', # Xenial
'18.04': '2023-05-31', # Bionic
'20.04': '2025-04-30', # Focal
'22.04': '2027-04-30', # Jammy
}
debian_eol = {
'10': '2024-06-30', # Buster
'11': '2026-06-30', # Bullseye
'12': '2028-06-30', # Bookworm
}
def check_eol(distro, version):
today = datetime.today().date()
if distro == 'Ubuntu':
eol_date_str = ubuntu_eol.get(version)
elif distro == 'Debian':
eol_date_str = debian_eol.get(version)
else:
return None
if eol_date_str:
eol_date = datetime.strptime(eol_date_str, "%Y-%m-%d").date()
return today > eol_date
return None
def guess_ubuntu_version(package):
# Handle different Ubuntu package patterns
if package.startswith('3ubuntu0') or package.startswith('3ubuntu13'):
return '22.04' # Jammy
if package.startswith('1ubuntu7') or package.startswith('1ubuntu6'):
return '20.04' # Focal (sometimes older 18.04 too, safe assumption for now)
if package.startswith('4ubuntu0') or package.startswith('4ubuntu0.'):
return '18.04' # Bionic
if package.startswith('4ubuntu2') or '+esm' in package:
return '16.04' # Xenial
return None
def guess_debian_version(package):
# Handle different Debian package patterns
if 'deb12' in package:
return '12' # Bookworm
if 'deb11' in package:
return '11' # Bullseye
if 'deb10' in package:
return '10' # Buster
return None
def parse_nmap_ssh_banner(xml_file):
tree = ET.parse(xml_file)
root = tree.getroot()
for host in root.findall('host'):
ip_elem = host.find('address')
ip = ip_elem.get('addr') if ip_elem is not None else "Unknown"
for port in host.findall(".//port[@portid='22']"):
service = port.find('service')
if service is not None:
product = service.get('product', '')
version = service.get('version', '')
banner = f"{product} {version}".strip()
distro = None
release_version = None
esm = False
# Ubuntu detection
if 'Ubuntu' in version:
match = re.search(r'Ubuntu\s+([^\s]+)', version)
if match:
package = match.group(1)
if '+esm' in package:
esm = True
release_version = guess_ubuntu_version(package)
distro = 'Ubuntu'
# Debian detection
elif 'Debian' in version:
match = re.search(r'Debian\s+([^\s]+)', version)
if match:
package = match.group(1)
release_version = guess_debian_version(package)
distro = 'Debian'
# Final print
if distro and release_version:
is_eol = check_eol(distro, release_version)
if is_eol is True:
status = f"{Fore.RED}EOL"
elif is_eol is False:
status = f"{Fore.GREEN}Supported"
else:
status = f"{Fore.YELLOW}Unknown"
esm_note = " (ESM)" if esm else ""
print(f"{Fore.CYAN}{ip} - {Fore.MAGENTA}{banner} - {distro} {release_version}{esm_note} - {status}{Style.RESET_ALL}")
else:
if banner:
print(f"{Fore.CYAN}{ip} - {Fore.MAGENTA}{banner} - {Fore.YELLOW}Could not detect distro/version{Style.RESET_ALL}")
else:
print(f"{Fore.CYAN}{ip} - {Fore.YELLOW}No SSH banner detected{Style.RESET_ALL}")
def main():
parser = argparse.ArgumentParser(description='Check if Linux distros (from SSH banners) are End of Life.')
parser.add_argument('xml_file', help='Path to Nmap XML output file')
args = parser.parse_args()
parse_nmap_ssh_banner(args.xml_file)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment