blob: 55e164b43bb1d9e33f3cd5a50df0d362f73a5323 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2error_reporting(0);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02003function get_spf_allowed_hosts($check_domain, $expand_ipv6 = false) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01004 $hosts = array();
5
6 $records = dns_get_record($check_domain, DNS_TXT);
7 foreach ($records as $record)
8 {
9 $txt = explode(' ', $record['entries'][0]);
10 if (array_shift($txt) != 'v=spf1') // only handle SPF records
11 continue;
12
13 foreach ($txt as $mech)
14 {
15 $qual = substr($mech, 0, 1);
16 if ($qual == '-' || $qual == '~') // only handle pass or neutral records
17 continue(2);
18
19 if ($qual == '+' || $qual == '?')
20 $mech = substr($mech, 1); // remove the qualifier
21
22 if (strpos($mech, '=') !== FALSE) // handle a modifier
23 {
24 $mod = explode('=', $mech);
25 if ($mod[0] == 'redirect') // handle a redirect
26 {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020027 $hosts = get_spf_allowed_hosts($mod[1],true);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010028 return $hosts;
29 }
30 }
31 else
32 {
33 unset($cidr);
34 // reset domain to check_domain
35 $domain = $check_domain;
36 if (strpos($mech, ':') !== FALSE) // handle a domain specification
37 {
38 $split = explode(':', $mech);
39 $mech = array_shift($split);
40 $domain = implode(':', $split);
41 if (strpos($domain, '/') !== FALSE) // remove CIDR specification
42 {
43 $split = explode('/', $domain);
44 $domain = $split[0];
45 $cidr = $split[1];
46 }
47 }
48
49 $new_hosts = array();
50 if ($mech == 'include' && $check_domain != $domain) // handle an inclusion
51 {
52 $new_hosts = get_spf_allowed_hosts($domain);
53 }
54 elseif ($mech == 'a') // handle a mechanism
55 {
56 $new_hosts = get_a_hosts($domain);
57 }
58 elseif ($mech == 'mx') // handle mx mechanism
59 {
60 $new_hosts = get_mx_hosts($domain);
61 }
62 elseif ($mech == 'ip4' || $mech == 'ip6') // handle ip mechanism
63 {
64 $new_hosts = array($domain);
65 }
66
67 if (isset($cidr)) // add CIDR specification if present
68 {
69 foreach ($new_hosts as &$host)
70 {
71 $host .= '/' . $cidr;
72 }
73 unset($host);
74 }
75
76 $hosts = array_unique(array_merge($hosts,$new_hosts), SORT_REGULAR);
77 }
78 }
79 }
80 foreach ($hosts as &$host) {
81 if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020082 if ($expand_ipv6 === true) {
83 $hex = unpack("H*hex", inet_pton($host));
84 $host = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
85 }
86 else {
87 $host = $host;
88 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010089 }
90 }
91 return $hosts;
92}
93
94
95function get_mx_hosts($domain)
96{
97 $hosts = array();
98 try {
99 $mx_records = dns_get_record($domain, DNS_MX);
100 if ($mx_records) {
101 foreach ($mx_records as $mx_record) {
102 $new_hosts = get_a_hosts($mx_record['target']);
103 $hosts = array_unique(array_merge($hosts,$new_hosts), SORT_REGULAR);
104 }
105 }
106 }
107 catch (Exception $e) {
108 if ($e->getMessage() !== 'dns_get_record(): A temporary server error occurred.') {
109 throw $e;
110 }
111 $mx_records = false;
112 }
113 return $hosts;
114}
115
116function get_a_hosts($domain)
117{
118 $hosts = array();
119
120 $a_records = dns_get_record($domain, DNS_A);
121 foreach ($a_records as $a_record)
122 {
123 $hosts[] = $a_record['ip'];
124 }
125 $a_records = dns_get_record($domain, DNS_AAAA);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200126 foreach ($a_records as $a_record) {
127 $hosts[] = $a_record['ipv6'];
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100128 }
129
130 return $hosts;
131}
132
133function get_outgoing_hosts_best_guess($domain)
134{
135 // try the SPF record to get hosts that are allowed to send outgoing mails for this domain
136 $hosts = get_spf_allowed_hosts($domain);
137 if ($hosts) return $hosts;
138
139 // try the MX record to get mail servers for this domain
140 $hosts = get_mx_hosts($domain);
141 if ($hosts) return $hosts;
142
143 // fall back to the A record to get the host name for this domain
144 return get_a_hosts($domain);
145}
146?>