Passed
Push — master ( 931a87...b396ae )
by John
18:03 queued 02:44
created

DnsPinMiddleware   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 60
c 1
b 0
f 0
dl 0
loc 102
rs 10
wmc 20

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
C dnsResolve() 0 45 13
B addDnsPinning() 0 41 6
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2021, Lukas Reschke <[email protected]>
7
 *
8
 * @author Lukas Reschke <[email protected]>
9
 *
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
namespace OC\Http\Client;
27
28
use Psr\Http\Message\RequestInterface;
29
30
class DnsPinMiddleware {
31
	/** @var NegativeDnsCache */
32
	private $negativeDnsCache;
33
	/** @var LocalAddressChecker */
34
	private $localAddressChecker;
35
36
	public function __construct(
37
		NegativeDnsCache $negativeDnsCache,
38
		LocalAddressChecker $localAddressChecker
39
	) {
40
		$this->negativeDnsCache = $negativeDnsCache;
41
		$this->localAddressChecker = $localAddressChecker;
42
	}
43
44
	private function dnsResolve(string $target, int $recursionCount) : array {
45
		if ($recursionCount >= 10) {
46
			return [];
47
		}
48
49
		$recursionCount = $recursionCount++;
50
		$targetIps = [];
51
52
		$soaDnsEntry = dns_get_record($target, DNS_SOA);
53
		if (isset($soaDnsEntry[0]) && isset($soaDnsEntry[0]['minimum-ttl'])) {
54
			$dnsNegativeTtl = $soaDnsEntry[0]['minimum-ttl'];
55
		} else {
56
			$dnsNegativeTtl = null;
57
		}
58
59
		$dnsTypes = [DNS_A, DNS_AAAA, DNS_CNAME];
60
		foreach ($dnsTypes as $key => $dnsType) {
61
			if ($this->negativeDnsCache->isNegativeCached($target, $dnsType)) {
62
				unset($dnsTypes[$key]);
63
				continue;
64
			}
65
66
			$dnsResponses = dns_get_record($target, $dnsType);
67
			$canHaveCnameRecord = true;
68
			if (count($dnsResponses) > 0) {
69
				foreach ($dnsResponses as $key => $dnsResponse) {
0 ignored issues
show
Comprehensibility Bug introduced by
$key is overwriting a variable from outer foreach loop.
Loading history...
70
					if (isset($dnsResponse['ip'])) {
71
						$targetIps[] = $dnsResponse['ip'];
72
						$canHaveCnameRecord = false;
73
					} elseif (isset($dnsResponse['ipv6'])) {
74
						$targetIps[] = $dnsResponse['ipv6'];
75
						$canHaveCnameRecord = false;
76
					} elseif (isset($dnsResponse['target']) && $canHaveCnameRecord) {
77
						$targetIps = array_merge($targetIps, $this->dnsResolve($dnsResponse['target'], $recursionCount));
78
						$canHaveCnameRecord = true;
79
					}
80
				}
81
			} else {
82
				if ($dnsNegativeTtl !== null) {
83
					$this->negativeDnsCache->setNegativeCacheForDnsType($target, $dnsType, $dnsNegativeTtl);
84
				}
85
			}
86
		}
87
88
		return $targetIps;
89
	}
90
91
	public function addDnsPinning() {
92
		return function (callable $handler) {
93
			return function (
94
				RequestInterface $request,
95
				array $options
96
			) use ($handler) {
97
				if ($options['nextcloud']['allow_local_address'] === true) {
98
					return $handler($request, $options);
99
				}
100
101
				$hostName = (string)$request->getUri()->getHost();
102
				$port = $request->getUri()->getPort();
103
104
				$ports = [
105
					'80',
106
					'443',
107
				];
108
109
				if ($port !== null) {
110
					$ports[] = (string)$port;
111
				}
112
113
				$targetIps = $this->dnsResolve($hostName, 0);
114
115
				$curlResolves = [];
116
117
				foreach ($ports as $port) {
118
					$curlResolves["$hostName:$port"] = [];
119
120
					foreach ($targetIps as $ip) {
121
						$this->localAddressChecker->ThrowIfLocalIp($ip);
122
						$curlResolves["$hostName:$port"][] = $ip;
123
					}
124
				}
125
126
				// Coalesce the per-host:port ips back into a comma separated list
127
				foreach ($curlResolves as $hostport => $ips) {
128
					$options['curl'][CURLOPT_RESOLVE][] = "$hostport:" . implode(',', $ips);
129
				}
130
131
				return $handler($request, $options);
132
			};
133
		};
134
	}
135
}
136