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