1# Copyright © 2019-2020 CZ.NIC, z. s. p. o. 2# SPDX-License-Identifier: GPL-3.0-or-later 3# 4# This file is part of dns-crawler. 5# 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19 20import json 21import socket 22 23from .dns_utils import get_record, parse_tlsa 24from .ip_utils import is_valid_ipv4_address, is_valid_ipv6_address 25 26 27def get_smtp_banner(host_ip, port, timeout): 28 result = {} 29 if is_valid_ipv4_address(host_ip): 30 inet = socket.AF_INET 31 elif is_valid_ipv6_address(host_ip): 32 inet = socket.AF_INET6 33 else: 34 return None 35 try: 36 s = socket.socket(inet, socket.SOCK_STREAM) 37 s.settimeout(timeout) 38 s.connect((host_ip, port)) 39 except (OSError, socket.timeout, ConnectionRefusedError) as e: 40 result["error"] = str(e) 41 else: 42 try: 43 banner = s.recv(1024).decode().replace("\r\n", "") 44 result["banner"] = banner 45 except Exception as e: 46 result["error"] = str(e) 47 s.close() 48 return result 49 50 51def get_mailserver_info(host, ports, timeout, get_banners, cache_timeout, resolver, redis, source_ipv4, source_ipv6): 52 cache_key_host = f"cache-mail-host-{host}" 53 if redis is not None: 54 cached_host = redis.get(cache_key_host) 55 if cached_host is not None: 56 redis.expire(cache_key_host, cache_timeout) 57 return json.loads(cached_host.decode("utf-8")) 58 result = {} 59 result["host"] = host 60 result["TLSA"] = {} 61 for port in ports: 62 result["TLSA"][port] = parse_tlsa(get_record(f"_{port}._tcp." + host, "TLSA", resolver)) 63 if get_banners: 64 result["banners"] = [] 65 if source_ipv4 and source_ipv6: 66 host_ip4s = get_record(host, "A", resolver) or [] 67 host_ip6s = get_record(host, "AAAA", resolver) or [] 68 host_ips = host_ip4s + host_ip6s 69 if source_ipv4 and not source_ipv6: 70 host_ips = get_record(host, "A", resolver) or [] 71 if source_ipv6 and not source_ipv4: 72 host_ips = get_record(host, "AAAA", resolver) or [] 73 for host_ip in host_ips: 74 host_ip = host_ip["value"] 75 cache_key_ip = f"cache-mail-ip-{host_ip}" 76 if redis is not None: 77 cached_ip = redis.get(cache_key_ip) 78 if cached_ip is not None: 79 redis.expire(cache_key_ip, cache_timeout) 80 result["banners"].append(json.loads(cached_ip.decode("utf-8"))) 81 continue 82 ip_banners = {"ip": host_ip, "banners": {}} 83 for port in ports: 84 ip_banners["banners"][port] = get_smtp_banner(host_ip, port, timeout) 85 if redis is not None: 86 redis.set(cache_key_ip, json.dumps(ip_banners), ex=cache_timeout) 87 result["banners"].append(ip_banners) 88 if len(result["banners"]) == 0: 89 result["banners"] = None 90 if redis is not None: 91 redis.set(cache_key_host, json.dumps(result), ex=cache_timeout) 92 return result 93 94 95def get_mx_info(mx_records, ports, timeout, get_banners, cache_timeout, resolver, redis, source_ipv4, source_ipv6): 96 results = [] 97 if not mx_records: 98 return None 99 for mx in mx_records: 100 if mx and mx["value"]: 101 host = mx["value"].split(" ")[-1] 102 if host and host != ".": 103 results.append(get_mailserver_info(host, ports, timeout, get_banners, cache_timeout, 104 resolver, redis, source_ipv4, source_ipv6)) 105 return results 106