From 4e3b90e45db1f44eb6289033c7eaa9bb70a21a0e Mon Sep 17 00:00:00 2001 From: Pascal Bouquet Date: Sat, 19 Apr 2025 14:30:37 +0200 Subject: [PATCH] inital upload --- chkip.py | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 chkip.py diff --git a/chkip.py b/chkip.py new file mode 100644 index 0000000..4d75702 --- /dev/null +++ b/chkip.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +chkip.py – DNS- und Mailserver-Check-Tool + +Erstellt von Pascal Bouquet am 17.04.2025 + +Dieses Programm ist freie Software: Sie können es unter den Bedingungen der +GNU General Public License, wie von der Free Software Foundation veröffentlicht, +weitergeben und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder +(nach Ihrer Wahl) jeder späteren Version. + +Dieses Programm wird in der Hoffnung verbreitet, dass es nützlich sein wird, +aber OHNE JEDE GEWÄHRLEISTUNG – sogar ohne die implizite Gewährleistung der +MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Siehe die GNU General +Public License für weitere Details. + +Sie sollten eine Kopie der GNU General Public License zusammen mit diesem +Programm erhalten haben. Falls nicht, siehe . +""" + +import sys +import re +import ipaddress +import dns.resolver +import dns.reversename +import requests +import argparse +import json + +# Resolver festlegen +resolver = dns.resolver.Resolver() +resolver.nameservers = ['1.1.1.1', '9.9.9.9'] +resolver.timeout = 2 +resolver.lifetime = 3 + +def is_ip_address(value): + try: + ipaddress.ip_address(value) + return True + except ValueError: + return False + +def resolve_a(domain): + try: + return str(resolver.resolve(domain, 'A')[0]) + except: + return None + +def resolve_aaaa(domain): + try: + return str(resolver.resolve(domain, 'AAAA')[0]) + except: + return None + +def resolve_mx(domain): + try: + answers = resolver.resolve(domain, 'MX') + return sorted([(r.preference, str(r.exchange).rstrip('.')) for r in answers]) + except: + return [] + +def get_ptr(ip): + try: + rev_name = dns.reversename.from_address(ip) + return str(resolver.resolve(rev_name, "PTR")[0]).rstrip('.') + except: + return None + +def fcrdns_check(ip, ptr): + try: + resolved_ips = [str(r) for r in resolver.resolve(ptr, 'A')] + return 'ok' if ip in resolved_ips else f'failed ({", ".join(resolved_ips)})' + except: + return 'failed (no A record)' + +def ipinfo_hostname(ip): + try: + r = requests.get(f"https://ipinfo.io/{ip}", timeout=2) + return r.json().get("hostname", "N/A") + except: + return "N/A" + +def resolve_spf(domain): + try: + txt = resolver.resolve(domain, 'TXT') + for r in txt: + s = b''.join(r.strings).decode() + if s.startswith('v=spf1'): + return s + return "No SPF record found" + except: + return "SPF lookup failed" + +def resolve_dmarc(domain): + try: + txt = resolver.resolve(f"_dmarc.{domain}", 'TXT') + return b''.join(txt[0].strings).decode() + except: + return "No DMARC record found" + +def resolve_mta_sts(domain): + try: + txt = resolver.resolve(f"_mta-sts.{domain}", 'TXT') + return b''.join(txt[0].strings).decode() + except: + return "No MTA-STS record found" + +def resolve_dkim(domain, selector): + try: + txt = resolver.resolve(f"{selector}._domainkey.{domain}", 'TXT') + return b''.join(txt[0].strings).decode() + except: + return f"No DKIM record found for selector '{selector}'" + +def resolve_tlsa(domain, mx_host): + tlsa_name = f"_25._tcp.{mx_host}" + try: + results = resolver.resolve(tlsa_name, 'TLSA') + return [f"{r.usage} {r.selector} {r.mtype} {r.cert.hex()}" for r in results] + except: + return "No TLSA record found" + +def print_json(output): + print(json.dumps(output, indent=2)) + +def print_text(output, is_ip_mode=False): + if is_ip_mode: + print(f"PTR: {output.get('PTR')}") + print(f"rDNS: {output.get('rDNS')}") + print(f"FCrDNS: {output.get('FCrDNS')}") + else: + print(f"A: {output.get('A')}") + print(f"AAAA: {output.get('AAAA')}") + mx = output.get("MX") + if isinstance(mx, list): + print("MX:") + for entry in mx: + print(f" Host: {entry.get('host')}") + print(f" IP: {entry.get('ip')}") + print(f" PTR: {entry.get('ptr')}") + print(f" FCrDNS: {entry.get('fcrdns')}") + print() + elif isinstance(mx, str): + print(f"MX: {mx}") + print(f"rDNS: {output.get('rDNS')}") + if "SPF" in output: + print(f"SPF: {output['SPF']}") + if "DMARC" in output: + print(f"DMARC: {output['DMARC']}") + if "MTA-STS" in output: + print(f"MTA-STS: {output['MTA-STS']}") + if "TLSA" in output: + for item in output["TLSA"]: + for host, tlsa in item.items(): + print(f"TLSA: _25._tcp.{host}") + if isinstance(tlsa, list): + for r in tlsa: + print(f" {r}") + else: + print(f" {tlsa}") + if "DKIM" in output: + print(f"DKIM: {output.get('DKIM')}") + +def main(): + parser = argparse.ArgumentParser(description="chkip.py – DNS- und Mailserver-Check-Tool") + parser.add_argument("domain", help="Domain oder IP-Adresse") + parser.add_argument("-sS", "--spf", action="store_true", help="Prüft den SPF-Eintrag") + parser.add_argument("-sD", "--dmarc", action="store_true", help="Prüft den DMARC-Eintrag") + parser.add_argument("-sM", "--mta_sts", action="store_true", help="Prüft den MTA-STS-Eintrag") + parser.add_argument("-sT", "--tlsa", action="store_true", help="Prüft den TLSA-Eintrag") + parser.add_argument("-sDK", "--dkim", type=str, help="Prüft den DKIM-Eintrag für den angegebenen Selector") + parser.add_argument("--json", action="store_true", help="Ausgabe als JSON") + args = parser.parse_args() + + domain = args.domain + is_ip = is_ip_address(domain) + output = {} + + if is_ip: + output["PTR"] = get_ptr(domain) or "No PTR record" + output["rDNS"] = ipinfo_hostname(domain) + ptr = output["PTR"] + if ptr and not ptr.startswith("No"): + resolved = resolve_a(ptr) + output["FCrDNS"] = "ok" if resolved == domain else f"failed ({resolved})" + else: + output["FCrDNS"] = "failed (no PTR)" + + if args.json: + print_json(output) + else: + print_text(output, is_ip_mode=True) + return # ✅ wichtig! + + # Domain-Zweig + output["A"] = resolve_a(domain) or "No A record" + output["AAAA"] = resolve_aaaa(domain) or "No AAAA record" + mx_records = resolve_mx(domain) + if not mx_records: + output["MX"] = "No MX record" + else: + mx_list = [] + for pref, mx_host in mx_records: + host = mx_host.rstrip('.') + ip = resolve_a(host) + ptr = get_ptr(ip) if ip else None + fcr = fcrdns_check(ip, ptr) if ip and ptr else "N/A" + mx_list.append({ + "host": host, + "ip": ip, + "ptr": ptr, + "fcrdns": fcr + }) + output["MX"] = mx_list + + output["rDNS"] = ipinfo_hostname(output["A"]) if output["A"] else None + if args.spf: + output["SPF"] = resolve_spf(domain) + if args.dmarc: + output["DMARC"] = resolve_dmarc(domain) + if args.mta_sts: + output["MTA-STS"] = resolve_mta_sts(domain) + if args.dkim: + output["DKIM"] = resolve_dkim(domain, args.dkim) + if args.tlsa and mx_records: + output["TLSA"] = [] + for _, mx in mx_records: + host = mx.rstrip('.') + output["TLSA"].append({host: resolve_tlsa(domain, host)}) + + if args.json: + print_json(output) + else: + print_text(output, is_ip_mode=False) + +if __name__ == "__main__": + main()