#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ chkip.py – DNS- und Mailserver-Check-Tool Erstellt von Pascal Bouquet am 17.04.2025 Aktualisiert am 19.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 import socket import subprocess # Resolver festlegen resolver = dns.resolver.Resolver() resolver.nameservers = ['159.69.110.93', '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 get_local_ipv4(): try: ip = next((ip for ip in resolver.nameservers if '.' in ip), '1.1.1.1') s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect((ip, 80)) local_ip = s.getsockname()[0] s.close() return local_ip except: return "unavailable" def get_my_ip(): def run_curl(protocol_flag): try: result = subprocess.run( ["curl", "-s", protocol_flag, "ifconfig.me/ip"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=2, text=True ) return result.stdout.strip() except: return "unavailable" local_v4 = get_local_ipv4() public_v4 = run_curl("-4") public_v6 = run_curl("-6") return local_v4, public_v4, public_v6 def is_cgnat(ip): try: ip_obj = ipaddress.ip_address(ip) return ip_obj.is_private or ip_obj in ipaddress.ip_network("100.64.0.0/10") except: return False 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", nargs="?", 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") parser.add_argument("--me", action="store_true", help="Zeigt lokale & öffentliche IPs und prüft auf CGNAT") args = parser.parse_args() if not args.me and not args.domain: print(parser.format_usage().strip()) return if args.me: local_v4, public_v4, public_v6 = get_my_ip() result = { "local_ipv4": local_v4, "public_ipv4": public_v4, "public_ipv6": public_v6 } if is_cgnat(public_v4): result["cgnat"] = True if args.json: print(json.dumps(result, indent=2)) else: print(f"Your local IPv4: {result['local_ipv4']}") print(f"Your public IPv4: {result['public_ipv4']}") print(f"Your public IPv6: {result['public_ipv6']}") if result.get("cgnat"): print("⚠️ Hinweis: Du befindest dich möglicherweise hinter CGNAT (Carrier-Grade NAT)") return 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 # 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()