inital upload
This commit is contained in:
commit
4e3b90e45d
239
chkip.py
Normal file
239
chkip.py
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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()
|
Loading…
Reference in New Issue
Block a user