| 1 | #include <Access/AllowedClientHosts.h> |
| 2 | #include <Common/Exception.h> |
| 3 | #include <common/SimpleCache.h> |
| 4 | #include <Common/StringUtils/StringUtils.h> |
| 5 | #include <IO/ReadHelpers.h> |
| 6 | #include <Poco/Net/SocketAddress.h> |
| 7 | #include <Poco/RegularExpression.h> |
| 8 | #include <common/logger_useful.h> |
| 9 | #include <ext/scope_guard.h> |
| 10 | #include <boost/range/algorithm/find.hpp> |
| 11 | #include <boost/range/algorithm/find_first_of.hpp> |
| 12 | #include <boost/algorithm/string/predicate.hpp> |
| 13 | #include <ifaddrs.h> |
| 14 | |
| 15 | |
| 16 | namespace DB |
| 17 | { |
| 18 | namespace ErrorCodes |
| 19 | { |
| 20 | extern const int DNS_ERROR; |
| 21 | extern const int IP_ADDRESS_NOT_ALLOWED; |
| 22 | } |
| 23 | |
| 24 | namespace |
| 25 | { |
| 26 | using IPAddress = Poco::Net::IPAddress; |
| 27 | using IPSubnet = AllowedClientHosts::IPSubnet; |
| 28 | const IPSubnet ALL_ADDRESSES{IPAddress{IPAddress::IPv6}, IPAddress{IPAddress::IPv6}}; |
| 29 | |
| 30 | const IPAddress & getIPV6Loopback() |
| 31 | { |
| 32 | static const IPAddress ip("::1" ); |
| 33 | return ip; |
| 34 | } |
| 35 | |
| 36 | bool isIPV4LoopbackMappedToIPV6(const IPAddress & ip) |
| 37 | { |
| 38 | static const IPAddress prefix("::ffff:127.0.0.0" ); |
| 39 | /// 104 == 128 - 24, we have to reset the lowest 24 bits of 128 before comparing with `prefix` |
| 40 | /// (IPv4 loopback means any IP from 127.0.0.0 to 127.255.255.255). |
| 41 | return (ip & IPAddress(104, IPAddress::IPv6)) == prefix; |
| 42 | } |
| 43 | |
| 44 | /// Converts an address to IPv6. |
| 45 | /// The loopback address "127.0.0.1" (or any "127.x.y.z") is converted to "::1". |
| 46 | IPAddress toIPv6(const IPAddress & ip) |
| 47 | { |
| 48 | IPAddress v6; |
| 49 | if (ip.family() == IPAddress::IPv6) |
| 50 | v6 = ip; |
| 51 | else |
| 52 | v6 = IPAddress("::ffff:" + ip.toString()); |
| 53 | |
| 54 | // ::ffff:127.XX.XX.XX -> ::1 |
| 55 | if (isIPV4LoopbackMappedToIPV6(v6)) |
| 56 | v6 = getIPV6Loopback(); |
| 57 | |
| 58 | return v6; |
| 59 | } |
| 60 | |
| 61 | /// Converts a subnet to IPv6. |
| 62 | IPSubnet toIPv6(const IPSubnet & subnet) |
| 63 | { |
| 64 | IPSubnet v6; |
| 65 | if (subnet.prefix.family() == IPAddress::IPv6) |
| 66 | v6.prefix = subnet.prefix; |
| 67 | else |
| 68 | v6.prefix = IPAddress("::ffff:" + subnet.prefix.toString()); |
| 69 | |
| 70 | if (subnet.mask.family() == IPAddress::IPv6) |
| 71 | v6.mask = subnet.mask; |
| 72 | else |
| 73 | v6.mask = IPAddress(96, IPAddress::IPv6) | IPAddress("::ffff:" + subnet.mask.toString()); |
| 74 | |
| 75 | v6.prefix = v6.prefix & v6.mask; |
| 76 | |
| 77 | // ::ffff:127.XX.XX.XX -> ::1 |
| 78 | if (isIPV4LoopbackMappedToIPV6(v6.prefix)) |
| 79 | v6 = {getIPV6Loopback(), IPAddress(128, IPAddress::IPv6)}; |
| 80 | |
| 81 | return v6; |
| 82 | } |
| 83 | |
| 84 | /// Helper function for isAddressOfHost(). |
| 85 | bool isAddressOfHostImpl(const IPAddress & address, const String & host) |
| 86 | { |
| 87 | IPAddress addr_v6 = toIPv6(address); |
| 88 | |
| 89 | /// Resolve by hand, because Poco don't use AI_ALL flag but we need it. |
| 90 | addrinfo * ai_begin = nullptr; |
| 91 | SCOPE_EXIT( |
| 92 | { |
| 93 | if (ai_begin) |
| 94 | freeaddrinfo(ai_begin); |
| 95 | }); |
| 96 | |
| 97 | addrinfo hints; |
| 98 | memset(&hints, 0, sizeof(hints)); |
| 99 | hints.ai_family = AF_UNSPEC; |
| 100 | hints.ai_flags |= AI_V4MAPPED | AI_ALL; |
| 101 | |
| 102 | int err = getaddrinfo(host.c_str(), nullptr, &hints, &ai_begin); |
| 103 | if (err) |
| 104 | throw Exception("Cannot getaddrinfo(" + host + "): " + gai_strerror(err), ErrorCodes::DNS_ERROR); |
| 105 | |
| 106 | for (const addrinfo * ai = ai_begin; ai; ai = ai->ai_next) |
| 107 | { |
| 108 | if (ai->ai_addrlen && ai->ai_addr) |
| 109 | { |
| 110 | if (ai->ai_family == AF_INET) |
| 111 | { |
| 112 | const auto & sin = *reinterpret_cast<const sockaddr_in *>(ai->ai_addr); |
| 113 | if (addr_v6 == toIPv6(IPAddress(&sin.sin_addr, sizeof(sin.sin_addr)))) |
| 114 | { |
| 115 | return true; |
| 116 | } |
| 117 | } |
| 118 | else if (ai->ai_family == AF_INET6) |
| 119 | { |
| 120 | const auto & sin = *reinterpret_cast<const sockaddr_in6*>(ai->ai_addr); |
| 121 | if (addr_v6 == IPAddress(&sin.sin6_addr, sizeof(sin.sin6_addr), sin.sin6_scope_id)) |
| 122 | { |
| 123 | return true; |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | return false; |
| 130 | } |
| 131 | |
| 132 | /// Whether a specified address is one of the addresses of a specified host. |
| 133 | bool isAddressOfHost(const IPAddress & address, const String & host) |
| 134 | { |
| 135 | /// We need to cache DNS requests. |
| 136 | static SimpleCache<decltype(isAddressOfHostImpl), isAddressOfHostImpl> cache; |
| 137 | return cache(address, host); |
| 138 | } |
| 139 | |
| 140 | /// Helper function for isAddressOfLocalhost(). |
| 141 | std::vector<IPAddress> getAddressesOfLocalhostImpl() |
| 142 | { |
| 143 | std::vector<IPAddress> addresses; |
| 144 | |
| 145 | ifaddrs * ifa_begin = nullptr; |
| 146 | SCOPE_EXIT({ |
| 147 | if (ifa_begin) |
| 148 | freeifaddrs(ifa_begin); |
| 149 | }); |
| 150 | |
| 151 | int err = getifaddrs(&ifa_begin); |
| 152 | if (err) |
| 153 | return {getIPV6Loopback()}; |
| 154 | |
| 155 | for (const ifaddrs * ifa = ifa_begin; ifa; ifa = ifa->ifa_next) |
| 156 | { |
| 157 | if (!ifa->ifa_addr) |
| 158 | continue; |
| 159 | if (ifa->ifa_addr->sa_family == AF_INET) |
| 160 | { |
| 161 | const auto & sin = *reinterpret_cast<const sockaddr_in *>(ifa->ifa_addr); |
| 162 | addresses.push_back(toIPv6(IPAddress(&sin.sin_addr, sizeof(sin.sin_addr)))); |
| 163 | } |
| 164 | else if (ifa->ifa_addr->sa_family == AF_INET6) |
| 165 | { |
| 166 | const auto & sin = *reinterpret_cast<const sockaddr_in6 *>(ifa->ifa_addr); |
| 167 | addresses.push_back(IPAddress(&sin.sin6_addr, sizeof(sin.sin6_addr), sin.sin6_scope_id)); |
| 168 | } |
| 169 | } |
| 170 | return addresses; |
| 171 | } |
| 172 | |
| 173 | /// Whether a specified address is one of the addresses of the localhost. |
| 174 | bool isAddressOfLocalhost(const IPAddress & address) |
| 175 | { |
| 176 | /// We need to cache DNS requests. |
| 177 | static const std::vector<IPAddress> local_addresses = getAddressesOfLocalhostImpl(); |
| 178 | return boost::range::find(local_addresses, toIPv6(address)) != local_addresses.end(); |
| 179 | } |
| 180 | |
| 181 | /// Helper function for getHostByAddress(). |
| 182 | String getHostByAddressImpl(const IPAddress & address) |
| 183 | { |
| 184 | Poco::Net::SocketAddress sock_addr(address, 0); |
| 185 | |
| 186 | /// Resolve by hand, because Poco library doesn't have such functionality. |
| 187 | char host[1024]; |
| 188 | int err = getnameinfo(sock_addr.addr(), sock_addr.length(), host, sizeof(host), nullptr, 0, NI_NAMEREQD); |
| 189 | if (err) |
| 190 | throw Exception("Cannot getnameinfo(" + address.toString() + "): " + gai_strerror(err), ErrorCodes::DNS_ERROR); |
| 191 | |
| 192 | /// Check that PTR record is resolved back to client address |
| 193 | if (!isAddressOfHost(address, host)) |
| 194 | throw Exception("Host " + String(host) + " isn't resolved back to " + address.toString(), ErrorCodes::DNS_ERROR); |
| 195 | |
| 196 | return host; |
| 197 | } |
| 198 | |
| 199 | /// Returns the host name by its address. |
| 200 | String getHostByAddress(const IPAddress & address) |
| 201 | { |
| 202 | /// We need to cache DNS requests. |
| 203 | static SimpleCache<decltype(getHostByAddressImpl), &getHostByAddressImpl> cache; |
| 204 | return cache(address); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | |
| 209 | String AllowedClientHosts::IPSubnet::toString() const |
| 210 | { |
| 211 | unsigned int prefix_length = mask.prefixLength(); |
| 212 | if (IPAddress{prefix_length, mask.family()} == mask) |
| 213 | return prefix.toString() + "/" + std::to_string(prefix_length); |
| 214 | |
| 215 | return prefix.toString() + "/" + mask.toString(); |
| 216 | } |
| 217 | |
| 218 | |
| 219 | AllowedClientHosts::AllowedClientHosts() |
| 220 | { |
| 221 | } |
| 222 | |
| 223 | |
| 224 | AllowedClientHosts::AllowedClientHosts(AllAddressesTag) |
| 225 | { |
| 226 | addAllAddresses(); |
| 227 | } |
| 228 | |
| 229 | |
| 230 | AllowedClientHosts::~AllowedClientHosts() = default; |
| 231 | |
| 232 | |
| 233 | AllowedClientHosts::AllowedClientHosts(const AllowedClientHosts & src) |
| 234 | { |
| 235 | *this = src; |
| 236 | } |
| 237 | |
| 238 | |
| 239 | AllowedClientHosts & AllowedClientHosts::operator =(const AllowedClientHosts & src) |
| 240 | { |
| 241 | addresses = src.addresses; |
| 242 | localhost = src.localhost; |
| 243 | subnets = src.subnets; |
| 244 | host_names = src.host_names; |
| 245 | host_regexps = src.host_regexps; |
| 246 | compiled_host_regexps.clear(); |
| 247 | return *this; |
| 248 | } |
| 249 | |
| 250 | |
| 251 | AllowedClientHosts::AllowedClientHosts(AllowedClientHosts && src) = default; |
| 252 | AllowedClientHosts & AllowedClientHosts::operator =(AllowedClientHosts && src) = default; |
| 253 | |
| 254 | |
| 255 | void AllowedClientHosts::clear() |
| 256 | { |
| 257 | addresses.clear(); |
| 258 | localhost = false; |
| 259 | subnets.clear(); |
| 260 | host_names.clear(); |
| 261 | host_regexps.clear(); |
| 262 | compiled_host_regexps.clear(); |
| 263 | } |
| 264 | |
| 265 | |
| 266 | bool AllowedClientHosts::empty() const |
| 267 | { |
| 268 | return addresses.empty() && subnets.empty() && host_names.empty() && host_regexps.empty(); |
| 269 | } |
| 270 | |
| 271 | |
| 272 | void AllowedClientHosts::addAddress(const IPAddress & address) |
| 273 | { |
| 274 | IPAddress addr_v6 = toIPv6(address); |
| 275 | if (boost::range::find(addresses, addr_v6) != addresses.end()) |
| 276 | return; |
| 277 | addresses.push_back(addr_v6); |
| 278 | if (addr_v6.isLoopback()) |
| 279 | localhost = true; |
| 280 | } |
| 281 | |
| 282 | |
| 283 | void AllowedClientHosts::addAddress(const String & address) |
| 284 | { |
| 285 | addAddress(IPAddress{address}); |
| 286 | } |
| 287 | |
| 288 | |
| 289 | void AllowedClientHosts::addSubnet(const IPSubnet & subnet) |
| 290 | { |
| 291 | IPSubnet subnet_v6 = toIPv6(subnet); |
| 292 | |
| 293 | if (subnet_v6.mask == IPAddress(128, IPAddress::IPv6)) |
| 294 | { |
| 295 | addAddress(subnet_v6.prefix); |
| 296 | return; |
| 297 | } |
| 298 | |
| 299 | if (boost::range::find(subnets, subnet_v6) == subnets.end()) |
| 300 | subnets.push_back(subnet_v6); |
| 301 | } |
| 302 | |
| 303 | |
| 304 | void AllowedClientHosts::addSubnet(const IPAddress & prefix, const IPAddress & mask) |
| 305 | { |
| 306 | addSubnet(IPSubnet{prefix, mask}); |
| 307 | } |
| 308 | |
| 309 | |
| 310 | void AllowedClientHosts::addSubnet(const IPAddress & prefix, size_t num_prefix_bits) |
| 311 | { |
| 312 | addSubnet(prefix, IPAddress(num_prefix_bits, prefix.family())); |
| 313 | } |
| 314 | |
| 315 | |
| 316 | void AllowedClientHosts::addSubnet(const String & subnet) |
| 317 | { |
| 318 | size_t slash = subnet.find('/'); |
| 319 | if (slash == String::npos) |
| 320 | { |
| 321 | addAddress(subnet); |
| 322 | return; |
| 323 | } |
| 324 | |
| 325 | IPAddress prefix{String{subnet, 0, slash}}; |
| 326 | String mask(subnet, slash + 1, subnet.length() - slash - 1); |
| 327 | if (std::all_of(mask.begin(), mask.end(), isNumericASCII)) |
| 328 | addSubnet(prefix, parseFromString<UInt8>(mask)); |
| 329 | else |
| 330 | addSubnet(prefix, IPAddress{mask}); |
| 331 | } |
| 332 | |
| 333 | |
| 334 | void AllowedClientHosts::addHostName(const String & host_name) |
| 335 | { |
| 336 | if (boost::range::find(host_names, host_name) != host_names.end()) |
| 337 | return; |
| 338 | host_names.push_back(host_name); |
| 339 | if (boost::iequals(host_name, "localhost" )) |
| 340 | localhost = true; |
| 341 | } |
| 342 | |
| 343 | |
| 344 | void AllowedClientHosts::addHostRegexp(const String & host_regexp) |
| 345 | { |
| 346 | if (boost::range::find(host_regexps, host_regexp) == host_regexps.end()) |
| 347 | host_regexps.push_back(host_regexp); |
| 348 | } |
| 349 | |
| 350 | |
| 351 | void AllowedClientHosts::addAllAddresses() |
| 352 | { |
| 353 | clear(); |
| 354 | addSubnet(ALL_ADDRESSES); |
| 355 | } |
| 356 | |
| 357 | |
| 358 | bool AllowedClientHosts::containsAllAddresses() const |
| 359 | { |
| 360 | return (boost::range::find(subnets, ALL_ADDRESSES) != subnets.end()) |
| 361 | || (boost::range::find(host_regexps, ".*" ) != host_regexps.end()) |
| 362 | || (boost::range::find(host_regexps, "$" ) != host_regexps.end()); |
| 363 | } |
| 364 | |
| 365 | |
| 366 | void AllowedClientHosts::checkContains(const IPAddress & address, const String & user_name) const |
| 367 | { |
| 368 | if (!contains(address)) |
| 369 | { |
| 370 | if (user_name.empty()) |
| 371 | throw Exception("It's not allowed to connect from address " + address.toString(), ErrorCodes::IP_ADDRESS_NOT_ALLOWED); |
| 372 | else |
| 373 | throw Exception("User " + user_name + " is not allowed to connect from address " + address.toString(), ErrorCodes::IP_ADDRESS_NOT_ALLOWED); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | |
| 378 | bool AllowedClientHosts::contains(const IPAddress & address) const |
| 379 | { |
| 380 | /// Check `ip_addresses`. |
| 381 | IPAddress addr_v6 = toIPv6(address); |
| 382 | if (boost::range::find(addresses, addr_v6) != addresses.end()) |
| 383 | return true; |
| 384 | |
| 385 | if (localhost && isAddressOfLocalhost(addr_v6)) |
| 386 | return true; |
| 387 | |
| 388 | /// Check `ip_subnets`. |
| 389 | for (const auto & subnet : subnets) |
| 390 | if ((addr_v6 & subnet.mask) == subnet.prefix) |
| 391 | return true; |
| 392 | |
| 393 | /// Check `hosts`. |
| 394 | for (const String & host_name : host_names) |
| 395 | { |
| 396 | try |
| 397 | { |
| 398 | if (isAddressOfHost(addr_v6, host_name)) |
| 399 | return true; |
| 400 | } |
| 401 | catch (const Exception & e) |
| 402 | { |
| 403 | if (e.code() != ErrorCodes::DNS_ERROR) |
| 404 | throw; |
| 405 | /// Try to ignore DNS errors: if host cannot be resolved, skip it and try next. |
| 406 | LOG_WARNING( |
| 407 | &Logger::get("AddressPatterns" ), |
| 408 | "Failed to check if the allowed client hosts contain address " << address.toString() << ". " << e.displayText() |
| 409 | << ", code = " << e.code()); |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | /// Check `host_regexps`. |
| 414 | try |
| 415 | { |
| 416 | String resolved_host = getHostByAddress(addr_v6); |
| 417 | if (!resolved_host.empty()) |
| 418 | { |
| 419 | compileRegexps(); |
| 420 | for (const auto & compiled_regexp : compiled_host_regexps) |
| 421 | { |
| 422 | Poco::RegularExpression::Match match; |
| 423 | if (compiled_regexp && compiled_regexp->match(resolved_host, match)) |
| 424 | return true; |
| 425 | } |
| 426 | } |
| 427 | } |
| 428 | catch (const Exception & e) |
| 429 | { |
| 430 | if (e.code() != ErrorCodes::DNS_ERROR) |
| 431 | throw; |
| 432 | /// Try to ignore DNS errors: if host cannot be resolved, skip it and try next. |
| 433 | LOG_WARNING( |
| 434 | &Logger::get("AddressPatterns" ), |
| 435 | "Failed to check if the allowed client hosts contain address " << address.toString() << ". " << e.displayText() |
| 436 | << ", code = " << e.code()); |
| 437 | } |
| 438 | |
| 439 | return false; |
| 440 | } |
| 441 | |
| 442 | |
| 443 | void AllowedClientHosts::compileRegexps() const |
| 444 | { |
| 445 | if (compiled_host_regexps.size() == host_regexps.size()) |
| 446 | return; |
| 447 | size_t old_size = compiled_host_regexps.size(); |
| 448 | compiled_host_regexps.reserve(host_regexps.size()); |
| 449 | for (size_t i = old_size; i != host_regexps.size(); ++i) |
| 450 | compiled_host_regexps.emplace_back(std::make_unique<Poco::RegularExpression>(host_regexps[i])); |
| 451 | } |
| 452 | |
| 453 | |
| 454 | bool operator ==(const AllowedClientHosts & lhs, const AllowedClientHosts & rhs) |
| 455 | { |
| 456 | return (lhs.addresses == rhs.addresses) && (lhs.subnets == rhs.subnets) && (lhs.host_names == rhs.host_names) |
| 457 | && (lhs.host_regexps == rhs.host_regexps); |
| 458 | } |
| 459 | } |
| 460 | |